bostrat-node 0.2.9 → 0.2.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/bostrat-flow +3 -0
- package/bin/bostrat-mock +2 -2
- package/dist/index.js +159 -136
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,117 +1,121 @@
|
|
|
1
|
-
var
|
|
1
|
+
var ik=Object.defineProperty;var td=(t,e)=>{for(var n in e)ik(t,n,{get:e[n],enumerable:!0})};import lt from"node:path";import Bt from"node:fs";import PO from"node:readline";import{fileURLToPath as BO}from"node:url";import ge from"express";import DO from"node:http";import jO from"node:https";import Zu from"node:os";import{WebSocketServer as LO}from"ws";import{execFile as px}from"node:child_process";import{promisify as mx}from"node:util";import Ri from"node:fs";import{randomUUID as hx}from"node:crypto";import Dt from"node:fs/promises";import od from"node:os";import $t from"node:path";import{execFile as fk}from"node:child_process";import{promisify as pk}from"node:util";import{createHash as ck,randomBytes as lk,timingSafeEqual as uk}from"node:crypto";var fs="BOSTRAT_HOOK_TOKEN",ps="x-bostrat-hook";function ed(){return lk(32).toString("hex")}function zr(t){let e=typeof t=="string"?t.trim():"";return e?ck("sha256").update(e).digest("hex"):null}function nd(t,e){return typeof t!="string"||typeof e!="string"||t.length!==e.length||!t?!1:uk(Buffer.from(t),Buffer.from(e))}var pi=new Map,mi=new Map,dk=6e4;function ms(t,e={},{now:n=Date.now(),log:r=console.warn}={}){if(pi.set(t,(pi.get(t)||0)+1),mi.has(t)&&n-mi.get(t)<dk)return!1;mi.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=${pi.get(t)}${o?` ${o}`:""}`),!0}async function rd(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 hs(){return/^(1|true|yes|on)$/i.test(process.env.BOSTRAT_CLAUDE_USE_API_KEY||"")&&process.env.ANTHROPIC_API_KEY?.trim()||null}function mk(){return process.env.CLAUDE_CONFIG_DIR?$t.join(process.env.CLAUDE_CONFIG_DIR,".claude.json"):$t.join(od.homedir(),".claude.json")}function hk(){return process.env.CLAUDE_CONFIG_DIR?$t.join(process.env.CLAUDE_CONFIG_DIR,"settings.json"):$t.join(od.homedir(),".claude","settings.json")}async function Pe(t){if(!t||typeof t!="string")return;let e=$t.resolve(t),n=mk(),r=hk(),o={};try{o=JSON.parse(await Dt.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 Dt.mkdir($t.dirname(n),{recursive:!0}),await Dt.writeFile(n,JSON.stringify(o,null,2));let i={};try{i=JSON.parse(await Dt.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 "${ps}: $${fs}" --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 Dt.mkdir($t.dirname(r),{recursive:!0}),await Dt.writeFile(r,JSON.stringify(i,null,2)),await wk(e)}async function wk(t){try{let e=$t.resolve(t),n=$t.join(e,".bostrat","memory","claude"),r=$t.join(e,".claude","settings.local.json"),o={};try{o=JSON.parse(await Dt.readFile(r,"utf8"))}catch{}let s=o.autoMemoryDirectory,a=typeof s=="string"&&s.includes($t.join(".bostrat","memory"));if(s&&!a||s===n)return;o.autoMemoryDirectory=n,await Dt.mkdir(n,{recursive:!0}),await Dt.mkdir($t.dirname(r),{recursive:!0}),await Dt.writeFile(r,JSON.stringify(o,null,2)),await gk(e,["/.claude/settings.local.json"])}catch{}}async function gk(t,e){try{let{stdout:n}=await pk(fk)("git",["-C",t,"rev-parse","--git-path","info/exclude"]),r=$t.resolve(t,n.trim()),o=await Dt.readFile(r,"utf8").catch(()=>""),s=e.filter(a=>!o.split(`
|
|
2
2
|
`).includes(a));if(!s.length)return;await Dt.mkdir($t.dirname(r),{recursive:!0}),await Dt.appendFile(r,(o&&!o.endsWith(`
|
|
3
3
|
`)?`
|
|
4
4
|
`:"")+s.join(`
|
|
5
5
|
`)+`
|
|
6
|
-
`)}catch{}}import
|
|
6
|
+
`)}catch{}}import hi from"node:fs/promises";import yk from"node:os";import ws from"node:path";function bk(){let t=process.env.CODEX_HOME||ws.join(yk.homedir(),".codex");return ws.join(t,"config.toml")}function sd(t){return`[projects."${t}"]`}function kk(t,e){let n=sd(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 qn(t){if(!t||typeof t!="string")return;let e=ws.resolve(t),n=bk(),r="";try{r=await hi.readFile(n,"utf8")}catch{}if(kk(r,e))return;let o=sd(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 hi.mkdir(ws.dirname(n),{recursive:!0}),await hi.writeFile(n,r)}catch(s){console.warn(`[codex-config] could not seed folder trust for ${e}: ${s?.message||s}`)}}import ad from"node:fs/promises";import xk from"node:os";import Jr from"node:path";function Sk(t){let n=Jr.resolve(t).slice(1).replace(/\//g,"-").replace(/\./g,""),r=process.env.CURSOR_CONFIG_DIR||Jr.join(xk.homedir(),".cursor");return Jr.join(r,"projects",n)}async function Kn(t){if(!t||typeof t!="string")return;let e=Jr.resolve(t),n=Sk(e),r=Jr.join(n,".workspace-trusted");await ad.mkdir(n,{recursive:!0}),await ad.writeFile(r,`${JSON.stringify({trustedAt:new Date().toISOString(),workspacePath:e},null,2)}
|
|
14
|
+
`,"utf8")}import j from"node:fs/promises";import Jk from"node:os";import J from"node:path";import{execFile as Xk}from"node:child_process";import{promisify as Yk}from"node:util";import Ck from"node:fs";import cd from"node:fs/promises";import Yr from"node:path";import Nk from"node:os";import{fileURLToPath as Pk}from"node:url";import{execFile as Bk}from"node:child_process";import{promisify as Dk}from"node:util";import gi from"node:fs/promises";import Be from"node:path";import{execFile as vk}from"node:child_process";import{promisify as Tk}from"node:util";var Ak=Tk(vk),id=process.env.HOME||"/home/globo",Ek=Be.join(process.env.BOSTRAT_STATE_DIR||Be.join(id,".bostrat"),"repos"),_k=[...(process.env.BOSTRAT_REPOS_ROOT||Be.join(id,"Documents")).split(","),Ek].map(t=>t.trim()).filter(Boolean),Ik=Number(process.env.BOSTRAT_REPOS_DEPTH)||3,Rk=new Set(["node_modules",".git",".cache","dist","build",".next","vendor","__pycache__","venv",".venv","target"]);async function $k(t){try{return(await gi.stat(Be.join(t,".git"))).isDirectory()?"repo":"worktree"}catch{return null}}async function Ok(t){let e=i=>gi.readFile(Be.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 ye(){let t=new Map;async function e(o,s){if(t.has(o))return;let a=await $k(o);if(a==="worktree")return;if(a==="repo"){t.set(o,{path:o,name:Be.basename(o),branch:await Ok(o)});return}if(s>=Ik)return;let i;try{i=await gi.readdir(o,{withFileTypes:!0})}catch{return}for(let c of i)!c.isDirectory()||c.name.startsWith(".")||Rk.has(c.name)||await e(Be.join(o,c.name),s+1)}for(let o of _k)await e(Be.resolve(o),0);let n=new Set,r=[];for(let o of t.values()){let s=(await tt(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 Mk(t){let e=String(t||"").trim().match(/github\.com[:/]+([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/i);return e?`${e[1]}/${e[2]}`:null}var wi=new Map;async function tt(t){if(!t)return null;if(wi.has(t))return wi.get(t);let e=null;try{let{stdout:n}=await Ak("git",["-C",t,"config","--get","remote.origin.url"],{timeout:5e3});e=Mk(n)}catch{e=null}return wi.set(t,e),e}async function Xr(t){let e=String(t||"").toLowerCase();if(!e)return null;for(let n of await ye())if((await tt(n.path))?.toLowerCase()===e)return n.path;return null}var ld=Dk(Bk),jk=process.env.BOSTRAT_STATE_DIR||Yr.join(process.env.HOME||Nk.tmpdir(),".bostrat"),Lk=Yr.join(jk,"repos"),ud=/^[^/\s]+\/[^/\s]+$/,Fk=5*6e4,Uk=6e4,Hk=10*6e4,dd=fetch,yi=new Map,mn=null;function be(t=process.env){let e=t.BOSTRAT_PLANE_URL||"";if(!e)return null;try{let n=new URL(e);return n.protocol=n.protocol==="ws:"?"http:":"https:",n.origin}catch{return null}}function gs(t=process.env){return!!(be(t)&&t.BOSTRAT_PLANE_TOKEN)}var fd=t=>({authorization:`Bearer ${t.BOSTRAT_PLANE_TOKEN||""}`}),Gn=(t,e)=>Object.assign(new Error(t),{status:e});async function ys(t,{env:e=process.env}={}){let n=String(t||"").toLowerCase();if(!ud.test(n))throw Gn("repo must be owner/name",400);let r=yi.get(n);if(r&&r.expiresAt-Date.now()>Fk)return r;if(!gs(e))throw Gn("node is not plane-linked \u2014 GitHub auth unavailable",503);let o;try{o=await dd(`${be(e)}/v1/github/token?repo=${encodeURIComponent(t)}`,{headers:fd(e)})}catch(i){if(r&&r.expiresAt>Date.now())return r;throw Gn(`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 Gn(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 yi.set(n,a),a}async function Zr({env:t=process.env,force:e=!1}={}){if(!gs(t))return{configured:!1,repos:[],username:null};if(!e&&mn&&Date.now()-mn.at<Uk)return mn.data;try{let n=await dd(`${be(t)}/v1/github/repos`,{headers:fd(t)});if(!n.ok)throw new Error(`plane replied ${n.status}`);let r=await n.json();return mn={at:Date.now(),data:r},r}catch(n){return mn?mn.data:{configured:!0,repos:[],username:null,error:n?.message||String(n)}}}function pd(){yi.clear(),mn=null}async function ne({cwd:t=null,slug:e=null,env:n=process.env}={}){try{let r=e||(t?await tt(t):null);if(!r)return{};let{token:o}=await ys(r,{env:n});return o?{GH_TOKEN:o}:{}}catch{return{}}}async function bi({env:t=process.env}={}){return(await Zr({env:t})).username??null}var Wk=()=>Pk(new URL("../bin/bostrat-git-credential",import.meta.url)),qk=(t=process.env)=>`http://127.0.0.1:${Number(t.PORT)||3362}`;function Kk(t){let[e,n]=String(t).split("/");return Yr.join(Lk,e,n)}async function ki(t,{env:e=process.env}={}){let n=(...r)=>ld("git",["-C",t,...r],{timeout:1e4});await n("config","--replace-all","credential.helper",""),await n("config","--add","credential.helper",`!"${Wk()}" ${qk(e)}`),await n("config","credential.useHttpPath","true")}async function xi(t,{env:e=process.env,baseUrl:n="https://github.com"}={}){if(!ud.test(String(t||"")))throw Gn(`bad repo slug "${t}"`,400);let r=Kk(t);if(Ck.existsSync(Yr.join(r,".git")))return r;let{token:o}=await ys(t,{env:e});await cd.mkdir(Yr.dirname(r),{recursive:!0});let s="!f() { echo username=x-access-token; echo password=$BOSTRAT_GIT_TOKEN; }; f";try{await ld("git",["-c","credential.helper=","-c",`credential.helper=${s}`,"clone","--no-tags","-q",`${n}/${t}.git`,r],{timeout:Hk,env:{...e,BOSTRAT_GIT_TOKEN:o,GIT_TERMINAL_PROMPT:"0"}})}catch(a){throw await cd.rm(r,{recursive:!0,force:!0}).catch(()=>{}),Gn(`clone of ${t} failed: ${a?.stderr?.trim?.()||a?.message||a}`,502)}return await ki(r,{env:e}),r}var Gk=6e4,Vk=5e3,Si=new Map,zk=(...t)=>fetch(...t);function md(t=process.env){return!!(be(t)&&t.BOSTRAT_PLANE_TOKEN)}var hd=t=>({authorization:`Bearer ${t.BOSTRAT_PLANE_TOKEN||""}`});async function wd(t,e,n){let r=be(n),o=new AbortController,s=setTimeout(()=>o.abort(),Vk);try{return await zk(`${r}${t}`,{...e,signal:o.signal})}finally{clearTimeout(s)}}async function gd(t,{env:e=process.env,force:n=!1}={}){if(!md(e))return{facts:[],source:"unconfigured"};let r=Si.get(t);if(!n&&r&&Date.now()-r.at<Gk)return{facts:r.facts,source:"cache"};try{let o=await wd(`/v1/memory/${encodeURIComponent(t)}`,{headers:hd(e)},e);if(!o.ok)throw new Error(`plane ${o.status}`);let s=await o.json(),a=Array.isArray(s?.facts)?s.facts:[];return Si.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 bs(t,e,{env:n=process.env}={}){if(!md(n))return{pushed:0,source:"unconfigured"};if(!e?.length)return{pushed:0,source:"empty"};try{let r=await wd(`/v1/memory/${encodeURIComponent(t)}`,{method:"POST",headers:{...hd(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 Si.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 Zk=Yk(Xk),vi=process.env.BOSTRAT_STATE_DIR||J.join(process.env.HOME||Jk.tmpdir(),".bostrat"),yd=200,Qk=25*1024;function ks(){return J.join(vi,"memory","repos")}var ke="operator";function hn(t){return t===ke?J.join(vi,"memory",ke):J.join(ks(),t)}var Sd=".tombstones.json";async function Qr(t){try{return JSON.parse(await j.readFile(J.join(t,Sd),"utf8"))}catch{return{}}}async function Ti(t,e){let n=await Qr(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(J.join(t,Sd),JSON.stringify(n))}function bd(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 kd(t){try{let{stdout:e}=await Zk("git",["-C",t,"config","--get","remote.origin.url"]);return e.trim()||null}catch{return null}}async function Ai(t){if(!t)return[];let e=bd(await kd(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=bd(await kd(J.join(t,o.name)));s&&!n.includes(s)&&n.push(s)}return n}function xs(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 De(t,e){let n=new RegExp(`^\\s*${e}:\\s*(.+?)\\s*$`,"m").exec(t||"");return n?n[1].replace(/^"|"$/g,""):null}async function re(t,e=null){let n=e??await j.readFile(t,"utf8").catch(()=>""),{fm:r}=xs(n),o=De(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 vd(t,e){let n=De(t,"scope"),r=/^repo:([a-z0-9][a-z0-9._-]*)$/i.exec(n||"");if(r)return r[1].toLowerCase();if((n||"").toLowerCase()===ke){let o=De(t,"trust");if(!o||/^operator/i.test(o))return ke}return e}var Vn=t=>/\]\(([^)]+\.md)\)/.exec(t)?.[1]||null;function Ei(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=Vn(c);l&&c.trim().startsWith("-")&&s.set(l,c)}let a=new Set,i=o.map(c=>{let l=Vn(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 Td(t,e){let n=De(e,"name")||t.replace(/\.md$/,""),r=De(e,"description")||"";return`- [${n}](${t})${r?` \u2014 ${r}`:""}`}async function to(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 Ad(t,e){let n=J.join(t,".bostrat","memory"),r=await to(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=J.join(n,l),f;try{f=await j.readFile(u,"utf8")}catch{continue}let{fm:d}=xs(f),m=vd(d,e);if(!m)continue;let h=hn(m),w=J.join(h,l),g=await re(u,f);if(i.has(h)||i.set(h,await Qr(h)),(i.get(h)[l]||0)>=g)continue;let b=await re(w).catch(()=>0);if(await j.access(w).then(()=>!0).catch(()=>!1)&&b>=g)continue;await j.mkdir(h,{recursive:!0}),await j.writeFile(w,f),a++;let A=o.get(h);A||o.set(h,A={won:new Set,generated:new Map}),A.won.add(l),A.generated.set(l,Td(l,d)),s.has(m)||s.set(m,new Set),s.get(m).add(l)}let c=await j.readFile(J.join(n,"MEMORY.md"),"utf8").catch(()=>"");for(let[l,u]of o){let f=J.join(l,"MEMORY.md"),d=await j.readFile(f,"utf8").catch(()=>"");await j.writeFile(f,Ei(d,c,u.won,u.generated))}return{harvested:a,changedByKey:s}}async function tx(){let t=J.join(vi,"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=J.join(t,o.name);if(!await j.access(J.join(s,".bostrat","memory")).then(()=>!0).catch(()=>!1))continue;let[i]=await Ai(s),c=await Ad(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 ex=/^[A-Za-z0-9][A-Za-z0-9._-]*\.md$/,nx=t=>{let e=new Map;for(let n of(t||"").split(`
|
|
20
|
+
`)){let r=Vn(n);r&&n.trim().startsWith("-")&&e.set(r,n)}return e};async function rx(t,e){let n=hn(t),r=await Qr(n),o=new Set,s=new Map,a=[],i=0;for(let c of e||[]){let l=String(c?.file||"");if(!ex.test(l)||l==="MEMORY.md")continue;let u=J.join(n,l),f=await j.access(u).then(()=>!0).catch(()=>!1),d=f?await re(u):0,m=Number(c.modified)||0;if(c.deleted){m>d&&(f&&await j.rm(u,{force:!0}),await Ti(n,[[l,m]]),f&&(a.push(l),i++));continue}f&&d>=m||(r[l]||0)>=m||(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=J.join(n,"MEMORY.md"),l=await j.readFile(c,"utf8").catch(()=>""),u=Ei(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(Vn(d))).join(`
|
|
23
|
+
`)}await j.mkdir(n,{recursive:!0}),await j.writeFile(c,u)}return{changed:i,tombstoned:a.length}}async function _i(t,e=null){let n=hn(t),r=e?.length?e:await to(n);if(!r.length)return{pushed:0,source:"empty"};let o=nx(await j.readFile(J.join(n,"MEMORY.md"),"utf8").catch(()=>"")),s=[];for(let a of r){let i=J.join(n,a),c;try{c=await j.readFile(i,"utf8")}catch{continue}s.push({file:a,content:c,modified:await re(i,c),indexLine:o.get(a)??null})}return bs(t,s)}async function ox(t){let{facts:e,source:n}=await gd(t);e.length?await rx(t,e):n==="plane"&&await _i(t)}function sx(t){let e=t.split(`
|
|
24
|
+
`);e.length>yd&&(e=e.slice(0,yd));let n=e.join(`
|
|
25
|
+
`);for(;Buffer.byteLength(n,"utf8")>Qk&&e.length;)e.pop(),n=e.join(`
|
|
26
|
+
`);return n}var ax=`# 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 ix(t,e){if(!t||!e?.length)return{materialized:!1,reason:"no-repo-key"};let n=J.join(t,".bostrat","memory"),r=0,o=0,s=[];for(let u of e){let f=hn(u),d=await Qr(f);for(let[g,b]of Object.entries(d)){let S=J.join(n,g);await j.access(S).then(()=>!0).catch(()=>!1)&&await re(S)<=b&&(await j.rm(S,{force:!0}),o++)}let m=await to(f);if(!m.length)continue;await j.mkdir(n,{recursive:!0});for(let g of m){let b=J.join(f,g),S=J.join(n,g),A=await re(b),C=await re(S).catch(()=>0);await j.access(S).then(()=>!0).catch(()=>!1)&&C>=A||(await j.copyFile(b,S),r++)}let h=await j.readFile(J.join(f,"MEMORY.md"),"utf8").catch(()=>""),w=u===ke?"## operator memory":`## repo: ${u}`;h.trim()&&s.push(e.length>1?`${w}
|
|
32
32
|
|
|
33
33
|
${h.trim()}`:h.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(m=>!s.has(
|
|
38
|
-
`);await j.writeFile(c,u).catch(()=>{});let f=J.join(o,"MEMORY.md"),d=await j.readFile(f,"utf8").catch(()=>"");await j.writeFile(f,
|
|
39
|
-
`).filter(l=>
|
|
40
|
-
`)).catch(()=>{});let c=await
|
|
41
|
-
`,"utf8");let n=(await
|
|
42
|
-
`).filter(Boolean);n.length
|
|
36
|
+
`,c=sx(i);await j.mkdir(n,{recursive:!0}),await j.writeFile(J.join(n,"MEMORY.md"),c);let l=J.join(n,"claude");return await j.mkdir(l,{recursive:!0}),await j.writeFile(J.join(l,"MEMORY.md"),ax,{flag:"wx"}).catch(()=>{}),{materialized:!0,facts:r,capped:c!==i}}var xd=0,cx=3e4;async function Ed(t){try{let e=await Ai(t);if(!e.length)return{materialized:!1,reason:"no-repo-key"};let n=[...e,ke];if(Date.now()-xd>cx){xd=Date.now(),await lx();let{changedByKey:r}=await tx();for(let[o,s]of r)await _i(o,[...s])}for(let r of n)await ox(r);return await ix(t,n)}catch(e){return{materialized:!1,reason:"error",error:e?.message||String(e)}}}async function lx(){let t=0,e=[];try{e=await j.readdir(ks(),{withFileTypes:!0})}catch{return{moved:t}}for(let n of e){if(!n.isDirectory())continue;let r=J.join(ks(),n.name),o=hn(ke),s=new Set,a=new Map;for(let m of await to(r)){let h=J.join(r,m),w;try{w=await j.readFile(h,"utf8")}catch{continue}let{fm:g}=xs(w);if(vd(g,n.name)!==ke)continue;let b=J.join(o,m),S=await re(b).catch(()=>0),A=await re(h,w);await j.mkdir(o,{recursive:!0}),A>S&&await j.writeFile(b,w),await j.rm(h,{force:!0}),s.add(m),a.set(m,Td(m,g)),t++}if(!s.size)continue;let i=Date.now();await Ti(r,[...s].map(m=>[m,i])),await bs(n.name,[...s].map(m=>({file:m,deleted:!0,modified:i})));let c=J.join(r,"MEMORY.md"),l=await j.readFile(c,"utf8").catch(()=>""),u=l.split(`
|
|
37
|
+
`).filter(m=>!s.has(Vn(m))).join(`
|
|
38
|
+
`);await j.writeFile(c,u).catch(()=>{});let f=J.join(o,"MEMORY.md"),d=await j.readFile(f,"utf8").catch(()=>"");await j.writeFile(f,Ei(d,l,s,a))}return{moved:t}}async function _d(){let t=[],e=[ke];try{for(let n of await j.readdir(ks(),{withFileTypes:!0}))n.isDirectory()&&e.push(n.name)}catch{}for(let n of e){let r=hn(n),o=await to(r),s=await Qr(r);if(!o.length&&!Object.keys(s).length)continue;let a=[];for(let i of o){let c=await j.readFile(J.join(r,i),"utf8").catch(()=>""),{fm:l}=xs(c);a.push({file:i,name:De(l,"name")||i.replace(/\.md$/,""),description:De(l,"description")||"",scope:De(l,"scope")||null,modified:await re(J.join(r,i),c)})}t.push({key:n,facts:a,tombstones:Object.keys(s).length})}return t}async function Id(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=hn(t),r=J.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 Ti(n,[[e,s]]);let a=J.join(n,"MEMORY.md"),i=await j.readFile(a,"utf8").catch(()=>"");await j.writeFile(a,i.split(`
|
|
39
|
+
`).filter(l=>Vn(l)!==e).join(`
|
|
40
|
+
`)).catch(()=>{});let c=await bs(t,[{file:e,deleted:!0,modified:s}]);return{deleted:e,key:t,plane:c.source}}async function Rd(t){try{let e=await Ai(t),{harvested:n,changedByKey:r}=await Ad(t,e[0]||null);for(let[o,s]of r)await _i(o,[...s]);return{harvested:n}}catch(e){return{harvested:0,error:e?.message||String(e)}}}import eo from"node:fs/promises";import ux from"node:os";import Od from"node:path";var dx=[/[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 no(t){let e=String(t??"");for(let n of dx)e=e.replace(n,"\u2039redacted\u203A");return e}var Ii=process.env.BOSTRAT_DEBUG_DIR||Od.join(ux.homedir(),".bostrat","debug"),zn=Od.join(Ii,"handoff.jsonl"),$d=Number(process.env.BOSTRAT_HANDOFF_DEBUG_MAX)||500;function Ss(){let t=process.env.BOSTRAT_HANDOFF_DEBUG;return t==="1"||t==="true"}async function fx(t){try{await eo.mkdir(Ii,{recursive:!0}),await eo.appendFile(zn,`${JSON.stringify(t)}
|
|
41
|
+
`,"utf8");let n=(await eo.readFile(zn,"utf8")).split(`
|
|
42
|
+
`).filter(Boolean);n.length>$d&&await eo.writeFile(zn,`${n.slice(-$d).join(`
|
|
43
43
|
`)}
|
|
44
|
-
`,"utf8")}catch(e){console.warn(`[handoff-debug] failed to write ${
|
|
45
|
-
`).filter(Boolean).slice(-t).map(r=>{try{let o=JSON.parse(r);return typeof o?.paneTail=="string"&&(o.paneTail=
|
|
46
|
-
`).filter(Boolean).map(r=>{let[o,s,a,i,c,l,u,f,d]=r.split("|"),m=Number(i);return{name:o,title:u||null,windows:Number(s),attached:Number(a),lastActivityMs:Number.isFinite(m)?n-m*1e3:null,cwd:c||null,isBostrat:l==="1",sessionId:f||null,agent:d||(l==="1"?"claude":null)}}).filter(r=>!
|
|
47
|
-
`).map(n=>n.trim()).includes(t)}catch{return!1}}async function
|
|
48
|
-
`).filter(Boolean).length}catch{return 0}}async function
|
|
49
|
-
`);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
|
|
50
|
-
`).trim():""}function
|
|
51
|
-
`)){let i=a.trim();if(!i||i[0]!=="{")continue;let c;try{c=JSON.parse(i)}catch{continue}if(c.type==="attachment"){let h=c.attachment;h?.type==="queued_command"&&h.commandMode==="task-notification"&&typeof h.prompt=="string"&&h.prompt.trim()&&o.push({role:"user",text:h.prompt.trim(),ts:c.timestamp?Date.parse(c.timestamp):null,model:null});continue}let l=c.message;if(!l)continue;let u=l.role||c.type;if(u!=="user"&&u!=="assistant"||u==="assistant"&&l.model==="<synthetic>"&&!c.isApiErrorMessage)continue;if(u==="assistant"&&Array.isArray(l.content))for(let h of l.content){let w=h?.type==="tool_use"?h.input?.file_path||h.input?.path:null;typeof w=="string"&&
|
|
52
|
-
|
|
53
|
-
`):""}import
|
|
54
|
-
`);if(!
|
|
55
|
-
`);for(let r=n.length-1;r>=0;r--)if(
|
|
56
|
-
`);return e}function
|
|
57
|
-
`),o=[],s=-1;for(let l=0;l<r.length;l++){let u=r[l].match(
|
|
58
|
-
`),o=r.findIndex(i=>
|
|
59
|
-
`).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
|
|
60
|
-
`),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
|
|
61
|
-
`))
|
|
62
|
-
`)
|
|
63
|
-
|
|
64
|
-
`)
|
|
44
|
+
`,"utf8")}catch(e){console.warn(`[handoff-debug] failed to write ${zn}: ${e?.message||e}`)}}async function G(t={}){let e={at:new Date().toISOString(),ts:Date.now(),...t},n=[e.flow||"handoff",e.phase||e.step||"?",e.trackId?`track=${e.trackId}`:"",e.sessionName?`box=${e.sessionName}`:"",e.error?`ERR=${e.error}`:""].filter(Boolean).join(" \xB7 ");if(console.log(`[handoff-debug] ${n}`),e.sessionName&&Ss())try{e.paneTail=no((await gt(e.sessionName,{lines:35})).slice(-4e3))}catch{e.paneTail=null}(Ss()||e.error||/^(checkpoint|spawn|hydrate|commit|done|summarize|clear|resume|ready|send|failed)/.test(String(e.phase||e.step||"")))&&await fx(e)}async function Md({limit:t=80}={}){try{return(await eo.readFile(zn,"utf8")).split(`
|
|
45
|
+
`).filter(Boolean).slice(-t).map(r=>{try{let o=JSON.parse(r);return typeof o?.paneTail=="string"&&(o.paneTail=no(o.paneTail)),o}catch{return{raw:no(r)}}})}catch{return[]}}var Cd={DEBUG_DIR:Ii,LOG_FILE:zn};var ut=mx(px);function ro(t){return new Promise(e=>setTimeout(e,t))}function vs(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 N=process.env.BOSTRAT_TMUX_BIN||"/usr/bin/tmux",jt=process.env.BOSTRAT_TMUX_SOCKET||"/tmp/tmux-1000/default",wx=process.env.BOSTRAT_DEFAULT_CWD||process.env.HOME||"/",$i=process.env.BOSTRAT_AGENT_CMD||"claude --dangerously-skip-permissions",Oi="bostrat-usage-probe",gx=new Set([Oi]);function Mi(t,e){let n=Jn(t),r=/(^|\s)(--session-id|--resume|--continue|-r|-c)(\s|=|$)/.test(t);return!n||r?t:`${t} --session-id ${e}`}function Jn(t){return/(^|\/|\s)claude(\s|$)/.test(String(t||""))}function Xn(t){return/(^|\/|\s)claude(\s|$)/.test(t)?"claude":/cursor/.test(t)?"cursor":/codex/.test(t)?"codex":/gemini/.test(t)?"gemini":"agent"}async function Ci(t,{loginShell:e=!1,failOpen:n=!1}={}){if(!t)return!1;let r=e?process.env.SHELL||"/bin/sh":"sh",o=e?"-lic":"-c";try{return await ut(r,[o,'command -v "$1" >/dev/null 2>&1',"_",t],{timeout:5e3}),!0}catch(s){return s?.code===1?!1:n}}async function Ts(t){return Ci(String(t||"").trim().split(/\s+/)[0])}var Ni={claude:"npm install -g @anthropic-ai/claude-code",codex:"npm install -g @openai/codex","cursor-agent":"curl https://cursor.com/install -fsS | bash"};function B(...t){return["-S",jt,...t]}async function je(){if(!Ri.existsSync(jt))return!1;try{return await ut(N,B("list-sessions")),!0}catch(t){let e=t&&t.stderr?String(t.stderr):"";return!/no server running/i.test(e)}}async function oo(){if(await je())return!0;try{Ri.mkdirSync(jt.replace(/\/[^/]+$/,""),{recursive:!0}),await ut(N,B("new-session","-d","-s","keeper","sleep 2147483647"))}catch{}return await je()}var yx=/^[A-Za-z0-9_.-]{1,64}$/;function yt(t){return typeof t=="string"&&yx.test(t)}async function wn(){if(!Ri.existsSync(jt))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 ut(N,B("list-sessions","-F",t)),n=Date.now();return e.trim().split(`
|
|
46
|
+
`).filter(Boolean).map(r=>{let[o,s,a,i,c,l,u,f,d]=r.split("|"),m=Number(i);return{name:o,title:u||null,windows:Number(s),attached:Number(a),lastActivityMs:Number.isFinite(m)?n-m*1e3:null,cwd:c||null,isBostrat:l==="1",sessionId:f||null,agent:d||(l==="1"?"claude":null)}}).filter(r=>!gx.has(r.name)&&!r.name.startsWith("bostrat-view-"))}catch{return[]}}async function Y(t){return(await wn()).find(n=>n.name===t)||null}var bx=/^[A-Za-z0-9 ._↻-]+$/,kx=64;function xx(t){if(typeof t!="string")return!1;let e=t.trim();return e.length>=1&&e.length<=kx&&bx.test(e)}function Pi(t){return t.trim().replace(/\s+/g,"-").toLowerCase().replace(/[^A-Za-z0-9_.-]/g,"").replace(/^-+|-+$/g,"")}async function Sx(t){try{let{stdout:e}=await ut(N,B("list-sessions","-F","#{session_name}"));return e.split(`
|
|
47
|
+
`).map(n=>n.trim()).includes(t)}catch{return!1}}async function gn(t,e=wx,n={}){if(!xx(t))throw new Error("displayName must be 1\u201364 chars (letters, digits, space . _ -)");let r=t.trim(),o=Pi(r);if(!yt(o))throw new Error("resulting tmux session name is invalid");if(!await oo())throw new Error(`host tmux server not running on ${jt} and auto-start failed \u2014 start one: ${N} -S ${jt} new-session -d -s keeper "sleep 2147483647"`);if(await Sx(o))throw new Error(`tmux session "${o}" already exists`);let s=n.agentCmd||$i,a=n.agent||Xn(s),i=!!n.resumeSessionId,c=n.resumeSessionId||hx(),l=i?s:Mi(s,c),u=i?n.pinned===!0:/(^|\s)--session-id(\s|=)/.test(l),f=a==="claude"&&Jn(s),d=n.firstRun===!0;d||(f&&await Pe(e),a==="codex"&&await qn(e),a==="cursor"&&await Kn(e),await Ed(e));let m=f&&!d?hs():null,h=ed(),w=["-e",`${fs}=${h}`];m&&w.push("-e",`ANTHROPIC_API_KEY=${m}`);try{await ut(N,B("new-session","-d","-s",o,"-c",e,...w))}catch(g){throw vs(m?vs(g,m):g,h)}return await yn(o),await ut(N,B("set-option","-t",o,"@bostrat-session","1")),await ut(N,B("set-option","-t",o,"@bostrat-title",r)),await ut(N,B("set-option","-t",o,"@bostrat-sid",c)),await ut(N,B("set-option","-t",o,"@bostrat-agent",a)),await ut(N,B("send-keys","-t",o,l,"Enter")),{name:o,title:r,cwd:e,sessionId:c,agent:a,pinned:u,hookToken:h}}async function oe(t){if(!yt(t))throw new Error("invalid session name");await ut(N,B("kill-session","-t",t))}async function Nd(t,e){if(yt(t))try{let n=e?B("set-option","-t",t,"@bostrat-sid",e):B("set-option","-u","-t",t,"@bostrat-sid");await ut(N,n)}catch{}}async function gt(t,{lines:e=40}={}){if(!yt(t))return"";try{let n=B("capture-pane","-t",t,"-p");Number.isFinite(e)&&e>0&&n.push("-S",`-${Math.floor(e)}`);let{stdout:r}=await ut(N,n);return String(r||"")}catch{return""}}var vx=Number(process.env.BOSTRAT_PROMPT_CAPTURE_COLS)||100,Tx=Number(process.env.BOSTRAT_PROMPT_CAPTURE_ROWS)||40,Tt=Number(process.env.BOSTRAT_PROMPT_CAPTURE_LINES)||80,Ax=Number(process.env.BOSTRAT_BOX_COLS)||120,Ex=Number(process.env.BOSTRAT_BOX_ROWS)||40;async function Pd(t){if(!yt(t))return 0;try{let{stdout:e}=await ut(N,B("list-clients","-t",t,"-F","1"));return String(e||"").trim().split(`
|
|
48
|
+
`).filter(Boolean).length}catch{return 0}}async function yn(t,{cols:e=Ax,rows:n=Ex}={}){if(yt(t)&&!await Pd(t))try{let r=await Dd(t);if(r.cols===e&&r.rows===n)return;await ut(N,B("resize-window","-t",t,"-x",String(e),"-y",String(n))),await ut(N,B("set-window-option","-t",t,"window-size","latest")),await ro(80)}catch{}}async function Bd(t){if(!yt(t))return 0;try{let{stdout:e}=await ut(N,B("display-message","-t",t,"-p","#{pane_pid}"));return Number(String(e||"").trim())||0}catch{return 0}}async function Dd(t){if(!yt(t))return{cols:0,rows:0};try{let{stdout:e}=await ut(N,B("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 As(t,{cols:e=vx,rows:n=Tx}={}){if(yt(t)&&!await Pd(t))try{let r=await Dd(t),o=Math.max(r.cols,e),s=Math.max(r.rows,n);(o>r.cols||s>r.rows)&&(await ut(N,B("resize-window","-t",t,"-x",String(o),"-y",String(s))),await ut(N,B("set-window-option","-t",t,"window-size","latest")),await ro(80))}catch{}}async function Le(t){return await As(t),gt(t,{lines:Tt})}var _x=new Set(["Up","Down","Left","Right","Space","Enter","Tab","BTab","Escape","BSpace"]);async function xt(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=>_x.has(s));for(let s=0;s<o.length;s++)await ut(N,B("send-keys","-t",t,o[s])),n>0&&s<o.length-1&&await ro(n);return G({flow:"tmux",phase:"send-keys",sessionName:t,kind:r||"keys",keys:o.join(" ")}).catch(()=>{}),o.length}async function Es(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 ut(N,B("send-keys","-t",t,"-N",String(r),"BSpace")),G({flow:"tmux",phase:"send-keys",sessionName:t,kind:n||"wipe",keys:`BSpace x${r}`}).catch(()=>{}),r}async function _s(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 ut(N,B("send-keys","-t",t,"-l",s)),n>0&&await ro(n);G({flow:"tmux",phase:"type",sessionName:t,kind:r||"type",textLen:o.length,textHead:String(e).slice(0,120)}).catch(()=>{})}async function vt(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 ut(N,B("send-keys","-t",t,"-l",e)),n){let s=Number(r);Number.isFinite(s)&&s>0&&await ro(s),await ut(N,B("send-keys","-t",t,"Enter"))}G({flow:"tmux",phase:"send",sessionName:t,kind:o||"text",textLen:typeof e=="string"?e.length:0,textHead:typeof e=="string"?e.slice(0,120):"",enter:n,submitDelayMs:r}).catch(()=>{})}import Ix from"node-pty";async function Ld(t,e,n={}){if(!yt(e)){Is(t,`invalid session name: ${e}`),t.close(1008,"invalid session name");return}if(!await je()){Is(t,`host tmux server not running on ${jt}`),t.close(1011,"tmux server unavailable");return}if(!await Y(e)){Is(t,`session "${e}" not found`),t.close(1008,"session not found");return}let r=jd(n.cols,120,Fd),o=jd(n.rows,32,Ud),s;try{s=Ix.spawn(N,["-S",jt,"attach-session","-d","-t",e],{name:"xterm-256color",cols:r,rows:o,cwd:process.env.HOME||"/",env:{...process.env,TERM:"xterm-256color"}})}catch(a){Is(t,`failed to spawn tmux attach: ${a.message}`),t.close(1011,"spawn failed");return}s.onData(a=>{t.readyState===t.OPEN&&Bi(t,{type:"data",data:a})}),s.onExit(({exitCode:a,signal:i})=>{t.readyState===t.OPEN&&(Bi(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"&&Rx(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{}yn(e).catch(()=>{})})}var Fd=20,Ud=6;function Rx(t,e){return Number.isFinite(Number(t))&&Number.isFinite(Number(e))&&Number(t)>=Fd&&Number(e)>=Ud}function jd(t,e,n){let r=Number(t);return!Number.isFinite(r)||r<n?e:Math.min(500,r|0)}function Bi(t,e){try{t.send(JSON.stringify(e))}catch{}}function Is(t,e){Bi(t,{type:"error",message:e})}function Hd(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 so from"node:fs";import $x from"node:fs/promises";import Yn from"node:path";import Ox from"node:os";var Wd={"claude-opus-4-8":1e6,"claude-opus-4-7":1e6,"claude-opus-4-6":1e6,"claude-sonnet-4-6":1e6,"claude-haiku-4-5":2e5,"claude-fable-5":1e6},Mx=2e5,Di=process.env.BOSTRAT_CLAUDE_PROJECTS||Yn.join(Ox.homedir(),".claude","projects");function ji(t){return t.replace(/[^A-Za-z0-9]/g,"-")}function Li(t){let e=Yn.join(Di,ji(t)),n;try{n=so.readdirSync(e).filter(o=>o.endsWith(".jsonl"))}catch{return null}let r=null;for(let o of n){let s=Yn.join(e,o);try{let{mtimeMs:a}=so.statSync(s);(!r||a>r.mtimeMs)&&(r={full:s,mtimeMs:a})}catch{}}return r?r.full:null}function xe(t,e){if(!e)return null;let n=Yn.join(Di,ji(t),`${e}.jsonl`);return so.existsSync(n)?n:null}function qd(t){if(!t)return[];let e=Yn.join(Di,ji(t)),n;try{n=so.readdirSync(e).filter(o=>o.endsWith(".jsonl"))}catch{return[]}let r=[];for(let o of n)try{let s=so.statSync(Yn.join(e,o));r.push({id:o.slice(0,-6),birthMs:s.birthtimeMs||s.ctimeMs||s.mtimeMs})}catch{}return r}async function Ot(t,e=131072){let n=await $x.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 Cx(t){let e=t.split(`
|
|
49
|
+
`);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 ao(t,e=null,{fallbackNewest:n=!0}={}){if(!t)return null;let r,o;if(e){if(r=xe(t,e),o=!0,!r)return null}else if(n){if(r=Li(t),o=!1,!r)return null}else return null;let s;try{s=Cx(await Ot(r))}catch{return null}if(!s)return null;let a=Object.prototype.hasOwnProperty.call(Wd,s.model),i=Wd[s.model]||Mx;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 Nx(t,e){return!t||!e?null:xe(t,e)}function Px(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(`
|
|
50
|
+
`).trim():""}function Bx(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 Dx(t){return Array.isArray(t)&&t.length>0&&t.every(e=>e&&e.type==="tool_result")}function Fi(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 Kd(t,e){if(!t.uuid)return null;let n=Fi(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 jx=/\.(png|jpe?g|gif|webp|bmp|svg|tiff?|avif|heic|heif|ico)$/i;async function Ui(t,{maxBytes:e=524288,limit:n=80}={}){let r;try{r=await Ot(t,e)}catch{return[]}let o=[],s=new Map;for(let a of r.split(`
|
|
51
|
+
`)){let i=a.trim();if(!i||i[0]!=="{")continue;let c;try{c=JSON.parse(i)}catch{continue}if(c.type==="attachment"){let h=c.attachment;h?.type==="queued_command"&&h.commandMode==="task-notification"&&typeof h.prompt=="string"&&h.prompt.trim()&&o.push({role:"user",text:h.prompt.trim(),ts:c.timestamp?Date.parse(c.timestamp):null,model:null});continue}let l=c.message;if(!l)continue;let u=l.role||c.type;if(u!=="user"&&u!=="assistant"||u==="assistant"&&l.model==="<synthetic>"&&!c.isApiErrorMessage)continue;if(u==="assistant"&&Array.isArray(l.content))for(let h of l.content){let w=h?.type==="tool_use"?h.input?.file_path||h.input?.path:null;typeof w=="string"&&jx.test(w)&&s.set(h.id,w.split("/").pop())}if(Dx(l.content)){let h=Kd(c,s);h&&o.push({role:"assistant",text:"",images:h,ts:c.timestamp?Date.parse(c.timestamp):null,model:null});continue}let f=Px(l.content),d=u==="assistant"?Bx(l.content):null,m=u==="user"?Kd(c,s):null;!f&&!d&&!m||o.push({role:u,text:f,tools:d||void 0,images:m||void 0,ts:c.timestamp?Date.parse(c.timestamp):null,model:l.model||null})}return o.slice(-n)}var Lx=(t,e)=>t.length>e?`${t.slice(0,e).trimEnd()}\u2026`:t;async function Gd(t,e,{turns:n=16}={}){let r=Nx(t,e);if(!r)return"";let o=await Ui(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}:** ${Lx(s.text||c,700)} ${i}`.trim()}).join(`
|
|
52
|
+
|
|
53
|
+
`):""}import P0 from"node:fs";import er from"node:fs/promises";import nr from"node:path";import Fx from"node:fs/promises";import{execFile as Ux}from"node:child_process";import{promisify as Hx}from"node:util";var Hi=Hx(Ux),Wx=Number(process.env.BOSTRAT_CODEX_UPDATE_TIMEOUT_MS)||5*6e4,Vd=new Set,zd=Promise.resolve(),Wi=null;function Xd(t){Wi=typeof t=="function"?t:null}function qx(t){let e=String(t||""),n=e.indexOf("/lib/node_modules/");return n>0?e.slice(0,n):null}function Yd({sessionName:t,from:e,to:n}={}){let r=String(n||"").trim();!r||Vd.has(r)||(Vd.add(r),zd=zd.then(()=>Kx({sessionName:t,from:e,to:r})).catch(()=>{}))}async function Kx({sessionName:t,from:e,to:n}){await G({flow:"codex-update",phase:"start",sessionName:t,from:e,to:n}).catch(()=>{});try{let r=(await Hi("/bin/sh",["-c","command -v codex"])).stdout.trim();if(!r)throw new Error("codex not on PATH");let o=await Fx.realpath(r),s=qx(o),a=s?{...process.env,PATH:`${s}/bin:${process.env.PATH||""}`}:process.env;await Hi("npm",["install","-g","@openai/codex"],{env:a,timeout:Wx});let i=(await Hi(r,["--version"])).stdout.trim().replace(/^codex-cli\s*/,"")||n;await G({flow:"codex-update",phase:"done",sessionName:t,from:e,to:n,version:i}).catch(()=>{}),await Jd({sessionName:t,from:e,to:n,version:i,status:"updated"})}catch(r){let o=String(r?.message||r).slice(0,300);await G({flow:"codex-update",phase:"failed",sessionName:t,from:e,to:n,error:o}).catch(()=>{}),await Jd({sessionName:t,from:e,to:n,status:"failed",error:o})}}async function Jd(t){if(Wi)try{await Wi(t)}catch{}}var Gx=/^\s*[›>*\-❯]?\s*(\d+)[.)]\s+(.+?)\s*$/,Zd=/\b(allow|approve|apply (this )?patch|run (this )?command|do you want to run|grant)\b/i;function Vx(t,{provider:e,box:n}){let r=String(t).replace(/\r/g,"").split(`
|
|
54
|
+
`);if(!Zd.test(t))return null;let o=[],s=!1;for(let l=r.length-1;l>=0;l--){let u=r[l].match(Gx);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"&&Zd.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 Rs=/(^|\n)\s*›/,ef=/^\s*Welcome to Codex\b/;function Zn(t){let e=String(t??"").replace(/\r/g,""),n=e.split(`
|
|
55
|
+
`);for(let r=n.length-1;r>=0;r--)if(ef.test(n[r]))return n.slice(r).join(`
|
|
56
|
+
`);return e}function zx(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 Jx(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 Qd=/^\s*(>)?\s*(\d+)\.\s+(.+?)\s*$/;function Xx(t,e=""){let n=Zn(t);if(Rs.test(n)||!/Sign in with ChatGPT/.test(n)||!/Press enter to continue/.test(n))return null;let r=n.split(`
|
|
57
|
+
`),o=[],s=-1;for(let l=0;l<r.length;l++){let u=r[l].match(Qd);if(!u)continue;s<0&&(s=l);let f="";for(let d=l+1;d<Math.min(l+3,r.length);d++){let m=r[d].trim();if(m){if(Qd.test(r[d])||/^Press enter/.test(m))break;f=m;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&&!ef.test(r[l])&&zx(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=Jx(l.label);return{label:l.label,description:[l.desc,u].filter(Boolean).join(" \xB7 ")}})}]}}var tf=/Enter this one-time code/,Yx=/^\s*([A-Z0-9]{2,10}-[A-Z0-9]{2,10})\s*$/;function Zx(t,e=""){let n=Zn(t);if(Rs.test(n)||!tf.test(n))return null;let r=n.split(`
|
|
58
|
+
`),o=r.findIndex(i=>tf.test(i)),s="";for(let i=o+1;i<r.length;i++){let c=r[i].match(Yx);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 Qx(t,e=""){let n=Zn(t);return Rs.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 t0(t,e=""){let n=Zn(t);return Rs.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 qi=/Run Everything|Add a follow-up|Plan, search, build anything/;function e0(t,e=""){let n=String(t);if(!/Press any key to log in/.test(n)||qi.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
59
|
+
`).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 n0=/click this link to log in|Signing in with the browser/;function r0(t,e=""){let n=String(t);if(!n0.test(n)||qi.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
60
|
+
`),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 o0(t,e=""){let n=String(t);return!/Scan this QR code to log in/.test(n)||qi.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 nf(t,e,n=""){if(t!=="cursor"&&t!=="codex")return null;if(t==="codex"){let r=Zx(e,n)||Qx(e,n)||t0(e,n)||Xx(e,n);if(r)return r}if(t==="cursor"){let r=r0(e,n)||o0(e,n)||e0(e,n);if(r)return r}return Vx(e,{provider:t,box:n})}function rf(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 s0=()=>Number(process.env.BOSTRAT_AGENT_READY_TIMEOUT_MS)||9e4,Ki=Number(process.env.BOSTRAT_AGENT_READY_POLL_MS)||500,io=Number(process.env.BOSTRAT_AGENT_READY_SETTLE_MS)||800,At={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/}},a0=/Trust this workspace|\[a\]\s*Trust/,i0=/Update available!\s*([\d][\w.-]*)\s*->\s*([\d][\w.-]*)/;function c0(t){let e=String(t||"");if(!At.codex.block.test(e))return null;let n=Zn(e);if(!At.codex.block.test(n))return null;let r=n.match(i0);return{from:r?.[1]||null,to:r?.[2]||null}}function Fe(t){return new Promise(e=>setTimeout(e,t))}var l0=()=>Number(process.env.BOSTRAT_GATE_RESEND_MS)||3e3,$s=new Map;function of(t,e){let n=Date.now(),r=l0();for(let[s,a]of $s)n-a>=r&&$s.delete(s);let o=`${t}::${e}`;return $s.has(o)?!1:($s.set(o,n),!0)}var u0=68,d0=/^\s*[─━]{10,}\s*$/;function f0(t){let e=0;for(let n of String(t).split(`
|
|
61
|
+
`))d0.test(n)&&(e=Math.max(e,n.trimEnd().length));return e}function Gi(t,e){let n=At[t];if(!n?.busy)return"idle";if(Vi(t,e))return"unknown";if(n.up&&!n.up.test(e))return"idle";if(n.busy.test(e)||n.busyAlt&&n.busyAlt.test(e))return"busy";let r=f0(e);return t==="claude"&&r>0&&r<u0?"unknown":"idle"}var p0=/\bWaiting for \d+\b[^\n]*\bto finish\b|\/workflows to (?:stop|monitor)\b/,m0=15;function af(t,e){if(t!=="claude")return!1;let n=String(e||"").split(`
|
|
62
|
+
`).slice(-m0).join(`
|
|
63
|
+
`);return p0.test(n)}function Ue(t,e){return Gi(t,e)==="busy"}function Os(t,e){return Gi(t,e)!=="idle"}function Ms(t,e){let n=At[t];return n?.up?n.up.test(e):!0}function h0(t,e){let n=At[t];return n?Vi(t,e)?!1:n.up.test(e)&&Gi(t,e)==="idle":!0}function Vi(t,e){let n=At[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 sf(t,e,n){let r=Date.now()+n,o=0;for(await yn(t).catch(()=>{});;){let s=await gt(t);if(h0(e,s)){await Fe(io);return}if(e==="cursor"&&a0.test(s)&&!At.cursor.up.test(s)&&o<3){if(!of(t,"cursor-trust")){await Fe(Ki);continue}o+=1,await G({flow:"agent-ready",phase:"gate-accept",sessionName:t,agent:e,attempt:o}).catch(()=>{}),await vt(t,"a",{enter:!1,kind:"cursor-trust-accept"}),await Fe(io);continue}if(e==="codex"&&o<3){let a=c0(s);if(a){if(!of(t,`codex-update:${a.from}->${a.to}`)){await Fe(Ki);continue}o+=1,await G({flow:"agent-ready",phase:"gate-accept",sessionName:t,agent:e,attempt:o,update:`${a.from} -> ${a.to}`}).catch(()=>{}),await vt(t,"3",{enter:!1,kind:"codex-update-skip"}),Yd({sessionName:t,from:a.from,to:a.to}),await Fe(io);continue}}if(Date.now()>r){if(Vi(e,s)){let a=`${e} is blocked on an interactive prompt`;throw e==="codex"?a=At.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"&&At.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"&&At.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"&&At.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'),G({flow:"agent-ready",phase:"blocked",sessionName:t,agent:e,timeoutMs:n,gateAccepts:o,error:a}).catch(()=>{}),new Error(a)}console.warn(`[agent-ready] ${e} readiness unconfirmed in ${t} after ${n}ms \u2014 driving the box anyway.`),G({flow:"agent-ready",phase:"timeout",sessionName:t,agent:e,timeoutMs:n,gateAccepts:o,paneBlocked:!!At[e]?.block?.test(s),paneUp:!!At[e]?.up?.test(s),paneBusy:!!At[e]?.busy?.test(s)}).catch(()=>{}),await Fe(io);return}await Fe(Ki)}}function cf({agent:t,agentCmd:e}={}){let n=e?Xn(e):Xn(t||"");return{label:n,realClaude:e?Jn(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 Cs({cwd:t,sessionName:e,agent:n,agentCmd:r}={}){let{realCodex:o,realCursor:s}=cf({agent:n,agentCmd:r});return!e||!o&&!s?null:He({cwd:t,sessionName:e,agent:n,agentCmd:r}).catch(()=>{})}async function He({cwd:t,sessionId:e,sessionName:n,agent:r,agentCmd:o,timeoutMs:s=s0()}={}){let{label:a,realClaude:i,realCodex:c,realCursor:l}=cf({agent:r,agentCmd:o});if(i&&t&&await Pe(t),c&&t&&await qn(t),l&&t&&await Kn(t),(i||c||l)&&n){await sf(n,a,s);return}let u=At[r]?r:At[a]?a:null;if(process.env.BOSTRAT_FAKE_POLL_READY==="1"&&n&&u){await sf(n,u,s);return}await Fe(io)}var w0=(()=>{let t=Number(process.env.BOSTRAT_CODEX_SUBMIT_SETTLE_MS??120);return Number.isFinite(t)&&t>=0?t:120})(),g0=(()=>{let t=Number(process.env.BOSTRAT_CLAUDE_SUBMIT_SETTLE_MS??120);return Number.isFinite(t)&&t>=0?t:120})();function Se(t){return t==="codex"?{enter:!0,submitDelayMs:w0}:t==="claude"?{enter:!0,submitDelayMs:g0}:{enter:!0}}import Ps from"node:fs/promises";import mf from"node:path";var y0=["bug-fix","orchestration","design","ops-infra","docs-writing","research","feature-build"],b0={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 Qn(t){let e=String(t||"").toLowerCase();if(!e.trim())return null;let n=null;for(let r of y0){let o=0;for(let s of b0[r])s.test(e)&&o++;o>0&&(!n||o>n.score)&&(n={category:r,score:o})}return n}var k0=new Set(["Read","Grep","Glob","LS","WebSearch","WebFetch","NotebookRead"]),x0=new Set(["Edit","Write","MultiEdit","NotebookEdit"]);function lf(t){let e=0,n=0;for(let[r,o]of Object.entries(t||{}))k0.has(r)&&(e+=o||0),x0.has(r)&&(n+=o||0);return e>=5&&n===0?{category:"research",score:1}:n>=3?{category:"feature-build",score:1}:null}function co(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 uf=1.5,df=.75,S0=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 pf(t){let e=co(String(t||"")).toLowerCase(),n=[];for(let r of e.split(/[^a-z0-9]+/))r.length<2||S0.has(r)||n.push(r);return n}function v0(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(ff(n,"user",o))}else if(n.type==="turn.agent"){if(!o)continue;e.push(ff(n,"assistant",o))}}return e}function ff(t,e,n){let r=Qn(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 lo(t){let e=v0(t),n=new Map,r=new Array(e.length).fill(0),o=0;e.forEach((a,i)=>{let c=pf(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 Ns(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(pf(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,m=Math.log(1+(t.N-d+.5)/(d+.5));for(let[h,w]of f){let g=t.lengths[h],b=w+uf*(1-df+df*(g/(t.avgdl||1))),S=m*(w*(uf+1)/b);c.set(h,(c.get(h)||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 hf="Relevant prior discussion (verbatim, retrieved";async function T0(t){let e=mf.join(t,".bostrat","events"),n;try{n=await Ps.readdir(e)}catch{return lo([])}let r=[];for(let o of n)if(o.endsWith(".ndjson"))try{let s=await Ps.readFile(mf.join(e,o),"utf8");for(let a of s.split(`
|
|
64
|
+
`))if(a.trim())try{r.push(JSON.parse(a))}catch{}}catch{}return lo(r)}function A0(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}
|
|
65
|
+
${o}`.trim()||e.slice(0,2e3)}function E0(t,e,{k:n=6,maxChars:r=4e3,exclude:o=null}={}){let s=Ns(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?["",`## ${hf}, 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(`
|
|
66
|
+
`):""}async function wf(t,e,n={}){let{k:r=6,maxChars:o=4e3,exclude:s=null}=n;try{let a=await Ps.readFile(e,"utf8");if(a.includes(hf))return{appended:!1,reason:"already-enriched"};let i=await T0(t);if(!i.N)return{appended:!1,reason:"empty-index"};let c=E0(i,A0(a),{k:r,maxChars:o,exclude:s});return c?(await Ps.appendFile(e,`
|
|
65
67
|
`+c),{appended:!0,chars:c.length,chunks:c.split(`
|
|
66
|
-
- `).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
|
|
67
|
-
`),Wt.emit("event",i),Wt.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"?
|
|
68
|
-
`))if(a.trim())try{s.push(JSON.parse(a))}catch{}return s}function
|
|
69
|
-
|
|
68
|
+
- `).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 zi from"node:fs/promises";import tr from"node:path";import _0 from"node:os";import{EventEmitter as I0}from"node:events";import{randomUUID as R0}from"node:crypto";var $0=new Set(["session.ready","turn.user","turn.agent","turn.retracted","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","provider.connected","track.summary.recorded","changelog.published","stall.detected","error"]),O0=new Set(["user","agent","orchestrator","system"]),M0=new Set(["hook","otel","statusline","transcript","pane","orchestrator","vendor"]),Wt=new I0;Wt.setMaxListeners(0);var Ji=process.env.BOSTRAT_STATE_DIR||tr.join(process.env.HOME||_0.tmpdir(),".bostrat");function Ds(t){return t?.cwd?tr.join(t.cwd,".bostrat","events"):tr.join(Ji,"events")}var gf=(t,e)=>tr.join(t,`${e}.ndjson`),Bs=new Map;async function C0(t,e){if(!Bs.has(e)){let o=(await L(t,e)).reduce((s,a)=>a.seq>s?a.seq:s,0);Bs.set(e,o)}let n=Bs.get(e)+1;return Bs.set(e,n),n}function N0(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 $(t,e){let n=typeof t=="string"?t:t?.id;if(!n)throw new Error("appendEvent: a track (or trackId) is required");if(!e||!$0.has(e.type))throw new Error(`appendEvent: unknown event type "${e?.type}"`);if(e.actor&&!O0.has(e.actor))throw new Error(`appendEvent: bad actor "${e.actor}"`);if(e.source&&!M0.has(e.source))throw new Error(`appendEvent: bad source "${e.source}"`);let r=typeof t=="string"?tr.join(Ji,"events"):Ds(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=N0(c,Number(e.payload?.ts??e.ts)||Date.now())}let s=await C0(r,n),a=e.userId??process.env.BOSTRAT_OWNER??null,i={id:e.id||R0(),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 zi.mkdir(r,{recursive:!0}),await zi.appendFile(gf(r,n),JSON.stringify(i)+`
|
|
69
|
+
`),Wt.emit("event",i),Wt.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"?tr.join(Ji,"events"):Ds(t));let o;try{o=await zi.readFile(gf(n,r),"utf8")}catch{return[]}let s=[];for(let a of o.split(`
|
|
70
|
+
`))if(a.trim())try{s.push(JSON.parse(a))}catch{}return s}function yf(t,e){let n=`track:${t}`;return Wt.on(n,e),()=>Wt.off(n,e)}var Sf=".bostrat/handoff",vf="latest.md",bf=32*1024;function B0(t){let e=no(String(t??"")),n=Buffer.byteLength(e,"utf8");return n<=bf?{content:e,bytes:n,truncated:!1}:{content:`${Buffer.from(e,"utf8").subarray(0,bf).toString("utf8").replace(/�+$/,"")}
|
|
71
|
+
|
|
72
|
+
[\u2026 truncated by bostrat at 32 KiB \u2014 full artifact in the workspace]`,bytes:n,truncated:!0}}var Tf="Checkpoint: write a work-in-progress handoff",Af="HANDOFF WRITTEN",Ef="Resume from .bostrat/handoff/latest.md",D0="/clear",j0=t=>`${Tf} 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 ${Af} and take no further action.`,L0=`${Ef}: read it, restate the Objective and the single next step in one line, then continue the work from 'Next steps'.`,_f="Beat: write a",If="BEAT WRITTEN",F0=".bostrat/arc",U0="beat.txt",kf=200,Rf=4,$f=5,H0=36,W0=t=>`${_f} 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 ${Rf} further lines, each starting with "- " and naming ONE piece in at most ${$f} 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 ${If} and take no further action.`,xf=/^(?:[-–—*•·]|\d+[.)])\s+/,q0=(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 K0(t){let e=String(t||"").split(/\r?\n/).map(s=>s.trim()).filter(Boolean),n=[],r=[];for(let s of e)if(xf.test(s)){if(r.length>=Rf)continue;let a=q0(s.replace(xf,"").replace(/[.;,:]+$/,""),$f,H0);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>kf&&(o=`${o.slice(0,kf-1).trimEnd()}\u2026`),{text:o,items:r.length>1?r:[]}}async function Of(t,e,{timeoutMs:n=9e4}={}){let r=nr.join(e,F0),o=nr.join(r,U0);await er.mkdir(r,{recursive:!0});let s=await js(o),a=await Y(t);if(!a)throw new Error(`session "${t}" not found`);return await Ls(t,a,W0(o)),await Lf(o,s,n),K0(await er.readFile(o,"utf8"))}function Mf(t,e){let n=(e||"").trim();return n?t==="assistant"?n===Af||n===If:n.startsWith(Tf)||n===D0||n.startsWith(Ef)||n.startsWith(_f):!1}var G0=6e5;function Cf(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-G0;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 Nf(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 Pf(t,{windows:e=[]}={}){if(Mf(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 V0=3500,z0=3e5,Xi=new Map;function Fs(t){return Xi.get(t)||null}async function Bf(t){let e=Xi.get(t);if(e&&!e.finishedAt)throw new Error("a handoff is already running for this session");let n=await Y(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:nr.join(n.cwd,Sf,vf)};return Xi.set(t,r),J0(r,n.cwd).catch(o=>{r.error=o?.message||String(o),r.finishedAt=Date.now()}),r}async function J0(t,e){t.step="summarize",await Df(t.name,e),t.step="clear";let n=await Y(t.name);await Ls(t.name,n,"/clear"),await Ff(V0),t.step="resume",await Ls(t.name,n,L0),t.step="done",t.finishedAt=Date.now()}async function Df(t,e){let n=nr.join(e,Sf),r=nr.join(n,vf);await er.mkdir(n,{recursive:!0});let o=await js(r),s=await Y(t);await G({flow:"checkpoint",phase:"summarize-start",sessionName:t,cwd:e,file:r,beforeMtime:o}),await Ls(t,s,j0(r)),await Lf(r,o,z0),await G({flow:"checkpoint",phase:"summarize-done",sessionName:t,file:r,afterMtime:await js(r)});let a=await wf(e,r,{exclude:c=>Mf(c.role,c.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 $({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 c=await er.readFile(r,"utf8");await $({id:i,cwd:e},{type:"handoff.artifact",actor:"orchestrator",payload:{...B0(c),ts:Date.now()}})}catch{}}return await Rd(e),await X0(n,r),r}async function jf(t){let e=await Y(t);if(!e)throw new Error(`session "${t}" not found`);if(!e.cwd)throw new Error(`can't resolve cwd for "${t}"`);return Df(t,e.cwd)}async function js(t){try{return(await er.stat(t)).mtimeMs}catch{return 0}}async function Lf(t,e,n){let r=Date.now()+n;for(;;){if(await js(t)>e)return;if(Date.now()>r)throw new Error(`timed out waiting for the handoff file to be written (${t})`);await Ff(2e3)}}async function X0(t,e){try{if(!P0.existsSync(e))return;let n=new Date().toISOString().replace(/[:.]/g,"-");await er.copyFile(e,nr.join(t,`${n}.md`))}catch{}}function Ff(t){return new Promise(e=>setTimeout(e,t))}async function Ls(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 He({cwd:e.cwd,sessionId:e.sessionId,sessionName:t,agent:e.agent,agentCmd:o})}let r=await Y(t);if(!r)throw new Error(`session "${t}" disappeared before the orchestration prompt could be sent`);await vt(t,n,{kind:"orchestration-drive",...Se(r.agent)})}import U from"node:fs/promises";import H from"node:path";import{randomUUID as zo}from"node:crypto";import{execFile as i_}from"node:child_process";import{promisify as c_}from"node:util";import xm from"node:path";var Yi={"claude-opus-4-8":{input:5,output:25,cacheWrite:6.25,cacheRead:.5},"claude-sonnet-5":{input:3,output:15,cacheWrite:3.75,cacheRead:.3},"claude-sonnet-4-6":{input:3,output:15,cacheWrite:3.75,cacheRead:.3},"claude-haiku-4-5":{input:1,output:5,cacheWrite:1.25,cacheRead:.1},"claude-fable-5":{input:10,output:50,cacheWrite:12.5,cacheRead:1},"gpt-5.5":{input:5,output:30,cacheWrite:5,cacheRead:.5},"gpt-5.6-sol":{input:5,output:30,cacheWrite:6.25,cacheRead:.5},"gpt-5.6-terra":{input:2.5,output:15,cacheWrite:3.125,cacheRead:.25},"gpt-5.6-luna":{input:1,output:6,cacheWrite:1.25,cacheRead:.1}},Uf={opus:"claude-opus-4-8",opusplan:"claude-opus-4-8",default:"claude-opus-4-8",sonnet:"claude-sonnet-4-6",haiku:"claude-haiku-4-5",fable:"claude-fable-5","gpt-5.6":"gpt-5.6-sol"};function Hf(t){if(!t)return null;let e=String(t).trim();return Yi[e]?e:Uf[e]?Uf[e]:Object.keys(Yi).find(r=>e.startsWith(r))||null}var Us=t=>Hf(t)!==null;function se(t,e={}){let n=Yi[Hf(t)];return n?((e.input||0)*n.input+(e.output||0)*n.output+(e.cacheWrite||0)*n.cacheWrite+(e.cacheRead||0)*n.cacheRead)/1e6:0}var Wf=()=>({input:0,output:0,cacheRead:0,cacheCreation:0});function Y0(t){return t?{input:t.input||0,output:t.output||0,cacheRead:t.cacheRead||0,cacheWrite:t.cacheWrite??t.cacheCreation??0}:null}function qf(t,e){e&&(t.input+=e.input,t.output+=e.output,t.cacheRead+=e.cacheRead,t.cacheCreation+=e.cacheWrite)}function Ws(t){let e=t.payload?.model||"unknown",n=Y0(t.payload?.tokens),r=Number(t.payload?.usd)||0,o=!1;!r&&n&&(r=se(e,n),r>0&&(o=!0));let s=n?n.input+n.output+n.cacheRead+n.cacheWrite:0,a=!r&&s>0&&!Us(e);return{usd:r,model:e,tokens:n,repriced:o,unpriced:a}}function ae(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=Wf();for(let f of n){let{usd:d,model:m,tokens:h,repriced:w,unpriced:g}=Ws(f),b=f.provider||"unknown",S=f.boxId||"unattributed";if(w&&i++,g&&(c++,l.add(m)),a+=d,qf(u,h),r[m]||={usd:0,tokens:Wf(),events:0},r[m].usd+=d,r[m].events+=1,qf(r[m].tokens,h),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,m!=="unknown"&&s[S].models.add(m),f.epoch!=null){let A=(s[S].epochs||={})[f.epoch]||={usd:0,events:0,provider:b,models:new Set};A.usd+=d,A.events+=1,A.provider=b,m!=="unknown"&&A.models.add(m)}}return{totalUsd:uo(a),totalTokens:u,turns:n.length,repricedTurns:i,unpricedTurns:c,unpricedModels:[...l],byModel:Hs(r,f=>({...f,usd:uo(f.usd)})),byProvider:Hs(o,f=>({...f,usd:uo(f.usd)})),byBox:Hs(s,f=>({usd:uo(f.usd),events:f.events,provider:f.provider,label:f.label,models:[...f.models],...f.epochs?{epochs:Hs(f.epochs,d=>({usd:uo(d.usd),events:d.events,provider:d.provider,models:[...d.models]}))}:{}}))}}var uo=t=>Math.round(t*1e4)/1e4,Hs=(t,e)=>Object.fromEntries(Object.entries(t).map(([n,r])=>[n,e(r)]));import St from"node:fs/promises";import Mt from"node:path";import fo from"node:os";import{randomUUID as tS}from"node:crypto";import{execFile as eS}from"node:child_process";import{promisify as nS}from"node:util";import{chromium as rS}from"playwright-core";import Zi from"node:fs/promises";import Z0 from"node:path";var Q0=0;async function qt(t,e,{mode:n}={}){await Zi.mkdir(Z0.dirname(t),{recursive:!0});let r=`${t}.${process.pid}.${++Q0}.tmp`;await Zi.writeFile(r,JSON.stringify(e,null,2)+`
|
|
73
|
+
`,n!=null?{mode:n}:void 0),await Zi.rename(r,t)}var Gf=nS(eS),Vf=process.env.BOSTRAT_STATE_DIR||Mt.join(process.env.HOME||fo.tmpdir(),".bostrat"),zf=Mt.join(Vf,"mocks.json"),oS=process.env.BOSTRAT_MOCK_ENGINE||"chromium",sS=process.env.BOSTRAT_CHROMIUM_BIN||"/usr/bin/chromium",aS=process.env.BOSTRAT_FIREFOX_BIN||"firefox",iS=1300,rr=2,cS=32,Jf=1500,or=Number(process.env.BOSTRAT_MOCK_TIMEOUT_MS)||75e3,lS=700*1024*1024;async function uS(){if(process.platform!=="darwin")return fo.freemem();try{let{stdout:t}=await Gf("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:fo.freemem()}catch{return fo.freemem()}}var Jt=class extends Error{constructor(e,n=""){super(n?`${e}
|
|
70
74
|
${n.trim().split(`
|
|
71
75
|
`).slice(-6).join(`
|
|
72
|
-
`)}`:e),this.name="MockRenderError"}},gn=null,zi=null;async function ve(){return gn||(zi||=(async()=>{try{gn=JSON.parse(await St.readFile(Uf,"utf8"))}catch{gn={mocks:[]}}finally{zi=null}return gn.mocks||=[],gn})(),zi)}async function ie(){await ae(Uf,gn)}function Jt(t,e,n={}){return O({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 Ds=t=>Mt.join(t,".bostrat","mocks");function qt(t,e){let n=Ds(t.cwd);return{html:Mt.join(n,`${t.id}.v${e}.html`),png:Mt.join(n,`${t.id}.v${e}.png`)}}function yn(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 Ji(t){return t.length<24||t.readUInt32BE(12)!==1229472850?null:{width:t.readUInt32BE(16),height:t.readUInt32BE(20)}}var B0=Buffer.from([137,80,78,71,13,10,26,10]);function Wf(t){return Buffer.isBuffer(t)&&t.length>24&&t.subarray(0,8).equals(B0)}async function D0(t){let e=Buffer.alloc(24),n=await St.open(t,"r");try{await n.read(e,0,24,0)}finally{await n.close()}return e}function j0({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 L0(t,e){return t.evaluate(async n=>{let r=new Image;await new Promise((h,w)=>{r.onload=h,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,m=-1;for(let h=0;h<s;h++)for(let w=h*o*4,g=0;g<o;g++,w+=4)Math.abs(c[w]-l[0])+Math.abs(c[w+1]-l[1])+Math.abs(c[w+2]-l[2])>24&&(g<u&&(u=g),g>d&&(d=g),h<f&&(f=h),m=h);return d<0?null:{imgW:o,imgH:s,bg:l,bbox:{left:u,top:f,right:d,bottom:m}}},e.toString("base64"))}async function F0(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 U0({htmlPath:t,outPath:e,width:n}){let r=Date.now(),o;try{o=await I0.launch({executablePath:$0,args:["--disable-gpu"],timeout:3e4})}catch(s){throw new Vt("chromium failed to launch",s?.message||"")}try{let a=await(await o.newContext({viewport:{width:n,height:800},deviceScaleFactor:Qn})).newPage();a.setDefaultTimeout(tr),await a.goto(`file://${t}`,{waitUntil:"load",timeout:tr}),await a.waitForTimeout(250);let i;for(let l=1;;l++)try{i=await a.screenshot({fullPage:!0,timeout:tr});break}catch(u){if(l>=3||!/captureScreenshot|Unable to capture/i.test(u?.message||""))throw u;await a.waitForTimeout(500)}try{let l=await L0(a,i),u=l&&j0({...l,pad:C0*Qn,align:Qn});u&&(i=await F0(a,i,{bg:l.bg,crop:u}))}catch{}if(i.length<Hf)throw new Vt(`capture produced a ${i.length}-byte image`);let c=Ji(i);if(!c)throw new Vt("output is not a PNG");return await St.writeFile(e,i),{width:Math.round(c.width/Qn),height:Math.round(c.height/Qn),dpr:Qn,renderMs:Date.now()-r}}catch(s){throw s instanceof Vt?s:new Vt(/timeout/i.test(s?.message||"")?`render timed out after ${tr}ms`:"chromium render failed",s?.message||"")}finally{await o.close().catch(()=>{})}}async function jf({htmlPath:t,outPath:e,width:n}){let r=await St.mkdtemp(Mt.join(so.tmpdir(),"bostrat-mock-profile-")),o=Date.now(),s="";try{try{s=(await Lf(O0,["--headless","--no-remote","--profile",r,`--window-size=${n}`,"--screenshot",e,`file://${t}`],{env:{...process.env,MOZ_HEADLESS:"1"},timeout:tr,killSignal:"SIGKILL",maxBuffer:4194304})).stderr||""}catch(c){throw s=c?.stderr||"",new Vt(c?.killed?`render timed out after ${tr}ms`:"firefox exited non-zero",s)}let a=await St.stat(e).catch(()=>null);if(!a||a.size<Hf)throw new Vt(`render produced ${a?`a ${a.size}-byte file`:"no file"} (silent no-op \u2014 another firefox holding the profile?)`,s);let i=Ji(await D0(e));if(!i)throw new Vt("output is not a PNG",s);return{width:i.width,height:i.height,dpr:1,renderMs:Date.now()-o}}finally{await St.rm(r,{recursive:!0,force:!0}).catch(()=>{})}}async function H0(t){if(R0==="firefox")return jf(t);try{return await U0(t)}catch(e){if(/failed to launch/i.test(e?.message||""))return jf(t);throw e}}var W0=H0;var Vi=Promise.resolve();function qf(t){return Vi=Vi.then(t,t),Vi}async function Kf(t,e){let n=await ve(),r=n.mocks.find(o=>o.id===t);if(r){r.state="rendering",await ie(),await Jt(r,"rendering",{version:e});try{let o=await P0();if(o<N0)throw new Vt(`box is low on memory (${Math.round(o/1e6)} MB available) \u2014 not spawning a browser`);let s=qt(r,e),a=await W0({htmlPath:s.html,outPath:s.png,width:r.width});if(!n.mocks.some(i=>i.id===t)){await St.rm(s.png,{force:!0}).catch(()=>{});return}Gf(r,{v:e,source:"html",htmlRel:Mt.relative(r.cwd,s.html),pngRel:Mt.relative(r.cwd,s.png),width:a.width,height:a.height,dpr:a.dpr,renderMs:a.renderMs,createdAt:Date.now()}),await ie(),await Jt(r,"ready",{version:e,width:a.width,height:a.height,dpr:a.dpr})}catch(o){if(!n.mocks.some(s=>s.id===t))return;r.state="error",r.error=o?.message||String(o),await ie(),await Jt(r,"error",{version:e,error:r.error})}}}function Gf(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 q0({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?M0:Number(r);if(!Number.isInteger(o)||o<320||o>4e3)throw new Error("invalid width (320-4000)");return o}async function js({trackId:t,group:e}={}){let n=await ve(),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 He(t){let n=(await ve()).mocks.find(r=>r.id===t);return n?{...n}:null}async function zf({trackId:t,name:e,html:n,image:r,cwd:o,width:s,group:a,box:i,agent:c,prompt:l}){let u=q0({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&&!Wf(r))throw new Error("image must be a PNG");let d=await ve(),m={id:A0().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 St.mkdir(Ds(o),{recursive:!0}),d.mocks.push(m),f?(await St.writeFile(qt(m,1).html,n),await ie(),await Jt(m,"created"),qf(()=>Kf(m.id,1))):(await Jf(m,1,r),await ie(),await Jt(m,"created"),await Jt(m,"ready",{version:1,width:m.width,height:m.height,dpr:m.dpr})),{...m}}async function Vf(t,{html:e,image:n,width:r}={}){let s=(await ve()).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&&!Wf(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 St.mkdir(Ds(s.cwd),{recursive:!0}),a?(s.state="queued",await St.writeFile(qt(s,i).html,e),await ie(),await Jt(s,"updated",{version:i}),qf(()=>Kf(s.id,i))):(await Jf(s,i,n),await ie(),await Jt(s,"updated",{version:i}),await Jt(s,"ready",{version:i,width:s.width,height:s.height,dpr:s.dpr})),{...s}}async function Jf(t,e,n){let r=Ji(n);if(!r)throw new Error("image must be a PNG");let o=qt(t,e);await St.writeFile(o.png,n),Gf(t,{v:e,source:"image",htmlRel:null,pngRel:Mt.relative(t.cwd,o.png),width:r.width,height:r.height,dpr:1,renderMs:null,createdAt:Date.now()}),t.width=r.width}async function Xf(t){let e=await ve(),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 Jt(n,"picked",{version:n.readyVersion,siblings:r}),{mock:{...n},siblings:r}}async function Yf(t){let e=await ve(),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=qt(r,o);await St.rm(s.html,{force:!0}).catch(()=>{}),await St.rm(s.png,{force:!0}).catch(()=>{})}return await ie(),await Jt(r,"deleted"),{ok:!0,id:t,name:r.name}}async function Zf(t){let n=(await ve()).mocks.filter(i=>i.trackId===t.id);if(!n.length)return 0;let r=Mt.join(Ff,"mock-archive",t.id),o=Ds(t.cwd),s=Mt.join(r,".bostrat","mocks");await St.mkdir(s,{recursive:!0});let a=await St.readdir(o).catch(()=>[]);for(let i of a)await St.copyFile(Mt.join(o,i),Mt.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 St.stat(Mt.join(r,l)).catch(()=>null))?.isFile())throw new Error(`mock evacuation incomplete: ${l} missing from the archive`)}i.cwd=r,i.trackDeletedAt=Date.now(),i.trackName=t.name||t.id}return await ie(),n.length}async function Qf(t){let e=await ve(),n=e.mocks.filter(r=>r.trackId===t);return n.length&&(e.mocks=e.mocks.filter(r=>r.trackId!==t),await ie()),n.length}import io from"node:fs/promises";import Ct from"node:path";import K0 from"node:os";import G0 from"node:net";import{randomUUID as z0}from"node:crypto";import{execFile as V0}from"node:child_process";import{promisify as J0}from"node:util";var le=J0(V0),X0=process.env.BOSTRAT_STATE_DIR||Ct.join(process.env.HOME||K0.tmpdir(),".bostrat"),op=Ct.join(X0,"views.json"),[tp,ep]=(process.env.BOSTRAT_VIEW_PORTS||"4300-4349").split("-").map(t=>Number(t)||0),We=Number(process.env.BOSTRAT_VIEW_MAX)||3,Y0="bostrat-view-",er=t=>`${Y0}${t}`,np=new Set(["bostrat","www","api","views","app"]),Ls=t=>Object.assign(new Error(t),{status:400}),Z0=new Set(["npm","pnpm","yarn","bun"]),Zi="package.json",Q0=/[|;&`]|\$\(/,tS=new Set(["sh","bash","zsh","dash","env","exec","nohup","time"]),eS=/(^|\s)(-C|--cwd|--manifest-path|--dir)(=|\s)/,sp=t=>String(t).trim().split(/\s+/).filter(Boolean),nS=/^[A-Za-z_][A-Za-z0-9_]*=/;function rS(t){for(let e of sp(t))if(!nS.test(e))return e;return null}function ap(t){let e=sp(t),n=e.indexOf("--");return n===-1?e:e.slice(0,n)}function oS(t,e){let n=ap(e);for(let r=0;r<n.length;r++){let o=/^--prefix=(.+)$/.exec(n[r]);if(o)return Ct.resolve(t,o[1]);if(n[r]==="--prefix"&&n[r+1])return Ct.resolve(t,n[r+1])}return t}var sS=t=>io.stat(t).then(e=>e.isDirectory(),()=>!1),ip=t=>io.stat(t).then(e=>e.isFile(),()=>!1),Fs=t=>io.realpath(t).catch(()=>Ct.resolve(t));async function aS(t,e){if(Ct.resolve(t)===Ct.resolve(e))return!0;let[n,r]=await Promise.all([Fs(t),Fs(e)]);return n===r}async function iS(t){let n=(await io.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 ip(Ct.join(t,o.name,Zi))?`./${o.name}`:null))).filter(Boolean)}async function cS(t,e){let[n,r]=await Promise.all([Fs(t),Fs(e)]),o=Ct.relative(n,r);if(o&&(o.startsWith("..")||Ct.isAbsolute(o)))throw Ls(`serve directory escapes the Track workspace: ${e}`)}async function lS(t,e){if(!await sS(t))throw Ls(`serve directory does not exist: ${t}`);let n=rS(e);if(!n)return;if(!n.includes("/")&&!await Ei(n,{loginShell:!0,failOpen:!0}))throw Ls(`\`${n}\` is not on the box's PATH \u2014 install it in the box first`);if(Q0.test(e)||tS.has(n)||!Z0.has(n)||!ap(e).includes("run")||eS.test(e))return;let r=oS(t,e);if(await ip(Ct.join(r,Zi)))return;let o=await iS(r),s=o.length?` \u2014 found one in ${o.join(", ")}; run bostrat-view from there`:"";throw Ls(`no ${Zi} in ${r}${s}`)}var ce=null,Xi=null;async function _e(){return ce||(Xi||=(async()=>{try{ce=JSON.parse(await io.readFile(op,"utf8"))}catch{ce={views:[],adoptWatermarks:{}}}finally{Xi=null}return ce.views||=[],ce.adoptWatermarks||={},ce})(),Xi)}async function ue(){await ae(op,ce)}function Ae(t,e,n={}){return O({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 ao=new Map,ec=Number(process.env.BOSTRAT_VIEW_JOB_TIMEOUT)||3e5,cp=12e3;function nc(t){return ao.get(t)||null}function lp(t,{name:e,box:n}={}){let r=ao.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 ao.set(t,o),o}function Qi(t,e){let n=ao.get(t);n&&!n.finishedAt&&!n.viewId&&(n.viewId=e,n.step="booting")}function Us(t,{error:e=null}={}){let n=ao.get(t);return!n||n.finishedAt?null:(n.finishedAt=Date.now(),e&&(n.error=e),n)}function up(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 tc(t){return String(t||"").trim().toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,40).replace(/-+$/g,"")||"view"}function uS(t,e){let n=tc(e),r=new Set(t.views.map(o=>o.slug));if(!r.has(n)&&!np.has(n))return n;for(let o=2;;o++){let s=`${n}-${o}`;if(!r.has(s)&&!np.has(s))return s}}function co(t,{timeoutMs:e=1200}={}){return new Promise(n=>{let r=G0.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 rc(t){if(process.platform!=="darwin"){await le("fuser",["-k",`${t}/tcp`]).catch(()=>{});return}let e="";try{({stdout:e}=await le("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 dp(t=process.env){return new Set([Number(t.PORT)||3362,Number(t.BOSTRAT_TLS_PORT)||3443])}async function dS(t,e){let n=new Set(t.views.map(o=>o.port)),r=dp();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=tp;o<=ep;o++)if(!(n.has(o)||r.has(o))&&!await co(o,{timeoutMs:250}))return o;throw new Error(`no free view port in ${tp}-${ep}`)}async function fS(t){try{let{stdout:e}=await le(N,B("list-sessions","-F","#{session_name}"));return e.split(`
|
|
73
|
-
`).some(n=>n.trim()===
|
|
76
|
+
`)}`:e),this.name="MockRenderError"}},bn=null,Qi=null;async function ve(){return bn||(Qi||=(async()=>{try{bn=JSON.parse(await St.readFile(zf,"utf8"))}catch{bn={mocks:[]}}finally{Qi=null}return bn.mocks||=[],bn})(),Qi)}async function ie(){await qt(zf,bn)}function Xt(t,e,n={}){return $({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 qs=t=>Mt.join(t,".bostrat","mocks");function Kt(t,e){let n=qs(t.cwd);return{html:Mt.join(n,`${t.id}.v${e}.html`),png:Mt.join(n,`${t.id}.v${e}.png`)}}function Te(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 ec(t){return t.length<24||t.readUInt32BE(12)!==1229472850?null:{width:t.readUInt32BE(16),height:t.readUInt32BE(20)}}var dS=Buffer.from([137,80,78,71,13,10,26,10]);function Xf(t){return Buffer.isBuffer(t)&&t.length>24&&t.subarray(0,8).equals(dS)}async function fS(t){let e=Buffer.alloc(24),n=await St.open(t,"r");try{await n.read(e,0,24,0)}finally{await n.close()}return e}function pS({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 mS(t,e){return t.evaluate(async n=>{let r=new Image;await new Promise((h,w)=>{r.onload=h,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,m=-1;for(let h=0;h<s;h++)for(let w=h*o*4,g=0;g<o;g++,w+=4)Math.abs(c[w]-l[0])+Math.abs(c[w+1]-l[1])+Math.abs(c[w+2]-l[2])>24&&(g<u&&(u=g),g>d&&(d=g),h<f&&(f=h),m=h);return d<0?null:{imgW:o,imgH:s,bg:l,bbox:{left:u,top:f,right:d,bottom:m}}},e.toString("base64"))}async function hS(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 wS({htmlPath:t,outPath:e,width:n}){let r=Date.now(),o;try{o=await rS.launch({executablePath:sS,args:["--disable-gpu"],timeout:3e4})}catch(s){throw new Jt("chromium failed to launch",s?.message||"")}try{let a=await(await o.newContext({viewport:{width:n,height:800},deviceScaleFactor:rr})).newPage();a.setDefaultTimeout(or),await a.goto(`file://${t}`,{waitUntil:"load",timeout:or}),await a.waitForTimeout(250);let i;for(let l=1;;l++)try{i=await a.screenshot({fullPage:!0,timeout:or});break}catch(u){if(l>=3||!/captureScreenshot|Unable to capture/i.test(u?.message||""))throw u;await a.waitForTimeout(500)}try{let l=await mS(a,i),u=l&&pS({...l,pad:cS*rr,align:rr});u&&(i=await hS(a,i,{bg:l.bg,crop:u}))}catch{}if(i.length<Jf)throw new Jt(`capture produced a ${i.length}-byte image`);let c=ec(i);if(!c)throw new Jt("output is not a PNG");return await St.writeFile(e,i),{width:Math.round(c.width/rr),height:Math.round(c.height/rr),dpr:rr,renderMs:Date.now()-r}}catch(s){throw s instanceof Jt?s:new Jt(/timeout/i.test(s?.message||"")?`render timed out after ${or}ms`:"chromium render failed",s?.message||"")}finally{await o.close().catch(()=>{})}}async function Kf({htmlPath:t,outPath:e,width:n}){let r=await St.mkdtemp(Mt.join(fo.tmpdir(),"bostrat-mock-profile-")),o=Date.now(),s="";try{try{s=(await Gf(aS,["--headless","--no-remote","--profile",r,`--window-size=${n}`,"--screenshot",e,`file://${t}`],{env:{...process.env,MOZ_HEADLESS:"1"},timeout:or,killSignal:"SIGKILL",maxBuffer:4194304})).stderr||""}catch(c){throw s=c?.stderr||"",new Jt(c?.killed?`render timed out after ${or}ms`:"firefox exited non-zero",s)}let a=await St.stat(e).catch(()=>null);if(!a||a.size<Jf)throw new Jt(`render produced ${a?`a ${a.size}-byte file`:"no file"} (silent no-op \u2014 another firefox holding the profile?)`,s);let i=ec(await fS(e));if(!i)throw new Jt("output is not a PNG",s);return{width:i.width,height:i.height,dpr:1,renderMs:Date.now()-o}}finally{await St.rm(r,{recursive:!0,force:!0}).catch(()=>{})}}async function gS(t){if(oS==="firefox")return Kf(t);try{return await wS(t)}catch(e){if(/failed to launch/i.test(e?.message||""))return Kf(t);throw e}}var yS=gS;var tc=Promise.resolve();function Yf(t){return tc=tc.then(t,t),tc}async function Zf(t,e){let n=await ve(),r=n.mocks.find(o=>o.id===t);if(r){r.state="rendering",await ie(),await Xt(r,"rendering",{version:e});try{let o=await uS();if(o<lS)throw new Jt(`box is low on memory (${Math.round(o/1e6)} MB available) \u2014 not spawning a browser`);let s=Kt(r,e),a=await yS({htmlPath:s.html,outPath:s.png,width:r.width});if(!n.mocks.some(i=>i.id===t)){await St.rm(s.png,{force:!0}).catch(()=>{});return}Qf(r,{v:e,source:"html",htmlRel:Mt.relative(r.cwd,s.html),pngRel:Mt.relative(r.cwd,s.png),width:a.width,height:a.height,dpr:a.dpr,renderMs:a.renderMs,createdAt:Date.now()}),await ie(),await Xt(r,"ready",{version:e,width:a.width,height:a.height,dpr:a.dpr})}catch(o){if(!n.mocks.some(s=>s.id===t))return;r.state="error",r.error=o?.message||String(o),await ie(),await Xt(r,"error",{version:e,error:r.error})}}}function Qf(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 bS({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?iS:Number(r);if(!Number.isInteger(o)||o<320||o>4e3)throw new Error("invalid width (320-4000)");return o}async function kn({trackId:t,group:e}={}){let n=await ve(),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 We(t){let n=(await ve()).mocks.find(r=>r.id===t);return n?{...n}:null}async function tp({trackId:t,name:e,html:n,image:r,cwd:o,width:s,group:a,box:i,agent:c,prompt:l}){let u=bS({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&&!Xf(r))throw new Error("image must be a PNG");let d=await ve(),m={id:tS().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 St.mkdir(qs(o),{recursive:!0}),d.mocks.push(m),f?(await St.writeFile(Kt(m,1).html,n),await ie(),await Xt(m,"created"),Yf(()=>Zf(m.id,1))):(await np(m,1,r),await ie(),await Xt(m,"created"),await Xt(m,"ready",{version:1,width:m.width,height:m.height,dpr:m.dpr})),{...m}}async function ep(t,{html:e,image:n,width:r}={}){let s=(await ve()).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&&!Xf(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 St.mkdir(qs(s.cwd),{recursive:!0}),a?(s.state="queued",await St.writeFile(Kt(s,i).html,e),await ie(),await Xt(s,"updated",{version:i}),Yf(()=>Zf(s.id,i))):(await np(s,i,n),await ie(),await Xt(s,"updated",{version:i}),await Xt(s,"ready",{version:i,width:s.width,height:s.height,dpr:s.dpr})),{...s}}async function np(t,e,n){let r=ec(n);if(!r)throw new Error("image must be a PNG");let o=Kt(t,e);await St.writeFile(o.png,n),Qf(t,{v:e,source:"image",htmlRel:null,pngRel:Mt.relative(t.cwd,o.png),width:r.width,height:r.height,dpr:1,renderMs:null,createdAt:Date.now()}),t.width=r.width}async function rp(t){let e=await ve(),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 Xt(n,"picked",{version:n.readyVersion,siblings:r}),{mock:{...n},siblings:r}}async function op(t){let e=await ve(),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=Kt(r,o);await St.rm(s.html,{force:!0}).catch(()=>{}),await St.rm(s.png,{force:!0}).catch(()=>{})}return await ie(),await Xt(r,"deleted"),{ok:!0,id:t,name:r.name}}async function sp(t){let n=(await ve()).mocks.filter(i=>i.trackId===t.id);if(!n.length)return 0;let r=Mt.join(Vf,"mock-archive",t.id),o=qs(t.cwd),s=Mt.join(r,".bostrat","mocks");await St.mkdir(s,{recursive:!0});let a=await St.readdir(o).catch(()=>[]);for(let i of a)await St.copyFile(Mt.join(o,i),Mt.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 St.stat(Mt.join(r,l)).catch(()=>null))?.isFile())throw new Error(`mock evacuation incomplete: ${l} missing from the archive`)}i.cwd=r,i.trackDeletedAt=Date.now(),i.trackName=t.name||t.id}return await ie(),n.length}async function ap(t){let e=await ve(),n=e.mocks.filter(r=>r.trackId===t);return n.length&&(e.mocks=e.mocks.filter(r=>r.trackId!==t),await ie()),n.length}import mo from"node:fs/promises";import Ct from"node:path";import kS from"node:os";import xS from"node:net";import{randomUUID as SS}from"node:crypto";import{execFile as vS}from"node:child_process";import{promisify as TS}from"node:util";var le=TS(vS),AS=process.env.BOSTRAT_STATE_DIR||Ct.join(process.env.HOME||kS.tmpdir(),".bostrat"),dp=Ct.join(AS,"views.json"),[ip,cp]=(process.env.BOSTRAT_VIEW_PORTS||"4300-4349").split("-").map(t=>Number(t)||0),qe=Number(process.env.BOSTRAT_VIEW_MAX)||3,ES="bostrat-view-",sr=t=>`${ES}${t}`,lp=new Set(["bostrat","www","api","views","app"]),Ks=t=>Object.assign(new Error(t),{status:400}),_S=new Set(["npm","pnpm","yarn","bun"]),oc="package.json",IS=/[|;&`]|\$\(/,RS=new Set(["sh","bash","zsh","dash","env","exec","nohup","time"]),$S=/(^|\s)(-C|--cwd|--manifest-path|--dir)(=|\s)/,fp=t=>String(t).trim().split(/\s+/).filter(Boolean),OS=/^[A-Za-z_][A-Za-z0-9_]*=/;function MS(t){for(let e of fp(t))if(!OS.test(e))return e;return null}function pp(t){let e=fp(t),n=e.indexOf("--");return n===-1?e:e.slice(0,n)}function CS(t,e){let n=pp(e);for(let r=0;r<n.length;r++){let o=/^--prefix=(.+)$/.exec(n[r]);if(o)return Ct.resolve(t,o[1]);if(n[r]==="--prefix"&&n[r+1])return Ct.resolve(t,n[r+1])}return t}var NS=t=>mo.stat(t).then(e=>e.isDirectory(),()=>!1),mp=t=>mo.stat(t).then(e=>e.isFile(),()=>!1),Gs=t=>mo.realpath(t).catch(()=>Ct.resolve(t));async function PS(t,e){if(Ct.resolve(t)===Ct.resolve(e))return!0;let[n,r]=await Promise.all([Gs(t),Gs(e)]);return n===r}async function BS(t){let n=(await mo.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 mp(Ct.join(t,o.name,oc))?`./${o.name}`:null))).filter(Boolean)}async function DS(t,e){let[n,r]=await Promise.all([Gs(t),Gs(e)]),o=Ct.relative(n,r);if(o&&(o.startsWith("..")||Ct.isAbsolute(o)))throw Ks(`serve directory escapes the Track workspace: ${e}`)}async function jS(t,e){if(!await NS(t))throw Ks(`serve directory does not exist: ${t}`);let n=MS(e);if(!n)return;if(!n.includes("/")&&!await Ci(n,{loginShell:!0,failOpen:!0}))throw Ks(`\`${n}\` is not on the box's PATH \u2014 install it in the box first`);if(IS.test(e)||RS.has(n)||!_S.has(n)||!pp(e).includes("run")||$S.test(e))return;let r=CS(t,e);if(await mp(Ct.join(r,oc)))return;let o=await BS(r),s=o.length?` \u2014 found one in ${o.join(", ")}; run bostrat-view from there`:"";throw Ks(`no ${oc} in ${r}${s}`)}var ce=null,nc=null;async function _e(){return ce||(nc||=(async()=>{try{ce=JSON.parse(await mo.readFile(dp,"utf8"))}catch{ce={views:[],adoptWatermarks:{}}}finally{nc=null}return ce.views||=[],ce.adoptWatermarks||={},ce})(),nc)}async function ue(){await qt(dp,ce)}function Ee(t,e,n={}){return $({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 po=new Map,ic=Number(process.env.BOSTRAT_VIEW_JOB_TIMEOUT)||3e5,hp=12e3;function cc(t){return po.get(t)||null}function wp(t,{name:e,box:n}={}){let r=po.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 po.set(t,o),o}function sc(t,e){let n=po.get(t);n&&!n.finishedAt&&!n.viewId&&(n.viewId=e,n.step="booting")}function Vs(t,{error:e=null}={}){let n=po.get(t);return!n||n.finishedAt?null:(n.finishedAt=Date.now(),e&&(n.error=e),n)}function gp(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 ac(t){return String(t||"").trim().toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,40).replace(/-+$/g,"")||"view"}function LS(t,e){let n=ac(e),r=new Set(t.views.map(o=>o.slug));if(!r.has(n)&&!lp.has(n))return n;for(let o=2;;o++){let s=`${n}-${o}`;if(!r.has(s)&&!lp.has(s))return s}}function ho(t,{timeoutMs:e=1200}={}){return new Promise(n=>{let r=xS.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 lc(t){if(process.platform!=="darwin"){await le("fuser",["-k",`${t}/tcp`]).catch(()=>{});return}let e="";try{({stdout:e}=await le("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 yp(t=process.env){return new Set([Number(t.PORT)||3362,Number(t.BOSTRAT_TLS_PORT)||3443])}async function FS(t,e){let n=new Set(t.views.map(o=>o.port)),r=yp();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=ip;o<=cp;o++)if(!(n.has(o)||r.has(o))&&!await ho(o,{timeoutMs:250}))return o;throw new Error(`no free view port in ${ip}-${cp}`)}async function US(t){try{let{stdout:e}=await le(N,B("list-sessions","-F","#{session_name}"));return e.split(`
|
|
77
|
+
`).some(n=>n.trim()===sr(t))}catch{return!1}}async function wo(t){try{await le(N,B("kill-session","-t",sr(t)))}catch{}}async function uc(t){if(!await oo())throw new Error("host tmux server not running and auto-start failed \u2014 cannot spawn a view server");let e=sr(t.slug),n=t.serveCwd??t.cwd,r=Ct.dirname(process.execPath);await le(N,B("new-session","-d","-s",e,"-c",n)),await le(N,B("send-keys","-t",e,"-l",`export PORT=${t.port} PATH="${r}:$PATH"`)),await le(N,B("send-keys","-t",e,"Enter")),await le(N,B("send-keys","-t",e,"-l",t.cmd)),await le(N,B("send-keys","-t",e,"Enter"))}async function up(t,e=12){try{let{stdout:n}=await le(N,B("capture-pane","-t",sr(t),"-p"));return String(n||"").trimEnd().split(`
|
|
74
78
|
`).filter(Boolean).slice(-e).join(`
|
|
75
|
-
`)}catch{return""}}var Te=new Map,pS=12e4;async function mS(t){if(t.state==="stopped"||t.state==="error")return!1;let e=t.origin!=="detected",n=e?await fS(t.slug):!0;if(e&&!n){let s=t.state==="starting";return t.state=s?"error":"stopped",s&&(t.error=await rp(t.slug)||"server exited before listening"),Te.delete(t.id),await Ae(t,t.state,t.error?{error:t.error}:{}),!0}if(await co(t.port))return Te.delete(t.id),t.lastHealthyAt=Date.now(),t.state!=="live"?(t.state="live",t.error=null,await Ae(t,"live"),!0):!1;if(t.state==="starting")return Date.now()-t.createdAt>pS?(t.state="error",t.error=await rp(t.slug)||"never started listening",await Ae(t,"error",{error:t.error}),!0):!1;let o=(Te.get(t.id)||0)+1;return Te.set(t.id,o),o>=3&&t.state==="live"?(t.state="unhealthy",!0):!1}var Yi=!1;async function Hs(){if(!Yi){Yi=!0;try{let t=await _e(),e=!1;for(let n of t.views)await mS(n)&&(e=!0);e&&await ue()}finally{Yi=!1}}}async function Ws({trackId:t}={}){await Hs();let e=await _e();return(t?e.views.filter(r=>r.trackId===t):e.views).map(r=>({...r}))}async function fp(t){let n=(await _e()).views.find(r=>r.id===t);return n?{...n}:null}function sc(t){return ce&&ce.views.find(e=>e.slug===t)||null}async function ac({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=Ct.resolve(r,o??"."),f=i==="detected"?null:async()=>{await cS(r,u),await lS(u,n)},d=await _e();if(i!=="detected"){let w=tc(e),g=String(n).trim(),b=d.views.find(v=>v.trackId===t&&(v.slug===w||tc(v.name)===w||v.cmd===g&&Ct.resolve(v.serveCwd??v.cwd)===u));if(b)return hS(d,b,{cmd:g,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>=We)throw new Error(`view cap reached (${We} live) \u2014 stop one first`);let h={id:z0().slice(0,8),trackId:t,box:a||null,name:e.trim(),slug:uS(d,e),cmd:n?String(n).trim():null,cwd:r,serveCwd:u,branch:c,port:await dS(d,s),origin:i,state:"starting",error:null,createdAt:Date.now(),lastHealthyAt:null};return i!=="detected"&&await oc(h),d.views.push(h),await ue(),await Ae(h,"created",{origin:i}),Qi(t,h.id),i==="detected"&&await co(h.port)&&(h.state="live",h.lastHealthyAt=Date.now(),await ue(),await Ae(h,"live")),{...h}}async function hS(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 aS(e.serveCwd??e.cwd,s);if((e.state==="live"||e.state==="starting")&&!i&&!c&&await co(e.port))return await ue(),Qi(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>=We)throw new Error(`view cap reached (${We} live) \u2014 stop one first`);return e.origin==="detected"?(await rc(e.port),e.origin="agent"):await lo(e.slug),e.cmd=n,s!==void 0&&(e.serveCwd=s),e.state="starting",e.error=null,e.createdAt=Date.now(),Te.delete(e.id),await oc(e),await ue(),await Ae(e,"created",{restarted:!0,reused:!0}),Qi(e.trackId,e.id),{...e,reused:!0}}async function pp(t){let n=(await _e()).views.find(r=>r.id===t);if(!n)throw new Error("view not found");return n.origin==="detected"?await rc(n.port):await lo(n.slug),n.state!=="stopped"&&(n.state="stopped",Te.delete(n.id),await ue(),await Ae(n,"stopped")),{...n}}async function mp(t){let e=await _e(),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>=We)throw new Error(`view cap reached (${We} live) \u2014 stop one first`);return await lo(n.slug),n.state="starting",n.error=null,n.createdAt=Date.now(),Te.delete(n.id),await oc(n),await ue(),await Ae(n,"created",{restarted:!0}),{...n}}async function hp(t){let e=await _e(),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 rc(r.port):await lo(r.slug),e.views.splice(n,1),Te.delete(r.id),await ue(),await Ae(r,"deleted"),{ok:!0,id:t,slug:r.slug}}async function wp(t){let e=await _e(),n=e.views.filter(r=>r.trackId===t);for(let r of n)r.origin!=="detected"&&await lo(r.slug),Te.delete(r.id);return n.length&&(e.views=e.views.filter(r=>r.trackId!==t),await ue()),n.length}var wS=/https?:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0)(?::(\d{2,5}))?(?:\/|\b)/g;async function gp(t,e){if(!t?.id||!Array.isArray(e)||!e.length)return[];let n=await _e(),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 ue()),!o.length)return[];let a=new Set;for(let c of o)for(let l of String(c.text||"").matchAll(wS)){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(!dp().has(c)&&!n.views.some(l=>l.port===c)&&await co(c))try{i.push(await ac({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 kn from"node:fs/promises";import qe from"node:path";import gS from"node:os";import{randomUUID as yS}from"node:crypto";var rr=process.env.BOSTRAT_STATE_DIR||qe.join(process.env.HOME||gS.tmpdir(),".bostrat"),bp=qe.join(rr,"runbooks.json"),cc=qe.join(rr,"runbooks"),yp=256*1024,bS=[{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 bS.filter(({re:e})=>e.test(t)).map(({label:e})=>e)}var bn=null,ic=null;async function Ke(){return bn||(ic||=(async()=>{try{bn=JSON.parse(await kn.readFile(bp,"utf8"))}catch{bn={runbooks:[]}}finally{ic=null}return bn.runbooks||=[],bn})(),ic)}async function or(){await ae(bp,bn)}function sr(t,e,n={}){return O({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 nr(t,e){return qe.join(cc,`${t.id}.v${e}.md`)}function kp(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 xp(t){if(typeof t!="string"||!t.trim())throw new Error("md (markdown string) required");let e=Buffer.byteLength(t,"utf8");if(e>yp)throw new Error(`runbook is ${Math.round(e/1024)} KB \u2014 over the ${yp/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 Sp(t,e){if(t.trackDeletedAt)throw new Error(`runbook is orphaned (its Track was deleted) \u2014 cannot ${e} it`)}async function qs({trackId:t,status:e}={}){let n=await Ke(),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 lc(t){let n=(await Ke()).runbooks.find(r=>r.id===t);return n?{...n}:null}async function vp({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=xp(r),c=await Ke(),l={id:yS().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 kn.mkdir(cc,{recursive:!0}),await kn.writeFile(nr(l,1),r),l.versions.push({v:1,mdRel:qe.relative(rr,nr(l,1)),bytes:i,note:null,createdAt:Date.now()}),c.runbooks.push(l),await or(),await sr(l,"created",{version:1}),{...l}}async function Tp(t,{md:e,note:n}={}){let o=(await Ke()).runbooks.find(i=>i.id===t);if(!o)throw new Error("runbook not found");Sp(o,"update");let s=xp(e),a=o.version+1;return o.version=a,o.updatedAt=Date.now(),await kn.mkdir(cc,{recursive:!0}),await kn.writeFile(nr(o,a),e),o.versions.push({v:a,mdRel:qe.relative(rr,nr(o,a)),bytes:s,note:n?String(n).slice(0,200):null,createdAt:Date.now()}),await or(),await sr(o,"updated",{version:a,...n?{note:String(n).slice(0,200)}:{}}),{...o}}async function Ap(t,e){if(e!=="open"&&e!=="done")throw new Error('status must be "open" or "done"');let r=(await Ke()).runbooks.find(o=>o.id===t);if(!r)throw new Error("runbook not found");return Sp(r,e==="done"?"complete":"reopen"),r.status===e?{...r}:(r.status=e,r.updatedAt=Date.now(),await or(),await sr(r,e==="done"?"done":"reopened"),{...r})}async function _p(t){let e=await Ke(),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 kn.rm(qe.join(rr,o.mdRel),{force:!0}).catch(()=>{});return await or(),r.trackDeletedAt||await sr(r,"deleted"),{ok:!0,id:t,name:r.name}}async function Ep(t){let n=(await Ke()).runbooks.filter(r=>r.trackId===t&&!r.trackDeletedAt);for(let r of n)r.trackDeletedAt=Date.now(),await sr(r,"orphaned"),r.cwd=null;return n.length&&await or(),n.length}async function Ip(t){let e=await Ke(),n=e.runbooks.filter(r=>r.trackId===t);for(let r of n){await sr(r,"deleted");for(let o of r.versions||[])await kn.rm(qe.join(rr,o.mdRel),{force:!0}).catch(()=>{})}return n.length&&(e.runbooks=e.runbooks.filter(r=>r.trackId!==t),await or()),n.length}import mo from"node:fs/promises";import Wp from"node:path";import JS from"node:os";import{randomUUID as qp}from"node:crypto";import kS from"node:fs/promises";import Rp from"node:path";import xS from"node:os";var $p=()=>Rp.join(process.env.BOSTRAT_STATE_DIR||Rp.join(process.env.HOME||xS.tmpdir(),".bostrat"),"integrations.json");async function Ee(){try{return JSON.parse(await kS.readFile($p(),"utf8"))||{}}catch{return{}}}var Ks=["linked","prOpened","prMerged","create"],uc=["prompt","auto","off"],SS={create:["prompt","off"]},Op=t=>SS[t]??uc;function Gs(t){let e=t.linear||{},n=e.transitions===!0?"auto":"prompt",r={};for(let o of Ks){let s=Op(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 xn(){let t=await Ee(),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:Gs(t),defaultTeam:n}:null}async function zs(){let t=await Ee(),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:Gs(t)}}async function dc(){return Gs(await Ee())}async function Mp(t){let e=Object.entries(t||{});if(!e.length)throw new Error(`nothing to set \u2014 phases: ${Ks.join(", ")}`);for(let[r,o]of e){if(!Ks.includes(r))throw new Error(`unknown phase "${r}" \u2014 configurable phases: ${Ks.join(", ")}`);let s=Op(r);if(!s.includes(o))throw new Error(s.length<uc.length?`mode "${o}" not allowed for "${r}" \u2014 ${r} is always confirmed (modes: ${s.join(", ")})`:`unknown mode "${o}" \u2014 modes: ${uc.join(", ")}`)}let n=await Ee();return n.ticketFlow={...Gs(n),...Object.fromEntries(e)},n.linear&&(delete n.linear.ticketFlow,delete n.linear.transitions),await ae($p(),n,{mode:384}),n.ticketFlow}async function fo(){let t=(await Ee()).deepgram||{},e=process.env.DEEPGRAM_API_KEY||t.apiKey;return e?{apiKey:e}:null}async function vS(){let t=(await Ee()).openai||{},e=process.env.BOSTRAT_OPENAI_API_KEY||t.apiKey;return e?{apiKey:e}:null}var TS={openai:{model:"gpt-realtime-2.1-mini",voice:"marin"}};async function Vs(){let e=(await Ee()).voiceChat||{},n=e.provider||"openai",r=TS[n];if(!r)return null;let o=n==="openai"?await vS():null;return o?{provider:n,model:e.model||r.model,voice:e.voice||r.voice,apiKey:o.apiKey}:null}async function Cp(){let t=(await Ee()).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 Sn(){let t=(await Ee()).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 pc={};Vu(pc,{createComment:()=>CS,findIssueUrl:()=>IS,issueStates:()=>BS,mdToAdf:()=>Bp,moveIssue:()=>DS,parseIssueRef:()=>ES,resolveIssue:()=>RS,uploadImage:()=>NS,upsertCard:()=>$S,whoami:()=>fc});var ar=t=>String(t.baseUrl||"").replace(/\/$/,""),Pp=t=>`Basic ${Buffer.from(`${t.email}:${t.apiToken}`).toString("base64")}`;async function vn(t,e,n,r){let o=await fetch(`${ar(t)}${n}`,{method:e,headers:{authorization:Pp(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 AS=/\/browse\/([A-Za-z][A-Za-z0-9]{0,9}-\d+)/,_S=/^([A-Z][A-Z0-9]{0,9}-\d+)$/;function ES(t){let e=String(t||"").trim();if(/^https?:\/\//i.test(e)){let r=e.match(AS);return r?r[1].toUpperCase():null}let n=e.match(_S);return n?n[1]:null}function IS(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 RS(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:`${ar(t)}/browse/${n.key}`,state:n.fields?.status?.name??null}}async function $S(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 OS=/(!\[[^\]]*\]\([^)\s]+\)|\*\*[^*\n]+\*\*|`[^`\n]+`|_[^_\n]+_|https?:\/\/[^\s)]+)/g;function MS(t){let e=[],n=0;for(let r of t.matchAll(OS)){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 Bp(t){let e=String(t??"").split(/\n{2,}/).map(n=>n.trim()).filter(Boolean).map(n=>{let r=[];return n.split(`
|
|
76
|
-
`).forEach((o,s)=>{s>0&&r.push({type:"hardBreak"}),r.push(...
|
|
79
|
+
`)}catch{return""}}var Ae=new Map,HS=12e4;async function WS(t){if(t.state==="stopped"||t.state==="error")return!1;let e=t.origin!=="detected",n=e?await US(t.slug):!0;if(e&&!n){let s=t.state==="starting";return t.state=s?"error":"stopped",s&&(t.error=await up(t.slug)||"server exited before listening"),Ae.delete(t.id),await Ee(t,t.state,t.error?{error:t.error}:{}),!0}if(await ho(t.port))return Ae.delete(t.id),t.lastHealthyAt=Date.now(),t.state!=="live"?(t.state="live",t.error=null,await Ee(t,"live"),!0):!1;if(t.state==="starting")return Date.now()-t.createdAt>HS?(t.state="error",t.error=await up(t.slug)||"never started listening",await Ee(t,"error",{error:t.error}),!0):!1;let o=(Ae.get(t.id)||0)+1;return Ae.set(t.id,o),o>=3&&t.state==="live"?(t.state="unhealthy",!0):!1}var rc=!1;async function zs(){if(!rc){rc=!0;try{let t=await _e(),e=!1;for(let n of t.views)await WS(n)&&(e=!0);e&&await ue()}finally{rc=!1}}}async function Js({trackId:t}={}){await zs();let e=await _e();return(t?e.views.filter(r=>r.trackId===t):e.views).map(r=>({...r}))}async function bp(t){let n=(await _e()).views.find(r=>r.id===t);return n?{...n}:null}function dc(t){return ce&&ce.views.find(e=>e.slug===t)||null}async function fc({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=Ct.resolve(r,o??"."),f=i==="detected"?null:async()=>{await DS(r,u),await jS(u,n)},d=await _e();if(i!=="detected"){let w=ac(e),g=String(n).trim(),b=d.views.find(S=>S.trackId===t&&(S.slug===w||ac(S.name)===w||S.cmd===g&&Ct.resolve(S.serveCwd??S.cwd)===u));if(b)return qS(d,b,{cmd:g,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>=qe)throw new Error(`view cap reached (${qe} live) \u2014 stop one first`);let h={id:SS().slice(0,8),trackId:t,box:a||null,name:e.trim(),slug:LS(d,e),cmd:n?String(n).trim():null,cwd:r,serveCwd:u,branch:c,port:await FS(d,s),origin:i,state:"starting",error:null,createdAt:Date.now(),lastHealthyAt:null};return i!=="detected"&&await uc(h),d.views.push(h),await ue(),await Ee(h,"created",{origin:i}),sc(t,h.id),i==="detected"&&await ho(h.port)&&(h.state="live",h.lastHealthyAt=Date.now(),await ue(),await Ee(h,"live")),{...h}}async function qS(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 PS(e.serveCwd??e.cwd,s);if((e.state==="live"||e.state==="starting")&&!i&&!c&&await ho(e.port))return await ue(),sc(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>=qe)throw new Error(`view cap reached (${qe} live) \u2014 stop one first`);return e.origin==="detected"?(await lc(e.port),e.origin="agent"):await wo(e.slug),e.cmd=n,s!==void 0&&(e.serveCwd=s),e.state="starting",e.error=null,e.createdAt=Date.now(),Ae.delete(e.id),await uc(e),await ue(),await Ee(e,"created",{restarted:!0,reused:!0}),sc(e.trackId,e.id),{...e,reused:!0}}async function kp(t){let n=(await _e()).views.find(r=>r.id===t);if(!n)throw new Error("view not found");return n.origin==="detected"?await lc(n.port):await wo(n.slug),n.state!=="stopped"&&(n.state="stopped",Ae.delete(n.id),await ue(),await Ee(n,"stopped")),{...n}}async function xp(t){let e=await _e(),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>=qe)throw new Error(`view cap reached (${qe} live) \u2014 stop one first`);return await wo(n.slug),n.state="starting",n.error=null,n.createdAt=Date.now(),Ae.delete(n.id),await uc(n),await ue(),await Ee(n,"created",{restarted:!0}),{...n}}async function Sp(t){let e=await _e(),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 lc(r.port):await wo(r.slug),e.views.splice(n,1),Ae.delete(r.id),await ue(),await Ee(r,"deleted"),{ok:!0,id:t,slug:r.slug}}async function vp(t){let e=await _e(),n=e.views.filter(r=>r.trackId===t);for(let r of n)r.origin!=="detected"&&await wo(r.slug),Ae.delete(r.id);return n.length&&(e.views=e.views.filter(r=>r.trackId!==t),await ue()),n.length}var KS=/https?:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0)(?::(\d{2,5}))?(?:\/|\b)/g;async function Tp(t,e){if(!t?.id||!Array.isArray(e)||!e.length)return[];let n=await _e(),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 ue()),!o.length)return[];let a=new Set;for(let c of o)for(let l of String(c.text||"").matchAll(KS)){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(!yp().has(c)&&!n.views.some(l=>l.port===c)&&await ho(c))try{i.push(await fc({trackId:t.id,name:`${t.name} preview`,cmd:null,cwd:t.cwd,port:c,origin:"detected",branch:t.workspace?.branch??null}))}catch{}return i}import Sn from"node:fs/promises";import Ke from"node:path";import GS from"node:os";import{randomUUID as VS}from"node:crypto";var ir=process.env.BOSTRAT_STATE_DIR||Ke.join(process.env.HOME||GS.tmpdir(),".bostrat"),Ep=Ke.join(ir,"runbooks.json"),mc=Ke.join(ir,"runbooks"),Ap=256*1024,zS=[{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 go(t){return zS.filter(({re:e})=>e.test(t)).map(({label:e})=>e)}var xn=null,pc=null;async function Ge(){return xn||(pc||=(async()=>{try{xn=JSON.parse(await Sn.readFile(Ep,"utf8"))}catch{xn={runbooks:[]}}finally{pc=null}return xn.runbooks||=[],xn})(),pc)}async function cr(){await qt(Ep,xn)}function lr(t,e,n={}){return $({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 ar(t,e){return Ke.join(mc,`${t.id}.v${e}.md`)}function _p(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 Ip(t){if(typeof t!="string"||!t.trim())throw new Error("md (markdown string) required");let e=Buffer.byteLength(t,"utf8");if(e>Ap)throw new Error(`runbook is ${Math.round(e/1024)} KB \u2014 over the ${Ap/1024} KB cap; a doc that size belongs in the repo, not a runbook`);let n=go(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 Rp(t,e){if(t.trackDeletedAt)throw new Error(`runbook is orphaned (its Track was deleted) \u2014 cannot ${e} it`)}async function Xs({trackId:t,status:e}={}){let n=await Ge(),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 hc(t){let n=(await Ge()).runbooks.find(r=>r.id===t);return n?{...n}:null}async function $p({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=Ip(r),c=await Ge(),l={id:VS().slice(0,8),trackId:t,trackName:e||t,box:s||null,agent:a||null,name:n.trim(),status:"open",version:1,versions:[],trackDeletedAt:null,cwd:o||null,createdAt:Date.now(),updatedAt:Date.now()};return await Sn.mkdir(mc,{recursive:!0}),await Sn.writeFile(ar(l,1),r),l.versions.push({v:1,mdRel:Ke.relative(ir,ar(l,1)),bytes:i,note:null,createdAt:Date.now()}),c.runbooks.push(l),await cr(),await lr(l,"created",{version:1}),{...l}}async function Op(t,{md:e,note:n}={}){let o=(await Ge()).runbooks.find(i=>i.id===t);if(!o)throw new Error("runbook not found");Rp(o,"update");let s=Ip(e),a=o.version+1;return o.version=a,o.updatedAt=Date.now(),await Sn.mkdir(mc,{recursive:!0}),await Sn.writeFile(ar(o,a),e),o.versions.push({v:a,mdRel:Ke.relative(ir,ar(o,a)),bytes:s,note:n?String(n).slice(0,200):null,createdAt:Date.now()}),await cr(),await lr(o,"updated",{version:a,...n?{note:String(n).slice(0,200)}:{}}),{...o}}async function Mp(t,e){if(e!=="open"&&e!=="done")throw new Error('status must be "open" or "done"');let r=(await Ge()).runbooks.find(o=>o.id===t);if(!r)throw new Error("runbook not found");return Rp(r,e==="done"?"complete":"reopen"),r.status===e?{...r}:(r.status=e,r.updatedAt=Date.now(),await cr(),await lr(r,e==="done"?"done":"reopened"),{...r})}async function Cp(t){let e=await Ge(),n=e.runbooks.findIndex(o=>o.id===t);if(n===-1)throw new Error("runbook not found");let r=e.runbooks[n];e.runbooks.splice(n,1);for(let o of r.versions||[])await Sn.rm(Ke.join(ir,o.mdRel),{force:!0}).catch(()=>{});return await cr(),r.trackDeletedAt||await lr(r,"deleted"),{ok:!0,id:t,name:r.name}}async function Np(t){let n=(await Ge()).runbooks.filter(r=>r.trackId===t&&!r.trackDeletedAt);for(let r of n)r.trackDeletedAt=Date.now(),await lr(r,"orphaned"),r.cwd=null;return n.length&&await cr(),n.length}async function Pp(t){let e=await Ge(),n=e.runbooks.filter(r=>r.trackId===t);for(let r of n){await lr(r,"deleted");for(let o of r.versions||[])await Sn.rm(Ke.join(ir,o.mdRel),{force:!0}).catch(()=>{})}return n.length&&(e.runbooks=e.runbooks.filter(r=>r.trackId!==t),await cr()),n.length}import ko from"node:fs/promises";import Xp from"node:path";import Tv from"node:os";import{randomUUID as Yp}from"node:crypto";import JS from"node:fs/promises";import Bp from"node:path";import XS from"node:os";var Dp=()=>Bp.join(process.env.BOSTRAT_STATE_DIR||Bp.join(process.env.HOME||XS.tmpdir(),".bostrat"),"integrations.json");async function Ie(){try{return JSON.parse(await JS.readFile(Dp(),"utf8"))||{}}catch{return{}}}var Ys=["linked","prOpened","prMerged","create"],wc=["prompt","auto","off"],YS={create:["prompt","off"]},jp=t=>YS[t]??wc;function Zs(t){let e=t.linear||{},n=e.transitions===!0?"auto":"prompt",r={};for(let o of Ys){let s=jp(o),a=s.includes(n)?n:"prompt",i=t.ticketFlow?.[o]??e.ticketFlow?.[o];r[o]=s.includes(i)?i:a}return r}async function vn(){let t=await Ie(),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:Zs(t),defaultTeam:n}:null}async function Qs(){let t=await Ie(),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:Zs(t)}}async function gc(){return Zs(await Ie())}async function Lp(t){let e=Object.entries(t||{});if(!e.length)throw new Error(`nothing to set \u2014 phases: ${Ys.join(", ")}`);for(let[r,o]of e){if(!Ys.includes(r))throw new Error(`unknown phase "${r}" \u2014 configurable phases: ${Ys.join(", ")}`);let s=jp(r);if(!s.includes(o))throw new Error(s.length<wc.length?`mode "${o}" not allowed for "${r}" \u2014 ${r} is always confirmed (modes: ${s.join(", ")})`:`unknown mode "${o}" \u2014 modes: ${wc.join(", ")}`)}let n=await Ie();return n.ticketFlow={...Zs(n),...Object.fromEntries(e)},n.linear&&(delete n.linear.ticketFlow,delete n.linear.transitions),await qt(Dp(),n,{mode:384}),n.ticketFlow}async function yo(){let t=(await Ie()).deepgram||{},e=process.env.DEEPGRAM_API_KEY||t.apiKey;return e?{apiKey:e}:null}async function ZS(){let t=(await Ie()).openai||{},e=process.env.BOSTRAT_OPENAI_API_KEY||t.apiKey;return e?{apiKey:e}:null}var QS={openai:{model:"gpt-realtime-2.1-mini",voice:"marin"}};async function ta(){let e=(await Ie()).voiceChat||{},n=e.provider||"openai",r=QS[n];if(!r)return null;let o=n==="openai"?await ZS():null;return o?{provider:n,model:e.model||r.model,voice:e.voice||r.voice,apiKey:o.apiKey}:null}async function Fp(){let t=(await Ie()).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 Tn(){let t=(await Ie()).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 bc={};td(bc,{createComment:()=>cv,findIssueUrl:()=>rv,issueStates:()=>dv,mdToAdf:()=>Wp,moveIssue:()=>fv,parseIssueRef:()=>nv,resolveIssue:()=>ov,uploadImage:()=>lv,upsertCard:()=>sv,whoami:()=>yc});var ur=t=>String(t.baseUrl||"").replace(/\/$/,""),Hp=t=>`Basic ${Buffer.from(`${t.email}:${t.apiToken}`).toString("base64")}`;async function An(t,e,n,r){let o=await fetch(`${ur(t)}${n}`,{method:e,headers:{authorization:Hp(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 tv=/\/browse\/([A-Za-z][A-Za-z0-9]{0,9}-\d+)/,ev=/^([A-Z][A-Z0-9]{0,9}-\d+)$/;function nv(t){let e=String(t||"").trim();if(/^https?:\/\//i.test(e)){let r=e.match(tv);return r?r[1].toUpperCase():null}let n=e.match(ev);return n?n[1]:null}function rv(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 ov(t,e){let n;try{n=await An(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:`${ur(t)}/browse/${n.key}`,state:n.fields?.status?.name??null}}async function sv(t,e,{url:n,title:r,subtitle:o}){await An(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 av=/(!\[[^\]]*\]\([^)\s]+\)|\*\*[^*\n]+\*\*|`[^`\n]+`|_[^_\n]+_|https?:\/\/[^\s)]+)/g;function iv(t){let e=[],n=0;for(let r of t.matchAll(av)){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 Wp(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(...iv(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 cv(t,e,n){let r=await An(t,"POST",`/rest/api/3/issue/${encodeURIComponent(e.key)}/comment`,{body:Wp(n)});return r?.id?`${ur(t)}/browse/${e.key}?focusedCommentId=${r.id}`:null}async function yc(t){let e=await An(t,"GET","/rest/api/3/myself"),n=null;try{n=new URL(ur(t)).host}catch{n=t.baseUrl??null}return{user:e?.displayName??e?.emailAddress??null,org:n}}async function lv(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(`${ur(t)}/rest/api/3/issue/${encodeURIComponent(e)}/attachments`,{method:"POST",headers:{authorization:Hp(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??`${ur(t)}/rest/api/3/attachment/content/${c.id}`}var uv={new:"unstarted",indeterminate:"started",done:"completed"},Up=t=>uv[t?.key]??"started";async function dv(t,e){let n=encodeURIComponent(e.key),[r,o]=await Promise.all([An(t,"GET",`/rest/api/3/issue/${n}?fields=status`),An(t,"GET",`/rest/api/3/issue/${n}/transitions`)]),s=r?.fields?.status??null,a=s?{id:`status:${s.id}`,name:s.name,type:Up(s.statusCategory)}:null,i=(o?.transitions??[]).map((c,l)=>({id:c.id,name:c.to?.name??c.name,type:Up(c.to?.statusCategory),position:l}));return{current:a,states:i}}async function fv(t,e,n){await An(t,"POST",`/rest/api/3/issue/${encodeURIComponent(e.key)}/transitions`,{transition:{id:String(n)}})}var vc={};td(vc,{createComment:()=>kv,createIssue:()=>Sc,createProject:()=>xc,findIssueUrl:()=>gv,issueStates:()=>Sv,moveIssue:()=>vv,parseIssueRef:()=>wv,resolveIssue:()=>yv,teams:()=>dr,uploadImage:()=>xv,upsertCard:()=>bv,whoami:()=>kc});var pv=()=>process.env.BOSTRAT_LINEAR_API_URL||"https://api.linear.app/graphql";async function de(t,e,n={}){let r=await fetch(pv(),{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 mv=/linear\.app\/[^/\s]+\/issue\/([A-Za-z][A-Za-z0-9]{0,9}-\d+)/,hv=/^([A-Z][A-Z0-9]{0,9}-\d+)$/;function wv(t){let e=String(t||"").trim(),n=e.match(mv);if(n)return n[1].toUpperCase();let r=e.match(hv);return r?r[1]:null}function gv(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 yv(t,e){let r=(await de(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 bv(t,e,{url:n,title:r,subtitle:o}){let s=await de(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 kv(t,e,n){let r=await de(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 kc(t){let e=await de(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 xv(t,{filename:e,contentType:n,buffer:r}){let o=await de(t,`mutation($contentType: String!, $filename: String!, $size: Int!) {
|
|
77
81
|
fileUpload(contentType: $contentType, filename: $filename, size: $size) {
|
|
78
82
|
success
|
|
79
83
|
uploadFile { uploadUrl assetUrl headers { key value } }
|
|
80
84
|
}
|
|
81
|
-
}`,{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 zS(t,e){let n=await de(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 VS(t,e,n){if(!(await de(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 ir(t){return((await de(t,"query { teams(first: 100) { nodes { id key name } } }")).teams?.nodes??[]).map(n=>({id:n.id,key:n.key,name:n.name}))}var Dp=255;async function hc(t,{teamId:e,name:n,description:r}){let o=r||null,s=!!o&&o.length>Dp,a=s?`${o.slice(0,Dp-1).trimEnd()}\u2026`:o,i=await de(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 wc(t,{teamId:e,title:n,description:r,projectId:o,priority:s}){let a=await de(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 Kp={linear:{api:gc,config:xn},jira:{api:pc,config:zs}};async function vc(){let t=[];for(let[e,{api:n,config:r}]of Object.entries(Kp)){let o=await r();o&&t.push({name:e,api:n,cfg:o})}return t}async function Tn(t){let e=Kp[t?.provider];if(!e)return null;let n=await e.config();return n?{name:t.provider,api:e.api,cfg:n}:null}function Tc(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 Gp=()=>process.env.BOSTRAT_STATE_DIR||Wp.join(process.env.HOME||JS.tmpdir(),".bostrat"),xc=()=>Wp.join(Gp(),"tickets.json"),zp=()=>(process.env.BOSTRAT_PUBLIC_URL||"http://bostrat.globopi").replace(/\/$/,""),Xs=t=>`${zp()}/track/${t}`,Js=null,yc=null,bc=Promise.resolve();async function Ys(){return Js||(yc??=mo.readFile(xc(),"utf8").then(t=>JSON.parse(t)?.tickets??{}).catch(()=>({})).then(t=>Js=t).finally(()=>yc=null),yc)}async function jp(){let t=JSON.stringify({tickets:Js},null,2),e=`${xc()}.${process.pid}.tmp`;await mo.mkdir(Gp(),{recursive:!0}),await mo.writeFile(e,t),await mo.rename(e,xc())}async function Ge(){bc=bc.then(jp,jp),await bc.catch(()=>{})}async function fe(t){return(await Ys())[t]??null}async function Zs(){return{...await Ys()}}async function Vp(){return(await vc()).length>0}async function cr(t,e,{source:n="api"}={}){let r=await vc();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 Ys())[t.id]=c,await Ge(),await O({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 ho(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 po(c,`**Bostrat** \u2014 Track **${c.track.name}** linked${l.length?` (${l.join(", ")})`:""}.`),await Yp(t.id,c,"linked"),c}async function Ac(t){let e=await Ys();return e[t]?(delete e[t],await Ge(),!0):!1}var Sc=t=>`$${t>=100?Math.round(t):t.toFixed(2)}`;async function Jp(t,e){if(!t.track?.cwd)return null;let n=se(await L({id:e,cwd:t.track.cwd}));return!n.totalUsd||n.totalUsd<.01?null:n}function XS(t){let e=Object.entries(t.byProvider).sort((r,o)=>o[1].usd-r[1].usd).map(([r,o])=>`${r} ${Sc(o.usd)}`).join(" \xB7 "),n=Object.keys(t.byBox).length;return`${Sc(t.totalUsd)}${e?` (${e})`:""}${n>1?` across ${n} boxes`:""}`}function YS(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(Sc(e.totalUsd)),n.join(" \xB7 ")||"linked"}async function ho(t,e){let n=await Tn(e);if(!n)return;let r=await Jp(e,t).catch(()=>null);await n.api.upsertCard(n.cfg,e,{url:Xs(t),title:`Bostrat Track: ${e.track.name}`,subtitle:YS(e,r)})}function Qs(t){let e=new URL(zp());return`${e.protocol}//${t}.${e.host}/`}var ZS={linked:"linked",opened:"prOpened",merged:"prMerged"};function QS(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 Xp(t,e,{from:n,to:r,mode:o,phase:s=null,actor:a="user"}){e.state=r,await Ge(),await O({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 Yp(t,e,n){let r=await Tn(e);if(!r)return;let o=r.cfg.ticketFlow?.[ZS[n]]??"prompt";if(o==="off")return;let{current:s,states:a}=await r.api.issueStates(r.cfg,e),i=Tc(s,a),c=QS(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 Xp(t,e,{from:l,to:c.name,mode:"auto",phase:n,actor:"system"});return}await O({id:t,cwd:e.track?.cwd},{type:"ticket.move.offered",actor:"system",source:"orchestrator",payload:{ts:Date.now(),offerId:qp(),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 Zp(t){let e=await fe(t);if(!e)throw new Error("no ticket linked to this Track");let n=await Tn(e);if(!n)throw new Error("no ticketing integration configured");let{current:r,states:o}=await n.api.issueStates(n.cfg,e),s=Tc(r,o);return s?.name&&e.state!==s.name&&(e.state=s.name,await Ge()),{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 _c(t,e,{mode:n="manual"}={}){if(!e)throw new Error("stateId required");let r=await fe(t);if(!r)throw new Error("no ticket linked to this Track");let o=await Tn(r);if(!o)throw new Error("no ticketing integration configured");let{current:s,states:a}=await o.api.issueStates(o.cfg,r),i=Tc(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 Ge()),{key:r.key,from:l,to:c.name,moved:!1}):(await o.api.moveIssue(o.cfg,r,c.id),await Xp(t,r,{from:l,to:c.name,mode:n}),{key:r.key,from:l,to:c.name,moved:!0})}async function Qp(t,e){let n=await fe(t);if(!n)throw new Error("no ticket linked to this Track");let r=await Tn(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 O({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 tm(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 _c(t.id,o.payload.to?.id,{mode:"offer"})),await O({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 Lp=50;async function em(){let t=await xn();if(!t)throw new Error("no ticketing integration configured");return ir(t)}function tv(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>Lp)throw new Error(`too many issues (${n.length}); cap is ${Lp} per proposal`);return{epic:r,issues:n,team:e.team?String(e.team).trim():null,linkCurrentTrack:e.linkCurrentTrack===!0}}async function ev(t,e,n,r){let o=r??await ir(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 fe(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 nm(t,e){let n=await xn();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=tv(e),o=await ir(n),s=await ev(n,r,t,o),a=s?{id:s.id,key:s.key,name:s.name}:null,i=qp();return await O({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 rm(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(h=>h.type==="ticket.create.offered"&&h.payload?.proposalId===e);if(!s)throw new Error("unknown proposal");if(o.some(h=>h.type==="ticket.create.answered"&&h.payload?.proposalId===e))throw new Error("proposal already answered");if(!n)return await O({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 xn();if(!a)throw new Error("no ticketing integration configured");let i=s.payload,c=i.team??null;if(!c&&r){let h=(await ir(a)).find(w=>w.id===r);if(!h)throw new Error("unknown team");c={id:h.id,key:h.key,name:h.name}}if(!c)throw new Error("a team is required to create \u2014 pick one");let l=[],u=[],f=null;if(i.epic){let h=await hc(a,{teamId:c.id,name:i.epic.name,description:i.epic.description});f=h.id,l.push({kind:"epic",title:h.name,url:h.url})}for(let h of i.issues||[])try{let w=await wc(a,{teamId:c.id,title:h.title,description:h.description,projectId:f,priority:h.priority});l.push({kind:"issue",key:w.key,title:w.title,url:w.url})}catch(w){u.push({title:h.title,message:w?.message||String(w)})}await O({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 O({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(h=>h.kind==="issue"),m=null;return i.linkCurrentTrack&&d.length===1&&!await fe(t.id)&&(m=await cr(t,d[0].key,{source:"created"}).catch(()=>null)),{proposalId:e,action:"created",created:l,failed:u,linked:m?m.key:null}}async function po(t,e){let n=await Tn(t);n&&await n.api.createComment(n.cfg,t,e)}async function nv(t,e,n){let r=await Tn(t);if(!r)return null;let o=await He(e);if(!o)return null;let s=yn(o,n);if(!s)return null;let a=await mo.readFile(qt(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 Fp=()=>Number(process.env.BOSTRAT_TICKET_COST_SYNC_MS??12e4),kc=new Map;function rv(t,e){if(Fp()<=0)return ho(t,e);if(kc.has(t))return;let n=setTimeout(()=>{kc.delete(t),fe(t).then(r=>r?ho(t,r):null).catch(()=>{})},Fp());n.unref?.(),kc.set(t,n)}var Up=new Map;async function Hp(t,e,n){let r=Up.get(t)??0;Date.now()-r<600*1e3||(Up.set(t,Date.now()),await O({id:t,cwd:e},{type:"ticket.sync.failed",actor:"system",source:"orchestrator",payload:{ts:Date.now(),message:n?.message||String(n)}}).catch(()=>{}))}async function ov(t,{resolveTrack:e=null}={}){let n=await fe(t.trackId),r=t.payload||{};if(!n){if(t.type!=="turn.user"||t.actor!=="user")return;let o=null;for(let a of await vc())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 Hp(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 Ge())}try{switch(t.type){case"handoff.done":n.track.leadAgent=r.toAgent??n.track.leadAgent,await Ge(),await ho(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 Ge(),await ho(t.trackId,n),r.change==="opened"||r.change==="merged"){let o=r.change==="merged"?await Jp(n,t.trackId).catch(()=>null):null;await po(n,`**Bostrat** \u2014 PR ${r.change} for **${n.track.name}**${r.url?`: ${r.url}`:r.number?` (#${r.number})`:""}`+(o?`
|
|
82
|
-
|
|
83
|
-
Agent cost for this Track: ${
|
|
84
|
-
|
|
85
|
-
`)}else if(r.change==="picked"){let o=(r.siblings??[]).map(s=>`**${s.name}**`).join(", ");await
|
|
86
|
-
_(runs on the Track's branch; reachable on the operator's private network only)_`);break}case"cost.recorded":await rv(t.trackId,n);break;default:break}}catch(o){let s=e?await e(t.trackId).catch(()=>null):null;await Hp(t.trackId,s?.cwd??null,o)}}function om({resolveTrack:t}){Wt.on("event",e=>{ov(e,{resolveTrack:t}).catch(()=>{})})}import ta from"node:fs/promises";import sm from"node:path";import sv from"node:os";var am=process.env.BOSTRAT_STATE_DIR||sm.join(process.env.HOME||sv.tmpdir(),".bostrat"),Ic=sm.join(am,"pr-links.json"),dt=(t,e)=>`${t}#${Number(e)}`,ea=null,Ec=null,D=Promise.resolve();async function kt(){return ea||(Ec??=ta.readFile(Ic,"utf8").then(t=>JSON.parse(t)?.links??{}).catch(()=>({})).then(t=>ea=t).finally(()=>Ec=null),Ec)}async function Z(){let t=JSON.stringify({links:ea},null,2),e=`${Ic}.${process.pid}.tmp`;await ta.mkdir(am,{recursive:!0}),await ta.writeFile(e,t),await ta.rename(e,Ic)}var av=new Set(["api","observed","reconciled"]);async function Lt({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=dt(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:av.has(a),firstSeen:Date.now()},D=D.then(Z,Z),await D.catch(()=>{}),{link:i[c],created:!0})}async function wo(t,e){if(!t||e==null)return null;let n=(await kt())[dt(t,e)];return!n||n.announced?null:(n.announced=!0,D=D.then(Z,Z),await D.catch(()=>{}),n)}async function Et(t,e){return!t||e==null?null:(await kt())[dt(t,e)]??null}async function ze(t,e,n){if(!t||e==null)return null;let r=(await kt())[dt(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,D=D.then(Z,Z),await D.catch(()=>{}),r):null}async function im(t,e,{headSha:n=null,trackId:r=null}={}){if(!t||e==null)return null;let o=(await kt())[dt(t,e)];return o?(o.ciFix={delegatedAt:Date.now(),headSha:n,trackId:r},D=D.then(Z,Z),await D.catch(()=>{}),o):null}async function cm(t,e,{headSha:n=null,trackId:r=null}={}){if(!t||e==null)return null;let o=(await kt())[dt(t,e)];return o?(o.reconcile={delegatedAt:Date.now(),headSha:n,trackId:r},D=D.then(Z,Z),await D.catch(()=>{}),o):null}async function na(t,e,{headSha:n=null}={}){if(!t||e==null)return null;let r=(await kt())[dt(t,e)];return!r||!r.ciFix?null:(r.ciFixAwait={since:Date.now(),trackId:r.ciFix.trackId??null,headSha:n},delete r.ciFix,D=D.then(Z,Z),await D.catch(()=>{}),r)}async function ra(t,e){if(!t||e==null)return null;let n=(await kt())[dt(t,e)];return!n||!n.ciFixAwait?null:(delete n.ciFixAwait,D=D.then(Z,Z),await D.catch(()=>{}),n)}async function oa(t,e,{headSha:n=null}={}){if(!t||e==null)return null;let r=(await kt())[dt(t,e)];return!r||!r.reconcile?null:(r.reconcileAwait={since:Date.now(),trackId:r.reconcile.trackId??null,headSha:n},delete r.reconcile,D=D.then(Z,Z),await D.catch(()=>{}),r)}async function sa(t,e){if(!t||e==null)return null;let n=(await kt())[dt(t,e)];return!n||!n.reconcileAwait?null:(delete n.reconcileAwait,D=D.then(Z,Z),await D.catch(()=>{}),n)}async function lm(t,e,{at:n=Date.now()}={}){if(!t||e==null)return null;let r=(await kt())[dt(t,e)];return r?(r.baseFoldAt=n,D=D.then(Z,Z),await D.catch(()=>{}),r):null}async function um(t,e){if(!t||e==null)return null;let n=(await kt())[dt(t,e)];return!n||!n.baseFoldAt?null:(delete n.baseFoldAt,D=D.then(Z,Z),await D.catch(()=>{}),n)}async function dm(t,e,n){if(!t||e==null||!n)return null;let r=(await kt())[dt(t,e)];return r?(r.lastHeadSha=n,D=D.then(Z,Z),await D.catch(()=>{}),r):null}async function go(t,e,{merge:n=null}={}){if(!t||e==null)return null;let r=(await kt())[dt(t,e)];return r?(r.armedAt=Date.now(),n&&(r.autoMerge={method:n.method||"merge",armedAt:r.armedAt}),D=D.then(Z,Z),await D.catch(()=>{}),r):null}async function lr(t,e,n=!0){if(!t||e==null)return null;let r=(await kt())[dt(t,e)];return r?(n?r.archiveOnMerge=!0:delete r.archiveOnMerge,D=D.then(Z,Z),await D.catch(()=>{}),r):null}async function aa(t,e,n=!0){if(!t||e==null)return null;let r=(await kt())[dt(t,e)];return r?(n?r.notifyOnMerge=!0:delete r.notifyOnMerge,D=D.then(Z,Z),await D.catch(()=>{}),r):null}async function fm(t,e,n){if(!t||e==null)return null;let r=(await kt())[dt(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()],D=D.then(Z,Z),await D.catch(()=>{}),r}async function yo(t,e){if(!t||e==null)return null;let n=(await kt())[dt(t,e)];return!n||!n.armedAt&&!n.autoMerge?null:(delete n.armedAt,delete n.autoMerge,delete n.archiveOnMerge,delete n.notifyOnMerge,delete n.notifyPending,D=D.then(Z,Z),await D.catch(()=>{}),n)}var iv=new Set(["merged","closed","lost"]);async function pm(){return Object.values(await kt()).filter(t=>!iv.has(t.state))}async function pe(){return Object.values(await kt()).sort((t,e)=>e.firstSeen-t.firstSeen)}var hm=t=>Math.round(t*100)/100;function cv(t){return!t||t.kind==="setup"?[]:Nt(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():mm.basename(e.base)}))}function wm(t){let e=new Map;for(let n of t||[])for(let r of cv(n)){let o=e.get(r.key);o||e.set(r.key,o={...r,tracks:[]}),o.tracks.push(n)}return e}function ia(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 Rc=(t,e)=>`${t} ${e}${t===1?"":"s"}`;function lv(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 $c(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,m]=f.split("#");return`${d.split("/").pop()}#${m}`});n.push(`Merged ${u.slice(0,4).join(", ")}${u.length>4?` +${u.length-4}`:""}`)}else s&&n.push(Rc(s,"PR")+" opened");let a=Object.keys(t?.subtasks||{}).length||r.filter(u=>u.type==="subtask-delegated").length;a&&n.push(Rc(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(`${Rc(i.length,"handoff")} ${u}${i.length>3?"\u2026":""}`)}let c=se(e);c.totalUsd>=.01&&n.push(`$${c.totalUsd.toFixed(2)}`);let l=Number.isFinite(t?.archivedAt)&&Number.isFinite(t?.createdAt)?lv(t.archivedAt-t.createdAt):null;return n.length&&l&&n.push(l),n.length?n.join(" \xB7 "):null}function uv(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 gm(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 m=ia(d);m&&(!i||m>i)&&(i=m),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:hm(l),firstActivityAt:c,lastActivityAt:i}}async function ym(t){let e=wm(t),n=await pe().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,se(c).totalUsd)}let s=[...e.values()].map(a=>gm(a,r,n)).sort((a,i)=>(i.lastActivityAt??0)-(a.lastActivityAt??0));return{generatedAt:Date.now(),arcs:s}}async function bm(t,e){let r=wm(t).get(e);if(!r)return null;let[o,s,a,i,c]=await Promise.all([pe().catch(()=>[]),js().catch(()=>[]),Ws().catch(()=>[]),qs().catch(()=>[]),Zs().catch(()=>({}))]),l=new Map,u=[],f=(d,m)=>(ia(m)||m.createdAt||0)-(ia(d)||d.createdAt||0);for(let d of[...r.tracks].sort(f)){let m=await L(d).catch(()=>[]),h=se(m);l.set(d.id,h.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:uv(d),handoffs:(d.events||[]).filter(g=>g.type==="lead-handoff").length,subtasks:Object.keys(d.subtasks||{}).length,repos:Nt(d.workspace).map(g=>g.repo||mm.basename(g.base||"")),prs:o.filter(g=>g.trackId===d.id&&(!r.slug||g.repo===r.slug)).map(g=>({repo:g.repo,number:g.number,state:g.state??null,title:g.title??null,url:g.url??null})),mocks:s.filter(g=>g.trackId===d.id).length,views:a.filter(g=>g.trackId===d.id).length,runbooks:i.filter(g=>g.trackId===d.id).map(g=>({name:g.name,status:g.status})),ticket:w?{provider:w.provider,key:w.key,url:w.url??null}:null,usd:hm(h.totalUsd),lastActivityAt:ia(d)})}return{...gm(r,l,o),generatedAt:Date.now(),segments:u}}import{execFile as dv}from"node:child_process";import{promisify as fv}from"node:util";import{randomUUID as pv}from"node:crypto";var An=fv(dv),ca=t=>new Promise(e=>setTimeout(e,t)),mv=Number(process.env.BOSTRAT_SWAP_EXIT_TIMEOUT_MS)||15e3,km=Number(process.env.BOSTRAT_SWAP_SHELL_TIMEOUT_MS)||8e3,hv=250,wv=400,xm={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 vm(t){let e=await $d(t);if(!e)return!1;try{return await An("pgrep",["-P",String(e)]),!1}catch(n){return n?.code===1}}async function Oc(t,e){let n=Date.now()+e;for(;;){if(await vm(t))return!0;if(Date.now()>n)return!1;await ca(hv)}}async function gv(t,e){let n=xm[e]||xm.default;n.escape&&(await xt(t,["Escape"],{kind:"swap-exit"}),await ca(150)),n.wipe&&await bs(t,400,{kind:"swap-exit"}),await ks(t,n.text,{perCharDelayMs:90,kind:"swap-exit"});let r=Se(e).submitDelayMs||0;await ca(Math.max(r,250)),await xt(t,["Enter"],{kind:"swap-exit"})}async function yv(t,e){try{let{stdout:n}=await An(N,B("show-environment","-t",t,e));return!String(n||"").trimStart().startsWith("-")}catch{return!1}}async function Sm(t){await An(N,B("respawn-pane","-k","-t",t))}async function Mc(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 Y(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||zn(n),a=Number(e.exitTimeoutMs)||mv;await G({flow:"swap",phase:"start",sessionName:t,from:o,to:s,cwd:r.cwd});let i=!1;if(!await vm(t)&&(await gv(t,o),!await Oc(t,a)&&(i=!0,await G({flow:"swap",phase:"respawn-forced",sessionName:t,from:o}),await Sm(t),!await Oc(t,km))))throw new Error(`pane shell did not come back after respawn-pane in "${t}"`);let c=s==="claude"&&Gn(n),l=c?cs():null,u=await yv(t,"ANTHROPIC_API_KEY"),f=!1;try{l?(await An(N,B("set-environment","-t",t,"ANTHROPIC_API_KEY",l)),f=!0):u&&(await An(N,B("set-environment","-u","-t",t,"ANTHROPIC_API_KEY")),f=!0)}catch(g){throw l?ws(g,l):g}if(f&&!i&&(await Sm(t),!await Oc(t,km)))throw new Error(`pane shell did not come back after env respawn in "${t}"`);let d=!!e.resumeSessionId,m=e.resumeSessionId||pv(),h=d?n:_i(n,m),w=d?e.pinned===!0:/(^|\s)--session-id(\s|=)/.test(h);return c&&await Ne(r.cwd),s==="codex"&&await Un(r.cwd),s==="cursor"&&await Hn(r.cwd),await wn(t),await An(N,B("set-option","-t",t,"@bostrat-sid",m)),await An(N,B("set-option","-t",t,"@bostrat-agent",s)),await ca(wv),await vt(t,h,{kind:"swap-launch"}),await G({flow:"swap",phase:"done",sessionName:t,from:o,to:s,sid:m,forced:i,envRespawned:f}),{name:t,cwd:r.cwd,sessionId:m,agent:s,pinned:w,forced:i,envRespawned:f}}import Yv from"node:fs/promises";function bv(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===`
|
|
87
|
-
`){e.push(n),n="";continue}n+=s}return e.push(n),e.map(o=>o.trim()).filter(Boolean)}function
|
|
88
|
-
`,n);i!==-1&&(e=e.slice(0,i))}let r=
|
|
89
|
-
`).map(n=>n.trim()).find(Boolean)||t;return e.length>88?`${e.slice(0,88).trimEnd()}\u2026`:e}function
|
|
90
|
-
`)){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(
|
|
91
|
-
`)){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 m=d.input||{};o.set(d.id,{id:d.id,kind:"agent",name:String(m.description||"").trim()||null,detail:String(m.subagent_type||"").trim()||null,ts:f,proven:!1})}else if(d.name==="Bash"&&d.input?.run_in_background===!0){let m=String(d.input.command||"").trim();o.set(d.id,{id:d.id,kind:"command",name:String(d.input.description||"").trim()||(m?
|
|
92
|
-
`).trim():"",
|
|
93
|
-
`);if(i!==-1)return o.slice(0,i);if(s+=a,o.length>=e)break}return o}finally{await n.close()}}async function
|
|
94
|
-
`).trim():"";async function
|
|
95
|
-
`)){let m=d.trim();if(!m||m[0]!=="{")continue;let h;try{h=JSON.parse(m)}catch{continue}if((h.type==="turn_context"&&h.payload?.model||h.type==="session_meta"&&h.payload?.model)&&(i=h.payload.model),h.type==="event_msg"){let
|
|
96
|
-
`;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
|
|
97
|
-
`)){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"&&
|
|
98
|
-
`),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(
|
|
99
|
-
`),r=n.length-1;for(;r>=0&&n[r].trim()==="";)r--;if(r<0||!
|
|
100
|
-
`),o=[];for(let c=0;c<r.length;c++){let l=r[c].match(
|
|
101
|
-
`),o=-1;for(let u=r.length-1;u>=0;u--)if(
|
|
102
|
-
`),o=
|
|
103
|
-
`),o=
|
|
104
|
-
`),o="";for(let s=0;s<r.length;s++)if(/^\s*https:\/\//.test(r[s])){o=
|
|
105
|
-
`)].reverse().find(a=>
|
|
106
|
-
`),o=
|
|
107
|
-
`),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(
|
|
108
|
-
`),o=
|
|
109
|
-
`).pop()||"")?!0:
|
|
110
|
-
`).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
|
|
111
|
-
`).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 Sv(t,e){let n=await de(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 vv(t,e,n){if(!(await de(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 de(t,"query { teams(first: 100) { nodes { id key name } } }")).teams?.nodes??[]).map(n=>({id:n.id,key:n.key,name:n.name}))}var qp=255;async function xc(t,{teamId:e,name:n,description:r}){let o=r||null,s=!!o&&o.length>qp,a=s?`${o.slice(0,qp-1).trimEnd()}\u2026`:o,i=await de(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 Sc(t,{teamId:e,title:n,description:r,projectId:o,priority:s}){let a=await de(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 Zp={linear:{api:vc,config:vn},jira:{api:bc,config:Qs}};async function Rc(){let t=[];for(let[e,{api:n,config:r}]of Object.entries(Zp)){let o=await r();o&&t.push({name:e,api:n,cfg:o})}return t}async function En(t){let e=Zp[t?.provider];if(!e)return null;let n=await e.config();return n?{name:t.provider,api:e.api,cfg:n}:null}function $c(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 Qp=()=>process.env.BOSTRAT_STATE_DIR||Xp.join(process.env.HOME||Tv.tmpdir(),".bostrat"),_c=()=>Xp.join(Qp(),"tickets.json"),tm=()=>(process.env.BOSTRAT_PUBLIC_URL||"http://bostrat.globopi").replace(/\/$/,""),na=t=>`${tm()}/track/${t}`,ea=null,Tc=null,Ac=Promise.resolve();async function ra(){return ea||(Tc??=ko.readFile(_c(),"utf8").then(t=>JSON.parse(t)?.tickets??{}).catch(()=>({})).then(t=>ea=t).finally(()=>Tc=null),Tc)}async function Kp(){let t=JSON.stringify({tickets:ea},null,2),e=`${_c()}.${process.pid}.tmp`;await ko.mkdir(Qp(),{recursive:!0}),await ko.writeFile(e,t),await ko.rename(e,_c())}async function Ve(){Ac=Ac.then(Kp,Kp),await Ac.catch(()=>{})}async function fe(t){return(await ra())[t]??null}async function oa(){return{...await ra()}}async function em(){return(await Rc()).length>0}async function fr(t,e,{source:n="api"}={}){let r=await Rc();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 ra())[t.id]=c,await Ve(),await $({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 bo(c,`**Bostrat** \u2014 Track **${c.track.name}** linked${l.length?` (${l.join(", ")})`:""}.`),await om(t.id,c,"linked"),c}async function Oc(t){let e=await ra();return e[t]?(delete e[t],await Ve(),!0):!1}var Ic=t=>`$${t>=100?Math.round(t):t.toFixed(2)}`;async function nm(t,e){if(!t.track?.cwd)return null;let n=ae(await L({id:e,cwd:t.track.cwd}));return!n.totalUsd||n.totalUsd<.01?null:n}function Av(t){let e=Object.entries(t.byProvider).sort((r,o)=>o[1].usd-r[1].usd).map(([r,o])=>`${r} ${Ic(o.usd)}`).join(" \xB7 "),n=Object.keys(t.byBox).length;return`${Ic(t.totalUsd)}${e?` (${e})`:""}${n>1?` across ${n} boxes`:""}`}function Ev(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(Ic(e.totalUsd)),n.join(" \xB7 ")||"linked"}async function xo(t,e){let n=await En(e);if(!n)return;let r=await nm(e,t).catch(()=>null);await n.api.upsertCard(n.cfg,e,{url:na(t),title:`Bostrat Track: ${e.track.name}`,subtitle:Ev(e,r)})}function sa(t){let e=new URL(tm());return`${e.protocol}//${t}.${e.host}/`}var _v={linked:"linked",opened:"prOpened",merged:"prMerged"};function Iv(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 rm(t,e,{from:n,to:r,mode:o,phase:s=null,actor:a="user"}){e.state=r,await Ve(),await $({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 om(t,e,n){let r=await En(e);if(!r)return;let o=r.cfg.ticketFlow?.[_v[n]]??"prompt";if(o==="off")return;let{current:s,states:a}=await r.api.issueStates(r.cfg,e),i=$c(s,a),c=Iv(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 rm(t,e,{from:l,to:c.name,mode:"auto",phase:n,actor:"system"});return}await $({id:t,cwd:e.track?.cwd},{type:"ticket.move.offered",actor:"system",source:"orchestrator",payload:{ts:Date.now(),offerId:Yp(),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 sm(t){let e=await fe(t);if(!e)throw new Error("no ticket linked to this Track");let n=await En(e);if(!n)throw new Error("no ticketing integration configured");let{current:r,states:o}=await n.api.issueStates(n.cfg,e),s=$c(r,o);return s?.name&&e.state!==s.name&&(e.state=s.name,await Ve()),{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 Mc(t,e,{mode:n="manual"}={}){if(!e)throw new Error("stateId required");let r=await fe(t);if(!r)throw new Error("no ticket linked to this Track");let o=await En(r);if(!o)throw new Error("no ticketing integration configured");let{current:s,states:a}=await o.api.issueStates(o.cfg,r),i=$c(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 Ve()),{key:r.key,from:l,to:c.name,moved:!1}):(await o.api.moveIssue(o.cfg,r,c.id),await rm(t,r,{from:l,to:c.name,mode:n}),{key:r.key,from:l,to:c.name,moved:!0})}async function am(t,e){let n=await fe(t);if(!n)throw new Error("no ticket linked to this Track");let r=await En(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 $({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 im(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 Mc(t.id,o.payload.to?.id,{mode:"offer"})),await $({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 Gp=50;async function cm(){let t=await vn();if(!t)throw new Error("no ticketing integration configured");return dr(t)}function Rv(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>Gp)throw new Error(`too many issues (${n.length}); cap is ${Gp} per proposal`);return{epic:r,issues:n,team:e.team?String(e.team).trim():null,linkCurrentTrack:e.linkCurrentTrack===!0}}async function $v(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 fe(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 lm(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=Rv(e),o=await dr(n),s=await $v(n,r,t,o),a=s?{id:s.id,key:s.key,name:s.name}:null,i=Yp();return await $({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 um(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(h=>h.type==="ticket.create.offered"&&h.payload?.proposalId===e);if(!s)throw new Error("unknown proposal");if(o.some(h=>h.type==="ticket.create.answered"&&h.payload?.proposalId===e))throw new Error("proposal already answered");if(!n)return await $({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 h=(await dr(a)).find(w=>w.id===r);if(!h)throw new Error("unknown team");c={id:h.id,key:h.key,name:h.name}}if(!c)throw new Error("a team is required to create \u2014 pick one");let l=[],u=[],f=null;if(i.epic){let h=await xc(a,{teamId:c.id,name:i.epic.name,description:i.epic.description});f=h.id,l.push({kind:"epic",title:h.name,url:h.url})}for(let h of i.issues||[])try{let w=await Sc(a,{teamId:c.id,title:h.title,description:h.description,projectId:f,priority:h.priority});l.push({kind:"issue",key:w.key,title:w.title,url:w.url})}catch(w){u.push({title:h.title,message:w?.message||String(w)})}await $({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 $({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(h=>h.kind==="issue"),m=null;return i.linkCurrentTrack&&d.length===1&&!await fe(t.id)&&(m=await fr(t,d[0].key,{source:"created"}).catch(()=>null)),{proposalId:e,action:"created",created:l,failed:u,linked:m?m.key:null}}async function bo(t,e){let n=await En(t);n&&await n.api.createComment(n.cfg,t,e)}async function Ov(t,e,n){let r=await En(t);if(!r)return null;let o=await We(e);if(!o)return null;let s=Te(o,n);if(!s)return null;let a=await ko.readFile(Kt(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 Vp=()=>Number(process.env.BOSTRAT_TICKET_COST_SYNC_MS??12e4),Ec=new Map;function Mv(t,e){if(Vp()<=0)return xo(t,e);if(Ec.has(t))return;let n=setTimeout(()=>{Ec.delete(t),fe(t).then(r=>r?xo(t,r):null).catch(()=>{})},Vp());n.unref?.(),Ec.set(t,n)}var zp=new Map;async function Jp(t,e,n){let r=zp.get(t)??0;Date.now()-r<600*1e3||(zp.set(t,Date.now()),await $({id:t,cwd:e},{type:"ticket.sync.failed",actor:"system",source:"orchestrator",payload:{ts:Date.now(),message:n?.message||String(n)}}).catch(()=>{}))}async function Cv(t,{resolveTrack:e=null}={}){let n=await fe(t.trackId),r=t.payload||{};if(!n){if(t.type!=="turn.user"||t.actor!=="user")return;let o=null;for(let a of await Rc())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 fr(s,o,{source:"chat"})}catch(a){await Jp(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 Ve())}try{switch(t.type){case"handoff.done":n.track.leadAgent=r.toAgent??n.track.leadAgent,await Ve(),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 Ve(),await xo(t.trackId,n),r.change==="opened"||r.change==="merged"){let o=r.change==="merged"?await nm(n,t.trackId).catch(()=>null):null;await bo(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: ${Av(o)}.`:"")),await om(t.trackId,n,r.change)}break}case"mock.state.changed":{if(r.change==="ready"&&r.mockId){let o=await Ov(n,r.mockId,r.version??null);if(!o)break;let s=(r.version??1)>1?` revised (v${r.version})`:"";await bo(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 bo(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 bo(n,`**Bostrat** \u2014 live preview **${r.name}**: ${sa(r.slug)}
|
|
90
|
+
_(runs on the Track's branch; reachable on the operator's private network only)_`);break}case"cost.recorded":await Mv(t.trackId,n);break;default:break}}catch(o){let s=e?await e(t.trackId).catch(()=>null):null;await Jp(t.trackId,s?.cwd??null,o)}}function dm({resolveTrack:t}){Wt.on("event",e=>{Cv(e,{resolveTrack:t}).catch(()=>{})})}import aa from"node:fs/promises";import fm from"node:path";import Nv from"node:os";var pm=process.env.BOSTRAT_STATE_DIR||fm.join(process.env.HOME||Nv.tmpdir(),".bostrat"),Nc=fm.join(pm,"pr-links.json"),ft=(t,e)=>`${t}#${Number(e)}`,ia=null,Cc=null,D=Promise.resolve();async function kt(){return ia||(Cc??=aa.readFile(Nc,"utf8").then(t=>JSON.parse(t)?.links??{}).catch(()=>({})).then(t=>ia=t).finally(()=>Cc=null),Cc)}async function Z(){let t=JSON.stringify({links:ia},null,2),e=`${Nc}.${process.pid}.tmp`;await aa.mkdir(pm,{recursive:!0}),await aa.writeFile(e,t),await aa.rename(e,Nc)}var Pv=new Set(["api","observed","reconciled"]);async function Lt({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=ft(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:Pv.has(a),firstSeen:Date.now()},D=D.then(Z,Z),await D.catch(()=>{}),{link:i[c],created:!0})}async function So(t,e){if(!t||e==null)return null;let n=(await kt())[ft(t,e)];return!n||n.announced?null:(n.announced=!0,D=D.then(Z,Z),await D.catch(()=>{}),n)}async function It(t,e){return!t||e==null?null:(await kt())[ft(t,e)]??null}async function ze(t,e,n){if(!t||e==null)return null;let r=(await kt())[ft(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,D=D.then(Z,Z),await D.catch(()=>{}),r):null}async function mm(t,e,{headSha:n=null,trackId:r=null}={}){if(!t||e==null)return null;let o=(await kt())[ft(t,e)];return o?(o.ciFix={delegatedAt:Date.now(),headSha:n,trackId:r},D=D.then(Z,Z),await D.catch(()=>{}),o):null}async function hm(t,e,{headSha:n=null,trackId:r=null}={}){if(!t||e==null)return null;let o=(await kt())[ft(t,e)];return o?(o.reconcile={delegatedAt:Date.now(),headSha:n,trackId:r},D=D.then(Z,Z),await D.catch(()=>{}),o):null}async function ca(t,e,{headSha:n=null}={}){if(!t||e==null)return null;let r=(await kt())[ft(t,e)];return!r||!r.ciFix?null:(r.ciFixAwait={since:Date.now(),trackId:r.ciFix.trackId??null,headSha:n},delete r.ciFix,D=D.then(Z,Z),await D.catch(()=>{}),r)}async function la(t,e){if(!t||e==null)return null;let n=(await kt())[ft(t,e)];return!n||!n.ciFixAwait?null:(delete n.ciFixAwait,D=D.then(Z,Z),await D.catch(()=>{}),n)}async function ua(t,e,{headSha:n=null}={}){if(!t||e==null)return null;let r=(await kt())[ft(t,e)];return!r||!r.reconcile?null:(r.reconcileAwait={since:Date.now(),trackId:r.reconcile.trackId??null,headSha:n},delete r.reconcile,D=D.then(Z,Z),await D.catch(()=>{}),r)}async function da(t,e){if(!t||e==null)return null;let n=(await kt())[ft(t,e)];return!n||!n.reconcileAwait?null:(delete n.reconcileAwait,D=D.then(Z,Z),await D.catch(()=>{}),n)}async function wm(t,e,{at:n=Date.now()}={}){if(!t||e==null)return null;let r=(await kt())[ft(t,e)];return r?(r.baseFoldAt=n,D=D.then(Z,Z),await D.catch(()=>{}),r):null}async function gm(t,e){if(!t||e==null)return null;let n=(await kt())[ft(t,e)];return!n||!n.baseFoldAt?null:(delete n.baseFoldAt,D=D.then(Z,Z),await D.catch(()=>{}),n)}async function ym(t,e,n){if(!t||e==null||!n)return null;let r=(await kt())[ft(t,e)];return r?(r.lastHeadSha=n,D=D.then(Z,Z),await D.catch(()=>{}),r):null}async function vo(t,e,{merge:n=null}={}){if(!t||e==null)return null;let r=(await kt())[ft(t,e)];return r?(r.armedAt=Date.now(),n&&(r.autoMerge={method:n.method||"merge",armedAt:r.armedAt}),D=D.then(Z,Z),await D.catch(()=>{}),r):null}async function pr(t,e,n=!0){if(!t||e==null)return null;let r=(await kt())[ft(t,e)];return r?(n?r.archiveOnMerge=!0:delete r.archiveOnMerge,D=D.then(Z,Z),await D.catch(()=>{}),r):null}async function fa(t,e,n=!0){if(!t||e==null)return null;let r=(await kt())[ft(t,e)];return r?(n?r.notifyOnMerge=!0:delete r.notifyOnMerge,D=D.then(Z,Z),await D.catch(()=>{}),r):null}async function bm(t,e,n){if(!t||e==null)return null;let r=(await kt())[ft(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()],D=D.then(Z,Z),await D.catch(()=>{}),r}async function To(t,e){if(!t||e==null)return null;let n=(await kt())[ft(t,e)];return!n||!n.armedAt&&!n.autoMerge?null:(delete n.armedAt,delete n.autoMerge,delete n.archiveOnMerge,delete n.notifyOnMerge,delete n.notifyPending,D=D.then(Z,Z),await D.catch(()=>{}),n)}var Bv=new Set(["merged","closed","lost"]);async function km(){return Object.values(await kt()).filter(t=>!Bv.has(t.state))}async function pe(){return Object.values(await kt()).sort((t,e)=>e.firstSeen-t.firstSeen)}var Sm=t=>Math.round(t*100)/100;function Dv(t){return!t||t.kind==="setup"?[]:Nt(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():xm.basename(e.base)}))}function vm(t){let e=new Map;for(let n of t||[])for(let r of Dv(n)){let o=e.get(r.key);o||e.set(r.key,o={...r,tracks:[]}),o.tracks.push(n)}return e}function pa(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 Pc=(t,e)=>`${t} ${e}${t===1?"":"s"}`;function jv(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 Bc(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,m]=f.split("#");return`${d.split("/").pop()}#${m}`});n.push(`Merged ${u.slice(0,4).join(", ")}${u.length>4?` +${u.length-4}`:""}`)}else s&&n.push(Pc(s,"PR")+" opened");let a=Object.keys(t?.subtasks||{}).length||r.filter(u=>u.type==="subtask-delegated").length;a&&n.push(Pc(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(`${Pc(i.length,"handoff")} ${u}${i.length>3?"\u2026":""}`)}let c=ae(e);c.totalUsd>=.01&&n.push(`$${c.totalUsd.toFixed(2)}`);let l=Number.isFinite(t?.archivedAt)&&Number.isFinite(t?.createdAt)?jv(t.archivedAt-t.createdAt):null;return n.length&&l&&n.push(l),n.length?n.join(" \xB7 "):null}function Lv(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 Tm(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 m=pa(d);m&&(!i||m>i)&&(i=m),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:Sm(l),firstActivityAt:c,lastActivityAt:i}}async function Am(t){let e=vm(t),n=await pe().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,ae(c).totalUsd)}let s=[...e.values()].map(a=>Tm(a,r,n)).sort((a,i)=>(i.lastActivityAt??0)-(a.lastActivityAt??0));return{generatedAt:Date.now(),arcs:s}}async function Em(t,e){let r=vm(t).get(e);if(!r)return null;let[o,s,a,i,c]=await Promise.all([pe().catch(()=>[]),kn().catch(()=>[]),Js().catch(()=>[]),Xs().catch(()=>[]),oa().catch(()=>({}))]),l=new Map,u=[],f=(d,m)=>(pa(m)||m.createdAt||0)-(pa(d)||d.createdAt||0);for(let d of[...r.tracks].sort(f)){let m=await L(d).catch(()=>[]),h=ae(m);l.set(d.id,h.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:Lv(d),handoffs:(d.events||[]).filter(g=>g.type==="lead-handoff").length,subtasks:Object.keys(d.subtasks||{}).length,repos:Nt(d.workspace).map(g=>g.repo||xm.basename(g.base||"")),prs:o.filter(g=>g.trackId===d.id&&(!r.slug||g.repo===r.slug)).map(g=>({repo:g.repo,number:g.number,state:g.state??null,title:g.title??null,url:g.url??null})),mocks:s.filter(g=>g.trackId===d.id).length,views:a.filter(g=>g.trackId===d.id).length,runbooks:i.filter(g=>g.trackId===d.id).map(g=>({name:g.name,status:g.status})),ticket:w?{provider:w.provider,key:w.key,url:w.url??null}:null,usd:Sm(h.totalUsd),lastActivityAt:pa(d)})}return{...Tm(r,l,o),generatedAt:Date.now(),segments:u}}import{execFile as Fv}from"node:child_process";import{promisify as Uv}from"node:util";import{randomUUID as Hv}from"node:crypto";var _n=Uv(Fv),ma=t=>new Promise(e=>setTimeout(e,t)),Wv=Number(process.env.BOSTRAT_SWAP_EXIT_TIMEOUT_MS)||15e3,_m=Number(process.env.BOSTRAT_SWAP_SHELL_TIMEOUT_MS)||8e3,qv=250,Kv=400,Im={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 $m(t){let e=await Bd(t);if(!e)return!1;try{return await _n("pgrep",["-P",String(e)]),!1}catch(n){return n?.code===1}}async function Dc(t,e){let n=Date.now()+e;for(;;){if(await $m(t))return!0;if(Date.now()>n)return!1;await ma(qv)}}async function Gv(t,e){let n=Im[e]||Im.default;n.escape&&(await xt(t,["Escape"],{kind:"swap-exit"}),await ma(150)),n.wipe&&await Es(t,400,{kind:"swap-exit"}),await _s(t,n.text,{perCharDelayMs:90,kind:"swap-exit"});let r=Se(e).submitDelayMs||0;await ma(Math.max(r,250)),await xt(t,["Enter"],{kind:"swap-exit"})}async function Vv(t,e){try{let{stdout:n}=await _n(N,B("show-environment","-t",t,e));return!String(n||"").trimStart().startsWith("-")}catch{return!1}}async function Rm(t){await _n(N,B("respawn-pane","-k","-t",t))}async function jc(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 Y(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||Xn(n),a=Number(e.exitTimeoutMs)||Wv;await G({flow:"swap",phase:"start",sessionName:t,from:o,to:s,cwd:r.cwd});let i=!1;if(!await $m(t)&&(await Gv(t,o),!await Dc(t,a)&&(i=!0,await G({flow:"swap",phase:"respawn-forced",sessionName:t,from:o}),await Rm(t),!await Dc(t,_m))))throw new Error(`pane shell did not come back after respawn-pane in "${t}"`);let c=s==="claude"&&Jn(n),l=c?hs():null,u=await Vv(t,"ANTHROPIC_API_KEY"),f=!1;try{l?(await _n(N,B("set-environment","-t",t,"ANTHROPIC_API_KEY",l)),f=!0):u&&(await _n(N,B("set-environment","-u","-t",t,"ANTHROPIC_API_KEY")),f=!0)}catch(g){throw l?vs(g,l):g}if(f&&!i&&(await Rm(t),!await Dc(t,_m)))throw new Error(`pane shell did not come back after env respawn in "${t}"`);let d=!!e.resumeSessionId,m=e.resumeSessionId||Hv(),h=d?n:Mi(n,m),w=d?e.pinned===!0:/(^|\s)--session-id(\s|=)/.test(h);return c&&await Pe(r.cwd),s==="codex"&&await qn(r.cwd),s==="cursor"&&await Kn(r.cwd),await yn(t),await _n(N,B("set-option","-t",t,"@bostrat-sid",m)),await _n(N,B("set-option","-t",t,"@bostrat-agent",s)),await ma(Kv),await vt(t,h,{kind:"swap-launch"}),await G({flow:"swap",phase:"done",sessionName:t,from:o,to:s,sid:m,forced:i,envRespawned:f}),{name:t,cwd:r.cwd,sessionId:m,agent:s,pinned:w,forced:i,envRespawned:f}}import ET from"node:fs/promises";function zv(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 Ao(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 Eo(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 hr=(t,e)=>t.length>e?`${t.slice(0,e).trimEnd()}\u2026`:t,Et=t=>t&&!/[$`]/.test(t)?t:null,mt=t=>t.replace(/\/+$/,"").split("/").pop()||t,mr=t=>{let e=t.replace(/\/+$/,"").split("/").filter(Boolean);return e.length>3?e.slice(-2).join("/"):t},Cm=t=>t.startsWith("-"),Om=new Set(["-g","--glob","-t","--type","-A","-B","-C","-m","--max-count","-e","--regexp","-f","--file"]);function at(t,e=new Set){let n=[];for(let r=1;r<t.length;r++){let o=t[r];if(o==="<<")break;if(Cm(o)){e.has(o)&&r++;continue}n.push(o)}return n}var Jv={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 ${mt(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"},Xv={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 Nm(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 Yv(t){let e=Nm(t),n=Et(at(t).find(r=>!/^\d+(,\d+)?p$/.test(r)));return e&&n?e.from===e.to?`Read line ${e.from} of ${mt(n)}`:`Read lines ${e.from}\u2013${e.to} of ${mt(n)}`:t.includes("-i")&&n?`Edit ${mt(n)}`:null}function Lc(t){if(t.includes("--files")){let s=Et(at(t,Om)[0]);return s&&s!=="."?`List files in ${mr(s)}`:"List files"}let e=at(t,Om),n=e[0];if(!n)return null;let r=Et(e.slice(1).find(s=>s!==".")),o=hr(n,40);return r?`Search for \u201C${o}\u201D in ${mr(r)}`:`Search for \u201C${o}\u201D`}function Fc(t){let e=at(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 Uc(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=at(t)[0];return n?`Run ${mt(n)}`:null}function Zv(t){let e=at(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 Mm(t){return t.find(e=>/^https?:\/\//.test(e))||null}var Qv={git:t=>{let e=at(t,new Set(["-C","-c"])),n=Jv[e[0]];return n?n(e,t):null},gh:t=>{let e=at(t),n=Xv[e[0]];return n?n(e):null},sed:Yv,rg:Lc,grep:Lc,egrep:Lc,nl:t=>{let e=Et(at(t)[0]);return e?`Read ${mt(e)}`:null},cat:t=>{let e=Et(at(t)[0]);return e?`Read ${mt(e)}`:null},head:t=>{let e=Et(at(t,new Set(["-n","-c"])).find(n=>!/^\d+$/.test(n)));return e?`Read the start of ${mt(e)}`:null},tail:t=>{let e=Et(at(t,new Set(["-n","-c"])).find(n=>!/^\d+$/.test(n)));return e?`Read the end of ${mt(e)}`:null},wc:t=>{let e=Et(at(t)[0]);return e?`Count lines in ${mt(e)}`:"Count input lines"},ls:t=>{let e=Et(at(t)[0]);return e&&e!=="."?`List files in ${mr(e)}`:"List files"},find:t=>Et(t[1])&&!Cm(t[1])&&t[1]!=="."?`Find files in ${mr(t[1])}`:"Find files",tree:()=>"List the directory tree",mkdir:t=>{let e=Et(at(t)[0]);return e?`Create directory ${mr(e)}`:null},touch:t=>{let e=Et(at(t)[0]);return e?`Create ${mt(e)}`:null},rm:t=>{let e=Et(at(t)[0]);return e?`Delete ${mr(e)}`:null},mv:t=>{let e=Et(at(t)[0]);return e?`Move ${mt(e)}`:null},cp:t=>{let e=Et(at(t)[0]);return e?`Copy ${mt(e)}`:null},chmod:t=>{let e=Et(at(t)[1]);return e?`Change permissions on ${mt(e)}`:null},diff:t=>{let e=at(t);return e.length>=2?`Compare ${mt(e[0])} and ${mt(e[1])}`:null},curl:t=>{let e=Mm(t);return e?`Fetch ${hr(e,56)}`:"Fetch a URL"},wget:t=>{let e=Mm(t);return e?`Fetch ${hr(e,56)}`:"Fetch a URL"},npm:Fc,pnpm:Fc,yarn:Fc,npx:t=>{let e=at(t)[0];return e?`Run ${e}`:null},node:t=>Uc(t,"Node"),python:t=>Uc(t,"Python"),python3:t=>Uc(t,"Python"),make:t=>{let e=at(t)[0];return e?`Run make ${e}`:"Run make"},systemctl:Zv,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=at(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=at(t)[0];return e?`Locate ${e}`:null},command:t=>t[1]==="-v"&&t[2]?`Locate ${t[2]}`:null,fly:t=>{let e=at(t)[0];return e==="deploy"?"Deploy to Fly":e?`Run fly ${e}`:null}},tT=new Set(["pwd","echo","cd","sleep","true","false","test","[","set","export","exit","read"]);function eT(t){let e=Eo(Ao(t));if(!e.length)return null;if(/^(ba|z)?sh$/.test(mt(e[0]))&&(e[1]==="-lc"||e[1]==="-c")&&e[2])return Hc(e[2]);let n=mt(e[0]);if(tT.has(n))return null;if(e.includes("--version"))return`Check ${n} version`;if(n.endsWith(".sh"))return`Run ${n}`;let r=Qv[n];return r?r(e.map((o,s)=>s===0?n:o)):null}function Hc(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=zv(e);for(let i=0;i<r.length-1;i++){let c=Eo(Ao(r[i])),l=Eo(Ao(r[i+1]));if(!(!c.length||!l.length)&&["nl","cat"].includes(mt(c[0]))&&mt(l[0])==="sed"){let u=Nm(l),f=at(c)[0];if(u&&f)return hr(u.from===u.to?`Read line ${u.from} of ${mt(f)}`:`Read lines ${u.from}\u2013${u.to} of ${mt(f)}`,88)}}let o=[];for(let i of r){let c=eT(i);c&&o.push({d:c,head:mt(Eo(Ao(i))[0]||""),sub:at(Eo(Ao(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":hr(s?s.d:o[0].d,88)}var qc=new Set(["Bash"]);function wr(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 Je(t,e=""){let n=e.trim();return n||Hc(t)||wr(t)}function Wc(t,e){if(t){if(Array.isArray(t)){for(let n of t)Wc(n,e);return}if(typeof t=="object"){if(t.type==="tool_use"&&qc.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))Wc(n,e)}}}async function Pm(t,{maxBytes:e=1048576,limit:n=400}={}){let r;try{r=await Ot(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(Wc(c.content,l),!l.length)continue;let u=i.timestamp?Date.parse(i.timestamp):null;for(let f of l)o.push({...f,summary:Je(f.command,f.description),ts:u})}return o.slice(-n)}function Bm(t,e){let n=e.filter(l=>l.role==="assistant"&&(l.text||"").trim()).sort((l,u)=>(l.ts||0)-(u.ts||0)),r=[...t].sort((l,u)=>(l.ts||0)-(u.ts||0)),o=[],s=0,a=0,i=l=>{let u=l.n??a+1;return u>a&&(a=u),u};for(let l of n){let u=r.findIndex((h,w)=>w>=s&&(h.ts||0)>(l.ts||0)),f=u===-1?r.length:u,d=r.slice(s,f);if(s=f,!d.length)continue;let m=d.map(h=>({...h,n:i(h)}));o.push({id:`batch-${l.ts}-${l.box}`,anchorTs:l.ts,anchorBox:l.box,agent:l.agent,boxRole:l.boxRole,commands:m,ts:Math.max(...m.map(h=>h.ts||0)),pending:!1})}let c=r.slice(s);if(c.length){let l=c.map(u=>({...u,n:i(u)}));o.push({id:`batch-pending-${l[0]?.box||"lead"}`,anchorTs:null,anchorBox:l[l.length-1]?.box??null,agent:l[l.length-1]?.agent??"claude",boxRole:l[l.length-1]?.boxRole??"lead",commands:l,ts:Math.max(...l.map(u=>u.ts||0)),pending:!0})}return o}var nT=/^Async (?:agent|task) launched/i,rT=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 Dm(t,{maxBytes:e=2097152,limit:n=100}={}){let r;try{r=await Ot(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 m=d.input||{};o.set(d.id,{id:d.id,kind:"agent",name:String(m.description||"").trim()||null,detail:String(m.subagent_type||"").trim()||null,ts:f,proven:!1})}else if(d.name==="Bash"&&d.input?.run_in_background===!0){let m=String(d.input.command||"").trim();o.set(d.id,{id:d.id,kind:"command",name:String(d.input.description||"").trim()||(m?wr(m):null),detail:m?wr(m):null,ts:f,proven:!0})}}else d.type==="tool_result"&&d.tool_use_id&&nT.test(rT(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 _o from"node:fs";import sT from"node:fs/promises";import In from"node:path";import aT from"node:os";import iT from"node:crypto";import cT from"node:zlib";var ha=new Set(["Shell","Read","Grep","Glob","StrReplace","Write","ReadLints","Delete","ApplyPatch","Await"]),Yt=(t,e=88)=>t.length>e?`${t.slice(0,e).trimEnd()}\u2026`:t,gr=t=>String(t).replace(/\/+$/,"").split("/").pop()||String(t);function Xe(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 Kc=t=>`"${String(t).replace(/"/g,'\\"')}"`;function oT(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 jm(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:Yt(`read ${Xe(r,n)}${o?` (lines ${o}\u2013${s??"end"})`:""}`,140),description:o?`Read lines ${o}\u2013${s??"end"} of ${gr(r)}`:`Read ${gr(r)}`}}case"Grep":{let r=e?.pattern;if(!r)return null;let o=e?.path?Xe(e.path,n):null,s=e?.glob?` --glob ${Kc(e.glob)}`:"",a=Yt(String(r),40);return{command:Yt(`grep ${Kc(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?Xe(e.target_directory,n):null;return{command:Yt(`glob ${Kc(r)}${o&&o!=="."?` ${o}`:""}`,140),description:`Find files matching \u201C${Yt(String(r),40)}\u201D`}}case"StrReplace":return e?.path?{command:Yt(`edit ${Xe(e.path,n)}`,140),description:`Edit ${gr(e.path)}`}:null;case"Write":return e?.path?{command:Yt(`write ${Xe(e.path,n)}`,140),description:`Write ${gr(e.path)}`}:null;case"Delete":return e?.path?{command:Yt(`delete ${Xe(e.path,n)}`,140),description:`Delete ${gr(e.path)}`}:null;case"ReadLints":{let o=(Array.isArray(e?.paths)?e.paths:[]).map(s=>Xe(s,n)).filter(Boolean);return{command:Yt(o.length?`lints ${o.join(" ")}`:"lints",140),description:"Check lints"}}case"ApplyPatch":{let r=oT(e);if(!r.length)return null;let o=r.length>1?` (+${r.length-1} more)`:"";return{command:Yt(`apply_patch ${Xe(r[0],n)}${o}`,140),description:r.length>1?`Patch ${r.length} files`:`Patch ${gr(r[0])}`}}case"Await":{let r=e?.shell_id??e?.task_id;return{command:Yt(`await ${r??"background output"}`,140),description:"Wait for a background command"}}default:return null}}var lT=process.env.BOSTRAT_CURSOR_CHATS||In.join(aT.homedir(),".cursor","chats");function Um(t){let e=iT.createHash("md5").update(String(t)).digest("hex");return In.join(lT,e)}function Hm(t){try{let e=JSON.parse(_o.readFileSync(In.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 uT(t){let e=Um(t),n;try{n=_o.readdirSync(e,{withFileTypes:!0}).filter(o=>o.isDirectory())}catch{return null}let r=null;for(let o of n){let s=In.join(e,o.name),a=In.join(s,"store.db");if(!_o.existsSync(a))continue;let i=Hm(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 wa(t,e){if(!t)return null;if(e){let n=In.join(Um(t),e),r=In.join(n,"store.db");if(_o.existsSync(r))return{id:e,dir:n,store:r,...Hm(n)||{createdAtMs:null,updatedAtMs:null}}}return uT(t)}function Lm(t,e){let n=t.prepare("SELECT data FROM blobs WHERE id = ?").get(e);return n?Buffer.from(n.data):null}function dT(t){if(!t)return null;let e=t;if(e.length>1&&e[0]===31&&e[1]===139)try{e=cT.gunzipSync(e)}catch{}try{return JSON.parse(e.toString("utf8"))}catch{return null}}function fT(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 pT(t,e){if(!Array.isArray(t))return[];let n=[];for(let r of t){if(!r||r.type!=="tool-call"||!ha.has(r.toolName))continue;let o=jm(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 mT=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():"",hT=t=>Array.isArray(t)?t.filter(e=>e&&e.type==="tool-call"&&e.toolName).map(e=>e.toolName):[],Fm=process.getBuiltinModule?.("node:sqlite"),yr={turns:[],trailingCommands:[],model:null,requestIds:[]};async function Gc(t,e){if(!Fm)return yr;let{DatabaseSync:n}=Fm,r;try{r=new n(t,{readOnly:!0})}catch{return yr}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 yr;let s;try{s=JSON.parse(Buffer.from(String(o.value),"hex").toString("utf8"))}catch{return yr}let a=s.lastUsedModel||null,i=s.latestRootBlobId?Lm(r,s.latestRootBlobId):null;if(!i)return{...yr,model:a};let c=[],l=[],u=[],f=null,d=[];for(let m of fT(i)){let h=dT(Lm(r,m));if(!h)continue;let w=h.providerOptions?.cursor;if(w?.requestId&&d.push(w.requestId),w?.modelProviderMessageId&&d.push(w.modelProviderMessageId),h.role!=="assistant")continue;if(Array.isArray(h.content))for(let S of h.content){let A=S?.providerOptions?.cursor?.modelName;A&&(f=A)}l.push(...pT(h.content,e).map((S,A)=>({...S,id:S.id||`${m}:${A}`}))),u.push(...hT(h.content));let g=mT(h.content);if(!g)continue;let b=[...new Set(u)];u=[],c.push({id:m,text:g,tools:b.length?b:void 0,commands:l,model:f}),l=[]}return{turns:c,trailingCommands:l,model:a,requestIds:d}}catch{return yr}finally{try{r.close()}catch{}}}function Wm(t,e){let n=t.updatedAtMs;if(!n)try{n=Math.round(_o.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 qm(t,e,{limit:n=80}={}){let r=wa(t,e);if(!r)return[];let{turns:o,model:s}=await Gc(r.store,t);if(!o.length)return[];let a=o.slice(-n),{tsAt:i}=Wm(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 Km(t,e){let n=wa(t,e);if(!n)return null;let{turns:r,requestIds:o,model:s}=await Gc(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 Gm(t,e,{limit:n=80}={}){let r=wa(t,e);if(!r)return[];let{turns:o,trailingCommands:s}=await Gc(r.store,t);if(!o.length&&!s.length)return[];let a=o.slice(-n),{end:i,tsAt:c}=Wm(r,a.length||1),l=[];a.forEach((u,f)=>{for(let d of u.commands)l.push({...d,summary:Je(d.command,d.description),ts:c(f)})});for(let u of s)l.push({...u,summary:Je(u.command,u.description),ts:i+1});return l}async function Vm(t,e){let n=wa(t,e);if(!n)return null;if(n.updatedAtMs)return n.updatedAtMs;try{return Math.round((await sT.stat(n.store)).mtimeMs)}catch{return null}}import zm from"node:fs";import zc from"node:fs/promises";import Vc from"node:path";import wT from"node:os";var gT=process.env.BOSTRAT_CODEX_SESSIONS||Vc.join(process.env.CODEX_HOME||Vc.join(wT.homedir(),".codex"),"sessions");async function Jm(t){try{return await zc.realpath(t)}catch{return t}}function yT(t=gT){let e=[],n=r=>{let o;try{o=zm.readdirSync(r,{withFileTypes:!0})}catch{return}for(let s of o){let a=Vc.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:zm.statSync(a).mtimeMs})}catch{}}}};return n(t),e.sort((r,o)=>o.mtimeMs-r.mtimeMs)}async function bT(t,e=1048576){let n;try{n=await zc.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 kT(t){let e=await bT(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 Rn(t,e){if(!t&&!e)return null;let n=yT();if(e){let o=n.find(s=>s.id===e);return o||null}let r=await Jm(t);for(let o of n){let s=await kT(o.file);if(s&&(s===t||await Jm(s)===r))return o}return null}var xT=new Set(["output_text","text"]),ST=t=>typeof t=="string"?t.trim():Array.isArray(t)?t.filter(e=>e&&typeof e.text=="string"&&xT.has(e.type)).map(e=>e.text).join(`
|
|
98
|
+
`).trim():"";async function Xm(t,e,{limit:n=80,maxBytes:r=524288}={}){let o=await Rn(t,e);if(!o)return[];let s;try{s=await Ot(o.file,r)}catch{return[]}let a=[],i=null,c=[],l=!1,u=!1,f=null;for(let d of s.split(`
|
|
99
|
+
`)){let m=d.trim();if(!m||m[0]!=="{")continue;let h;try{h=JSON.parse(m)}catch{continue}if((h.type==="turn_context"&&h.payload?.model||h.type==="session_meta"&&h.payload?.model)&&(i=h.payload.model),h.type==="event_msg"){let S=h.payload?.type;S==="task_started"?(l=!0,u=!1,f=h.payload?.turn_id||null):S==="task_complete"&&l&&(l=!1,!u&&h.payload?.last_agent_message==null&&a.push({role:"assistant",failed:!0,text:Xc,sourceId:h.payload?.turn_id||f||void 0,ts:h.timestamp?Date.parse(h.timestamp):null,model:i}));continue}if(h.type!=="response_item")continue;let w=h.payload;if(!w)continue;if((w.type==="function_call"||w.type==="custom_tool_call")&&w.name){u=!0,Jc.has(w.name)||c.push(w.name);continue}if(w.type!=="message"||w.role!=="assistant")continue;u=!0;let g=ST(w.content),b=c.length?c.slice():null;c=[],!(!g&&!b)&&a.push({role:"assistant",text:g,tools:b||void 0,ts:h.timestamp?Date.parse(h.timestamp):null,model:i})}if(i)for(let d of a)d.model||(d.model=i);return a.slice(-n)}var Io=new Set(["exec_command","shell","exec"]),Jc=new Set(["wait","write_stdin"]),Xc="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 vT(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 TT(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 AT(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=TT(i.slice(c.index+c[0].length).trimStart());l&&l.trim()&&o.push(l.trim())}),o}async function Ym(t,e,{limit:n=400,maxBytes:r=524288}={}){let o=await Rn(t,e);if(!o)return[];let s;try{s=await Ot(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"&&Io.has(u.name)){let m;try{m=JSON.parse(u.arguments||"{}")}catch{continue}let h=vT(m);h&&(f=[h])}else u.type==="custom_tool_call"&&u.name==="exec"&&(f=AT(u.input));if(!f.length)continue;let d=u.call_id||u.id||null;f.forEach((m,h)=>{a.push({id:d?h?`${d}:${h}`:d:null,tool:u.name,command:m,description:"",summary:Je(m),ts:l.timestamp?Date.parse(l.timestamp):null})})}return a.slice(-n)}async function Zm(t,e){let n=await Rn(t,e);if(!n)return null;try{return Math.round((await zc.stat(n.file)).mtimeMs)}catch{return n.mtimeMs||null}}var br=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}},Yc=class extends br{transcript(e){return!e?.cwd||!e?.sessionId?null:xe(e.cwd,e.sessionId)}async readMessages(e,n){let r=this.transcript(e);return r?Ui(r,n):[]}async readCommands(e,n){let r=this.transcript(e);return r?Pm(r,n):[]}async readJobs(e,n){let r=this.transcript(e);return r?Dm(r,n):[]}async updatedAt(e){let n=this.transcript(e);if(!n)return null;try{return Math.round((await ET.stat(n)).mtimeMs)}catch{return null}}},Zc=class extends br{async readMessages(e,n){return qm(e.cwd,e.sessionId,n)}async readCommands(e,n){return Gm(e.cwd,e.sessionId,n)}async updatedAt(e){return Vm(e.cwd,e.sessionId)}},Qc=class extends br{sessionId(e){return e?.pinned?e.sessionId:null}async readMessages(e,n){return Xm(e.cwd,this.sessionId(e),n)}async readCommands(e,n){return Ym(e.cwd,this.sessionId(e),n)}async updatedAt(e){return Zm(e.cwd,this.sessionId(e))}},_T=new br("unknown"),Qm=new Map;function tl(t){return Qm.set(t.agent,t),t}function Ro(t){return Qm.get(t)||_T}tl(new Yc("claude",qc));tl(new Zc("cursor",ha));tl(new Qc("codex",Io));var el=t=>e=>({...e,box:t.name,agent:t.agent,boxRole:t.role});async function nl(t,e={}){return(await Ro(t.agent).readMessages(t,e)).map(el(t))}async function th(t,e={}){return(await Ro(t.agent).readCommands(t,e)).map(el(t))}async function eh(t,e={}){return(await Ro(t.agent).readJobs(t,e)).map(el(t))}var oh=/to navigate\b.*\bEsc to cancel|Enter to select\b/,sl=/Chat about this/i,$n=/^\s*(❯)?\s*(\d+)\.\s+(?:\[([ xX✔])\]\s+)?(.+?)\s*$/,nh=/^(type something|chat about this|submit)\b/i,On=/^[─—-]{6,}\s*$/,al=/[\u2500-\u257F\u2550-\u256F┌┐└┘├┤│┬┴┼─✂]/;function Mn(t){let e=t.search(al);return(e>=0?t.slice(0,e):t).trim()}function IT(t){if(!t)return"";let e=t.replace(/[│┌└├─┘┤┬┴┼─✂].*$/,"").trim();return!e||/hidden|\d+\s+lines?/i.test(e)?"":e}function RT(t){let e=String(t).trim();return!!(!e||On.test(t)||al.test(e)||/hidden|\d+\s+lines?/i.test(e)||/notespress|press n to add/i.test(e)||/^\s*notes?\s*$/i.test(e))}function $T(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=OT(n,e);return n.forEach((a,i)=>{a.current=i===s}),{tabs:n,questionIndex:s,questionTotal:n.length,currentTabLabel:n[s].label}}function OT(t,e){let n=String(e).toLowerCase();if(n){let o=t.map((s,a)=>({i:a,label:s.label.toLowerCase()})).sort((s,a)=>a.label.length-s.label.length);for(let{i:s,label:a}of o){if(a.split(/\s+/).filter(l=>l.length>3).some(l=>n.includes(l)))return s;let c=a.slice(0,8).trim();if(c.length>=4&&n.includes(c))return s}}let r=t.findIndex(o=>!o.answered);return r>=0?r:t.length-1}function rh(t){return t.replace(/[←→✔☐☒●▶]/g," ").replace(/\bSubmit\b/g," ").replace(/\s+/g," ").trim()}var sh=/^\s*Switch model\?\s*$/m;function MT(t,e=""){let n=String(t);if(!sh.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($n);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&&!On.test(r[u])&&a.push(d)}}if(s.length<2)return null;let i=s.map(u=>({label:Mn(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 ah=/^\s*Enter to confirm\s*·\s*Esc to cancel\s*$/,CT=/^\s*[─—▔-]{6,}\s*$/;function NT(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||!ah.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($n);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&&!(CT.test(n[c])||On.test(n[c]));c--){let l=n[c].trim();l&&a.unshift(l)}let i=s.map(c=>({label:Mn(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 PT(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($n);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 rl=/Resume from summary/i,BT=/^\s*This session is\b/;function DT(t,e=""){let n=String(t);if(!rl.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
105
|
+
`),o=-1;for(let u=r.length-1;u>=0;u--)if(BT.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($n);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&&!On.test(r[u])&&a.push(d)}}if(s.length<2||!s.some(u=>rl.test(u.label)))return null;let i=s.map(u=>({label:Mn(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 Cn=/shift\+tab to cycle|⏵⏵ bypass permissions on|⏸ manual mode on/;function il(t){let e=[];for(let r=0;r<t.length;r++){let o=t[r].match($n);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 ih=/Choose the text style that looks best with your terminal/,ch=/Let's get started\./;function jT(t,e=""){let n=String(t);if(!ih.test(n)||!ch.test(n)||Cn.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
106
|
+
`),o=il(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:Mn(s.label),description:""}))}]}:null}var lh=/Select login method:/;function LT(t,e=""){let n=String(t);if(!lh.test(n)||Cn.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
107
|
+
`),o=il(r);if(!o)return null;let s=o.map(a=>{let[i,...c]=a.label.split("\xB7");return{label:Mn(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 cl=/Paste code here if prompted/,FT=/^[A-Za-z0-9%&=+._~#?/:\-]+$/;function UT(t,e){let n=t[e].trim();for(let r=e+1;r<t.length;r++){let o=t[r].trim();if(!o||!FT.test(o)||cl.test(t[r]))break;n+=o}return n}function HT(t,e=""){let n=String(t);if(!cl.test(n)||Cn.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=UT(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 ol=/OAuth error:/,WT=/Press Enter to retry\./;function qT(t,e=""){let n=String(t);if(!ol.test(n)||!WT.test(n)||Cn.test(n))return null;let s=([...n.replace(/\r/g,"").split(`
|
|
109
|
+
`)].reverse().find(a=>ol.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 uh=/Quick safety check: Is this a project you created or one you trust/;function KT(t,e=""){let n=String(t);if(!uh.test(n)||Cn.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
110
|
+
`),o=il(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:Mn(s.label),description:""}))}]}:null}var dh=/^(.{0,60}?)Press\s+Enter\s+to\s+continue\s*[.…]{0,3}\s*$/;function GT(t,e=""){let n=String(t);if(Cn.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(dh);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(On.test(r[l])||al.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 VT(t){let e=-1;for(let r=t.length-1;r>=0;r--)if(sl.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 zT(t,e=""){let n=String(t);if(!oh.test(n)||!sl.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
112
|
+
`),o=VT(r);if(!o)return null;let{headerLine:s,chatLine:a,optStart:i,optEnd:c}=o,l=[],u=-1;for(let T=i;T<c;T++){let M=r[T].match($n);M&&(u<0&&(u=T),l.push({line:T,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 T of l)for(let M=T.line+1;M<Math.min(r.length,T.line+4)&&!($n.test(r[M])||On.test(r[M]));M++){let W=r[M].trim();if(!(!W||RT(r[M])||nh.test(W))){T.desc=IT(W);break}}let f=l.filter(T=>!nh.test(T.label));if(!f.length)return null;let d=f.some(T=>T.hasCheckbox),m=Math.max(0,f.findIndex(T=>T.cursor)),h=s>=0?s+1:0,w=[];for(let T=h;T<u;T++){let M=r[T].trim();M&&!On.test(r[T])&&!/[☐☒]/.test(r[T])&&w.push(M)}let g=w.join(" ")||(s>=0?rh(r[s]):"")||"The agent is asking a question",b=s>=0?$T(r[s],g):null,S=b?.currentTabLabel||(s>=0?rh(r[s]):""),A=f.map(T=>({label:Mn(T.label),description:T.desc})),C=A.map(T=>T.label).join("|"),O=`claude:${b?`q${b.questionIndex+1}/${b.questionTotal}`:"q1/1"}:${S}:${g}:${C}`.slice(0,240),F=b?`claude:ask:${b.tabs.map(T=>T.label).join("|")}`.slice(0,240):O;return{provider:"claude",box:e,multiSelect:d,cursorIndex:m,promptId:O,groupId:F,...b?{questionIndex:b.questionIndex,questionTotal:b.questionTotal,currentTabLabel:b.currentTabLabel,tabs:b.tabs}:{},questions:[{question:g,header:S,multiSelect:d,options:A}]}}function kr(t,e){let n=String(e||"");return n?t==="claude"?oh.test(n)&&sl.test(n)||sh.test(n)||rl.test(n)||ah.test(n.trimEnd().split(`
|
|
113
|
+
`).pop()||"")?!0:Cn.test(n)?!1:ih.test(n)&&ch.test(n)||lh.test(n)||cl.test(n)||ol.test(n)||uh.test(n)||dh.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 Nn(t,e){return!t||!e?null:t.agent==="claude"?PT(e,t.name)||zT(e,t.name)||MT(e,t.name)||DT(e,t.name)||qT(e,t.name)||HT(e,t.name)||LT(e,t.name)||jT(e,t.name)||KT(e,t.name)||GT(e,t.name)||NT(e,t.name):t.agent==="cursor"||t.agent==="codex"?nf(t.agent,e,t.name):null}async function ll(t){if(!t)return null;let e=await Le(t.name).catch(()=>"");return Nn(t,e)}function fh(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 ph(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 xr(t){return typeof t=="string"?t.trim():""}var mh=/^\s*─{8,}\s*$/,JT=/^❯[ ]?(.*)$/;function $o(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(mh.test(n[c])){r=c;break}if(r<=0)return null;let o=-1;for(let c=r-1;c>=0;c--)if(mh.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(JT);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 Zt(t){return String(t||"").replace(/\s+/g," ").trim()}function hh(t,e,{slackMs:n=2e4}={}){let r=Zt(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&&Zt(o.text).includes(r))}function wh(t,e,n,{busy:r=!1}={}){let o=$o(t,n);return o&&ul(e,o)?"stuck":r||o?"submitted":"unknown"}function ul(t,e){let n=Zt(t),r=Zt(e).replace(/^…\s*/,"").replace(/\s*…$/,"");return!n||!r||r.length<Math.min(n.length,12)?!1:n===r||n.includes(r)}import XT from"node:crypto";var dl=new Set(["cursor","codex"]),yh=4,YT=4,ZT=2,QT=24,gh=/^[ \t]{0,3}(`{3,})bostrat-question[ \t]*\r?\n([\s\S]*?)\r?\n[ \t]{0,3}\1`*[ \t]*$/gm,Oo=t=>typeof t=="string"?t.trim():"";function tA(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,yh)){let o=Oo(r?.question);if(!o)return null;let s=Array.isArray(r?.options)?r.options:[],a=[];for(let i of s.slice(0,YT)){let c=Oo(i?.label);if(!c)return null;let l=Oo(i?.description);a.push(l?{label:c,description:l}:{label:c})}if(a.length<ZT)return null;n.push({question:o,header:Oo(r?.header).slice(0,QT),multiSelect:r?.multiSelect===!0,options:a})}return n.length?n:null}function fl(t){if(typeof t!="string"||!t.includes("```bostrat-question"))return null;gh.lastIndex=0;let e=null;for(let o;o=gh.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=tA(n);return r?{text:t.slice(0,e.index).trimEnd(),questions:r}:null}function eA(t,e,n){let r=XT.createHash("sha256").update(JSON.stringify(n)).digest("hex").slice(0,8);return`${t}:ask:${e||"turn"}:${r}`}function pl(t,{leadBoxId:e,provider:n,isAnswered:r,maxQuestions:o=yh}={}){if(!Array.isArray(t)||!e||!dl.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||fl(s.text)?.questions;if(!a?.length)return null;let i=a.slice(0,Math.max(1,o)),c=eA(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 bh(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=Oo(n),s;return r.length?r.length===1?s=`${r[0].name}: ${r[0].picks}`:s=`Answers:
|
|
112
116
|
${r.map(a=>`- ${a.name}: ${a.picks}`).join(`
|
|
113
117
|
`)}`:s=o,r.length&&o&&(s+=`
|
|
114
|
-
${o}`),s||""}var
|
|
118
|
+
${o}`),s||""}var kh=`
|
|
115
119
|
When you are BLOCKED on a decision only the operator can make \u2014 an ambiguous
|
|
116
120
|
requirement, an irreversible choice, several valid approaches \u2014 do not bury the
|
|
117
121
|
question in prose. End your message with exactly ONE fenced code block tagged
|
|
@@ -131,40 +135,40 @@ your turn. The operator sees the questions as an interactive card in their chat
|
|
|
131
135
|
answer. Never use the block for rhetorical questions, progress updates, or
|
|
132
136
|
anything you can decide yourself; keep asking in prose for questions without
|
|
133
137
|
enumerable options.
|
|
134
|
-
`;import{execFile as
|
|
135
|
-
`).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
|
|
136
|
-
`).map(s=>s.trim()).filter(Boolean)}async function
|
|
137
|
-
`).map(n=>n.trim()).filter(Boolean)}catch{return[]}}import
|
|
138
|
-
`),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
|
|
139
|
-
`),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
|
|
140
|
-
`);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
|
|
141
|
-
`),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 Eh(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&&!Th.has(o))throw new Error(`unknown codex effort "${o}" (low | medium | high | xhigh)`);return{model:r,effort:o||null}}if(t==="cursor"){let r=Ah.find(o=>o.alias===n);return{model:n,filter:r?r.label:n}}if(!BT.has(n))throw new Error(`unknown model "${n}"`);return{model:n}}async function _o(t,e,{timeoutMs:n=NT}={}){let r=Date.now()+n;for(;;){let o=await gt(t,{lines:80}).catch(()=>""),s=e(o);if(s)return s;if(Date.now()>r)return null;await pa(PT)}}async function LT(t,e){return await vt(t,`/model ${e.model}`,Se("claude")),{verified:!1,note:"confirmed when the next turn's transcript model flips"}}async function FT(t,e){await ys(t),await vt(t,"/model",{enter:!1}),await pa(250),await xt(t,["Enter"],{kind:"model-switch"});let n=await _o(t,kh);if(!n)throw new Error("codex model picker didn't open");let r=n.find(i=>i.id===e.model);if(!r)throw await xt(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=jT(i);return!c||c.model!==e.model||e.effort&&c.effort&&c.effort.toLowerCase()!==e.effort.toLowerCase()?null:c};await vt(t,String(r.n),{enter:!1,kind:"model-switch"});let s=await _o(t,i=>xh(i)||!kh(i)&&o(i)&&{done:o(i)});if(!s)throw await xt(t,["Escape"],{kind:"model-switch"}).catch(()=>{}),new Error("codex reasoning-level picker didn't open");if(!s.done){let i=e.effort?Th.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 xt(t,["Escape","Escape"],{kind:"model-switch"}).catch(()=>{}),new Error(`codex doesn't offer effort "${e.effort}" for ${e.model}`);await vt(t,String(c.n),{enter:!1,kind:"model-switch"})}let a=s.done||await _o(t,i=>!xh(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 UT(){let t=bh.join(process.env.CURSOR_CONFIG_DIR||bh.join(CT.homedir(),".cursor"),"cli-config.json");try{return JSON.parse(await MT.readFile(t,"utf8"))?.model||null}catch{return null}}async function HT(t,e){await ys(t),await ks(t,`/model ${e.filter}`,{kind:"model-switch"});let n=await _o(t,Sh),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 _o(t,Sh,{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 xt(t,["Down"],{kind:"model-switch"}),await pa(250)}if(!n||!s||!o(s)){await xt(t,["Escape"],{kind:"model-switch"}).catch(()=>{});let c=Array(`/model ${e.filter}`.length).fill("BSpace");throw await xt(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 xt(t,["Enter"],{kind:"model-switch"}),await pa(800);let a=await UT();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 Ih(t,e,n){return t==="codex"?FT(e,n):t==="cursor"?HT(e,n):LT(e,n)}var Ft=t=>t||null;function ha(t){let e=t||{},n=WT(e);return n&&(n.payload={ts:e.at,...n.payload}),n}function WT(t){switch(t.type){case"said":return{type:"turn.user",actor:"user",source:"orchestrator",boxId:t.box,provider:Ft(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:Ft(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:Ft(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:Ft(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:Ft(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:Ft(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:Ft(t.agent),payload:{...br(t)}};case"subtask-failed":return{type:"error",actor:"system",source:"orchestrator",boxId:t.box||t.to,provider:Ft(t.agent||t.toAgent),payload:{kind:"subtask-failed",...br(t)}};case"subtask-resumed":return{type:"box.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Ft(t.agent),payload:{change:"subtask-resumed",...br(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",...br(t)}};case"archived":case"unarchived":case"track-deleted":return{type:"box.state.changed",actor:"user",source:"orchestrator",payload:{change:t.type,...br(t)}};case"rehydrated":return{type:"box.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Ft(t.agent),payload:{change:"rehydrated"}};case"retagged":return{type:"box.state.changed",actor:"user",source:"orchestrator",payload:{change:"retagged",...br(t)}};case"rehydrate-failed":return{type:"error",actor:"system",source:"orchestrator",boxId:t.box,provider:Ft(t.agent),payload:{kind:"rehydrate-failed",reason:t.reason}};default:return null}}var ma=Promise.resolve(),bl=t=>(ma=ma.then(t).catch(()=>{}),ma),$h=()=>ma;function Oh(t,e){let n=ha(e);if(!n)return;let r=e?.type==="track-deleted"?{id:t.id}:t;bl(()=>O(r,n))}var Rh=new Set;async function Mh(t){if(!(!t?.id||Rh.has(t.id))){Rh.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=ha(r);!o||n.has(`${o.type}|${o.payload?.ts??"?"}`)||await O(t,o)}}catch{}}}function br(t){let{type:e,at:n,...r}=t;return r}var yl=new Map;function Ch(t,e,n){!n?.promptId||yl.get(t.id)===n.promptId||(yl.set(t.id,n.promptId),bl(()=>O(t,{type:"prompt.raised",actor:"agent",source:"pane",boxId:e?.name,provider:Ft(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 kl(t,e,n,r={}){yl.delete(t.id),bl(()=>O(t,{type:"prompt.answered",actor:"user",source:"orchestrator",boxId:e?.name,provider:Ft(n?.provider||e?.agent),payload:{promptId:n?.promptId,...r}}))}import{randomUUID as xA}from"node:crypto";var Ph=/(?<!\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,Bh=/(?<!\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,Dh=/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,jh=/(?<!\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,qT=new RegExp(`\\b(?:${Ph}|${Bh}|${Dh}|${jh})\\b`,"i"),KT=/\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,GT=/^(?:commit|push|open|create|raise|rebase|ready|ok(?:ay)?\s+to|good\s+to)\b/i,zT=90;function VT(t){return String(t).split(/(?<=[.?!])\s+|\n+/).map(e=>e.trim()).filter(Boolean)}function Lh(t){let e=String(t||"").trim();if(!e)return null;let n=e.slice(-600);for(let r of VT(n).reverse()){if(!qT.test(r))continue;let o=r.endsWith("?")&&r.length<=zT&>.test(r);if(KT.test(r)||o)return r.length>200?r.slice(0,197)+"\u2026":r}return null}var Fh=["commit","rebase","push","pr"],JT=new RegExp(`\\b(?:${Ph})\\b`,"i"),XT=new RegExp(`\\b(?:${jh})\\b`,"i"),YT=new RegExp(`\\b(?:${Bh})\\b`,"i"),ZT=new RegExp(`\\b(?:${Dh})\\b`,"i"),QT=/\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 Uh(t){let e=String(t||""),n=[];JT.test(e)&&n.push("commit"),XT.test(e)&&n.push("rebase"),YT.test(e)&&n.push("push");let r=QT.test(e);!r&&ZT.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 wa(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(m=>`${m.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 m=a.has("commit")?"the branch":`this branch${e?` (${e})`:""}`;u.push(i?`rebase ${m} in each changed checkout (${c}) onto its own base branch (fetch origin first), resolving any conflicts so both sides' intent is preserved`:`rebase ${m} 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 m=r&&!a.has("pr")?` (the open PR${r.number?` #${r.number}`:""} picks it up)`:"",h=a.has("rebase")?" with `--force-with-lease` (the rebase rewrote history)":"";i?u.push(a.has("commit")||a.has("rebase")?`push each one to its remote${h}${m}`:`push this branch${e?` (${e})`:""} from each changed checkout (${c}) to its remote${m}`):u.push(a.has("commit")||a.has("rebase")?`push it to the remote${h}${m}`:`push this branch${l} to the remote${m}`)}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(/^./,m=>m.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 tA=3e4;function Hh(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<=tA&&n.push({box:r.box,from:r.from,to:e+1e12}),n}function Wh(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 Nh={commit:"commit",rebase:"rebase",push:"push",pr:"open a PR"};function qh(t){let e=(Array.isArray(t)?t:[]).filter(r=>Nh[r]).map(r=>Nh[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 sA}from"node:child_process";import{promisify as aA}from"node:util";import eA from"node:fs";import xl from"node:fs/promises";import Vh from"node:path";import nA from"node:os";var Jh=process.env.BOSTRAT_STATE_DIR||Vh.join(process.env.HOME||nA.tmpdir(),".bostrat"),ga=Vh.join(Jh,"github-metrics.json"),Xh=60*6e4,Kh=5*6e4,rA=6e4,kr=Date.now(),Ie=new Map,Xe=null,Cn=0,Gh=0,ya=t=>[...t.values()].reduce((e,n)=>e+n,0);try{let t=JSON.parse(eA.readFileSync(ga,"utf8"));t?.windowStart&&(Date.now()-t.windowStart<Xh?(kr=t.windowStart,Ie=new Map(Object.entries(t.byCaller||{})),Xe=t.lastHour||null):ya(new Map(Object.entries(t.byCaller||{})))&&(Xe={start:t.windowStart,total:ya(new Map(Object.entries(t.byCaller||{}))),byCaller:t.byCaller||{}}))}catch{}var zh=Promise.resolve();function Yh(t,e=!1){if(!e&&t-Gh<rA)return;Gh=t;let n=JSON.stringify({windowStart:kr,byCaller:Object.fromEntries(Ie),lastHour:Xe});zh=zh.then(()=>xl.mkdir(Jh,{recursive:!0})).then(()=>xl.writeFile(`${ga}.tmp`,n)).then(()=>xl.rename(`${ga}.tmp`,ga)).catch(()=>{})}function Zh(t){t-kr<Xh||(Xe={start:kr,total:ya(Ie),byCaller:Object.fromEntries(Ie)},Xe.total&&console.log(`[github] ${Xe.total} calls last hour`,Xe.byCaller),kr=t,Ie=new Map,Yh(t,!0))}function Re(t="other",e=Date.now()){Zh(e),Ie.set(t,(Ie.get(t)||0)+1),Yh(e)}function Qh(t=Date.now()){return Zh(t),{windowStart:kr,total:ya(Ie),byCaller:Object.fromEntries(Ie),lastHour:Xe,backoffUntil:Cn>t?Cn:null}}var oA=/rate limit|too quickly|abuse detection|HTTP 429/i;function Ye(t){return oA.test(String(t?.message||t||""))}function Ze(t,e=Date.now()){return Cn<=e&&console.warn(`[github] rate limited \u2014 backing off ${Kh/6e4} min:`,String(t?.message||t||"").slice(0,200)),Cn=Math.max(Cn,e+Kh),Cn}function ba(t=Date.now()){return Math.max(0,Cn-t)}async function Sl(t,e){let n={working:!1,prompted:!1};if(!t)return n;let r=await gt(t,{lines:Tt}).catch(()=>"");if(!Fe(e,r))return tf(e,r)?{working:!0,prompted:!1}:n;if(wr(e,r)){let o=await je(t).catch(()=>r);if(Mn({name:t,agent:e},o))return{working:!1,prompted:!0}}return{working:!0,prompted:!1}}async function ka(t,e){return(await Sl(t,e)).working}var tw=aA(sA),ew=[4e3,15e3,45e3],iA=2500,cA=5*6e4;async function vl(t,e,{midTurn:n=ka,pollMs:r=iA,capMs:o=cA}={}){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 lA=6e4,uA=9e4,dA=2e3,fA=/(?:^|[;&|]\s*|\)\s*)gh\s+(?:[^\s;&|"'`$]+\s+)*?pr\s+create\b/,pA=/(?:^|[;&|]\s*|\)\s*)gh\s+(?:[^\s;&|"'`$]+\s+)*?pr\s+merge\b/,mA=/\s--web\b/,nw=/(?:--repo|-R)[\s=]+([\w.-]+\/[\w.-]+)/;function Tl(t,e){return t.slice(e).replace(/^[\s;&|)]+/,"").replace(/"[^"]*"|'[^']*'/g,'""').split(/[;&|]/)[0]}function rw(t){let e=String(t||""),n=e.match(fA);if(!n)return null;let r=Tl(e,n.index);return mA.test(r)?null:{repo:r.match(nw)?.[1]??null}}var hA=new Set(["--subject","-t","--body","-b","--body-file","-F","--match-head-commit","--author-email","-A","--repo","-R"]);function ow(t){let e=String(t||""),n=e.match(pA);if(!n)return null;let r=Tl(e,n.index),o=Tl(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("-")){hA.has(c)&&i++;continue}s=c.replace(/^["']+|["']+$/g,"")||null;break}return{repo:r.match(nw)?.[1]??null,selector:s,auto:/\s--auto\b/.test(r)}}var wA="number,url,title,headRefName,createdAt";async function gA(t,e){let n=async s=>{Re("observer");let{stdout:a}=await tw("gh",["pr","list","--repo",t,"--author","@me","--state","all","--limit","10","--json",wA],{timeout:3e4,maxBuffer:4*1024*1024,env:{...process.env,...s}}).catch(i=>{throw Ye(i)&&Ze(i),i});return JSON.parse(a||"[]")},r=await ee({slug:t}),o=await n(r);return!o.length&&r.GH_TOKEN&&(o=await n({}).catch(()=>[])),o.filter(s=>Date.parse(s.createdAt)>=e-lA).sort((s,a)=>Date.parse(a.createdAt)-Date.parse(s.createdAt))}async function yA(t,e){if(e)return[e];let n=[];for(let r of sw(t)){let o=await tt(r).catch(()=>null);o&&!n.includes(o)&&n.push(o)}for(let r of await ye().catch(()=>[])){let o=await tt(r.path).catch(()=>null);o&&!n.includes(o)&&n.push(o)}return n}function sw(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 bA(t,e,n){let r=t.workspace?.branch,o=[];for(let s of await yA(t,e)){let a;try{a=await gA(s,n)}catch{continue}for(let i of a){let c=await Et(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 Qe=new Set;function aw(t,e,{delays:n=ew,turnEnd:r}={}){let o=rw(e.command);if(!o)return null;let s=!!ow(e.command),a=`${t.id}|${e.id??e.ts??e.command}`;if(Qe.has(a))return null;Qe.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 bA(t,o.repo,i).catch(()=>null);if(!u)continue;let{slug:f,pr:d}=u,{link:m,created:h}=await Lt({repo:f,number:d.number,trackId:t.id,url:d.url,headRef:d.headRefName,title:d.title,source:"observed"});return!h&&!(m?.trackId===t.id&&await wo(f,d.number))?null:(await vl(e.box,e.agent,r),await O({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{Qe.delete(a)}})();return c.catch(()=>Qe.delete(a)),c}var kA="number,url,title,headRefName,state";function iw(t,e,{delays:n=ew,turnEnd:r,openedWait:o}={}){let s=ow(e.command);if(!s)return null;let a=`merge|${t.id}|${e.id??e.ts??e.command}`;if(Qe.has(a))return null;Qe.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 h=sw(t);if(h.length>1&&/^\d+$/.test(String(s.selector??"")))return null;for(let w of h){let g=await tt(w).catch(()=>null);g&&!f.includes(g)&&f.push(g)}}if(l||=t.workspace?.branch,!f.length||!l)return null;c=f[0];let d=async(h,w)=>{let g=await Et(c,h.number);return g&&g.trackId!==t.id?null:(g||await Lt({repo:c,number:h.number,trackId:t.id,url:h.url,headRef:h.headRefName,title:h.title,source:"merge-observed"}),await go(c,h.number),w&&((await L(t).catch(()=>[])).some(A=>A.type==="pr.state.changed"&&(A.payload?.change==="auto-armed"||A.payload?.change==="merged")&&A.payload?.number===h.number)||(await vl(e.box,e.agent,r),await O({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:Date.now(),change:"auto-armed",number:h.number,url:h.url,title:h.title,branch:h.headRefName,repo:c}}))),h)},m=null;for(let h of n){await new Promise($=>setTimeout($,h));let w=[];for(let $ of f)try{Re("observer");let{stdout:F}=await tw("gh",["pr","view",String(l),"--repo",$,"--json",kA],{timeout:3e4,maxBuffer:4*1024*1024,env:{...process.env,...await ee({slug:$})}}),T=JSON.parse(F||"null");T?.number&&w.push({slug:$,pr:T})}catch(F){Ye(F)&&Ze(F)}let g=w.find($=>$.pr.state==="MERGED")??(w.length===1?w[0]:null);if(!g)continue;c=g.slug;let b=g.pr;if(!b?.number)continue;if(b.state==="CLOSED")return null;if(b.state!=="MERGED"){if(m=b,s.auto)return d(b,!0);continue}let v=await Et(c,b.number);if(v&&v.trackId!==t.id)return null;v||await Lt({repo:c,number:b.number,trackId:t.id,url:b.url,headRef:b.headRefName,title:b.title,source:"merge-observed"}),await vl(e.box,e.agent,r);let A=await L(t).catch(()=>[]);if(A.some($=>$.type==="pr.state.changed"&&$.payload?.change==="merged"&&$.payload?.number===b.number))return null;let M=$=>$.some(F=>F.type==="pr.state.changed"&&F.payload?.change==="opened"&&F.payload?.number===b.number),z=0;if(!M(A)){if(rw(e.command)&&await wo(c,b.number)){let $=Date.now();z=$+1,await O({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:$,change:"opened",number:b.number,url:b.url,title:b.title,branch:b.headRefName,repo:c,selfMerge:!0}})}else if((await Et(c,b.number))?.announced){let $=o?.capMs??uA,F=o?.pollMs??dA,T=Date.now()+$;for(;Date.now()<T&&(await new Promise(C=>setTimeout(C,F)),!M(await L(t).catch(()=>[]))););}}return await O({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:b.number,url:b.url,title:b.title,branch:b.headRefName,repo:c}}),await en(c,b.number).catch(()=>{}),await tn(c,b.number).catch(()=>{}),await ze(c,b.number,"merged"),b}return m?d(m,!1):null}finally{Qe.delete(a)}})();return i.catch(()=>Qe.delete(a)),i}function _l(t){return t.sourceId?`${t.role}|id:${t.sourceId}`:`${t.role}|${t.ts??"?"}|${String(t.text||"").slice(0,40)}`}function cw(t){return t.id?`cmd|${t.box}|id:${t.id}`:`cmd|${t.box}|${t.ts??"?"}|${String(t.command||"").slice(0,40)}`}function lw(t){return`job|${t.box}|id:${t.id}`}var Al=new Map,xa=new Map;async function El(t){return Al.has(t.id)?Al.get(t.id):(xa.has(t.id)||xa.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(_l({role:o,sourceId:r.payload.sourceId})),n.keys.add(_l({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(cw({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(lw({id:r.payload.id,box:r.boxId}));return Al.set(t.id,n),n}).finally(()=>xa.delete(t.id))),xa.get(t.id))}async function uw(t,e){try{let n=(await El(t)).keys;for(let r of e||[]){if(!r||r.ts==null||!String(r.text||"").trim()&&!r.images?.length)continue;let o=_l(r);if(!n.has(o)&&(n.add(o),await O(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=Lh(r.text);if(s){let{offered:a,toPr:i}=Uh(s),c;if(t.workspace?.kind==="multi"){let l=await Eo(t).catch(()=>[]);c=l.length?l.map(u=>({name:u.name,slug:u.repo??null})):void 0}await O(t,{type:"pr.offered",actor:"agent",source:"transcript",boxId:r.box,provider:r.agent||null,payload:{offerId:xA(),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 dw(t,e){try{let n=await El(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=cw(o);n.keys.has(s)||(n.keys.add(s),n.cmdN+=1,await O(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}}),aw(t,o),iw(t,o))}}catch{}}async function fw(t,e){try{let n=await El(t),r=[...e||[]].filter(o=>o&&o.id).sort((o,s)=>(o.ts||0)-(s.ts||0));for(let o of r){let s=lw(o);n.keys.has(s)||(n.keys.add(s),await O(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 SA=/^Base directory for this skill:\s*(\S+)/,vA=/^Run the "([^"]+)" workflow\./,TA=/<command-name>\s*\/?([a-z0-9:_-]+)\s*<\/command-name>/i,AA=/^<local-command-(?:stdout|caveat)>/i,_A=/^\[Image: original \d+x\d+, displayed at \d+x\d+\.\s*Multiply coordinates by [\d.]+ to map to original image\.\]$/,EA=/^Continue from where you left off\.$/,IA=/^<task-notification>/i,RA=/<summary>([\s\S]*?)<\/summary>/i,$A=/<task-id>([\s\S]*?)<\/task-id>/i,OA=/<tool-use-id>([\s\S]*?)<\/tool-use-id>/i,MA=/<status>([\s\S]*?)<\/status>/i,CA=/<result>([\s\S]*?)(?:<\/result>|$)/i;var Il=new Set(["model","clear","compact","login","exit","quit"]);function pw(t){return Il.has(String(t||"").replace(/^\//,"").toLowerCase())}var NA=/^\/([a-z0-9:_-]+)$/i;function Sa(t){let e=String(t||"").trim();if(!e)return null;let n=e.match(SA);if(n)return{kind:"skill",name:n[1].split("/").filter(Boolean).pop()||"skill"};let r=e.match(vA);if(r)return{kind:"skill",name:r[1]};let o=e.match(TA);if(o){let a=o[1].toLowerCase();return Il.has(a)?{kind:"suppress"}:{kind:"command",command:`/${a}`}}let s=e.match(NA);if(s&&Il.has(s[1].toLowerCase()))return{kind:"suppress"};if(AA.test(e))return{kind:"suppress"};if(EA.test(e))return{kind:"suppress"};if(_A.test(e))return{kind:"suppress"};if(IA.test(e)){let a=c=>PA(e.match(c)?.[1].trim()||"")||null,i=a(CA);return{kind:"task",summary:a(RA)||"",taskId:a($A),toolUseId:a(OA),status:a(MA)?.toLowerCase()||null,resultHead:i?i.length>600?`${i.slice(0,600).trimEnd()}\u2026`:i:null}}return null}function PA(t){return t.replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/'/g,"'").replace(/&/g,"&")}function mw(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 hw(t){return t?.kind==="skill"?"skill-run":t?.kind==="task"?"task-done":"command-run"}var it=t=>t.payload?.ts??t.ts,BA=new Set(["opened","ci-fixed","reconciled","pushed"]),ww=t=>`${t.repo??""}#${t.number}`;function DA(t,e,n){let r=ww(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 Io(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 y of t||[]){if(y.type!=="tool.called"||y.source!=="transcript")continue;let p=String(y.payload?.command||"");if(/\bbostrat-mock\s+(?:add|update)\b/.test(p))for(let S of p.matchAll(/[\w./~-]+\.(?:png|jpe?g|gif|webp|bmp|svg)\b/gi)){let _=S[0].split("/").pop().toLowerCase();f.has(_)||f.set(_,[]),f.get(_).push(y.payload?.ts??y.ts)}}let d=3*6e4,m=20*6e4,h=(y,p)=>!!(y?.name&&f.get(String(y.name).toLowerCase()))?.some(_=>_>=p-d&&_<=p+m);for(let y of t||[])if(y.type==="turn.retracted"){r.add(`${y.boxId}|${y.payload?.saidTs}`);let p=Yt(y.payload?.text);p&&(o.has(y.boxId)||o.set(y.boxId,[]),o.get(y.boxId).push(p))}else if(y.type==="turn.user"&&y.source==="orchestrator"){let p=Yt(y.payload?.text);p&&(s.has(y.boxId)||s.set(y.boxId,new Set),s.get(y.boxId).add(p))}else if(y.type==="job.started"&&y.payload?.id)a.add(y.payload.id),(y.payload.kind||"agent")==="agent"&&y.payload.name&&c.add(y.payload.name);else if(y.type==="turn.user"&&y.payload?.note?.kind==="task"){let p=y.payload.note,S={status:p.status||null,summary:p.summary||null,resultHead:p.resultHead??null,ts:it(y)};p.toolUseId&&i.set(p.toolUseId,S);let _=String(p.summary||"").match(u);_&&l.set(_[1],S)}let w=new Map,g=new Map,b=null,v=new Map,A=new Map,M=new Map,z=new Map,$=new Map,F=new Map,T=new Map,C=6e4,W=new Map,ot=(y,p)=>{let S=o.get(y);if(!S?.length)return!1;let _=Yt(p),rt=s.get(y);return S.some(I=>{if(!_.startsWith(I))return!1;let R=Yt(_.slice(I.length));return!!R&&!!rt?.has(R)})};for(let y of t||[]){let p=y.payload||{};switch(y.type){case"turn.user":{if(y.source==="hook"||!String(p.text||"").trim()&&!p.images?.length)break;{let S=p.note||Sa(p.text);if(S){if(S.kind==="task"){if(S.toolUseId&&a.has(S.toolUseId))break;let _=String(S.summary||"").match(u);if(!S.toolUseId&&_&&c.has(_[1]))break}if(S.kind==="command"&&pw(S.command))break;S.kind!=="suppress"&&e.push({id:y.id,kind:"event",type:hw(S),name:S.name??null,command:S.command??null,summary:S.summary??null,ts:it(y),box:y.boxId,agent:y.provider});break}}if(r.has(`${y.boxId}|${it(y)}`)||y.source==="transcript"&&ot(y.boxId,p.text))break;if(p.kind==="merge-notify"||p.kind==="ship-notify"){e.push({id:y.id,kind:"event",type:p.kind==="ship-notify"?"ship-notified":"merge-notified",text:p.text,ts:it(y),box:y.boxId,agent:y.provider});break}if(p.kind==="mock-picked"||p.kind==="ci-fix"||p.kind==="reconcile"||p.kind==="pr-go"||p.kind==="push-go"||p.kind==="spin-up-view"||p.kind==="prompt-answer")break;for(let S of v.values())S.status="stale";v.clear(),e.push({id:y.id,kind:"message",role:"user",text:p.text,ts:it(y),box:y.boxId,agent:y.provider,boxRole:p.boxRole,...y.actor==="orchestrator"?{orchestration:p.kind||!0}:{},...p.sourceId?{sourceId:p.sourceId}:{},...p.attachments?.length?{attachments:p.attachments}:{},...p.images?.length?{images:p.images}:{}});break}case"turn.agent":{if(p.model==="<synthetic>"&&/^No response requested\.$/.test(String(p.text||"").trim()))break;T.clear();let S=(p.images||[]).filter(R=>!h(R,it(y)));if(!String(p.text||"").trim()&&!S.length)break;if(y.provider==="cursor"){let R=`${y.boxId}|${p.text}`;if(n.has(R))break;n.add(R)}let _=y.provider==="codex"?(p.tools||[]).filter(R=>!Wc.has(R)&&!So.has(R)):p.tools||[],rt=p.text,I=null;if(sl.has(y.provider)){let R=al(String(p.text||""));R&&(rt=R.text,I={questions:R.questions})}e.push({id:y.id,kind:"message",role:"assistant",text:rt,ts:it(y),box:y.boxId,agent:y.provider,boxRole:p.boxRole,...I?{ask:I}:{},..._.length?{tools:_}:{},...p.model?{model:p.model}:{},...p.sourceId?{sourceId:p.sourceId}:{},...S.length?{images:S}:{}});break}case"job.started":{if(!p.id)break;let S=it(y),_=i.get(p.id);if(!_&&(p.kind||"agent")==="agent"&&p.name){let P=l.get(p.name);P&&P.ts>=S&&(_=P)}let rt={jobId:p.id,jobKind:p.kind||"agent",name:p.name||null,detail:p.detail||null,status:_?_.status&&_.status!=="completed"?"failed":"done":"running",startedTs:S,finishedTs:_?.ts??null,summary:_?.summary??null,resultHead:_?.resultHead??null},I=W.get(y.boxId),R=I?.jobs[I.jobs.length-1]?.startedTs;if(I&&R!=null&&S-R<=C)I.jobs.push(rt);else{let P={id:y.id,kind:"job-dock",dockId:p.id,ts:S,box:y.boxId,agent:y.provider,jobs:[rt]};W.set(y.boxId,P),e.push(P)}break}case"handoff.done":e.push(nn(y,"lead-handoff"));break;case"subtask.delegated":e.push({...nn(y,"subtask-delegated"),to:y.boxId,toAgent:y.provider});break;case"subtask.merged":e.push(nn(y,"subtask-merged"));break;case"pr.state.changed":{if(!p.change)break;if(p.change==="opened"){for(let _ of v.values())_.status="stale";v.clear()}let S=nn(y,`pr-${p.change}`);if(BA.has(p.change)&&p.number!=null){let _=DA(w,g,p);if(w.size>=2){for(let I of g.values())for(let R of I)R.grouped=!0;S.groupPrs=[...w.values()];let rt=[S];for(let I of w.keys())g.set(I,rt)}else{let rt=g.get(_)??[];rt.push(S),g.set(_,rt)}}else if(p.change==="merged"||p.change==="closed"||p.change==="lost"){let _=ww(p),rt=b&&!b.complete||w.size>=2;if(w.has(_))w.delete(_),g.delete(_);else if(p.number!=null)for(let I of[...w.keys()])I.endsWith(`#${p.number}`)&&(w.delete(I),g.delete(I));if(rt&&p.number!=null){(!b||b.complete)&&(b={id:y.id,kind:"event",type:"pr-group-settled",prs:[],remaining:[],complete:!1,at:it(y),ts:it(y),box:y.boxId,agent:y.provider},e.push(b)),b.prs.push({number:p.number,url:p.url??null,title:p.title??null,repo:p.repo??null,branch:p.branch??null,change:p.change,ts:it(y)}),b.remaining=[...w.values()],b.complete=w.size===0;break}}e.push(S);break}case"pr.offered":{if(!p.offerId)break;for(let _ of v.values())_.status="stale";v.clear();let S={id:y.id,kind:"pr-offer",offerId:p.offerId,excerpt:p.excerpt||null,branch:p.branch||null,base:p.base||null,offered:Array.isArray(p.offered)&&p.offered.length?p.offered:null,toPr:p.toPr&&typeof p.toPr=="object"?{number:p.toPr.number??null}:null,repos:Array.isArray(p.repos)&&p.repos.length?p.repos:null,status:"open",ts:it(y),box:y.boxId,agent:y.provider};v.set(p.offerId,S),A.set(p.offerId,S),e.push(S);break}case"pr.offer.answered":{let S=A.get(p.offerId);S&&(S.status=p.action||"dismiss",S.actions=Array.isArray(p.actions)?p.actions:null,S.notify=p.notify===!0,v.delete(p.offerId));break}case"box.state.changed":{let S=p.change==="model"?"model-change":p.change;if(!S)break;let _=nn(y,S);S==="model-change"&&(_.to=p.model),e.push(_);break}case"view.state.changed":{if(p.change!=="created"||p.restarted)break;e.push({id:y.id,kind:"view-card",viewId:p.viewId,name:p.name,slug:p.slug,port:p.port,origin:p.origin,ts:it(y),box:y.boxId,agent:y.provider});break}case"view.spinup.failed":{e.push({id:y.id,kind:"view-spinup-failed",name:p.name||null,reason:p.reason||"no-view",ts:it(y),box:y.boxId,agent:y.provider});break}case"mock.state.changed":{if(p.change==="picked"){e.push({id:y.id,kind:"mock-picked",mockId:p.mockId,name:p.name,group:p.group??null,version:p.version??null,siblings:p.siblings??[],ts:it(y),box:y.boxId,agent:y.provider});break}if(p.change!=="created")break;e.push({id:y.id,kind:"mock-card",mockId:p.mockId,name:p.name,group:p.group??null,ts:it(y),box:y.boxId,agent:y.provider});break}case"runbook.state.changed":{if(p.change!=="created")break;e.push({id:y.id,kind:"runbook-card",runbookId:p.runbookId,name:p.name,ts:it(y),box:y.boxId,agent:y.provider});break}case"ticket.linked":{e.push({id:y.id,kind:"ticket-card",provider:p.provider||"linear",key:p.key,url:p.url,title:p.title??null,linkSource:p.linkSource??null,ts:it(y),box:y.boxId,agent:y.provider});break}case"ticket.move.offered":{if(!p.offerId)break;for(let _ of M.values())_.key===p.key&&(_.status="stale",M.delete(_.offerId));let S={id:y.id,kind:"ticket-offer",offerId:p.offerId,provider:p.provider||"linear",key:p.key,url:p.url??null,phase:p.phase??null,from:p.from??null,to:p.to?.name??null,prNumber:p.prNumber??null,status:"open",ts:it(y),box:y.boxId,agent:y.provider};M.set(p.offerId,S),z.set(p.offerId,S),e.push(S);break}case"ticket.move.answered":{let S=z.get(p.offerId);S&&(S.status=p.action==="moved"?"moved":"dismiss",M.delete(p.offerId));break}case"ticket.state.changed":{for(let S of M.values())S.key===p.key&&S.to===p.to&&(S.status="stale",M.delete(S.offerId));if(p.mode==="offer")break;e.push(nn(y,"ticket-state"));break}case"ticket.commented":{e.push({id:y.id,kind:"ticket-comment",key:p.key,url:p.url??null,excerpt:p.excerpt??null,ts:it(y),box:y.boxId,agent:y.provider});break}case"ticket.flow.changed":e.push(nn(y,"ticket-flow"));break;case"ticket.create.offered":{if(!p.proposalId)break;for(let _ of $.values())_.status="stale";$.clear();let S={id:y.id,kind:"ticket-create-offer",proposalId:p.proposalId,provider:p.provider||"linear",epic:p.epic??null,issues:Array.isArray(p.issues)?p.issues:[],team:p.team??null,needsTeam:!!p.needsTeam,linkCurrentTrack:!!p.linkCurrentTrack,status:"open",ts:it(y),box:y.boxId,agent:y.provider};$.set(p.proposalId,S),F.set(p.proposalId,S),e.push(S);break}case"ticket.create.answered":{let S=F.get(p.proposalId);S&&(S.status=p.action==="created"?"created":"dismiss",S.createdCount=p.createdCount??null,S.failedCount=p.failedCount??null,$.delete(p.proposalId));break}case"ticket.created":{let S={items:Array.isArray(p.items)?p.items:[],failed:Array.isArray(p.failed)?p.failed:[],team:p.team??null},_=p.proposalId?F.get(p.proposalId):null;if(_){_.created=S;break}e.push({id:y.id,kind:"ticket-created",...S,ts:it(y),box:y.boxId,agent:y.provider});break}case"error":{if(!p.kind)break;e.push(nn(y,p.kind));break}case"provider.connected":{e.push({id:y.id,kind:"event",type:"provider-connected",provider:p.provider||y.provider,ts:it(y),at:it(y),box:y.boxId,agent:y.provider});break}case"prompt.answered":{if(p.control||!p.question)break;let S=p.groupId||p.promptId;if(!S)break;let _=T.get(S);_||(_={id:y.id,kind:"prompt",groupId:S,provider:y.provider,box:y.boxId,agent:y.provider,questionTotal:p.questionTotal??1,questions:[],ts:it(y)},T.set(S,_));let rt=p.questionIndex??0,I={index:rt,header:p.header||"",question:p.question,multiSelect:!!p.multiSelect,chosen:Array.isArray(p.chosen)?p.chosen:[]},R=_.questions.findIndex(P=>P.index===rt);R>=0?_.questions[R]=I:_.questions.push(I),_.questions.sort((P,et)=>P.index-et.index),_.questionTotal=Math.max(_.questionTotal,p.questionTotal??1),new Set(_.questions.map(P=>P.index)).size>=_.questionTotal&&(e.push(_),T.delete(S));break}default:break}}return LA(e.sort((y,p)=>(y.ts??0)-(p.ts??0)))}var jA=2*6e4;function LA(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<=jA){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 Rl(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:FA(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??Io(t)).filter(o=>o.kind==="message");return Rm(n,r)}function FA(t){let e=String(t.summary||"").trim();return e&&e!==t.command&&e!==fr(t.command)?e:Ve(t.command,t.description||"")}function nn(t,e){let{ts:n,change:r,...o}=t.payload||{};return{id:t.id,kind:"event",type:e,...o,at:it(t),ts:it(t)}}function UA(t){let e=[],n=null;for(let r of String(t||"").split(`
|
|
142
|
-
`)){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
|
|
143
|
-
`)}catch{}}function Aw(){let t={};for(let[e,n]of Nl)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:Aa.slice(-20)}}var Ra=o_(r_),s_={timeout:3e4,maxBuffer:8*1024*1024};async function Gt(t,e,n={}){let{caller:r="prs",...o}=n,s=await ee({cwd:t});Re(r);try{let{stdout:a}=await Ra("gh",e,{cwd:t,...s_,...o,env:{...process.env,...s,...o.env||{}}});return String(a||"")}catch(a){throw Ye(a)&&Ze(a),a}}var Ea=null;async function Dl(){if(Ea&&Date.now()-Ea.at<6e4)return Ea.data;let t;try{Re("auth");let e=await Ra("gh",["auth","status"],{timeout:1e4});t={installed:!0,authenticated:!0,login:`${e.stdout||""}${e.stderr||""}`.match(/account\s+(\S+)/)?.[1]??null}}catch(e){t=e?.code==="ENOENT"?{installed:!1,authenticated:!1,login:null}:{installed:!0,authenticated:!1,login:null}}if(t.installed&&!t.authenticated)try{let e=await Gr();e.configured&&(t={installed:!0,authenticated:!0,login:e.username,via:"broker"})}catch{}return Ea={at:Date.now(),data:t},t}async function a_(t){try{let{stdout:e}=await Ra("git",["-C",t,"config","--get","remote.origin.url"],{timeout:5e3});return/github\.com/.test(e)}catch{return!1}}function Ew(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 Pl(t){let e=(t||[]).map(Ew),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 jl="number,title,url,headRefName,headRefOid,baseRefName,isDraft,author,reviewDecision,mergeable,mergeStateStatus,statusCheckRollup,autoMergeRequest,additions,deletions,createdAt,updatedAt",Iw=jl.replace("statusCheckRollup,","");function Ll(t){return/Resource not accessible by personal access token/.test(String(t?.stderr||t?.message||""))}async function Rw(t,e){if(!e)return null;let n=await tt(t);if(!n)return null;try{let r=await Gt(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 $w(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 Ow(t,e){let n=await Rw(t,e);return n===null?{...Pl([]),unavailable:!0}:Pl($w(n))}function Ia(t,e=Pl(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 i_=9e4,xr=new Map;function c_(t,e,n){let r=new Set;for(let o of e){let s=`${t}#${o.number}`;if(o.mergeable!=="UNKNOWN"&&o.mergeStateStatus!=="UNKNOWN"){xr.delete(s);continue}r.add(s),xr.has(s)||xr.set(s,n),o.mergeabilityPending=n-xr.get(s)<i_}for(let o of xr.keys())o.startsWith(`${t}#`)&&!r.has(o)&&xr.delete(o);return e}async function Sr(t,e){let n=await $e(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");try{let o=await Gt(n.path,["pr","view",String(r),"--json",jl]);return Ia(JSON.parse(o))}catch(o){if(!Ll(o))throw o;let s=await Gt(n.path,["pr","view",String(r),"--json",Iw]),a=JSON.parse(s);return Ia(a,await Ow(n.path,a.headRefOid))}}var _w=(t,e)=>t.number-e.number;async function $o(t,{caller:e="prs"}={}){try{let n=await Gt(t,["pr","list","--json",jl,"--limit","50"],{caller:e});return JSON.parse(n||"[]").map(r=>Ia(r)).sort(_w)}catch(n){if(!Ll(n))throw n;let r=await Gt(t,["pr","list","--json",Iw,"--limit","50"],{caller:e});return(await Promise.all(JSON.parse(r||"[]").map(async o=>Ia(o,await Ow(t,o.headRefOid))))).sort(_w)}}var l_=3e4,u_=1500,Ro=new Map;function he(){Ro.clear()}async function d_(t,e){if(!t)return e;for(let n of e){let r=await Et(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 na(t,n.number,{headSha:n.headSha}).catch(()=>{}):n.ciFix={delegatedAt:r.ciFix.delegatedAt,trackId:r.ciFix.trackId??null}),r.reconcile&&(o(r.reconcile)?await oa(t,n.number,{headSha:n.headSha}).catch(()=>{}):n.reconcile={delegatedAt:r.reconcile.delegatedAt,trackId:r.reconcile.trackId??null})}return e}async function Mw(){let t=await Dl();if(!t.authenticated)return{auth:t,repos:[]};let e=[];for(let a of await ye())await a_(a.path)&&e.push(a);let n=Date.now(),r=ba(n)>0,o=await Promise.all(e.map(async a=>{let i=Ro.get(a.path),c=i?.pending?u_:l_;if(i&&(r||n-i.at<c))return i.entry;try{let l=await tt(a.path).catch(()=>null),u=c_(a.path,await $o(a.path,{caller:"tab"}),n);if(await d_(l,u),i?.entry?.prs)for(let m of u)i.entry.prs.find(w=>w.number===m.number)?.checks?.pending>0&&m.checks.total>0&&m.checks.pending===0&&_a({change:m.checks.failed>0?"checks-failed":"checks-green",repo:l||a.name,number:m.number,githubAt:m.checks.settledAt,source:"tab"});let f={name:a.name,path:a.path,prs:u},d=u.some(m=>m.mergeabilityPending||m.checks.pending>0||m.autoMergeArmed);return Ro.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:me(l)}}})),s=new Set(e.map(a=>a.path));for(let a of Ro.keys())s.has(a)||Ro.delete(a);return{auth:t,repos:o}}async function $e(t){let e=(await ye()).find(n=>n.path===t);if(!e)throw new Error(`unknown repo: ${t}`);return e}async function Nn(t){return qr(t)}var Oo=new Set(["squash","merge","rebase"]);async function vr(t,e,{method:n="squash"}={}){let r=await $e(t);if(!Oo.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 ee({cwd:r.path})).GH_TOKEN){let a=await fi().catch(()=>null);a&&s.push("--body",`Merged by bostrat on behalf of @${a}`)}try{await Gt(r.path,s)}catch(a){throw new Error(me(a))}finally{he()}return{ok:!0,number:o}}async function Mo(t,e){let n=await $e(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");try{await Gt(n.path,["api","-X","PUT",`repos/{owner}/{repo}/pulls/${r}/update-branch`])}catch(s){throw new Error(me(s))}finally{he()}let o=await tt(n.path).catch(()=>null);return o&&await lm(o,r).catch(()=>{}),{ok:!0,number:r}}async function Fl(t,e){let n=await $e(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");try{await Gt(n.path,["pr","merge",String(r),"--disable-auto"])}catch(o){throw new Error(me(o))}finally{he()}return{ok:!0,number:r}}var f_=2500,p_=9e4,m_=t=>new Promise(e=>setTimeout(e,t));function Ul(t){return t.green&&!t.isDraft}function h_(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 Cw(t,{update:e=!0}={}){return t.isDraft?{action:"skip",reason:"draft"}:Ul(t)?{action:"merge"}:e&&t.mergeStateStatus==="BEHIND"&&t.mergeable!=="CONFLICTING"&&t.checks.failed===0?{action:"update-arm"}:{action:"arm"}}function Nw(t){return[...t.filter(e=>e.plan.action!=="merge"),...t.filter(e=>e.plan.action==="merge")]}var Bl=new Map;function Pw(){let t={};for(let[e,n]of Bl)t[e]={...n};return t}async function Bw(t,{method:e="merge",onMerged:n=null}={}){let r=await $e(t);if(!Oo.has(e))throw new Error(`invalid merge method: ${e}`);if(Bl.get(r.path)?.running)throw new Error("a merge-all is already running for this repo");let o=(await $o(r.path)).filter(Ul).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 Bl.set(r.path,s),g_(s,o,{listPrs:()=>$o(r.path),merge:a=>vr(r.path,a,{method:e}),sleep:m_,onMerged:n}).catch(()=>{}),{ok:!0,repo:r.path,total:s.total,queue:o.map(a=>a.number)}}async function w_(t,e){let n=Date.now()+(e.settleMaxMs??p_),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(f_)}}async function g_(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 w_(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: ${me(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(!Ul(a)){t.skipped.push({number:a.number,title:a.title,reason:h_(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:me(i)})}}}finally{t.current=null,t.running=!1,t.finishedAt=Date.now(),he()}}async function Hl(t,{title:e,body:n="",base:r=null}={}){if(typeof e!="string"||!e.trim())throw new Error("title required");try{await Ra("git",["-C",t,"push","-u","origin","HEAD"],{timeout:12e4,maxBuffer:4*1024*1024})}catch(l){throw new Error(`push failed: ${me(l)}`)}let o=n||"";if((await ee({cwd:t})).GH_TOKEN){let l=await fi().catch(()=>null);l&&(o=`${o?`${o}
|
|
144
|
-
|
|
145
|
-
`:""}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
|
|
138
|
+
`;import{execFile as nA}from"node:child_process";import{promisify as rA}from"node:util";var hl=rA(nA),xh=64*1024*1024;async function ht(t,e,{input:n}={}){let r=hl("git",["-C",t,...e],{maxBuffer:xh});return n!==void 0&&r.child.stdin&&(r.child.stdin.on("error",()=>{}),r.child.stdin.end(n)),r}async function ga(t){if(!t)return!1;try{let{stdout:e}=await ht(t,["rev-parse","--is-inside-work-tree"]);return e.trim()==="true"}catch{return!1}}async function Sh(t){let{stdout:e}=await ht(t,["rev-parse","--show-toplevel"]);return e.trim()}async function oA(t){try{return await ht(t,["rev-parse","--verify","HEAD"]),!0}catch{return!1}}async function Mo(t,e="bostrat: subtask snapshot"){let{stdout:n}=await ht(t,["rev-parse","--absolute-git-dir"]),r=`${n.trim()}/bostrat-snapshot-index`,o={...process.env,GIT_INDEX_FILE:r},s=(a,i)=>hl("git",["-C",t,...a],{maxBuffer:xh,env:o,...i});try{let a=await oA(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 hl("rm",["-f",r])}catch{}}}async function wl(t,e){let{stdout:n}=await ht(t,["status","--porcelain"]);return n.trim()?(await ht(t,["add","-A"]),await ht(t,["-c","user.email=agent@bostrat.local","-c","user.name=bostrat","commit","-m",e]),!0):!1}async function gl(t,{ignorePlumbing:e=!1}={}){let{stdout:n}=await ht(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 yl(t,e){if(!e)return 0;try{let{stdout:n}=await ht(t,["rev-list","--count",`${e}..HEAD`]);return Number(n.trim())||0}catch{return 0}}async function Sr(t,e,n,r){return await ht(t,["worktree","add","--no-track","-b",n,e,r]),e}async function vh(t,e){try{return await ht(t,["rev-parse","--verify","--quiet",`refs/heads/${e}`]),!0}catch{return!1}}async function Th(t,e,n){return await ht(t,["worktree","add",e,n]),e}async function ya(t){if(process.env.BOSTRAT_TRACK_BASE_REF)return process.env.BOSTRAT_TRACK_BASE_REF;try{let{stdout:e}=await ht(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 ht(t,["rev-parse","--verify","--quiet",e]),n}catch{}return"HEAD"}async function Ah(t){await ht(t,["init","-q"]),await ht(t,["-c","user.email=agent@bostrat.local","-c","user.name=bostrat","commit","--allow-empty","-qm","bostrat: track workspace"])}async function Gt(t,e){try{await ht(t,["worktree","remove","--force",e])}catch{try{await ht(t,["worktree","prune"])}catch{}}}function sA(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 bl(t,e){return!e||!e.length?t:[...t,"--",".",...e.map(n=>`:(exclude)${n}`)]}async function kl(t,e,n,r){let{stdout:o}=await ht(t,bl(["diff","--shortstat",e,n],r));return sA(o)}async function xl(t,e,n,r){let{stdout:o}=await ht(t,bl(["diff","--name-only",e,n],r));return o.split(`
|
|
140
|
+
`).map(s=>s.trim()).filter(Boolean)}async function Sl(t,e,n,r){let{stdout:o}=await ht(t,bl(["diff","--binary",e,n],r));if(!o.trim())return{applied:!0,empty:!0,conflicts:[]};if((await ml(t,o,["--whitespace=nowarn"])).ok)return{applied:!0,empty:!1,conflicts:[]};let s=await ml(t,o,["--3way","--whitespace=nowarn"]),a=await aA(t);return a.length>0?{applied:!1,empty:!1,conflicts:a}:s.ok?{applied:!0,empty:!1,conflicts:[]}:(await ml(t,o,["--reject","--whitespace=nowarn"]),{applied:!1,empty:!1,conflicts:[`patch did not apply cleanly: ${s.err}`]})}async function ml(t,e,n){try{return await ht(t,["apply",...n,"-"],{input:e}),{ok:!0}}catch(r){return{ok:!1,err:r?.stderr?.trim()||r?.message||String(r)}}}async function aA(t){try{let{stdout:e}=await ht(t,["diff","--name-only","--diff-filter=U"]);return e.split(`
|
|
141
|
+
`).map(n=>n.trim()).filter(Boolean)}catch{return[]}}import iA from"node:fs/promises";import cA from"node:os";import Eh from"node:path";var ba=t=>new Promise(e=>setTimeout(e,t)),lA=Number(process.env.BOSTRAT_MODEL_PICKER_TIMEOUT_MS)||8e3,uA=300,$h=[{alias:"default",label:"Default"},{alias:"opus",label:"Opus 4.8"},{alias:"sonnet",label:"Sonnet 4.6"},{alias:"haiku",label:"Haiku 4.5"},{alias:"fable",label:"Fable 5"},{alias:"opusplan",label:"Opus Plan"}],dA=new Set([...$h.map(t=>t.alias),"best","opus[1m]","sonnet[1m]","claude-opus-4-8","claude-sonnet-4-6","claude-haiku-4-5","claude-fable-5"]),fA=[{alias:"gpt-5.5",label:"GPT-5.5"},{alias:"gpt-5.4",label:"GPT-5.4"},{alias:"gpt-5.4-mini",label:"GPT-5.4 Mini"},{alias:"gpt-5.3-codex",label:"Codex 5.3"},{alias:"gpt-5.2",label:"GPT-5.2"}],Oh=new Map([["low","Low"],["medium","Medium"],["high","High"],["xhigh","Extra high"]]),Mh=[{alias:"auto",label:"Auto"},{alias:"composer-2.5",label:"Composer 2.5"},{alias:"claude-opus-4-8",label:"Opus 4.8"},{alias:"claude-fable-5",label:"Fable 5"},{alias:"claude-sonnet-5",label:"Sonnet 5"},{alias:"claude-sonnet-4-6",label:"Sonnet 4.6"},{alias:"gpt-5.5",label:"GPT-5.5"},{alias:"gpt-5.3-codex",label:"Codex 5.3"}];function Ch(t){return t==="codex"?fA:t==="cursor"?Mh:$h}function _h(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 Ih(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 pA(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 Rh(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 Nh(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&&!Oh.has(o))throw new Error(`unknown codex effort "${o}" (low | medium | high | xhigh)`);return{model:r,effort:o||null}}if(t==="cursor"){let r=Mh.find(o=>o.alias===n);return{model:n,filter:r?r.label:n}}if(!dA.has(n))throw new Error(`unknown model "${n}"`);return{model:n}}async function Co(t,e,{timeoutMs:n=lA}={}){let r=Date.now()+n;for(;;){let o=await gt(t,{lines:80}).catch(()=>""),s=e(o);if(s)return s;if(Date.now()>r)return null;await ba(uA)}}async function mA(t,e){return await vt(t,`/model ${e.model}`,Se("claude")),{verified:!1,note:"confirmed when the next turn's transcript model flips"}}async function hA(t,e){await As(t),await vt(t,"/model",{enter:!1}),await ba(250),await xt(t,["Enter"],{kind:"model-switch"});let n=await Co(t,_h);if(!n)throw new Error("codex model picker didn't open");let r=n.find(i=>i.id===e.model);if(!r)throw await xt(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=pA(i);return!c||c.model!==e.model||e.effort&&c.effort&&c.effort.toLowerCase()!==e.effort.toLowerCase()?null:c};await vt(t,String(r.n),{enter:!1,kind:"model-switch"});let s=await Co(t,i=>Ih(i)||!_h(i)&&o(i)&&{done:o(i)});if(!s)throw await xt(t,["Escape"],{kind:"model-switch"}).catch(()=>{}),new Error("codex reasoning-level picker didn't open");if(!s.done){let i=e.effort?Oh.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 xt(t,["Escape","Escape"],{kind:"model-switch"}).catch(()=>{}),new Error(`codex doesn't offer effort "${e.effort}" for ${e.model}`);await vt(t,String(c.n),{enter:!1,kind:"model-switch"})}let a=s.done||await Co(t,i=>!Ih(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 wA(){let t=Eh.join(process.env.CURSOR_CONFIG_DIR||Eh.join(cA.homedir(),".cursor"),"cli-config.json");try{return JSON.parse(await iA.readFile(t,"utf8"))?.model||null}catch{return null}}async function gA(t,e){await As(t),await _s(t,`/model ${e.filter}`,{kind:"model-switch"});let n=await Co(t,Rh),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 Co(t,Rh,{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 xt(t,["Down"],{kind:"model-switch"}),await ba(250)}if(!n||!s||!o(s)){await xt(t,["Escape"],{kind:"model-switch"}).catch(()=>{});let c=Array(`/model ${e.filter}`.length).fill("BSpace");throw await xt(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 xt(t,["Enter"],{kind:"model-switch"}),await ba(800);let a=await wA();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 Ph(t,e,n){return t==="codex"?hA(e,n):t==="cursor"?gA(e,n):mA(e,n)}var Ft=t=>t||null;function xa(t){let e=t||{},n=yA(e);return n&&(n.payload={ts:e.at,...n.payload}),n}function yA(t){switch(t.type){case"said":return{type:"turn.user",actor:"user",source:"orchestrator",boxId:t.box,provider:Ft(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:Ft(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:Ft(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:Ft(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:Ft(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:Ft(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:Ft(t.agent),payload:{...vr(t)}};case"subtask-failed":return{type:"error",actor:"system",source:"orchestrator",boxId:t.box||t.to,provider:Ft(t.agent||t.toAgent),payload:{kind:"subtask-failed",...vr(t)}};case"subtask-resumed":return{type:"box.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Ft(t.agent),payload:{change:"subtask-resumed",...vr(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",...vr(t)}};case"archived":case"unarchived":case"track-deleted":return{type:"box.state.changed",actor:"user",source:"orchestrator",payload:{change:t.type,...vr(t)}};case"rehydrated":return{type:"box.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Ft(t.agent),payload:{change:"rehydrated"}};case"retagged":return{type:"box.state.changed",actor:"user",source:"orchestrator",payload:{change:"retagged",...vr(t)}};case"rehydrate-failed":return{type:"error",actor:"system",source:"orchestrator",boxId:t.box,provider:Ft(t.agent),payload:{kind:"rehydrate-failed",reason:t.reason}};default:return null}}var ka=Promise.resolve(),Tl=t=>(ka=ka.then(t).catch(()=>{}),ka),Dh=()=>ka;function jh(t,e){let n=xa(e);if(!n)return;let r=e?.type==="track-deleted"?{id:t.id}:t;Tl(()=>$(r,n))}var Bh=new Set;async function Lh(t){if(!(!t?.id||Bh.has(t.id))){Bh.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=xa(r);!o||n.has(`${o.type}|${o.payload?.ts??"?"}`)||await $(t,o)}}catch{}}}function vr(t){let{type:e,at:n,...r}=t;return r}var vl=new Map;function Fh(t,e,n){!n?.promptId||vl.get(t.id)===n.promptId||(vl.set(t.id,n.promptId),Tl(()=>$(t,{type:"prompt.raised",actor:"agent",source:"pane",boxId:e?.name,provider:Ft(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 Al(t,e,n,r={}){vl.delete(t.id),Tl(()=>$(t,{type:"prompt.answered",actor:"user",source:"orchestrator",boxId:e?.name,provider:Ft(n?.provider||e?.agent),payload:{promptId:n?.promptId,...r}}))}import{randomUUID as XA}from"node:crypto";var Hh=/(?<!\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,Wh=/(?<!\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,qh=/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,Kh=/(?<!\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,bA=new RegExp(`\\b(?:${Hh}|${Wh}|${qh}|${Kh})\\b`,"i"),kA=/\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,xA=/^(?:commit|push|open|create|raise|rebase|ready|ok(?:ay)?\s+to|good\s+to)\b/i,SA=90;function vA(t){return String(t).split(/(?<=[.?!])\s+|\n+/).map(e=>e.trim()).filter(Boolean)}function Gh(t){let e=String(t||"").trim();if(!e)return null;let n=e.slice(-600);for(let r of vA(n).reverse()){if(!bA.test(r))continue;let o=r.endsWith("?")&&r.length<=SA&&xA.test(r);if(kA.test(r)||o)return r.length>200?r.slice(0,197)+"\u2026":r}return null}var Vh=["commit","rebase","push","pr"],TA=new RegExp(`\\b(?:${Hh})\\b`,"i"),AA=new RegExp(`\\b(?:${Kh})\\b`,"i"),EA=new RegExp(`\\b(?:${Wh})\\b`,"i"),_A=new RegExp(`\\b(?:${qh})\\b`,"i"),IA=/\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 zh(t){let e=String(t||""),n=[];TA.test(e)&&n.push("commit"),AA.test(e)&&n.push("rebase"),EA.test(e)&&n.push("push");let r=IA.test(e);!r&&_A.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 Sa(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(m=>`${m.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 m=a.has("commit")?"the branch":`this branch${e?` (${e})`:""}`;u.push(i?`rebase ${m} in each changed checkout (${c}) onto its own base branch (fetch origin first), resolving any conflicts so both sides' intent is preserved`:`rebase ${m} 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 m=r&&!a.has("pr")?` (the open PR${r.number?` #${r.number}`:""} picks it up)`:"",h=a.has("rebase")?" with `--force-with-lease` (the rebase rewrote history)":"";i?u.push(a.has("commit")||a.has("rebase")?`push each one to its remote${h}${m}`:`push this branch${e?` (${e})`:""} from each changed checkout (${c}) to its remote${m}`):u.push(a.has("commit")||a.has("rebase")?`push it to the remote${h}${m}`:`push this branch${l} to the remote${m}`)}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(/^./,m=>m.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 RA=3e4;function Jh(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<=RA&&n.push({box:r.box,from:r.from,to:e+1e12}),n}function Xh(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 Uh={commit:"commit",rebase:"rebase",push:"push",pr:"open a PR"};function Yh(t){let e=(Array.isArray(t)?t:[]).filter(r=>Uh[r]).map(r=>Uh[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 NA}from"node:child_process";import{promisify as PA}from"node:util";import $A from"node:fs";import El from"node:fs/promises";import ew from"node:path";import OA from"node:os";var nw=process.env.BOSTRAT_STATE_DIR||ew.join(process.env.HOME||OA.tmpdir(),".bostrat"),va=ew.join(nw,"github-metrics.json"),rw=60*6e4,Zh=5*6e4,MA=6e4,Tr=Date.now(),Re=new Map,Ye=null,Pn=0,Qh=0,Ta=t=>[...t.values()].reduce((e,n)=>e+n,0);try{let t=JSON.parse($A.readFileSync(va,"utf8"));t?.windowStart&&(Date.now()-t.windowStart<rw?(Tr=t.windowStart,Re=new Map(Object.entries(t.byCaller||{})),Ye=t.lastHour||null):Ta(new Map(Object.entries(t.byCaller||{})))&&(Ye={start:t.windowStart,total:Ta(new Map(Object.entries(t.byCaller||{}))),byCaller:t.byCaller||{}}))}catch{}var tw=Promise.resolve();function ow(t,e=!1){if(!e&&t-Qh<MA)return;Qh=t;let n=JSON.stringify({windowStart:Tr,byCaller:Object.fromEntries(Re),lastHour:Ye});tw=tw.then(()=>El.mkdir(nw,{recursive:!0})).then(()=>El.writeFile(`${va}.tmp`,n)).then(()=>El.rename(`${va}.tmp`,va)).catch(()=>{})}function sw(t){t-Tr<rw||(Ye={start:Tr,total:Ta(Re),byCaller:Object.fromEntries(Re)},Ye.total&&console.log(`[github] ${Ye.total} calls last hour`,Ye.byCaller),Tr=t,Re=new Map,ow(t,!0))}function $e(t="other",e=Date.now()){sw(e),Re.set(t,(Re.get(t)||0)+1),ow(e)}function aw(t=Date.now()){return sw(t),{windowStart:Tr,total:Ta(Re),byCaller:Object.fromEntries(Re),lastHour:Ye,backoffUntil:Pn>t?Pn:null}}var CA=/rate limit|too quickly|abuse detection|HTTP 429/i;function Ze(t){return CA.test(String(t?.message||t||""))}function Qe(t,e=Date.now()){return Pn<=e&&console.warn(`[github] rate limited \u2014 backing off ${Zh/6e4} min:`,String(t?.message||t||"").slice(0,200)),Pn=Math.max(Pn,e+Zh),Pn}function Aa(t=Date.now()){return Math.max(0,Pn-t)}async function _l(t,e){let n={working:!1,prompted:!1};if(!t)return n;let r=await gt(t,{lines:Tt}).catch(()=>"");if(!Ue(e,r))return af(e,r)?{working:!0,prompted:!1}:n;if(kr(e,r)){let o=await Le(t).catch(()=>r);if(Nn({name:t,agent:e},o))return{working:!1,prompted:!0}}return{working:!0,prompted:!1}}async function Ea(t,e){return(await _l(t,e)).working}var iw=PA(NA),cw=[4e3,15e3,45e3],BA=2500,DA=5*6e4;async function Il(t,e,{midTurn:n=Ea,pollMs:r=BA,capMs:o=DA}={}){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 jA=6e4,LA=9e4,FA=2e3,UA=/(?:^|[;&|]\s*|\)\s*)gh\s+(?:[^\s;&|"'`$]+\s+)*?pr\s+create\b/,HA=/(?:^|[;&|]\s*|\)\s*)gh\s+(?:[^\s;&|"'`$]+\s+)*?pr\s+merge\b/,WA=/\s--web\b/,lw=/(?:--repo|-R)[\s=]+([\w.-]+\/[\w.-]+)/;function Rl(t,e){return t.slice(e).replace(/^[\s;&|)]+/,"").replace(/"[^"]*"|'[^']*'/g,'""').split(/[;&|]/)[0]}function uw(t){let e=String(t||""),n=e.match(UA);if(!n)return null;let r=Rl(e,n.index);return WA.test(r)?null:{repo:r.match(lw)?.[1]??null}}var qA=new Set(["--subject","-t","--body","-b","--body-file","-F","--match-head-commit","--author-email","-A","--repo","-R"]);function dw(t){let e=String(t||""),n=e.match(HA);if(!n)return null;let r=Rl(e,n.index),o=Rl(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("-")){qA.has(c)&&i++;continue}s=c.replace(/^["']+|["']+$/g,"")||null;break}return{repo:r.match(lw)?.[1]??null,selector:s,auto:/\s--auto\b/.test(r)}}var KA="number,url,title,headRefName,createdAt";async function GA(t,e){let n=async a=>{$e("observer");let{stdout:i}=await iw("gh",["pr","list","--repo",t,"--author","@me","--state","all","--limit","10","--json",KA],{timeout:3e4,maxBuffer:4*1024*1024,env:{...process.env,...a}}).catch(c=>{throw Ze(c)&&Qe(c),c});return JSON.parse(i||"[]")},r=a=>(a||[]).filter(i=>Date.parse(i.createdAt)>=e-jA),o=await ne({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 VA(t,e){if(e)return[e];let n=[];for(let r of fw(t)){let o=await tt(r).catch(()=>null);o&&!n.includes(o)&&n.push(o)}for(let r of await ye().catch(()=>[])){let o=await tt(r.path).catch(()=>null);o&&!n.includes(o)&&n.push(o)}return n}function fw(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 zA(t,e,n){let r=t.workspace?.branch,o=[];for(let s of await VA(t,e)){let a;try{a=await GA(s,n)}catch{continue}for(let i of a){let c=await It(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 pw(t,e,{delays:n=cw,turnEnd:r}={}){let o=uw(e.command);if(!o)return null;let s=!!dw(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 zA(t,o.repo,i).catch(()=>null);if(!u)continue;let{slug:f,pr:d}=u,{link:m,created:h}=await Lt({repo:f,number:d.number,trackId:t.id,url:d.url,headRef:d.headRefName,title:d.title,source:"observed"});return!h&&!(m?.trackId===t.id&&await So(f,d.number))?null:(await Il(e.box,e.agent,r),await $({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 JA="number,url,title,headRefName,state";function mw(t,e,{delays:n=cw,turnEnd:r,openedWait:o}={}){let s=dw(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 h=fw(t);if(h.length>1&&/^\d+$/.test(String(s.selector??"")))return null;for(let w of h){let g=await tt(w).catch(()=>null);g&&!f.includes(g)&&f.push(g)}}if(l||=t.workspace?.branch,!f.length||!l)return null;c=f[0];let d=async(h,w)=>{let g=await It(c,h.number);return g&&g.trackId!==t.id?null:(g||await Lt({repo:c,number:h.number,trackId:t.id,url:h.url,headRef:h.headRefName,title:h.title,source:"merge-observed"}),await vo(c,h.number),w&&((await L(t).catch(()=>[])).some(A=>A.type==="pr.state.changed"&&(A.payload?.change==="auto-armed"||A.payload?.change==="merged")&&A.payload?.number===h.number)||(await Il(e.box,e.agent,r),await $({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:Date.now(),change:"auto-armed",number:h.number,url:h.url,title:h.title,branch:h.headRefName,repo:c}}))),h)},m=null;for(let h of n){await new Promise(O=>setTimeout(O,h));let w=[];for(let O of f)try{$e("observer");let{stdout:F}=await iw("gh",["pr","view",String(l),"--repo",O,"--json",JA],{timeout:3e4,maxBuffer:4*1024*1024,env:{...process.env,...await ne({slug:O})}}),T=JSON.parse(F||"null");T?.number&&w.push({slug:O,pr:T})}catch(F){Ze(F)&&Qe(F)}let g=w.find(O=>O.pr.state==="MERGED")??(w.length===1?w[0]:null);if(!g)continue;c=g.slug;let b=g.pr;if(!b?.number)continue;if(b.state==="CLOSED")return null;if(b.state!=="MERGED"){if(m=b,s.auto)return d(b,!0);continue}let S=await It(c,b.number);if(S&&S.trackId!==t.id)return null;S||await Lt({repo:c,number:b.number,trackId:t.id,url:b.url,headRef:b.headRefName,title:b.title,source:"merge-observed"}),await Il(e.box,e.agent,r);let A=await L(t).catch(()=>[]);if(A.some(O=>O.type==="pr.state.changed"&&O.payload?.change==="merged"&&O.payload?.number===b.number))return null;let C=O=>O.some(F=>F.type==="pr.state.changed"&&F.payload?.change==="opened"&&F.payload?.number===b.number),V=0;if(!C(A)){if(uw(e.command)&&await So(c,b.number)){let O=Date.now();V=O+1,await $({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:b.number,url:b.url,title:b.title,branch:b.headRefName,repo:c,selfMerge:!0}})}else if((await It(c,b.number))?.announced){let O=o?.capMs??LA,F=o?.pollMs??FA,T=Date.now()+O;for(;Date.now()<T&&(await new Promise(M=>setTimeout(M,F)),!C(await L(t).catch(()=>[]))););}}return await $({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(),V),change:"merged",number:b.number,url:b.url,title:b.title,branch:b.headRefName,repo:c}}),await nn(c,b.number).catch(()=>{}),await en(c,b.number).catch(()=>{}),await ze(c,b.number,"merged"),b}return m?d(m,!1):null}finally{tn.delete(a)}})();return i.catch(()=>tn.delete(a)),i}function Ol(t){return t.sourceId?`${t.role}|id:${t.sourceId}`:`${t.role}|${t.ts??"?"}|${String(t.text||"").slice(0,40)}`}function hw(t){return t.id?`cmd|${t.box}|id:${t.id}`:`cmd|${t.box}|${t.ts??"?"}|${String(t.command||"").slice(0,40)}`}function ww(t){return`job|${t.box}|id:${t.id}`}var $l=new Map,_a=new Map;async function Ml(t){return $l.has(t.id)?$l.get(t.id):(_a.has(t.id)||_a.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(Ol({role:o,sourceId:r.payload.sourceId})),n.keys.add(Ol({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(hw({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(ww({id:r.payload.id,box:r.boxId}));return $l.set(t.id,n),n}).finally(()=>_a.delete(t.id))),_a.get(t.id))}async function gw(t,e){try{let n=(await Ml(t)).keys;for(let r of e||[]){if(!r||r.ts==null||!String(r.text||"").trim()&&!r.images?.length)continue;let o=Ol(r);if(!n.has(o)&&(n.add(o),await $(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=Gh(r.text);if(s){let{offered:a,toPr:i}=zh(s),c;if(t.workspace?.kind==="multi"){let l=await No(t).catch(()=>[]);c=l.length?l.map(u=>({name:u.name,slug:u.repo??null})):void 0}await $(t,{type:"pr.offered",actor:"agent",source:"transcript",boxId:r.box,provider:r.agent||null,payload:{offerId:XA(),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 yw(t,e){try{let n=await Ml(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=hw(o);n.keys.has(s)||(n.keys.add(s),n.cmdN+=1,await $(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}}),pw(t,o),mw(t,o))}}catch{}}async function bw(t,e){try{let n=await Ml(t),r=[...e||[]].filter(o=>o&&o.id).sort((o,s)=>(o.ts||0)-(s.ts||0));for(let o of r){let s=ww(o);n.keys.has(s)||(n.keys.add(s),await $(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 YA=/^Base directory for this skill:\s*(\S+)/,ZA=/^Run the "([^"]+)" workflow\./,QA=/<command-name>\s*\/?([a-z0-9:_-]+)\s*<\/command-name>/i,tE=/^<local-command-(?:stdout|caveat)>/i,eE=/^\[Image: original \d+x\d+, displayed at \d+x\d+\.\s*Multiply coordinates by [\d.]+ to map to original image\.\]$/,nE=/^Continue from where you left off\.$/,rE=/^<task-notification>/i,oE=/<summary>([\s\S]*?)<\/summary>/i,sE=/<task-id>([\s\S]*?)<\/task-id>/i,aE=/<tool-use-id>([\s\S]*?)<\/tool-use-id>/i,iE=/<status>([\s\S]*?)<\/status>/i,cE=/<result>([\s\S]*?)(?:<\/result>|$)/i;var Cl=new Set(["model","clear","compact","login","exit","quit"]);function kw(t){return Cl.has(String(t||"").replace(/^\//,"").toLowerCase())}var lE=/^\/([a-z0-9:_-]+)$/i;function Ia(t){let e=String(t||"").trim();if(!e)return null;let n=e.match(YA);if(n)return{kind:"skill",name:n[1].split("/").filter(Boolean).pop()||"skill"};let r=e.match(ZA);if(r)return{kind:"skill",name:r[1]};let o=e.match(QA);if(o){let a=o[1].toLowerCase();return Cl.has(a)?{kind:"suppress"}:{kind:"command",command:`/${a}`}}let s=e.match(lE);if(s&&Cl.has(s[1].toLowerCase()))return{kind:"suppress"};if(tE.test(e))return{kind:"suppress"};if(nE.test(e))return{kind:"suppress"};if(eE.test(e))return{kind:"suppress"};if(rE.test(e)){let a=c=>uE(e.match(c)?.[1].trim()||"")||null,i=a(cE);return{kind:"task",summary:a(oE)||"",taskId:a(sE),toolUseId:a(aE),status:a(iE)?.toLowerCase()||null,resultHead:i?i.length>600?`${i.slice(0,600).trimEnd()}\u2026`:i:null}}return null}function uE(t){return t.replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/'/g,"'").replace(/&/g,"&")}function xw(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 Sw(t){return t?.kind==="skill"?"skill-run":t?.kind==="task"?"task-done":"command-run"}var it=t=>t.payload?.ts??t.ts,dE=new Set(["opened","ci-fixed","reconciled","pushed"]),vw=t=>`${t.repo??""}#${t.number}`;function fE(t,e,n){let r=vw(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 Po(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 y of t||[]){if(y.type!=="tool.called"||y.source!=="transcript")continue;let p=String(y.payload?.command||"");if(/\bbostrat-mock\s+(?:add|update)\b/.test(p))for(let v of p.matchAll(/[\w./~-]+\.(?:png|jpe?g|gif|webp|bmp|svg)\b/gi)){let E=v[0].split("/").pop().toLowerCase();f.has(E)||f.set(E,[]),f.get(E).push(y.payload?.ts??y.ts)}}let d=3*6e4,m=20*6e4,h=(y,p)=>!!(y?.name&&f.get(String(y.name).toLowerCase()))?.some(E=>E>=p-d&&E<=p+m);for(let y of t||[])if(y.type==="turn.retracted"){r.add(`${y.boxId}|${y.payload?.saidTs}`);let p=Zt(y.payload?.text);p&&(o.has(y.boxId)||o.set(y.boxId,[]),o.get(y.boxId).push(p))}else if(y.type==="turn.user"&&y.source==="orchestrator"){let p=Zt(y.payload?.text);p&&(s.has(y.boxId)||s.set(y.boxId,new Set),s.get(y.boxId).add(p))}else if(y.type==="job.started"&&y.payload?.id)a.add(y.payload.id),(y.payload.kind||"agent")==="agent"&&y.payload.name&&c.add(y.payload.name);else if(y.type==="turn.user"&&y.payload?.note?.kind==="task"){let p=y.payload.note,v={status:p.status||null,summary:p.summary||null,resultHead:p.resultHead??null,ts:it(y)};p.toolUseId&&i.set(p.toolUseId,v);let E=String(p.summary||"").match(u);E&&l.set(E[1],v)}let w=new Map,g=new Map,b=null,S=new Map,A=new Map,C=new Map,V=new Map,O=new Map,F=new Map,T=new Map,M=6e4,W=new Map,st=(y,p)=>{let v=o.get(y);if(!v?.length)return!1;let E=Zt(p),rt=s.get(y);return v.some(I=>{if(!E.startsWith(I))return!1;let R=Zt(E.slice(I.length));return!!R&&!!rt?.has(R)})};for(let y of t||[]){let p=y.payload||{};switch(y.type){case"turn.user":{if(y.source==="hook"||!String(p.text||"").trim()&&!p.images?.length)break;{let v=p.note||Ia(p.text);if(v){if(v.kind==="task"){if(v.toolUseId&&a.has(v.toolUseId))break;let E=String(v.summary||"").match(u);if(!v.toolUseId&&E&&c.has(E[1]))break}if(v.kind==="command"&&kw(v.command))break;v.kind!=="suppress"&&e.push({id:y.id,kind:"event",type:Sw(v),name:v.name??null,command:v.command??null,summary:v.summary??null,ts:it(y),box:y.boxId,agent:y.provider});break}}if(r.has(`${y.boxId}|${it(y)}`)||y.source==="transcript"&&st(y.boxId,p.text))break;if(p.kind==="merge-notify"||p.kind==="ship-notify"){e.push({id:y.id,kind:"event",type:p.kind==="ship-notify"?"ship-notified":"merge-notified",text:p.text,ts:it(y),box:y.boxId,agent:y.provider});break}if(p.kind==="mock-picked"||p.kind==="ci-fix"||p.kind==="reconcile"||p.kind==="pr-go"||p.kind==="push-go"||p.kind==="spin-up-view"||p.kind==="prompt-answer")break;for(let v of S.values())v.status="stale";S.clear(),e.push({id:y.id,kind:"message",role:"user",text:p.text,ts:it(y),box:y.boxId,agent:y.provider,boxRole:p.boxRole,...y.actor==="orchestrator"?{orchestration:p.kind||!0}:{},...p.sourceId?{sourceId:p.sourceId}:{},...p.attachments?.length?{attachments:p.attachments}:{},...p.images?.length?{images:p.images}:{}});break}case"turn.agent":{if(p.model==="<synthetic>"&&/^No response requested\.$/.test(String(p.text||"").trim()))break;T.clear();let v=(p.images||[]).filter(R=>!h(R,it(y)));if(!String(p.text||"").trim()&&!v.length)break;if(y.provider==="cursor"){let R=`${y.boxId}|${p.text}`;if(n.has(R))break;n.add(R)}let E=y.provider==="codex"?(p.tools||[]).filter(R=>!Jc.has(R)&&!Io.has(R)):p.tools||[],rt=p.text,I=null;if(dl.has(y.provider)){let R=fl(String(p.text||""));R&&(rt=R.text,I={questions:R.questions})}e.push({id:y.id,kind:"message",role:"assistant",text:rt,ts:it(y),box:y.boxId,agent:y.provider,boxRole:p.boxRole,...I?{ask:I}:{},...E.length?{tools:E}:{},...p.model?{model:p.model}:{},...p.sourceId?{sourceId:p.sourceId}:{},...v.length?{images:v}:{}});break}case"job.started":{if(!p.id)break;let v=it(y),E=i.get(p.id);if(!E&&(p.kind||"agent")==="agent"&&p.name){let P=l.get(p.name);P&&P.ts>=v&&(E=P)}let rt={jobId:p.id,jobKind:p.kind||"agent",name:p.name||null,detail:p.detail||null,status:E?E.status&&E.status!=="completed"?"failed":"done":"running",startedTs:v,finishedTs:E?.ts??null,summary:E?.summary??null,resultHead:E?.resultHead??null},I=W.get(y.boxId),R=I?.jobs[I.jobs.length-1]?.startedTs;if(I&&R!=null&&v-R<=M)I.jobs.push(rt);else{let P={id:y.id,kind:"job-dock",dockId:p.id,ts:v,box:y.boxId,agent:y.provider,jobs:[rt]};W.set(y.boxId,P),e.push(P)}break}case"handoff.done":e.push(rn(y,"lead-handoff"));break;case"subtask.delegated":e.push({...rn(y,"subtask-delegated"),to:y.boxId,toAgent:y.provider});break;case"subtask.merged":e.push(rn(y,"subtask-merged"));break;case"pr.state.changed":{if(!p.change)break;if(p.change==="opened"){for(let E of S.values())E.status="stale";S.clear()}let v=rn(y,`pr-${p.change}`);if(dE.has(p.change)&&p.number!=null){let E=fE(w,g,p);if(w.size>=2){for(let I of g.values())for(let R of I)R.grouped=!0;v.groupPrs=[...w.values()];let rt=[v];for(let I of w.keys())g.set(I,rt)}else{let rt=g.get(E)??[];rt.push(v),g.set(E,rt)}}else if(p.change==="merged"||p.change==="closed"||p.change==="lost"){let E=vw(p),rt=b&&!b.complete||w.size>=2;if(w.has(E))w.delete(E),g.delete(E);else if(p.number!=null)for(let I of[...w.keys()])I.endsWith(`#${p.number}`)&&(w.delete(I),g.delete(I));if(rt&&p.number!=null){(!b||b.complete)&&(b={id:y.id,kind:"event",type:"pr-group-settled",prs:[],remaining:[],complete:!1,at:it(y),ts:it(y),box:y.boxId,agent:y.provider},e.push(b)),b.prs.push({number:p.number,url:p.url??null,title:p.title??null,repo:p.repo??null,branch:p.branch??null,change:p.change,ts:it(y)}),b.remaining=[...w.values()],b.complete=w.size===0;break}}e.push(v);break}case"pr.offered":{if(!p.offerId)break;for(let E of S.values())E.status="stale";S.clear();let v={id:y.id,kind:"pr-offer",offerId:p.offerId,excerpt:p.excerpt||null,branch:p.branch||null,base:p.base||null,offered:Array.isArray(p.offered)&&p.offered.length?p.offered:null,toPr:p.toPr&&typeof p.toPr=="object"?{number:p.toPr.number??null}:null,repos:Array.isArray(p.repos)&&p.repos.length?p.repos:null,status:"open",ts:it(y),box:y.boxId,agent:y.provider};S.set(p.offerId,v),A.set(p.offerId,v),e.push(v);break}case"pr.offer.answered":{let v=A.get(p.offerId);v&&(v.status=p.action||"dismiss",v.actions=Array.isArray(p.actions)?p.actions:null,v.notify=p.notify===!0,S.delete(p.offerId));break}case"box.state.changed":{let v=p.change==="model"?"model-change":p.change;if(!v)break;let E=rn(y,v);v==="model-change"&&(E.to=p.model),e.push(E);break}case"view.state.changed":{if(p.change!=="created"||p.restarted)break;e.push({id:y.id,kind:"view-card",viewId:p.viewId,name:p.name,slug:p.slug,port:p.port,origin:p.origin,ts:it(y),box:y.boxId,agent:y.provider});break}case"view.spinup.failed":{e.push({id:y.id,kind:"view-spinup-failed",name:p.name||null,reason:p.reason||"no-view",ts:it(y),box:y.boxId,agent:y.provider});break}case"mock.state.changed":{if(p.change==="picked"){e.push({id:y.id,kind:"mock-picked",mockId:p.mockId,name:p.name,group:p.group??null,version:p.version??null,siblings:p.siblings??[],ts:it(y),box:y.boxId,agent:y.provider});break}if(p.change!=="created")break;e.push({id:y.id,kind:"mock-card",mockId:p.mockId,name:p.name,group:p.group??null,ts:it(y),box:y.boxId,agent:y.provider});break}case"flow.state.changed":{if(p.change!=="created")break;e.push({id:y.id,kind:"flow-card",flowId:p.flowId,name:p.name,stepCount:p.stepCount??null,mockIds:p.mockIds??[],ts:it(y),box:y.boxId,agent:y.provider});break}case"runbook.state.changed":{if(p.change!=="created")break;e.push({id:y.id,kind:"runbook-card",runbookId:p.runbookId,name:p.name,ts:it(y),box:y.boxId,agent:y.provider});break}case"ticket.linked":{e.push({id:y.id,kind:"ticket-card",provider:p.provider||"linear",key:p.key,url:p.url,title:p.title??null,linkSource:p.linkSource??null,ts:it(y),box:y.boxId,agent:y.provider});break}case"ticket.move.offered":{if(!p.offerId)break;for(let E of C.values())E.key===p.key&&(E.status="stale",C.delete(E.offerId));let v={id:y.id,kind:"ticket-offer",offerId:p.offerId,provider:p.provider||"linear",key:p.key,url:p.url??null,phase:p.phase??null,from:p.from??null,to:p.to?.name??null,prNumber:p.prNumber??null,status:"open",ts:it(y),box:y.boxId,agent:y.provider};C.set(p.offerId,v),V.set(p.offerId,v),e.push(v);break}case"ticket.move.answered":{let v=V.get(p.offerId);v&&(v.status=p.action==="moved"?"moved":"dismiss",C.delete(p.offerId));break}case"ticket.state.changed":{for(let v of C.values())v.key===p.key&&v.to===p.to&&(v.status="stale",C.delete(v.offerId));if(p.mode==="offer")break;e.push(rn(y,"ticket-state"));break}case"ticket.commented":{e.push({id:y.id,kind:"ticket-comment",key:p.key,url:p.url??null,excerpt:p.excerpt??null,ts:it(y),box:y.boxId,agent:y.provider});break}case"ticket.flow.changed":e.push(rn(y,"ticket-flow"));break;case"ticket.create.offered":{if(!p.proposalId)break;for(let E of O.values())E.status="stale";O.clear();let v={id:y.id,kind:"ticket-create-offer",proposalId:p.proposalId,provider:p.provider||"linear",epic:p.epic??null,issues:Array.isArray(p.issues)?p.issues:[],team:p.team??null,needsTeam:!!p.needsTeam,linkCurrentTrack:!!p.linkCurrentTrack,status:"open",ts:it(y),box:y.boxId,agent:y.provider};O.set(p.proposalId,v),F.set(p.proposalId,v),e.push(v);break}case"ticket.create.answered":{let v=F.get(p.proposalId);v&&(v.status=p.action==="created"?"created":"dismiss",v.createdCount=p.createdCount??null,v.failedCount=p.failedCount??null,O.delete(p.proposalId));break}case"ticket.created":{let v={items:Array.isArray(p.items)?p.items:[],failed:Array.isArray(p.failed)?p.failed:[],team:p.team??null},E=p.proposalId?F.get(p.proposalId):null;if(E){E.created=v;break}e.push({id:y.id,kind:"ticket-created",...v,ts:it(y),box:y.boxId,agent:y.provider});break}case"error":{if(!p.kind)break;e.push(rn(y,p.kind));break}case"provider.connected":{e.push({id:y.id,kind:"event",type:"provider-connected",provider:p.provider||y.provider,ts:it(y),at:it(y),box:y.boxId,agent:y.provider});break}case"prompt.answered":{if(p.control||!p.question)break;let v=p.groupId||p.promptId;if(!v)break;let E=T.get(v);E||(E={id:y.id,kind:"prompt",groupId:v,provider:y.provider,box:y.boxId,agent:y.provider,questionTotal:p.questionTotal??1,questions:[],ts:it(y)},T.set(v,E));let rt=p.questionIndex??0,I={index:rt,header:p.header||"",question:p.question,multiSelect:!!p.multiSelect,chosen:Array.isArray(p.chosen)?p.chosen:[]},R=E.questions.findIndex(P=>P.index===rt);R>=0?E.questions[R]=I:E.questions.push(I),E.questions.sort((P,et)=>P.index-et.index),E.questionTotal=Math.max(E.questionTotal,p.questionTotal??1),new Set(E.questions.map(P=>P.index)).size>=E.questionTotal&&(e.push(E),T.delete(v));break}default:break}}return mE(e.sort((y,p)=>(y.ts??0)-(p.ts??0)))}var pE=2*6e4;function mE(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<=pE){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 Nl(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:hE(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??Po(t)).filter(o=>o.kind==="message");return Bm(n,r)}function hE(t){let e=String(t.summary||"").trim();return e&&e!==t.command&&e!==wr(t.command)?e:Je(t.command,t.description||"")}function rn(t,e){let{ts:n,change:r,...o}=t.payload||{};return{id:t.id,kind:"event",type:e,...o,at:it(t),ts:it(t)}}function wE(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 Pl=new Map;async function gE(t,e){let n=`${t.id}:${e.name}`;if(!Pl.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);Pl.set(n,o)}return Pl.get(n)}async function Tw(t,e){try{if(e?.agent!=="codex"||!e.cwd)return;let n=await Rn(e.cwd,e.pinned?e.sessionId:null);if(!n)return;let r=await gE(t,e),o=wE(await Ot(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 $(t,{type:"cost.recorded",actor:"agent",source:"transcript",boxId:e.name,provider:"codex",payload:{usd:se(c,i),model:c,tokens:i,reasoningOutput:a.last.reasoningOutput||void 0,codexTs:a.ts,ts:a.ts}}),a===s&&a.contextWindow&&await $(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 Ra from"node:fs";import Dl from"node:path";import yE from"node:os";var bE=process.env.BOSTRAT_STATE_DIR||Dl.join(process.env.HOME||yE.tmpdir(),".bostrat"),Ew=(t=process.env)=>!!t.BOSTRAT_CURSOR_USAGE_COOKIE,kE="https://cursor.com/api/dashboard/get-filtered-usage-events",Aw=100;async function xE({sinceMs:t,cookie:e,fetchImpl:n=fetch}){let r=[];for(let o=1;o<=20;o++){let s=await n(kE,{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:Aw})});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<Aw)break}return r}function SE(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 vE=5*6e4,on={at:0,rows:null,error:null},$a=()=>Dl.join(bE,"cursor-usage.json");function _w(){try{return JSON.parse(Ra.readFileSync($a(),"utf8"))}catch{return{sinceMs:0,seen:[]}}}function TE(t){try{Ra.mkdirSync(Dl.dirname($a()),{recursive:!0});let e=$a()+".tmp";Ra.writeFileSync(e,JSON.stringify(t)),Ra.renameSync(e,$a())}catch{}}var AE=5e3;async function EE({fetchImpl:t,now:e=Date.now()}={}){if(on.rows&&e-on.at<vE)return on.rows;let n=process.env.BOSTRAT_CURSOR_USAGE_COOKIE;if(!n)return null;let r=_w(),o=Math.max(r.sinceMs-6e4,e-24*36e5);try{let s=await xE({sinceMs:o,cookie:n,fetchImpl:t});on={at:e,rows:s.map(SE).filter(Boolean),error:null}}catch(s){on={at:e,rows:null,error:String(s?.message||s)}}return on.rows}var jl=()=>({enabled:Ew(),lastPollAt:on.at||null,error:on.error}),Bl=new Map;async function _E(t,e){let n=`${t.id}:${e.name}`;if(!Bl.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);Bl.set(n,o)}return Bl.get(n)}async function Iw(t,e,{fetchImpl:n}={}){try{if(e?.agent!=="cursor"||!e.cwd||!Ew())return;let r=await EE({fetchImpl:n});if(!r||!r.length)return;let o=await Km(e.cwd,e.pinned?e.sessionId:null);if(!o?.chatId)return;let s=await _E(t,e),a=_w(),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=se(u,f)),!d&&l.chargedCents!=null&&(d=l.chargedCents/100),await $(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}})}TE({sinceMs:c,seen:[...i].slice(-AE)})}catch{}}import{execFile as ME}from"node:child_process";import{promisify as CE}from"node:util";import Rw from"node:fs/promises";import $w from"node:path";import IE from"node:os";var Ow=process.env.BOSTRAT_STATE_DIR||$w.join(process.env.HOME||IE.tmpdir(),".bostrat"),RE=$w.join(Ow,"github-latency.ndjson"),$E=100,Oa=[],Ll=new Map;function Ma({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};Oa.push(l),Oa.length>$E&&Oa.shift();let u=Ll.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)),Ll.set(t,u),OE(l),l}async function OE(t){try{await Rw.mkdir(Ow,{recursive:!0}),await Rw.appendFile(RE,JSON.stringify(t)+`
|
|
147
|
+
`)}catch{}}function Mw(){let t={};for(let[e,n]of Ll)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:Oa.slice(-20)}}async function NE(t){let e=/workspaces[/\\]([0-9a-f]{8})(?:[/\\]|$)/.exec(t||"")?.[1];if(e)try{let[{stdout:n},{stdout:r}]=await Promise.all([Er("git",["-C",t,"rev-parse","HEAD"]),Er("git",["-C",t,"rev-parse","--abbrev-ref","HEAD"])]);await $({id:e,cwd:t},{type:"code.pushed",actor:"orchestrator",payload:{ref:r.trim(),sha:n.trim(),wip:!1,ts:Date.now()}})}catch{}}var Er=CE(ME),PE={timeout:3e4,maxBuffer:8*1024*1024};async function Vt(t,e,n={}){let{caller:r="prs",...o}=n,s=await ne({cwd:t});$e(r);try{let{stdout:a}=await Er("gh",e,{cwd:t,...PE,...o,env:{...process.env,...s,...o.env||{}}});return String(a||"")}catch(a){throw Ze(a)&&Qe(a),a}}var Ca=null;async function Hl(){if(Ca&&Date.now()-Ca.at<6e4)return Ca.data;let t;try{$e("auth");let e=await Er("gh",["auth","status"],{timeout:1e4});t={installed:!0,authenticated:!0,login:`${e.stdout||""}${e.stderr||""}`.match(/account\s+(\S+)/)?.[1]??null}}catch(e){t=e?.code==="ENOENT"?{installed:!1,authenticated:!1,login:null}:{installed:!0,authenticated:!1,login:null}}if(t.installed&&!t.authenticated)try{let e=await Zr();e.configured&&(t={installed:!0,authenticated:!0,login:e.username,via:"broker"})}catch{}return Ca={at:Date.now(),data:t},t}async function BE(t){try{let{stdout:e}=await Er("git",["-C",t,"config","--get","remote.origin.url"],{timeout:5e3});return/github\.com/.test(e)}catch{return!1}}function Nw(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 Fl(t){let e=(t||[]).map(Nw),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 Wl="number,title,url,headRefName,headRefOid,baseRefName,isDraft,author,reviewDecision,mergeable,mergeStateStatus,statusCheckRollup,autoMergeRequest,additions,deletions,createdAt,updatedAt",Pw=Wl.replace("statusCheckRollup,","");function ql(t){return/Resource not accessible by personal access token/.test(String(t?.stderr||t?.message||""))}async function Bw(t,e){if(!e)return null;let n=await tt(t);if(!n)return null;try{let r=await Vt(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 Dw(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 jw(t,e){let n=await Bw(t,e);return n===null?{...Fl([]),unavailable:!0}:Fl(Dw(n))}function Na(t,e=Fl(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 DE=9e4,Ar=new Map;function jE(t,e,n){let r=new Set;for(let o of e){let s=`${t}#${o.number}`;if(o.mergeable!=="UNKNOWN"&&o.mergeStateStatus!=="UNKNOWN"){Ar.delete(s);continue}r.add(s),Ar.has(s)||Ar.set(s,n),o.mergeabilityPending=n-Ar.get(s)<DE}for(let o of Ar.keys())o.startsWith(`${t}#`)&&!r.has(o)&&Ar.delete(o);return e}async function _r(t,e){let n=await Oe(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");try{let o=await Vt(n.path,["pr","view",String(r),"--json",Wl]);return Na(JSON.parse(o))}catch(o){if(!ql(o))throw o;let s=await Vt(n.path,["pr","view",String(r),"--json",Pw]),a=JSON.parse(s);return Na(a,await jw(n.path,a.headRefOid))}}var Cw=(t,e)=>t.number-e.number;async function Do(t,{caller:e="prs"}={}){try{let n=await Vt(t,["pr","list","--json",Wl,"--limit","50"],{caller:e});return JSON.parse(n||"[]").map(r=>Na(r)).sort(Cw)}catch(n){if(!ql(n))throw n;let r=await Vt(t,["pr","list","--json",Pw,"--limit","50"],{caller:e});return(await Promise.all(JSON.parse(r||"[]").map(async o=>Na(o,await jw(t,o.headRefOid))))).sort(Cw)}}var LE=3e4,FE=1500,Bo=new Map;function he(){Bo.clear()}async function UE(t,e){if(!t)return e;for(let n of e){let r=await It(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 ca(t,n.number,{headSha:n.headSha}).catch(()=>{}):n.ciFix={delegatedAt:r.ciFix.delegatedAt,trackId:r.ciFix.trackId??null}),r.reconcile&&(o(r.reconcile)?await ua(t,n.number,{headSha:n.headSha}).catch(()=>{}):n.reconcile={delegatedAt:r.reconcile.delegatedAt,trackId:r.reconcile.trackId??null})}return e}async function Lw(){let t=await Hl();if(!t.authenticated)return{auth:t,repos:[]};let e=[];for(let a of await ye())await BE(a.path)&&e.push(a);let n=Date.now(),r=Aa(n)>0,o=await Promise.all(e.map(async a=>{let i=Bo.get(a.path),c=i?.pending?FE:LE;if(i&&(r||n-i.at<c))return i.entry;try{let l=await tt(a.path).catch(()=>null),u=jE(a.path,await Do(a.path,{caller:"tab"}),n);if(await UE(l,u),i?.entry?.prs)for(let m of u)i.entry.prs.find(w=>w.number===m.number)?.checks?.pending>0&&m.checks.total>0&&m.checks.pending===0&&Ma({change:m.checks.failed>0?"checks-failed":"checks-green",repo:l||a.name,number:m.number,githubAt:m.checks.settledAt,source:"tab"});let f={name:a.name,path:a.path,prs:u},d=u.some(m=>m.mergeabilityPending||m.checks.pending>0||m.autoMergeArmed);return Bo.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:me(l)}}})),s=new Set(e.map(a=>a.path));for(let a of Bo.keys())s.has(a)||Bo.delete(a);return{auth:t,repos:o}}async function Oe(t){let e=(await ye()).find(n=>n.path===t);if(!e)throw new Error(`unknown repo: ${t}`);return e}async function Bn(t){return Xr(t)}var jo=new Set(["squash","merge","rebase"]);async function Ir(t,e,{method:n="squash"}={}){let r=await Oe(t);if(!jo.has(n))throw new Error(`invalid merge method: ${n}`);let o=Number(e);if(!Number.isInteger(o)||o<=0)throw new Error("invalid PR number");let s=["pr","merge",String(o),`--${n}`];if(n!=="rebase"&&(await ne({cwd:r.path})).GH_TOKEN){let a=await bi().catch(()=>null);a&&s.push("--body",`Merged by bostrat on behalf of @${a}`)}try{await Vt(r.path,s)}catch(a){throw new Error(me(a))}finally{he()}return{ok:!0,number:o}}async function Lo(t,e){let n=await Oe(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");try{await Vt(n.path,["api","-X","PUT",`repos/{owner}/{repo}/pulls/${r}/update-branch`])}catch(s){throw new Error(me(s))}finally{he()}let o=await tt(n.path).catch(()=>null);return o&&await wm(o,r).catch(()=>{}),{ok:!0,number:r}}async function Kl(t,e){let n=await Oe(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");try{await Vt(n.path,["pr","merge",String(r),"--disable-auto"])}catch(o){throw new Error(me(o))}finally{he()}return{ok:!0,number:r}}var HE=2500,WE=9e4,qE=t=>new Promise(e=>setTimeout(e,t));function Gl(t){return t.green&&!t.isDraft}function KE(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 Fw(t,{update:e=!0}={}){return t.isDraft?{action:"skip",reason:"draft"}:Gl(t)?{action:"merge"}:e&&t.mergeStateStatus==="BEHIND"&&t.mergeable!=="CONFLICTING"&&t.checks.failed===0?{action:"update-arm"}:{action:"arm"}}function Uw(t){return[...t.filter(e=>e.plan.action!=="merge"),...t.filter(e=>e.plan.action==="merge")]}var Ul=new Map;function Hw(){let t={};for(let[e,n]of Ul)t[e]={...n};return t}async function Ww(t,{method:e="merge",onMerged:n=null}={}){let r=await Oe(t);if(!jo.has(e))throw new Error(`invalid merge method: ${e}`);if(Ul.get(r.path)?.running)throw new Error("a merge-all is already running for this repo");let o=(await Do(r.path)).filter(Gl).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 Ul.set(r.path,s),VE(s,o,{listPrs:()=>Do(r.path),merge:a=>Ir(r.path,a,{method:e}),sleep:qE,onMerged:n}).catch(()=>{}),{ok:!0,repo:r.path,total:s.total,queue:o.map(a=>a.number)}}async function GE(t,e){let n=Date.now()+(e.settleMaxMs??WE),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(HE)}}async function VE(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 GE(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: ${me(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(!Gl(a)){t.skipped.push({number:a.number,title:a.title,reason:KE(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:me(i)})}}}finally{t.current=null,t.running=!1,t.finishedAt=Date.now(),he()}}async function Vl(t,{title:e,body:n="",base:r=null}={}){if(typeof e!="string"||!e.trim())throw new Error("title required");try{await Er("git",["-C",t,"push","-u","origin","HEAD"],{timeout:12e4,maxBuffer:4*1024*1024})}catch(l){throw new Error(`push failed: ${me(l)}`)}await NE(t);let o=n||"";if((await ne({cwd:t})).GH_TOKEN){let l=await bi().catch(()=>null);l&&(o=`${o?`${o}
|
|
148
|
+
|
|
149
|
+
`:""}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 Vt(t,s)}catch(l){throw new Error(me(l))}finally{he()}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 qw(t,e){let n=await Oe(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");let o;try{let i;try{i=await Vt(n.path,["pr","checks",String(r),"--json","name,state,link,bucket"])}catch(c){if(i=String(c?.stdout||""),!i.trim())throw new Error(me(c))}o=JSON.parse(i||"[]").filter(c=>c.bucket==="fail")}catch(i){if(!ql(i))throw i;let c=await Vt(n.path,["pr","view",String(r),"--json","headRefOid"]),l=await Bw(n.path,JSON.parse(c).headRefOid);if(l===null)throw new Error(me(i));o=Dw(l).map(Nw).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=zE(i.link),l=null;c&&!s.has(c)&&(s.add(c),l=await Vt(n.path,["run","view",c,"--log-failed"]).then(u=>JE(u)).catch(()=>null)),a.push({name:i.name,link:i.link||null,log:l})}return a}function zE(t){return String(t||"").match(/\/actions\/runs\/(\d+)/)?.[1]??null}function JE(t,{maxLines:e=400,maxBytes:n=64*1024}={}){let r=String(t||""),o=r.split(`
|
|
146
150
|
`);return o.length>e&&(r=`\u2026 (${o.length-e} earlier lines truncated)
|
|
147
151
|
`+o.slice(-e).join(`
|
|
148
152
|
`)),Buffer.byteLength(r)>n&&(r=`\u2026 (truncated)
|
|
149
153
|
`+r.slice(-n)),r}function me(t){return String(t?.stderr||t?.message||t||"").trim().split(`
|
|
150
|
-
`).find(n=>n.trim())||"gh failed"}var
|
|
154
|
+
`).find(n=>n.trim())||"gh failed"}var XE=6e3,Pa=new Map;function zl(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 Kw(t){let e=t?.workspace?.branch;if(!e)return;let n;try{n=await L(t)}catch{return}if(zl(n)==null)return;let r=(t.boxes||[]).find(c=>c.name===t.leadBox);if(await Ea(t.leadBox,r?.agent).catch(()=>!1))return;let o=Date.now();if(o-(Pa.get(t.id)||0)<XE)return;Pa.set(t.id,o);let s;try{s=await Do(t.cwd,{caller:"reconcile"})}catch{return}let a=(s||[]).find(c=>c.headRefName===e);if(!a)return;let i=await tt(t.cwd).catch(()=>null);if(i){let{link:c,created:l}=await Lt({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 So(i,a.number))){Pa.delete(t.id);return}}await $(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(()=>{}),Pa.delete(t.id)}import YE from"node:fs/promises";import Xw from"node:path";import ZE from"node:os";import{randomUUID as Yw}from"node:crypto";var QE=process.env.BOSTRAT_STATE_DIR||Xw.join(process.env.HOME||ZE.tmpdir(),".bostrat"),Zw=Xw.join(QE,"flows.json"),Gw=20,Vw=80,zw=500,Jw=80,Dn=null,Jl=null;async function sn(){return Dn||(Jl||=(async()=>{try{Dn=JSON.parse(await YE.readFile(Zw,"utf8"))}catch{Dn={flows:[]}}finally{Jl=null}return Dn.flows||=[],Dn})(),Jl)}async function Fo(){await qt(Zw,Dn)}function Xl(t,e,n={}){return $({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 Qw({trackId:t,steps:e,prevSteps:n=[]}){if(!Array.isArray(e)||e.length<1||e.length>Gw)throw new Error(`steps must be an array of 1-${Gw}`);let r=await kn({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>zw)throw new Error(`${l}: caption over ${zw} chars`);let f=c.title==null?null:String(c.title).trim()||null;if(f&&f.length>Jw)throw new Error(`${l}: title over ${Jw} chars`);let d=String(c.mock??"").trim().replace(/^mock:/,"");if(!d)throw new Error(`${l}: mock (name or id) required`);let m=r.filter(S=>S.id===d);if(m.length||(m=r.filter(S=>S.name.toLowerCase()===d.toLowerCase())),!m.length)throw new Error(`${l}: no mock matching "${d}" in this Track \u2014 see \`bostrat-mock ls\``);if(m.length>1)throw new Error(`${l}: "${d}" is ambiguous (${m.map(S=>`mock:${S.id}`).join(", ")}) \u2014 use the id`);let h=m[0],w=null;if(c.v!=null&&(w=Number(c.v),!Number.isInteger(w)||!(h.versions||[]).some(S=>S.v===w)))throw new Error(`${l}: "${h.name}" has no captured v${c.v} to pin`);let g=o.findIndex(S=>S.mockId===h.id),b=g>=0?o.splice(g,1)[0].id:Yw().slice(0,8);s.push({id:b,mockId:h.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 t_(t){if(typeof t!="string"||!t.trim())throw new Error("name (string) required");if(t.trim().length>Vw)throw new Error(`name over ${Vw} chars`);return t.trim()}async function tg({trackId:t}={}){let e=await sn();return(t?e.flows.filter(r=>r.trackId===t):e.flows).map(r=>({...r}))}async function e_(t){let n=(await sn()).flows.find(r=>r.id===t);return n?{...n}:null}async function eg(t){let e=await e_(t);if(!e)return null;let n=new Map((await kn({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=Te(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 ng({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=t_(e),i=await sn(),c=i.flows.find(m=>m.trackId===t&&m.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 Qw({trackId:t,steps:n}),f=Date.now(),d={id:Yw().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 Fo(),await Xl(d,"created"),{...d}}async function rg(t,{steps:e,note:n}={}){let o=(await sn()).flows.find(i=>i.id===t);if(!o)throw new Error("flow not found");let{steps:s,resolved:a}=await Qw({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 Fo(),await Xl(o,"updated",{version:o.version,...n?{note:String(n).slice(0,200)}:{}}),{...o}}async function og(t){let e=await sn(),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 Fo(),await Xl(r,"deleted"),{ok:!0,id:t,name:r.name}}async function sg(t){return(await sn()).flows.filter(n=>n.steps.some(r=>r.mockId===t)).map(n=>({...n}))}async function ag(t){let n=(await sn()).flows.filter(r=>r.trackId===t.id);for(let r of n)r.trackDeletedAt=Date.now(),r.trackName=t.name||t.id;return n.length&&await Fo(),n.length}async function ig(t){let e=await sn(),n=e.flows.filter(r=>r.trackId===t);return n.length&&(e.flows=e.flows.filter(r=>r.trackId!==t),await Fo()),n.length}import Yl from"node:fs/promises";import Uo from"node:path";import{randomUUID as n_}from"node:crypto";var lg=".bostrat/uploads",Zl=t=>Uo.join(t,lg),cg=Number(process.env.BOSTRAT_MAX_UPLOAD_BYTES)||24*1024*1024,r_=new Set(["png","jpg","jpeg","gif","webp","svg","bmp","tif","tiff","heic","heif","avif","ico"]),o_=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 Ql(t){let e=String(t||"").toLowerCase().replace(/^\./,"");return r_.has(e)?"image":o_.has(e)?"doc":null}function s_(t){let r=Uo.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 ug(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>cg)throw new Error(`file too large \u2014 max ${Math.floor(cg/(1024*1024))} MB`);let r=s_(e),o=Uo.extname(r).slice(1),s=Ql(o);if(!s)throw new Error(`unsupported file type "${o||"unknown"}" \u2014 attach an image or a document`);let a=n_().slice(0,8),i=Zl(t);await Yl.mkdir(i,{recursive:!0});let c=`${a}-${r}`;return await Yl.writeFile(Uo.join(i,c),n,{mode:384}),await Yl.chmod(Uo.join(i,c),384).catch(()=>{}),{id:a,name:r,rel:`${lg}/${c}`,kind:s,size:n.length}}function dg(t){let e=t.map(r=>`\`${r}\``).join(", ");return`[Attached \u2014 please read ${t.length===1?"this file":`these ${t.length} files`}: ${e}]`}var a_=/\[Attached — please read (?:this file|these \d+ files): [^\]]*\]$/;function tu(t){return t.role==="user"&&a_.test((t.text||"").trim())}function Rr(t,e,n=Date.now()){return e?t.idleSince==null?(t.idleSince=n,!1):n-t.idleSince>=6500:(t.idleSince=null,!1)}var l_=c_(i_),Me=process.env.BOSTRAT_STATE_DIR||H.join(process.env.HOME||"/tmp",".bostrat"),kg=H.join(Me,"tracks.json"),u_=H.join(Me,"events.jsonl"),iu=H.join(Me,"workspaces"),d_=process.env.BOSTRAT_DEFAULT_REPO||null,ja={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"}},La=Object.keys(ja);function f_(t,{skipPermissions:e=!0}={}){let n=ja[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 xg(t,e,{skipPermissions:n=!0}={}){let r=ja[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 Jo(t){let e=ja[t];if(e)return process.env[e.envVar]||e.base}var jn={at:0,value:null};async function lu({maxAgeMs:t=6e4}={}){if(jn.value&&Date.now()-jn.at<t)return jn.value;let e=await Promise.all(La.map(async n=>[n,await Ts(Jo(n))]));return jn.value=Object.fromEntries(e),jn.at=Date.now(),jn.value}async function Fa(t,e=!0){let n=f_(t,{skipPermissions:e});if(!await Ts(n)){jn.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.":Ni[r]?`Install it on this node: ${Ni[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 Ua=".bostrat/handoff/latest.md";function p_(t,e=Ua){return`${t.trim()}
|
|
151
155
|
|
|
152
|
-
(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
|
|
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 m_=/\(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 fg(t){return t.role==="user"&&m_.test((t.text||"").trim())}var Ba=".bostrat/handoff/subtask-result.md",Wo=".bostrat/handoff/lead-context.md",h_=(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 ${Wo} \u2014 read that first for context. Your scoped task:
|
|
153
157
|
|
|
154
158
|
${t}
|
|
155
159
|
|
|
156
|
-
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 ${
|
|
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 ${Ba} (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 ${Wo} \u2014 read that first for context. Your scoped task:
|
|
157
161
|
|
|
158
162
|
${t}
|
|
159
163
|
|
|
160
|
-
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 ${
|
|
161
|
-
`;return Jl=Jl.then(()=>U.mkdir(Oe,{recursive:!0})).then(()=>U.appendFile(R_,n)).catch(()=>{}),Jl}var at=(t,e,n={})=>{let r={at:Date.now(),type:e,...n};return t.events.push(r),B_(t.id,r),Oh(t,r),r};qd(async({sessionName:t,from:e,to:n,version:r,status:o,error:s})=>{let i=(await q()).tracks.find(c=>c.leadBox===t||(c.boxes||[]).some(l=>l.name===t));i&&(at(i,"codex-update",{box:t,from:e,to:r||n,status:o,...s?{error:s}:{}}),await V().catch(()=>{}))});var Xl=(t,e,n)=>e!=null&&n.some(r=>t===r.box&&e>=r.from&&e<=r.to),D_=new Set(["stuck","wrong-approach","too-slow","want-different-model","context-refresh","cost","parallelize","other"]);function Qw(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=>D_.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 tg(t,e,n,{kind:r}={}){let o=await Y(e);if(!o)throw new Error(`box "${e}" is not running`);let s=Uo(o.agent);await Ue({cwd:o.cwd,sessionId:o.sessionId,sessionName:e,agent:o.agent,agentCmd:s}),await vt(e,n,{...Se(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 q(),i=nt(a,t);if(!i)return;let c=i.boxes.find(l=>l.name===e);at(i,"orchestration-prompt",{box:e,agent:c?.agent||o.agent,boxRole:c?.role,prompt:n,kind:r||"orchestration"}),await V()}var Bn=new Set(["merged","conflict","canceled","failed","handed-off"]),qw=new Map;async function j_(t){let e=H.join($s(t),`${t.id}.ndjson`),n;try{n=await U.stat(e)}catch{return null}let r=qw.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 qw.set(t.id,{mtimeMs:n.mtimeMs,at:o}),o}async function L_(t,e){let n=e.find(r=>r.name===t.leadBox);return n?.state!=="live"?{working:!1,prompted:!1}:Sl(n.name,n.agent)}async function on(t,e=null){let n=e??new Map((await mn()).map(i=>[i.name,i])),r=t.boxes.map(i=>{let c=n.get(i.name),l=Bn.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([j_(t),L_(t,r)]);return{...t,boxes:r,lastResponseAt:o,working:s,prompted:a}}var F_=5e3;function U_(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-F_,d=r[u+1]?.createdAt??1/0,m=o.find(h=>!s.has(h.id)&&h.birthMs>=f&&h.birthMs<d);m&&(s.add(m.id),c.push({box:l.name,sessionId:m.id}))}return c}async function eg(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=jd(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_(u,a,d=>i.has(d));for(let d of f){let m=u.find(h=>h.name===d.box);m&&(m.sessionId=d.sessionId),await Id(d.box,d.sessionId),n=!0}}return n}async function ct(){let t=await q();await eg(t)&&await V();let e=new Map((await mn()).map(n=>[n.name,n]));return Promise.all(t.tracks.map(n=>on(n,e)))}async function ng(t,e){let n=await q();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 rg(t){let e=Hr(t);if(!e)return null;let n=await q();for(let r of n.tracks||[])for(let o of r.boxes||[])if(o.hookTokenHash&&Xu(o.hookTokenHash,e))return{track:r,box:o};return null}async function Q(t){let e=await q(),n=nt(e,t);return n?(await eg(e)&&await V(),on(n)):null}var H_={claude:[/^\[Request interrupted by user(?: for tool use)?\]$/i],codex:[/^\[?Request interrupted/i,/^Conversation interrupted/i],cursor:[/^\[?Request interrupted/i]};function Kw(t){if(t.role!=="user")return!1;let e=(t.text||"").trim();return(H_[t.agent]||[]).some(n=>n.test(e))}var W_=[/^\/model(?:\s|$)/i,/<command-(?:name|message|args)>[^<]*\bmodel\b/i,/^Model changed to\b/i];function Gw(t){let e=(t.text||"").trim();return e?W_.some(n=>n.test(e)):!1}var q_=10*6e4;async function ru(t,e={}){let n=await Q(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=Ho(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=nc(n.id),c=!!(i&&!i.finishedAt),l=c&&i.startedAt&&i.box?[{box:i.box,from:i.startedAt,to:Date.now()+1e12}]:[],u=[...up(n),...l],d={windows:[..._f(n),...Ef(n,Cs),...a,...u]},h=(await Promise.all(n.boxes.map(k=>Xc(k,e)))).flat(),w=h.map(k=>{let K={kind:"message",...k};if(K.role==="assistant"&&(K.text||"").trim()&&K.tools?.length){let Ln=vo(K.agent).cliTools,os=K.tools.filter(Fn=>!Ln.has(Fn));return{...K,tools:os.length?os:void 0}}return K}).map(k=>{if(k.role!=="user")return k;let K=Sa(k.text);return K?K.kind==="suppress"?null:{...k,note:K,text:mw(K)}:k}).filter(Boolean).filter(k=>!(k.role==="user"&&r.has((k.text||"").trim()))).filter(k=>!Kw(k)).filter(k=>!Gw(k)).filter(k=>!Ww(k)).filter(k=>!zl(k)).filter(k=>k.role!=="assistant"||!!(k.text||"").trim()||!!k.images?.length).filter(k=>!If(k,d)),g=w.filter(k=>k.failed&&k.text===qc);if(g.length){let k=new Map;for(let K of g){if(!k.has(K.box)){let Fn=[...(await gt(K.box,{lines:Tt}).catch(()=>"")).matchAll(/^\s*■\s+(.+?)\s*$/gm)].map(si=>si[1]);k.set(K.box,Fn.length?Fn[Fn.length-1]:null)}let Ln=k.get(K.box);Ln&&(K.text=`\u25A0 ${Ln}`)}}let b=await Promise.all(n.boxes.map(k=>Vm(k,e)));await fw(n,b.flat()),await uw(n,w),await Mh(n),await gp(n,w).catch(()=>{});for(let k of n.boxes)k.agent==="codex"&&k.state==="live"&&await gw(n,k),k.agent==="cursor"&&k.state==="live"&&await xw(n,k);let v=await Promise.all(n.boxes.map(k=>zm(k,e)));await dw(n,v.flat().filter(k=>!Xl(k.box,k.ts,u)));let A=await L(n),M=c&&i.viewId?i.viewId:null,z=Hh(A),$=Io(A).filter(k=>!(k.kind==="message"&&(Kw(k)||Gw(k)||Ww(k)||zl(k)))&&!(k.kind==="view-card"&&M&&k.viewId===M)),F=Wh($,z),T=$.filter(k=>!(k.kind==="message"&&k.role==="assistant"&&!F.has(k)&&Xl(k.box,k.ts,z))),C=Rl(A,T).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=>!Xl(k.anchorBox,k.anchorTs??k.ts,u)),W=[...T,...C].filter(k=>k.ts!=null).sort((k,K)=>k.ts-K.ts),ot=K_(n),y=ot.size?W.filter(k=>k.kind==="event"||!ot.has(k.box??k.anchorBox)):W,p=n.boxes.find(k=>k.name===n.leadBox)||null,S=p?await Qr(p.cwd,p.sessionId,{fallbackNewest:!1}):null;if(!S&&p){let k=[...A].reverse().find(K=>K.type==="context.usage"&&K.boxId===p.name);k?.payload?.window&&(S={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].reverse().find(k=>k.role==="assistant"&&k.model),rt=_?.model||null,I=[...n.events||[]].reverse().find(k=>k.type==="model-change"),R=S?.model||rt;I&&(!_?.ts||I.at>=_.ts)&&(R=I.to);let E=p?await gt(p.name,{lines:Tt}).catch(()=>""):"",P=p&&wr(p.agent,E)?await je(p.name).catch(()=>E):E,et=p?Mn(p,P):null;!et&&p&&!Fe(p.agent,E)&&!zw(n.id).length&&(et=il(y,{leadBoxId:p.name,provider:p.agent,isAnswered:k=>A.some(K=>K.type==="prompt.answered"&&K.payload?.promptId===k)})),et&&Ch(n,p,et);let wt=y.filter(k=>!(k.kind==="message"&&k.role==="assistant"&&k.ask&&!String(k.text||"").trim()&&!k.images?.length)),bt=!!p&&!et&&Fe(p.agent,E);p&&!p.readyAt&&(bt||et||E&&As(p.agent,E))&&(p.readyAt=Date.now(),await V());let Rt=!!p&&!p.readyAt&&Date.now()-(n.createdAt||0)<q_;if(n.kind==="setup"&&n.setup?.provider&&!n.setup.connectedAt&&p&&As(p.agent,E)&&!et){let k=await q(),K=nt(k,n.id);K?.setup&&!K.setup.connectedAt&&(K.setup.connectedAt=Date.now(),n.setup.connectedAt=K.setup.connectedAt,await O(K,{type:"provider.connected",actor:"system",source:"pane",boxId:p.name,provider:p.agent,payload:{provider:p.agent,ts:K.setup.connectedAt}}).catch(()=>{}),await V())}let Ht=s?{running:!0,step:o.step,toAgent:o.toAgent||null}:null,zt=null;if(c){let k=Date.now()-i.startedAt;if(i.viewId&&!bt){let K=Us(n.id);K&&(at(n,"view-spinup",{box:i.box,startedAt:i.startedAt,endedAt:K.finishedAt}),await V())}else if(et||k>cp&&!bt&&!i.viewId||k>ec){let K=et?"needs-input":k>ec?"timeout":"no-view";Us(n.id,{error:K})&&await O(n,{type:"view.spinup.failed",actor:"orchestrator",source:"orchestrator",boxId:i.box,provider:p?.agent||null,payload:{ts:i.startedAt,name:i.name,reason:K}}).catch(()=>{})}else zt={running:!0,step:i.step,name:i.name}}return{trackId:n.id,leadBox:n.leadBox,items:wt,context:S?{pct:S.pct,occupancy:S.occupancy,window:S.window,windowKnown:S.windowKnown,model:S.model}:null,models:{current:R,options:_h(p?.agent)},pendingPrompt:et,working:bt,leadStarting:Rt,handoff:Ht,view:zt,queued:zw(n.id).filter(k=>k.kind!=="ship-notify"&&k.kind!=="prompt-answer")}}function K_(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 og(t,e){let n=await Q(t);if(!n)return null;let r=$r(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=Io(s).filter(f=>f.box===o&&!(f.kind==="message"&&f.role==="assistant"&&f.ask&&!String(f.text||"").trim()&&!f.images?.length)),i=Rl(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 gt(o,{lines:Tt}).catch(()=>"");u=Fe(l.agent||r.agent,f)}return{items:c,working:u,box:o,agent:r.agent,task:r.task}}async function ou(t,{promptId:e,answers:n,text:r}={}){let o=nt(await q(),t);if(!o)throw new Error(`track "${t}" not found`);let s=o.boxes.find(d=>d.name===o.leadBox);if(!s)throw new Error(`track "${t}" has no lead box`);if(!await Y(o.leadBox))throw new Error(`lead box "${o.leadBox}" is not running`);let i=await rl(s);if(i||(i=await G_(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 vt(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 xt(o.leadBox,["Escape"],{kind:"answer-prompt"}),c={mode:"keys",value:"Escape",count:1};else if(i.answerMode==="say"){let d=fh(i,n||[],typeof r=="string"?r:"");if(!d)throw new Error("no answer selected \u2014 pick an option or reply in chat");await Qt(o.id,d,{kind:"prompt-answer"}),c={mode:"say",value:d,count:1}}else if(i.provider==="claude"||i.answerMode==="keys"){let d=sh(i,n||[]);await xt(o.leadBox,d,{kind:"answer-prompt"}),c={mode:"keys",value:d.join(" "),count:d.length}}else{let d=Yd(i,n||[]);await vt(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 m=0;m<d.length;m++){let h=d[m],w=(Array.isArray(n?.[m])?n[m]:[]).map(g=>h.options?.[g]).filter(Boolean).map(g=>({label:g.label,description:g.description||""}));kl(o,s,i,{...c,groupId:i.groupId||i.promptId,questionIndex:m,questionTotal:i.questionTotal??d.length,header:h.header||"",question:h.question||"",multiSelect:!!h.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(()=>{}),kl(o,s,i,c),{ok:!0,promptId:i.promptId,sent:c.count}}async function G_(t,e){if(!e)return null;let n=await L(t).catch(()=>[]);return il(Io(n),{leadBoxId:e.name,provider:e.agent,isAnswered:r=>n.some(o=>o.type==="prompt.answered"&&o.payload?.promptId===r)})}async function sg(t,{promptId:e,questionIndex:n}={}){let r=nt(await q(),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 Y(r.leadBox))throw new Error(`lead box "${r.leadBox}" is not running`);let a=await rl(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=ah(a,n);return c.length?(await xt(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 z_=1500,Bo=new Map;function su(t){let e=Bo.get(t);return e||(e={items:[],timer:null,flushing:!1,idleSince:null},Bo.set(t,e)),e}function zw(t){return(Bo.get(t)?.items??[]).map(e=>({...e}))}function Da(t){let e=Bo.get(t);if(!e)return[];let n=e.items.splice(0);return e.idleSince=null,e.timer&&(clearInterval(e.timer),e.timer=null),n}function ag(t){let e=su(t);e.timer||(e.timer=setInterval(()=>V_(t).catch(()=>{}),z_))}async function V_(t){let e=Bo.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=nt(await q(),t);if(!n||n.archivedAt){Da(t);return}let r=Ho(t);if(r&&!r.finishedAt&&r.step!=="done"&&!r.error){Tr(e,!1);return}let o=await Y(n.leadBox);if(!o){Tr(e,!1);return}let s=n.boxes.find(c=>c.name===n.leadBox),a=await gt(n.leadBox,{lines:Tt}).catch(()=>"");if(Ts(s?.agent,a)){Tr(e,!1);return}if(wr(s?.agent,a)){let c=await je(n.leadBox).catch(()=>a);if(Mn(s,c)){Tr(e,!1);return}}if(!Tr(e,!0))return;let i=e.items.shift();e.idleSince=null,await ig(n,s,o,i.text,i.kind??null,i.attachments??null),await V()}finally{e.flushing=!1}}}async function ig(t,e,n,r,o=null,s=null){let a=r,i=!1;t.pendingHandoffHydrate&&(a=M_(r,t.pendingHandoffHydrate.artifact||Ba),delete t.pendingHandoffHydrate,i=!0);let c=Array.isArray(s)?s.filter(u=>u&&u.rel):[];if(c.length){let u=Hw(c.map(f=>f.rel));a=a.trim()?`${a.trim()}
|
|
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 ${Ba} 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.`,w_=(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."),we=null,eu=null;async function q(){return we||(eu||=(async()=>{try{we=JSON.parse(await U.readFile(kg,"utf8"))}catch{we={tracks:[]}}finally{eu=null}return we})(),eu)}async function z(){await qt(kg,we)}var nt=(t,e)=>t.tracks.find(n=>n.id===e)||null,nu=Promise.resolve();function g_(t,e){let n=JSON.stringify({trackId:t,...e})+`
|
|
165
|
+
`;return nu=nu.then(()=>U.mkdir(Me,{recursive:!0})).then(()=>U.appendFile(u_,n)).catch(()=>{}),nu}var ct=(t,e,n={})=>{let r={at:Date.now(),type:e,...n};return t.events.push(r),g_(t.id,r),jh(t,r),r};Xd(async({sessionName:t,from:e,to:n,version:r,status:o,error:s})=>{let i=(await q()).tracks.find(c=>c.leadBox===t||(c.boxes||[]).some(l=>l.name===t));i&&(ct(i,"codex-update",{box:t,from:e,to:r||n,status:o,...s?{error:s}:{}}),await z().catch(()=>{}))});var ru=(t,e,n)=>e!=null&&n.some(r=>t===r.box&&e>=r.from&&e<=r.to),y_=new Set(["stuck","wrong-approach","too-slow","want-different-model","context-refresh","cost","parallelize","other"]);function Sg(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=>y_.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 vg(t,e,n,{kind:r}={}){let o=await Y(e);if(!o)throw new Error(`box "${e}" is not running`);let s=Jo(o.agent);await He({cwd:o.cwd,sessionId:o.sessionId,sessionName:e,agent:o.agent,agentCmd:s}),await vt(e,n,{...Se(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 q(),i=nt(a,t);if(!i)return;let c=i.boxes.find(l=>l.name===e);ct(i,"orchestration-prompt",{box:e,agent:c?.agent||o.agent,boxRole:c?.role,prompt:n,kind:r||"orchestration"}),await z()}var Ln=new Set(["merged","conflict","canceled","failed","handed-off"]),pg=new Map;async function b_(t){let e=H.join(Ds(t),`${t.id}.ndjson`),n;try{n=await U.stat(e)}catch{return null}let r=pg.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 pg.set(t.id,{mtimeMs:n.mtimeMs,at:o}),o}async function k_(t,e){let n=e.find(r=>r.name===t.leadBox);return n?.state!=="live"?{working:!1,prompted:!1}:_l(n.name,n.agent)}async function an(t,e=null){let n=e??new Map((await wn()).map(i=>[i.name,i])),r=t.boxes.map(i=>{let c=n.get(i.name),l=Ln.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([b_(t),k_(t,r)]);return{...t,boxes:r,lastResponseAt:o,working:s,prompted:a}}var x_=5e3;function S_(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-x_,d=r[u+1]?.createdAt??1/0,m=o.find(h=>!s.has(h.id)&&h.birthMs>=f&&h.birthMs<d);m&&(s.add(m.id),c.push({box:l.name,sessionId:m.id}))}return c}async function Tg(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=qd(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=S_(u,a,d=>i.has(d));for(let d of f){let m=u.find(h=>h.name===d.box);m&&(m.sessionId=d.sessionId),await Nd(d.box,d.sessionId),n=!0}}return n}async function ot(){let t=await q();await Tg(t)&&await z();let e=new Map((await wn()).map(n=>[n.name,n]));return Promise.all(t.tracks.map(n=>an(n,e)))}async function Ag(t,e){let n=await q();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 Eg(t){let e=zr(t);if(!e)return null;let n=await q();for(let r of n.tracks||[])for(let o of r.boxes||[])if(o.hookTokenHash&&nd(o.hookTokenHash,e))return{track:r,box:o};return null}async function Q(t){let e=await q(),n=nt(e,t);return n?(await Tg(e)&&await z(),an(n)):null}var v_={claude:[/^\[Request interrupted by user(?: for tool use)?\]$/i],codex:[/^\[?Request interrupted/i,/^Conversation interrupted/i],cursor:[/^\[?Request interrupted/i]};function mg(t){if(t.role!=="user")return!1;let e=(t.text||"").trim();return(v_[t.agent]||[]).some(n=>n.test(e))}var T_=[/^\/model(?:\s|$)/i,/<command-(?:name|message|args)>[^<]*\bmodel\b/i,/^Model changed to\b/i];function hg(t){let e=(t.text||"").trim();return e?T_.some(n=>n.test(e)):!1}var A_=10*6e4;async function uu(t,e={}){let n=await Q(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=Xo(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=cc(n.id),c=!!(i&&!i.finishedAt),l=c&&i.startedAt&&i.box?[{box:i.box,from:i.startedAt,to:Date.now()+1e12}]:[],u=[...gp(n),...l],d={windows:[...Cf(n),...Nf(n,Fs),...a,...u]},h=(await Promise.all(n.boxes.map(k=>nl(k,e)))).flat(),w=h.map(k=>{let K={kind:"message",...k};if(K.role==="assistant"&&(K.text||"").trim()&&K.tools?.length){let Hn=Ro(K.agent).cliTools,ds=K.tools.filter(Wn=>!Hn.has(Wn));return{...K,tools:ds.length?ds:void 0}}return K}).map(k=>{if(k.role!=="user")return k;let K=Ia(k.text);return K?K.kind==="suppress"?null:{...k,note:K,text:xw(K)}:k}).filter(Boolean).filter(k=>!(k.role==="user"&&r.has((k.text||"").trim()))).filter(k=>!mg(k)).filter(k=>!hg(k)).filter(k=>!fg(k)).filter(k=>!tu(k)).filter(k=>k.role!=="assistant"||!!(k.text||"").trim()||!!k.images?.length).filter(k=>!Pf(k,d)),g=w.filter(k=>k.failed&&k.text===Xc);if(g.length){let k=new Map;for(let K of g){if(!k.has(K.box)){let Wn=[...(await gt(K.box,{lines:Tt}).catch(()=>"")).matchAll(/^\s*■\s+(.+?)\s*$/gm)].map(fi=>fi[1]);k.set(K.box,Wn.length?Wn[Wn.length-1]:null)}let Hn=k.get(K.box);Hn&&(K.text=`\u25A0 ${Hn}`)}}let b=await Promise.all(n.boxes.map(k=>eh(k,e)));await bw(n,b.flat()),await gw(n,w),await Lh(n),await Tp(n,w).catch(()=>{});for(let k of n.boxes)k.agent==="codex"&&k.state==="live"&&await Tw(n,k),k.agent==="cursor"&&k.state==="live"&&await Iw(n,k);let S=await Promise.all(n.boxes.map(k=>th(k,e)));await yw(n,S.flat().filter(k=>!ru(k.box,k.ts,u)));let A=await L(n),C=c&&i.viewId?i.viewId:null,V=Jh(A),O=Po(A).filter(k=>!(k.kind==="message"&&(mg(k)||hg(k)||fg(k)||tu(k)))&&!(k.kind==="view-card"&&C&&k.viewId===C)),F=Xh(O,V),T=O.filter(k=>!(k.kind==="message"&&k.role==="assistant"&&!F.has(k)&&ru(k.box,k.ts,V))),M=Nl(A,T).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=>!ru(k.anchorBox,k.anchorTs??k.ts,u)),W=[...T,...M].filter(k=>k.ts!=null).sort((k,K)=>k.ts-K.ts),st=E_(n),y=st.size?W.filter(k=>k.kind==="event"||!st.has(k.box??k.anchorBox)):W,p=n.boxes.find(k=>k.name===n.leadBox)||null,v=p?await ao(p.cwd,p.sessionId,{fallbackNewest:!1}):null;if(!v&&p){let k=[...A].reverse().find(K=>K.type==="context.usage"&&K.boxId===p.name);k?.payload?.window&&(v={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 E=[...h].reverse().find(k=>k.role==="assistant"&&k.model),rt=E?.model||null,I=[...n.events||[]].reverse().find(k=>k.type==="model-change"),R=v?.model||rt;I&&(!E?.ts||I.at>=E.ts)&&(R=I.to);let _=p?await gt(p.name,{lines:Tt}).catch(()=>""):"",P=p&&kr(p.agent,_)?await Le(p.name).catch(()=>_):_,et=p?Nn(p,P):null;!et&&p&&!Ue(p.agent,_)&&!wg(n.id).length&&(et=pl(y,{leadBoxId:p.name,provider:p.agent,isAnswered:k=>A.some(K=>K.type==="prompt.answered"&&K.payload?.promptId===k)})),et&&Fh(n,p,et);let wt=y.filter(k=>!(k.kind==="message"&&k.role==="assistant"&&k.ask&&!String(k.text||"").trim()&&!k.images?.length)),bt=!!p&&!et&&Ue(p.agent,_);p&&!p.readyAt&&(bt||et||_&&Ms(p.agent,_))&&(p.readyAt=Date.now(),await z());let Rt=!!p&&!p.readyAt&&Date.now()-(n.createdAt||0)<A_;if(n.kind==="setup"&&n.setup?.provider&&!n.setup.connectedAt&&p&&Ms(p.agent,_)&&!et){let k=await q(),K=nt(k,n.id);K?.setup&&!K.setup.connectedAt&&(K.setup.connectedAt=Date.now(),n.setup.connectedAt=K.setup.connectedAt,await $(K,{type:"provider.connected",actor:"system",source:"pane",boxId:p.name,provider:p.agent,payload:{provider:p.agent,ts:K.setup.connectedAt}}).catch(()=>{}),await z())}let Ht=s?{running:!0,step:o.step,toAgent:o.toAgent||null}:null,zt=null;if(c){let k=Date.now()-i.startedAt;if(i.viewId&&!bt){let K=Vs(n.id);K&&(ct(n,"view-spinup",{box:i.box,startedAt:i.startedAt,endedAt:K.finishedAt}),await z())}else if(et||k>hp&&!bt&&!i.viewId||k>ic){let K=et?"needs-input":k>ic?"timeout":"no-view";Vs(n.id,{error:K})&&await $(n,{type:"view.spinup.failed",actor:"orchestrator",source:"orchestrator",boxId:i.box,provider:p?.agent||null,payload:{ts:i.startedAt,name:i.name,reason:K}}).catch(()=>{})}else zt={running:!0,step:i.step,name:i.name}}return{trackId:n.id,leadBox:n.leadBox,items:wt,context:v?{pct:v.pct,occupancy:v.occupancy,window:v.window,windowKnown:v.windowKnown,model:v.model}:null,models:{current:R,options:Ch(p?.agent)},pendingPrompt:et,working:bt,leadStarting:Rt,handoff:Ht,view:zt,queued:wg(n.id).filter(k=>k.kind!=="ship-notify"&&k.kind!=="prompt-answer")}}function E_(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 _g(t,e){let n=await Q(t);if(!n)return null;let r=Pr(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=Po(s).filter(f=>f.box===o&&!(f.kind==="message"&&f.role==="assistant"&&f.ask&&!String(f.text||"").trim()&&!f.images?.length)),i=Nl(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 gt(o,{lines:Tt}).catch(()=>"");u=Ue(l.agent||r.agent,f)}return{items:c,working:u,box:o,agent:r.agent,task:r.task}}async function du(t,{promptId:e,answers:n,text:r}={}){let o=nt(await q(),t);if(!o)throw new Error(`track "${t}" not found`);let s=o.boxes.find(d=>d.name===o.leadBox);if(!s)throw new Error(`track "${t}" has no lead box`);if(!await Y(o.leadBox))throw new Error(`lead box "${o.leadBox}" is not running`);let i=await ll(s);if(i||(i=await __(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 vt(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 xt(o.leadBox,["Escape"],{kind:"answer-prompt"}),c={mode:"keys",value:"Escape",count:1};else if(i.answerMode==="say"){let d=bh(i,n||[],typeof r=="string"?r:"");if(!d)throw new Error("no answer selected \u2014 pick an option or reply in chat");await te(o.id,d,{kind:"prompt-answer"}),c={mode:"say",value:d,count:1}}else if(i.provider==="claude"||i.answerMode==="keys"){let d=fh(i,n||[]);await xt(o.leadBox,d,{kind:"answer-prompt"}),c={mode:"keys",value:d.join(" "),count:d.length}}else{let d=rf(i,n||[]);await vt(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 m=0;m<d.length;m++){let h=d[m],w=(Array.isArray(n?.[m])?n[m]:[]).map(g=>h.options?.[g]).filter(Boolean).map(g=>({label:g.label,description:g.description||""}));Al(o,s,i,{...c,groupId:i.groupId||i.promptId,questionIndex:m,questionTotal:i.questionTotal??d.length,header:h.header||"",question:h.question||"",multiSelect:!!h.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(()=>{}),Al(o,s,i,c),{ok:!0,promptId:i.promptId,sent:c.count}}async function __(t,e){if(!e)return null;let n=await L(t).catch(()=>[]);return pl(Po(n),{leadBoxId:e.name,provider:e.agent,isAnswered:r=>n.some(o=>o.type==="prompt.answered"&&o.payload?.promptId===r)})}async function Ig(t,{promptId:e,questionIndex:n}={}){let r=nt(await q(),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 Y(r.leadBox))throw new Error(`lead box "${r.leadBox}" is not running`);let a=await ll(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=ph(a,n);return c.length?(await xt(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 I_=1500,qo=new Map;function fu(t){let e=qo.get(t);return e||(e={items:[],timer:null,flushing:!1,idleSince:null},qo.set(t,e)),e}function wg(t){return(qo.get(t)?.items??[]).map(e=>({...e}))}function Ha(t){let e=qo.get(t);if(!e)return[];let n=e.items.splice(0);return e.idleSince=null,e.timer&&(clearInterval(e.timer),e.timer=null),n}function Rg(t){let e=fu(t);e.timer||(e.timer=setInterval(()=>R_(t).catch(()=>{}),I_))}async function R_(t){let e=qo.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=nt(await q(),t);if(!n||n.archivedAt){Ha(t);return}let r=Xo(t);if(r&&!r.finishedAt&&r.step!=="done"&&!r.error){Rr(e,!1);return}let o=await Y(n.leadBox);if(!o){Rr(e,!1);return}let s=n.boxes.find(c=>c.name===n.leadBox),a=await gt(n.leadBox,{lines:Tt}).catch(()=>"");if(Os(s?.agent,a)){Rr(e,!1);return}if(kr(s?.agent,a)){let c=await Le(n.leadBox).catch(()=>a);if(Nn(s,c)){Rr(e,!1);return}}if(!Rr(e,!0))return;let i=e.items.shift();e.idleSince=null,await $g(n,s,o,i.text,i.kind??null,i.attachments??null),await z()}finally{e.flushing=!1}}}async function $g(t,e,n,r,o=null,s=null){let a=r,i=!1;t.pendingHandoffHydrate&&(a=p_(r,t.pendingHandoffHydrate.artifact||Ua),delete t.pendingHandoffHydrate,i=!0);let c=Array.isArray(s)?s.filter(u=>u&&u.rel):[];if(c.length){let u=dg(c.map(f=>f.rel));a=a.trim()?`${a.trim()}
|
|
162
166
|
|
|
163
|
-
${u}`:u}let l=
|
|
167
|
+
${u}`:u}let l=ct(t,"said",{box:t.leadBox,agent:e?.agent,boxRole:e?.role,text:r,...o?{kind:o}:{},...c.length?{attachments:c}:{}});delete t.draft;try{await He({cwd:n.cwd,sessionId:n.sessionId,sessionName:t.leadBox,agent:n.agent,agentCmd:Jo(n.agent)}),await vt(t.leadBox,a,{...Se(e?.agent),kind:i?"lead-handoff-first-prompt":"say"})}catch(u){throw ct(t,"unsaid",{box:t.leadBox,agent:e?.agent,boxRole:e?.role,saidTs:l.at,text:r}),u}await F_(t,e,a)||(await Cg(t.leadBox,e?.agent,a.length+8).catch(()=>{}),ct(t,"unsaid",{box:t.leadBox,agent:e?.agent,boxRole:e?.role,saidTs:l.at,text:r,reason:"unsubmitted"}),o||(t.draft={text:r,updatedAt:Date.now()})),i&&await G({flow:"lead-handoff",phase:"first-prompt",trackId:t.id,sessionName:t.leadBox,agent:e?.agent,userTextLen:r.length})}function Og(t,{actions:e}={}){fu(t).items.push({id:zo(),text:Yh(e),ts:Date.now(),kind:"ship-notify"}),Rg(t)}async function te(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"||!xr(e))&&!o.length)throw new Error("text (string) or an attachment required");let s=nt(await q(),t);if(!s)throw new Error(`track "${t}" not found`);if(s.archivedAt)throw new Error(`track "${t}" is archived \u2014 restore it before driving it`);let a=await Y(s.leadBox);if(!a&&await wu(s)&&(await z(),a=await Y(s.leadBox)),!a)throw new Error(`lead box "${s.leadBox}" is not running`);let i=s.boxes.find(d=>d.name===s.leadBox),c=xr(e),l=fu(t),u=await gt(s.leadBox,{lines:Tt}).catch(()=>""),f=l.items.length>0||Os(i?.agent,u);if(!f&&kr(i?.agent,u)){let d=await Le(s.leadBox).catch(()=>u);f=!!Nn(i,d)}if(f){let d={id:zo(),text:c,ts:Date.now(),...n?{kind:n}:{},...o.length?{attachments:o}:{}};return l.items.push(d),Rg(t),delete s.draft,await z(),{ok:!0,leadBox:s.leadBox,queued:!0,id:d.id,position:l.items.length}}return await $g(s,i,a,c,n,o),await z(),{ok:!0,leadBox:s.leadBox}}var gg="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 Mg(t,{hint:e}={}){let n=nt(await q(),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=cc(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";wp(n.id,{name:s,box:n.leadBox});let a=String(e||"").trim()?`${gg}
|
|
164
168
|
|
|
165
|
-
What to preview: ${String(e).trim()}`:
|
|
169
|
+
What to preview: ${String(e).trim()}`:gg;try{await te(t,a,{kind:"spin-up-view"})}catch(i){throw Vs(n.id,{error:"drive-failed"}),i}return{ok:!0,running:!0}}async function pu(t){let e=nt(await q(),t);if(!e)throw new Error(`track "${t}" not found`);if(e.archivedAt)throw new Error(`track "${t}" is archived`);let n=e.boxes.find(f=>f.name===e.leadBox),r=Ha(t),o=await Y(e.leadBox),s=o?await M_(e.leadBox,n?.agent):{stopped:!1,sends:0},a=o&&s.stopped?await P_(e,n):null,i=!a&&o&&s.stopped?await j_(e,n):null,c=o?await C_(e).catch(()=>null):null,l=r.filter(f=>f.text&&!f.kind).map(f=>f.text),u=a||i;return l.length&&(u=[u,...l].filter(Boolean).join(`
|
|
166
170
|
|
|
167
|
-
`),e.draft={text:u,updatedAt:Date.now()},await
|
|
171
|
+
`),e.draft={text:u,updatedAt:Date.now()},await z()),await G({flow:"interrupt",phase:"sent",trackId:t,sessionName:e.leadBox,agent:n?.agent,dropped:r.length,escSends:s.sends,stopped:s.stopped,unsent:!!a,lostRestored:!!i,queuedRestored:l.length,shipDismissed:!!c}).catch(()=>{}),{ok:!0,interrupted:!!o,stopped:s.stopped,dropped:r.length,restored:u||null}}var $_=[300,350,400],O_=3;async function M_(t,e){let n=0;for(let r=0;r<O_;r++){await xt(t,["Escape"],{kind:"interrupt"}),n++;for(let o of $_){await Br(o);let s=await gt(t,{lines:Tt}).catch(()=>"");if(!Ue(e,s))return{stopped:!0,sends:n}}}return{stopped:!1,sends:n}}async function C_(t){let e=zl(await L(t));return e?.offerId?(await $(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 N_=[250,400,500,600];async function P_(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 N_){await Br(r);let o=await gt(t.leadBox,{lines:Tt}).catch(()=>""),s=$o(e.agent,o);if(s&&ul(n.text,s))return await Cg(t.leadBox,e.agent,n.text.length+8),t.draft={text:n.text,updatedAt:Date.now()},ct(t,"unsaid",{box:t.leadBox,agent:e.agent,boxRole:e.role,saidTs:n.at,text:n.text}),await z(),n.text}return null}var B_=3e4,D_=[350,650,900];async function j_(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>B_||n.some(s=>s.type==="unsaid"&&s.saidTs===r.at)||!Zt(r.text))return null;for(let s of D_){await Br(s);let a;try{a=await nl(e,{})}catch{return null}if(hh(a,r))return null;let i=await gt(t.leadBox,{lines:Tt}).catch(()=>"");if($o(e.agent,i))return null}return t.draft={text:r.text,updatedAt:Date.now()},ct(t,"unsaid",{box:t.leadBox,agent:e.agent,boxRole:e.role,saidTs:r.at,text:r.text,reason:"lost"}),await z(),r.text}var L_=[250,450,700],ou=2;async function F_(t,e,n){if(e?.agent!=="claude")return!0;for(let r=0;r<=ou;r++){let o=!1;for(let s of L_){await Br(s);let a=await gt(t.leadBox,{lines:Tt}).catch(()=>""),i=wh(e.agent,n,a,{busy:Ue(e.agent,a)});if(i==="stuck"){o=!0;break}if(i==="submitted")return!0}if(!o)return!0;r<ou&&(await xt(t.leadBox,["Enter"],{kind:"submit-retry"}),G({flow:"say",phase:"submit-retry",trackId:t.id,sessionName:t.leadBox,agent:e.agent,attempt:r+1,textHead:n.slice(0,120)}).catch(()=>{}))}return G({flow:"say",phase:"submit-failed",trackId:t.id,sessionName:t.leadBox,agent:e.agent,retries:ou,textHead:n.slice(0,120)}).catch(()=>{}),!1}async function Cg(t,e,n){for(let r=0;r<3;r++){await Es(t,n,{kind:"unsend-wipe"}).catch(()=>{}),await Br(200);let o=await gt(t,{lines:Tt}).catch(()=>""),s=$o(e,o);if(!s)return!0;n=s.length+8}return!1}async function Ng(t,e){if(typeof e!="string")throw new Error("text (string) required");let n=nt(await q(),t);if(!n)throw new Error(`track "${t}" not found`);let r=xr(e);return r?n.draft={text:r,updatedAt:Date.now()}:delete n.draft,await z(),{ok:!0,draft:n.draft||null}}async function Pg(t,e){if(typeof e!="string"||!e.trim())throw new Error("model (string) required");let n=e.trim(),r=nt(await q(),t);if(!r)throw new Error(`track "${t}" not found`);let o=await Y(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=Nh(a,n),l=[...r.events||[]].reverse().find(f=>f.type==="model-change")?.to??null;l||(l=(await ao(o.cwd,o.sessionId,{fallbackNewest:!1}).catch(()=>null))?.model??null),await He({cwd:o.cwd,sessionId:o.sessionId,sessionName:r.leadBox,agent:o.agent,agentCmd:Jo(o.agent)});let u=await Ph(a,r.leadBox,i);return ct(r,"model-change",{to:n,from:l,box:r.leadBox,agent:a,verified:!!u.verified}),await z(),{ok:!0,requested:n,leadBox:r.leadBox,...u}}async function Bg(t){for(let e of t){if(!e||!await ga(e))continue;let n=await Sh(e).catch(()=>null);if(!n)continue;if((await U.stat(H.join(n,".git")).catch(()=>null))?.isDirectory())return n}return null}function Nt(t){let e=t;return e?e.kind==="multi"?Array.isArray(e.repos)?e.repos.filter(Boolean):[]:e.base?[{name:H.basename(e.base),dir:e.dir??null,base:e.base,baseRef:e.baseRef??null,repo:e.repo??null}]:[]:[]}async function No(t){let e=[];for(let n of Nt(t?.workspace)){if(!n?.dir)continue;let r=await gl(n.dir,{ignorePlumbing:!0}).catch(()=>!1),o=await yl(n.dir,n.baseRef).catch(()=>0);(r||o>0)&&e.push({...n,dirty:r,ahead:o})}return e}async function Dg(t){let e=t;typeof e=="string"&&!H.isAbsolute(e)&&/^[^/\s]+\/[^/\s]+$/.test(e)&&(e=await Xr(e)??await xi(e));let n=await Bg([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 U_(t,e,n,r){let o=`track/${bu(e)}-${t}`,s=[];try{await U.mkdir(n,{recursive:!0});let a=new Set([".bostrat"]);for(let i of r){let c=await Dg(i);if(s.some(m=>m.base===c))throw new Error(`repo tagged twice: ${i}`);let l=await tt(c).catch(()=>null),u=H.basename(c);a.has(u)&&l&&(u=l.replace("/","-"));for(let m=2;a.has(u);m++)u=`${H.basename(c)}-${m}`;a.add(u);let f=H.join(n,u),d=await ya(c);await Sr(c,f,o,d),s.push({name:u,dir:f,base:c,baseRef:d,repo:l})}return await Wa(n,s,o),{dir:n,kind:"multi",branch:o,repos:s}}catch(a){for(let i of s)await Gt(i.base,i.dir).catch(()=>{});throw await U.rm(n,{recursive:!0,force:!0}).catch(()=>{}),a}}var jg=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 Lg(t,e){let n=await q(),r=nt(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 Dg(typeof e=="string"?e.trim():e);if((o.repos??[]).some(d=>d?.base===s))throw new Error(`repo already tagged: ${e}`);let a=await tt(s).catch(()=>null),i=new Set([".bostrat",...(o.repos??[]).map(d=>d?.name).filter(Boolean)]),c=H.basename(s);i.has(c)&&a&&(c=a.replace("/","-"));for(let d=2;i.has(c);d++)c=`${H.basename(s)}-${d}`;let l=H.join(o.dir,c),u=await ya(s);await vh(s,o.branch)?await Th(s,l,o.branch):await Sr(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 Wa(o.dir,o.repos,o.branch),ct(r,"retagged",{action:"add",repo:a??s,name:c,workspace:jg(o)}),await z(),f}async function Fg(t,e){let n=await q(),r=nt(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 Qt.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 gl(s.dir,{ignorePlumbing:!0}).catch(()=>!1),i=await yl(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 Gt(s.base,s.dir).catch(()=>{}),await U.rm(s.dir,{recursive:!0,force:!0}).catch(()=>{}),o.repos=(o.repos??[]).filter(c=>c&&c!==s),await Wa(o.dir,o.repos,o.branch),ct(r,"retagged",{action:"remove",repo:s.repo??s.base,name:s.name,workspace:jg(o)}),await z(),{removed:s.name}}async function H_(t,e,{repo:n,repos:r,cwd:o}={}){await U.mkdir(iu,{recursive:!0});let s=H.join(iu,t),a=Array.isArray(r)?r.map(c=>typeof c=="string"?c.trim():"").filter(Boolean):[];if(a.length>=2)return U_(t,e,s,a);a.length===1&&!n&&(n=a[0]),typeof n=="string"&&!H.isAbsolute(n)&&/^[^/\s]+\/[^/\s]+$/.test(n)&&(n=await Xr(n)??await xi(n));let i=await Bg([n,o,d_]);if(i){let c=`track/${bu(e)}-${t}`;try{let l=await ya(i);await Sr(i,s,c,l),await cu(s);let u=await tt(i).catch(()=>null);return{dir:s,base:i,branch:c,kind:"worktree",baseRef:l,repo:u}}catch{await U.rm(s,{recursive:!0,force:!0}).catch(()=>{})}}return await U.mkdir(s,{recursive:!0}),await Ah(s),await cu(s),{dir:s,base:null,branch:null,kind:"repo"}}var Ko="# bostrat workspace",Ug=`${Ko}
|
|
168
172
|
|
|
169
173
|
TRACK MEMORY: if \`.bostrat/memory/MEMORY.md\` exists in your workspace, read it
|
|
170
174
|
before substantive work \u2014 it is the shared cross-provider memory for this repo
|
|
@@ -222,6 +226,24 @@ Each update is a new version; the history stays reviewable. For a batch ("3
|
|
|
222
226
|
options"), give every comp the same --group so they can be compared side by side.
|
|
223
227
|
\`bostrat-mock ls\` lists mocks, \`bostrat-mock rm <name-or-id>\` deletes one.
|
|
224
228
|
|
|
229
|
+
When asked for a USER JOURNEY ("flow", "storyboard", "walk through the screens"),
|
|
230
|
+
publish each screen with \`bostrat-mock add\` FIRST, then bind them IN ORDER with:
|
|
231
|
+
|
|
232
|
+
bostrat-flow add --name "<journey>" <flow.json>
|
|
233
|
+
|
|
234
|
+
flow.json: \`{ "steps": [ { "mock": "<mock name or id>", "caption": "<what the
|
|
235
|
+
user does at this step>" }, \u2026 ] }\` (optional per step: \`"title"\` short kicker,
|
|
236
|
+
\`"v"\` version pin). The operator reads it as a storyboard \u2014 each step's caption
|
|
237
|
+
above its fully rendered mock. Captions are plain text describing the user's
|
|
238
|
+
ACTION. Know the difference: a --group is ALTERNATIVES for one screen (pick one);
|
|
239
|
+
a flow is a SEQUENCE of different screens (read in order) \u2014 they compose (a flow
|
|
240
|
+
step can reference a group's picked winner). On feedback revise the SAME flow \u2014
|
|
241
|
+
\`bostrat-flow update <name-or-id> [--note "<what changed>"] <flow.json>\`
|
|
242
|
+
(declarative: send the full new step list) \u2014 never add a near-duplicate. Mocks
|
|
243
|
+
referenced by a flow update through automatically (\`bostrat-mock update\` is
|
|
244
|
+
enough; no flow edit needed). \`bostrat-flow ls\` lists flows, \`show <name-or-id>\`
|
|
245
|
+
prints the steps, \`rm\` deletes one (its mocks stay).
|
|
246
|
+
|
|
225
247
|
When asked to link this work to a TICKET (a Linear issue URL or a key like
|
|
226
248
|
ABC-123), run:
|
|
227
249
|
|
|
@@ -313,9 +335,9 @@ which steps to run (and which to skip). An offer buried mid-message, phrased as
|
|
|
313
335
|
statement, or omitted entirely renders no controls \u2014 so close EVERY completed
|
|
314
336
|
implementation turn this way, every time. When the operator has already asked you
|
|
315
337
|
to commit / push / open the PR, just do it; do not re-offer.
|
|
316
|
-
`,
|
|
317
|
-
`)),
|
|
318
|
-
`);return`${
|
|
338
|
+
`,W_=Ug,Ho=Ug+kh;async function cu(t){try{await U.writeFile(H.join(t,"AGENTS.md"),Ho,{flag:"wx"})}catch{return}await G_(t,["/AGENTS.md"])}var su=t=>typeof t=="string"&&(t===Ko||t.startsWith(`${Ko}
|
|
339
|
+
`)),q_="# bostrat multi-repo workspace";function K_(t,e){let n=t.map(r=>`- \`${r.name}/\`${r.repo?` \u2014 github.com/${r.repo}`:""}${r.baseRef?` (forked from ${r.baseRef})`:""}`).join(`
|
|
340
|
+
`);return`${q_}
|
|
319
341
|
|
|
320
342
|
This directory is NOT a git repo. It is a bostrat Track workspace holding one
|
|
321
343
|
git worktree per tagged repo, ALL on the same branch \`${e}\`:
|
|
@@ -327,25 +349,25 @@ push / open PRs) in each repo separately, always on that shared branch. Work
|
|
|
327
349
|
only needs to land in the repos it actually touches \u2014 an untouched repo's
|
|
328
350
|
branch simply stays at its fork point and never gets pushed. When asked to run
|
|
329
351
|
or preview an app, run it from that app's own repo directory.
|
|
330
|
-
`}async function
|
|
331
|
-
`,`${
|
|
352
|
+
`}async function Wa(t,e,n){let r=K_(e,n);for(let o of["CLAUDE.md","AGENTS.md"])await U.writeFile(H.join(t,o),r).catch(()=>{})}var au=Ho.replace(`${Ko}
|
|
353
|
+
`,`${Ko}
|
|
332
354
|
|
|
333
355
|
(Global copy managed by bostrat \u2014 rewritten on service start; edits will be lost.)
|
|
334
356
|
SCOPE: these instructions apply ONLY when your working directory is under
|
|
335
357
|
~/.bostrat/workspaces/ or ~/.bostrat/worktrees/ \u2014 a bostrat-orchestrated box.
|
|
336
358
|
When working anywhere else, ignore everything below.
|
|
337
|
-
`);async function
|
|
359
|
+
`);async function Hg(t){let{stdout:e}=await l_("git",["-C",t,"rev-parse","--git-path","info/exclude"]);return H.resolve(t,e.trim())}async function G_(t,e){try{let n=await Hg(t);await U.mkdir(H.dirname(n),{recursive:!0});let r=await U.readFile(n,"utf8").catch(()=>""),o=e.filter(s=>!r.split(`
|
|
338
360
|
`).includes(s));o.length&&await U.appendFile(n,(r.endsWith(`
|
|
339
361
|
`)||!r?"":`
|
|
340
362
|
`)+o.join(`
|
|
341
363
|
`)+`
|
|
342
|
-
`)}catch{}}async function
|
|
364
|
+
`)}catch{}}async function V_(t,e){try{let n=await Hg(t),r=await U.readFile(n,"utf8"),o=r.split(`
|
|
343
365
|
`).filter(s=>s!==e);o.length!==r.split(`
|
|
344
366
|
`).length&&await U.writeFile(n,o.join(`
|
|
345
|
-
`))}catch{}}async function
|
|
367
|
+
`))}catch{}}async function Wg(){let t={refreshed:0,removed:0,codexGlobal:null};try{await U.mkdir(Me,{recursive:!0}),await U.writeFile(H.join(Me,"CLAUDE.md"),W_).catch(()=>{}),await U.writeFile(H.join(Me,"AGENTS.md"),Ho).catch(()=>{});let e=process.env.CODEX_HOME||H.join(process.env.HOME||"/tmp",".codex");if((await U.stat(e).catch(()=>null))?.isDirectory()){let r=H.join(e,"AGENTS.md"),o=await U.readFile(r,"utf8").catch(()=>null);o===null?(await U.writeFile(r,au,{flag:"wx"}).catch(()=>{}),t.codexGlobal="created"):su(o)&&o!==au&&(await U.writeFile(r,au).catch(()=>{}),t.codexGlobal="refreshed")}for(let r of[iu,Mr]){let o=await U.readdir(r,{withFileTypes:!0}).catch(()=>[]);for(let s of o){if(!s.isDirectory())continue;let a=H.join(r,s.name),i=H.join(a,"AGENTS.md"),c=await U.readFile(i,"utf8").catch(()=>null);c!==null&&su(c)&&c!==Ho&&(await U.writeFile(i,Ho).catch(()=>{}),t.refreshed++);let l=H.join(a,"CLAUDE.md"),u=await U.readFile(l,"utf8").catch(()=>null);u!==null&&su(u)&&(await U.rm(l,{force:!0}).catch(()=>{}),await V_(a,"/CLAUDE.md"),t.removed++)}}}catch{}return t}async function z_(t){let e=t.workspace;if(!(!e||!e.dir))try{if(e.kind==="multi"){for(let n of Nt(e))n.base&&n.dir&&await Gt(n.base,n.dir).catch(()=>{});await U.rm(e.dir,{recursive:!0,force:!0})}else e.kind==="worktree"&&e.base?await Gt(e.base,e.dir):await U.rm(e.dir,{recursive:!0,force:!0})}catch{}}async function mu({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 Fa(o,s),i=await q(),c=zo().slice(0,8),l=await H_(c,t,{repo:n,repos:r,cwd:e}),u=new Set(i.tracks.flatMap(h=>(h.boxes||[]).map(w=>w.name))),f=`${t.trim()} lead`;u.has(Pi(f))&&(f=`${t.trim().slice(0,50).trim()} ${c} lead`);let d=await gn(f,l.dir,{agentCmd:a,agent:o});Cs({cwd:d.cwd,sessionName:d.name,agent:o,agentCmd:a});let m={id:c,name:t.trim(),cwd:d.cwd,workspace:l,leadBox:d.name,boxes:[Qg(d,"lead","live")],events:[],createdAt:Date.now()};return ct(m,"opened",{agent:o,box:d.name,skipPermissions:s,name:m.name,cwd:m.cwd,workspace:{base:l.base??null,branch:l.branch,kind:l.kind,...l.baseRef?{baseRef:l.baseRef}:{},...l.repo?{repo:l.repo}:{},...l.repos?{repos:l.repos.map(({name:h,base:w,baseRef:g,repo:b})=>({name:h,base:w,baseRef:g,repo:b}))}:{}}}),i.tracks.push(m),await z(),an(m)}var J_=new Set(["claude","codex","cursor"]);async function qg({provider:t="claude",fresh:e=!1}={}){if(!J_.has(t))throw new Error(`unknown setup provider "${t}" (claude | codex | cursor)`);let r=(await q()).tracks.find(u=>u.kind==="setup"&&u.setup?.provider===t&&!u.archivedAt);if(r){if(!e){let u=await an(r);if(u.boxes.find(d=>d.name===u.leadBox)?.state==="live")return u}await Nr(r.id).catch(()=>{})}let o=(await Fa(t,!1)).replace(/\s+--(?:dangerously-skip-permissions|dangerously-bypass-approvals-and-sandbox)\b/g,""),s=zo().slice(0,8),a=H.join(Me,"setup",s);await U.mkdir(a,{recursive:!0});let i=await gn(`Connect ${t} ${s}`,a,{agentCmd:o,agent:t,firstRun:!0});t==="codex"&&Cs({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:[Qg(i,"lead","live")],events:[],createdAt:Date.now()};return ct(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 q()).tracks.push(c),await z(),an(c)}async function Kg(t,e,n){await vp(t).catch(()=>{});let r=Go.get(t);r&&!r.finishedAt&&(r.error=n,r.finishedAt=Date.now()),Go.delete(t),e.handoff&&!e.handoff.finishedAt&&(e.handoff.step="error",e.handoff.error=n,e.handoff.finishedAt=Date.now());for(let a of Pr(t))a.finishedAt||(a.canceled=!0,a.finishedAt=Date.now()),await xu(a).catch(()=>{}),Qt.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 q(),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 oe(a.name).catch(()=>{})}async function Gg(t,{cascadeMocks:e=!0,cascadeRunbooks:n=!1}={}){let r=await q(),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 sp(s),await Kg(t,s,"track deleted"),n?await Pp(t).catch(()=>{}):await Np(t).catch(()=>{}),await Dh(),await z_(s),e&&await ap(t).catch(()=>{}),e?await ig(t).catch(()=>{}):await ag(s).catch(()=>{}),ct(s,"track-deleted",{name:s.name,boxes:s.boxes.length,ageMs:s.createdAt?Date.now()-s.createdAt:null}),r.tracks.splice(o,1),await z(),{ok:!0,id:t,name:s.name,boxes:s.boxes.length}}async function Nr(t){let e=await q(),n=nt(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(),ct(n,"archived",{boxes:n.boxes.length}),await z(),n.kind==="setup"?await Vg(t):Y_(t).catch(()=>{}),an(n)}var X_=9e4;async function Y_(t){try{await Z_(t)}catch{}await Vg(t).catch(()=>{})}async function Vg(t){let e=await q(),n=nt(e,t);if(!(!n||!n.archivedAt)){await Kg(t,n,"track archived");for(let r of n.boxes)Ln.has(r.state)||(r.state="stopped");await z()}}async function Z_(t){let e=await q(),n=nt(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 Y(n.leadBox).catch(()=>null):null;if(i?.cwd&&a)try{({text:r,items:o}=await Of(n.leadBox,i.cwd,{timeoutMs:X_})),s="lead-cli"}catch{}if(r||(r=Bc(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 $(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 z()}async function zg(){let t=await q(),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=Bc(o,await L(o).catch(()=>[]));if(!s){r++;continue}o.beat={text:s,source:"derived",at:Date.now()},await $(o,{type:"track.summary.recorded",actor:"orchestrator",source:"orchestrator",payload:{text:s,source:"derived",backfill:!0}}).catch(()=>{}),e++}return await z(),{recorded:e,skipped:n,empty:r}}async function en(t,e){let n=await It(t,e).catch(()=>null);if(!n?.archiveOnMerge||!n.trackId)return!1;let r=await Jg(n);if(r.length){for(let o of r)await pr(o.repo,o.number,!0).catch(()=>{});return!1}try{return await Nr(n.trackId),!0}catch(o){if(/already archived/i.test(String(o?.message||o)))return!1;throw o}}async function Jg(t){return(await pe().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 nn(t,e){return hu(t,e,"merged")}async function hu(t,e,n="merged"){let r=await It(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 Jg(r);if(s.length){let i=[...r.notifyPending||[],o];for(let c of s)await bm(c.repo,c.number,i).catch(()=>{});return!1}let a=[...r.notifyPending||[],o];try{return await te(r.trackId,Q_(a),{kind:"merge-notify"}),!0}catch{return!1}}var yg={merged:"merged",closed:"closed on GitHub WITHOUT merging",lost:"no longer found on GitHub (deleted, or its repo left the grant)"};function Q_(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 ${yg[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 ${yg[i.outcome]??i.outcome}`).join(`
|
|
346
368
|
`),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}
|
|
347
369
|
${s}
|
|
348
|
-
${a}`}async function lu(t){if(t.archivedAt)return!1;let e=t.boxes.find(r=>r.name===t.leadBox);if(!e||Bn.has(e.state)||await Y(e.name))return!1;let n=Zw(e.agent,e.sessionId);if(!await gs(n))return at(t,"rehydrate-failed",{box:e.name,agent:e.agent,reason:`agent "${e.agent}" not available on this box`}),!1;try{let r=await hn(e.name,e.cwd,{agentCmd:n,agent:e.agent,resumeSessionId:e.sessionId,pinned:e.pinned===!0});return e.hookTokenHash=Hr(r.hookToken),_s({cwd:e.cwd,sessionName:e.name,agent:e.agent,agentCmd:n}),e.state="live",at(t,"rehydrated",{box:e.name,agent:e.agent}),!0}catch(r){return at(t,"rehydrate-failed",{box:e.name,agent:e.agent,reason:String(r?.message||r)}),!1}}async function Eg(t){let e=await q(),n=nt(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,at(n,"unarchived",{}),await lu(n),await V(),on(n)}async function Ig(){let t=await q(),e=new Set((await mn()).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 re(a.name).catch(()=>{}),at(s,"orphan-reaped",{box:a.name,agent:a.agent}),r++,o=!0),!Bn.has(a.state)&&a.state!=="stopped"&&(a.state="stopped",o=!0);return o&&await V(),r}async function Rg(t){let e=await q(),n=nt(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(Bn.has(r.state))throw new Error("lead box is terminal \u2014 nothing to restart");if(await Y(n.leadBox)){let a=await gt(n.leadBox,{lines:Tt}).catch(()=>"");if(Ts(r.agent,a))throw new Error("lead is mid-turn \u2014 retry when it goes idle");await re(n.leadBox),r.state="stopped"}let s=await lu(n);if(await V(),!s)throw new Error("rehydrate failed \u2014 see the track's rehydrate-failed event");return on(n)}var $g=(t,e,n)=>({name:t.name,agent:t.agent,role:e,state:n,cwd:t.cwd,sessionId:t.sessionId,pinned:t.pinned===!0,hookTokenHash:Hr(t.hookToken),createdAt:Date.now()});function Og(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 jo=new Map,Ho=t=>jo.get(t)||(we?nt(we,t)?.handoff:null)||null;async function Ma(t,e){let n=await q(),r=nt(n,t);r&&(r.handoff={...r.handoff||{},...e},await V())}function bE(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 Mg(){let t=await q(),e=!1;for(let n of t.tracks||[]){let r=n.handoff,o=bE(n);if(!o)continue;e=!0;let s=!!r.toBox&&r.toBox===r.fromBox;if(s&&o.step==="error"){let c=await Y(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=Og(l),d=f[f.length-1],m=d.agent,h=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:Ba,at:u},at(n,"lead-handoff",{from:r.fromBox,to:r.fromBox,fromAgent:m,toAgent:r.toAgent,fromSessionId:h,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 re(i).catch(()=>{}),a&&await re(a).catch(()=>{})}return e&&await V(),e}async function uu(t,e,n=!0,r=null){let o=jo.get(t);if(o&&!o.finishedAt)throw new Error("a lead handoff is already running for this Track");let s=nt(await q(),t);if(!s)throw new Error(`track "${t}" not found`);let a=await Pa(e,n),i=await Y(s.leadBox);if(!i)throw new Error(`lead box "${s.leadBox}" is not running`);let c={trackId:t,step:"checkpoint",fromBox:s.leadBox,toAgent:e,skipPermissions:n,reason:Qw(r),toBox:s.leadBox,startedAt:Date.now(),finishedAt:null,error:null};return jo.set(t,c),await Ma(t,{step:c.step,fromBox:c.fromBox,toAgent:e,toBox:c.toBox,skipPermissions:n,startedAt:c.startedAt,finishedAt:null,error:null}),kE(c,s,i,a,e).catch(async l=>{c.error=l?.message||String(l),c.finishedAt=Date.now(),console.error(`[lead-handoff ${t}] FAILED at step "${c.step}": ${c.error}`),await Ma(t,{step:c.step,error:c.error,finishedAt:c.finishedAt}).catch(()=>{}),G({flow:"lead-handoff",phase:"failed",trackId:t,step:c.step,error:c.error,fromBox:c.fromBox,toAgent:c.toAgent,toBox:c.toBox}).catch(()=>{})}),G({flow:"lead-handoff",phase:"started",trackId:t,fromBox:c.fromBox,toAgent:e,cwd:i.cwd}).catch(()=>{}),c}async function kE(t,e,n,r,o){let s=async(A,M={})=>{t.step=A,console.log(`[lead-handoff ${t.trackId}] step \u2192 ${A}`),await Ma(t.trackId,{step:A,toBox:t.toBox}),await G({flow:"lead-handoff",phase:A,trackId:t.trackId,fromBox:t.fromBox,toBox:t.toBox,toAgent:o,sessionName:M.sessionName,...M})};console.log(`[lead-handoff ${t.trackId}] ${t.fromBox}: ${n.agent} \u2192 ${o} IN PLACE`);let a=await q(),i=nt(a,t.trackId)?.boxes.find(A=>A.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 Of(t.fromBox),await s("swap",{cwd:n.cwd,agentCmd:r});let f=await Mc(t.fromBox,{agentCmd:r,agent:o});await s("hydrate",{sessionName:t.fromBox}),await Ue({cwd:n.cwd,sessionId:f.sessionId,sessionName:t.fromBox,agent:o,agentCmd:Uo(o)});let d=await gt(t.fromBox,{lines:Tt}).catch(()=>"");if(!As(o,d)){await s("rollback",{sessionName:t.fromBox,fromAgent:c});let A=Zw(c,l,{skipPermissions:t.skipPermissions});await Mc(t.fromBox,{agentCmd:A,agent:c,resumeSessionId:l,pinned:u});let M=await q(),z=nt(M,t.trackId);if(z){let $=z.boxes.find(F=>F.name===t.fromBox);$&&($.state="live"),at(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 m=await q(),h=nt(m,t.trackId);if(!h)throw new Error(`track "${t.trackId}" not found during handoff commit \u2014 was it deleted or archived mid-flight?`);let w=h.boxes.find(A=>A.name===t.fromBox);if(!w)throw new Error(`lead box row "${t.fromBox}" missing during handoff commit`);let g=Date.now(),b=Og(w),v=b[b.length-1];v.endedAt=g,t.reason&&(v.endReason=t.reason),b.push({agent:o,sessionId:f.sessionId,pinned:f.pinned,startedAt:g,endedAt:null}),w.agent=o,w.sessionId=f.sessionId,w.pinned=f.pinned,w.state="live",w.role="lead",h.pendingHandoffHydrate={artifact:Ba,at:g},at(h,"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 V(),await s("done",{sessionName:t.fromBox}),await Ma(t.trackId,{finishedAt:Date.now(),error:null}),t.finishedAt=Date.now()}var xE=Number(process.env.BOSTRAT_SUBTASK_TIMEOUT_MS)||18e5,SE=Number(process.env.BOSTRAT_SUBTASK_POLL_MS)||3e3,Er=H.join(Oe,"worktrees"),Ar=[Po,Oa],Zt=new Map;var $r=t=>{let e=new Map([...Zt.values()].filter(r=>r.trackId===t).map(r=>[r.id,r])),n=we?nt(we,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)},du=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 vE(t,e,n){let r=await q(),o=nt(r,t);o&&(o.subtasks=o.subtasks||{},o.subtasks[e]={...o.subtasks[e]||{},...n},await V())}var Ir=t=>vE(t.trackId,t.id,du(t));async function _r(t,e){t.step=e,await Ir(t)}function TE(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 AE(t){return!!t&&!t.finishedAt&&!t.canceled&&t.step==="working"&&!!t.box&&!!t.dir&&!!t.base&&!!t.branch}async function _E(t,e,n){if(!AE(n)||Zt.has(e)||!await Y(n.box).catch(()=>null))return!1;try{await U.access(n.dir)}catch{return!1}let o=await Y(t.leadBox).catch(()=>null);if(!o?.cwd)return!1;let s={...n,id:e,leadBox:t.leadBox,step:"working",canceled:!1,forceMerge:!1};return Zt.set(e,s),Dg(s,o).catch(a=>Pg(s,a)),!0}async function Cg(){let t=await q(),e=!1;for(let n of t.tracks||[]){for(let[r,o]of Object.entries(n.subtasks||{})){if(Zt.has(r))continue;if(await _E(n,r,o)){e=!0,at(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=TE(o);if(!a)continue;e=!0,n.subtasks[r]=a;let i=n.boxes.find(c=>c.subtaskId===r);i&&!Bn.has(i.state)&&(i.state="canceled"),at(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 Xw(n,o.box,o.dir)}for(let r of n.boxes){if(r.role!=="subtask"||Bn.has(r.state)||Zt.has(r.subtaskId)||n.subtasks?.[r.subtaskId])continue;e=!0,r.state="canceled";let o=r.subtaskId?H.join(Er,`${n.id}-${r.subtaskId}`):null;await G({flow:"subtask",phase:"reaped-legacy-orphan",trackId:n.id,subtaskId:r.subtaskId,box:r.name}).catch(()=>{}),await Xw(n,r.name,o)}}return e=await EE(t)||e,e&&await V(),e}async function Xw(t,e,n){if(e&&await re(e).catch(()=>{}),!n)return;if(t.workspace?.kind==="multi"){for(let o of Nt(t.workspace)){let s=H.join(n,o.name);await Kt(o.base,s).then(()=>!0,()=>!1)||await Kt(o.dir,s).catch(()=>{})}await U.rm(n,{recursive:!0,force:!0}).catch(()=>{});return}let r=await Y(t.leadBox).catch(()=>null);r?.cwd&&await Kt(r.cwd,n).catch(()=>{})}async function EE(t){let e;try{e=await U.readdir(Er)}catch{return!1}let n=new Set;for(let o of t.tracks||[]){for(let s of Object.keys(o.subtasks||{}))n.add(`${o.id}-${s}`);for(let s of o.boxes||[])s.subtaskId&&n.add(`${o.id}-${s.subtaskId}`)}for(let o of Zt.values())o.dir&&n.add(H.basename(o.dir));let r=!1;for(let o of e){if(n.has(o))continue;let s=H.join(Er,o),a=(t.tracks||[]).find(i=>o.startsWith(`${i.id}-`));if(a?.workspace?.kind==="multi"){for(let i of Nt(a.workspace))await Kt(i.base,H.join(s,i.name)).catch(()=>{});await U.rm(s,{recursive:!0,force:!0}).catch(()=>{})}else{let i=a?await Y(a.leadBox).catch(()=>null):null;i?.cwd?await Kt(i.cwd,s).catch(()=>{}):await U.rm(s,{recursive:!0,force:!0}).catch(()=>{})}await G({flow:"subtask",phase:"swept-orphan-worktree",dir:s}).catch(()=>{}),r=!0}return r}var fu=t=>t.trim().replace(/\s+/g,"-").replace(/[^A-Za-z0-9._-]/g,"").toLowerCase()||"track",Lo=class extends Error{};async function Ng(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 Pa(e,r),a=nt(await q(),t);if(!a)throw new Error(`track "${t}" not found`);let i=await Y(a.leadBox);if(!i)throw new Error(`lead box "${a.leadBox}" is not running`);if(!i.cwd)throw new Error("can't resolve the lead's working directory");let c=a.workspace?.kind==="multi",l=null;if(c){let d=Nt(a.workspace);if(!d.length)throw new Error("this multi-repo Track has no tagged repos to delegate into");for(let m of d)if(!m?.dir||!await da(m.dir))throw new Error(`tagged checkout "${m?.name??"?"}" is missing from the lead workspace (${m?.dir??"no dir"})`);l=d.map(m=>({name:m.name,leadDir:m.dir,base:null}))}else if(!await da(i.cwd))throw new Error(`subtask delegation needs the lead's workspace to be a git repo (got ${i.cwd})`);let u=Fo().slice(0,8),f={id:u,trackId:t,trackName:a.name,agent:e,task:gr(n),skipPermissions:r,reason:Qw(o),step:"snapshot",leadBox:a.leadBox,box:null,branch:`bostrat/${fu(a.name)}-${u}`,base:null,repos:l,dir:H.join(Er,`${t}-${u}`),stat:null,conflicts:[],applied:null,startedAt:Date.now(),finishedAt:null,error:null,canceled:!1,forceMerge:!1};return Zt.set(u,f),await Ir(f),IE(f,i,s).catch(d=>Pg(f,d)),f}async function Pg(t,e){t.failedStep=t.step,t.error=e instanceof Lo?"canceled":e?.message||String(e),t.step=e instanceof Lo?"canceled":"error",t.finishedAt=Date.now(),await mu(t).catch(()=>{}),await Ir(t).catch(()=>{}),await ME(t).catch(()=>{})}function pu(t){let e=Zt.get(t);if(!e)throw new Error(`subtask "${t}" not found`);if(e.finishedAt)throw new Error("subtask already finished");return e.canceled=!0,e}function Bg(t){let e=Zt.get(t);if(!e)throw new Error(`subtask "${t}" not found`);if(e.finishedAt)throw new Error("subtask already finished");if(e.step!=="working")throw new Error(`subtask is not waiting to merge (step: ${e.step})`);return e.forceMerge=!0,e}async function IE(t,e,n){if(await _r(t,"snapshot"),t.repos){for(let s of t.repos)s.base=await ul(s.leadDir,`bostrat: snapshot for subtask ${t.id}`);await Ir(t)}else t.base=await ul(e.cwd,`bostrat: snapshot for subtask ${t.id}`);if(await _r(t,"spawn"),await U.mkdir(Er,{recursive:!0}),t.repos){await U.mkdir(t.dir,{recursive:!0});for(let a of t.repos)await yr(a.leadDir,H.join(t.dir,a.name),t.branch,a.base);let s=Nt((nt(await q(),t.trackId)||{}).workspace);await ja(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 yr(e.cwd,t.dir,t.branch,t.base),await eu(t.dir);await RE(t,e);let r=t.trackName.slice(0,38).trim(),o=await hn(`${r} ${t.agent} sub ${t.id}`,t.dir,{agentCmd:n,agent:t.agent});t.box=o.name,await $E(t,o),await tg(t.trackId,o.name,N_(t.task,!!t.repos),{kind:"subtask"}),await Dg(t,e)}async function Dg(t,e){await _r(t,"working");let n=H.join(t.dir,Oa);if(await NE(n,t),t.canceled)throw new Lo;if(await _r(t,"merge"),t.repos){let o={files:0,insertions:0,deletions:0},s=[],a=[],i=!0;for(let c of t.repos){let l=H.join(t.dir,c.name);await dl(l,`bostrat: subtask ${t.id} result (auto-committed)`),c.stat=await hl(c.leadDir,c.base,t.branch,Ar),c.files=await wl(c.leadDir,c.base,t.branch,Ar);let u=await gl(c.leadDir,c.base,t.branch,Ar);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=>H.join(c.name,f))),a.push(...c.conflicts.map(f=>f.startsWith("patch did not apply cleanly")?`${c.name}: ${f}`:H.join(c.name,f))),u.applied||(i=!1)}t.stat=o,t.files=s,t.conflicts=a,t.applied=i}else{await dl(t.dir,`bostrat: subtask ${t.id} result (auto-committed)`),t.stat=await hl(e.cwd,t.base,t.branch,Ar),t.files=await wl(e.cwd,t.base,t.branch,Ar);let o=await gl(e.cwd,t.base,t.branch,Ar);t.applied=o.applied,t.conflicts=o.conflicts}await Ir(t);let r=await CE(t,e,n);await _r(t,"resume"),await tg(t.trackId,t.leadBox,P_(t.task,t.stat,t.conflicts,r),{kind:"subtask-resume"}),await _r(t,"reap"),await mu(t),t.step="done",t.finishedAt=Date.now(),await OE(t),await Ir(t)}async function RE(t,e){let n=H.join(t.dir,H.dirname(Po));await U.mkdir(n,{recursive:!0});let r="";try{r=await Fd(e.cwd,e.sessionId)}catch{}if(!r)try{r=await U.readFile(H.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}
|
|
370
|
+
${a}`}async function wu(t){if(t.archivedAt)return!1;let e=t.boxes.find(r=>r.name===t.leadBox);if(!e||Ln.has(e.state)||await Y(e.name))return!1;let n=xg(e.agent,e.sessionId);if(!await Ts(n))return ct(t,"rehydrate-failed",{box:e.name,agent:e.agent,reason:`agent "${e.agent}" not available on this box`}),!1;try{let r=await gn(e.name,e.cwd,{agentCmd:n,agent:e.agent,resumeSessionId:e.sessionId,pinned:e.pinned===!0});return e.hookTokenHash=zr(r.hookToken),Cs({cwd:e.cwd,sessionName:e.name,agent:e.agent,agentCmd:n}),e.state="live",ct(t,"rehydrated",{box:e.name,agent:e.agent}),!0}catch(r){return ct(t,"rehydrate-failed",{box:e.name,agent:e.agent,reason:String(r?.message||r)}),!1}}async function Xg(t){let e=await q(),n=nt(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,ct(n,"unarchived",{}),await wu(n),await z(),an(n)}async function Yg(){let t=await q(),e=new Set((await wn()).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 oe(a.name).catch(()=>{}),ct(s,"orphan-reaped",{box:a.name,agent:a.agent}),r++,o=!0),!Ln.has(a.state)&&a.state!=="stopped"&&(a.state="stopped",o=!0);return o&&await z(),r}async function Zg(t){let e=await q(),n=nt(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(Ln.has(r.state))throw new Error("lead box is terminal \u2014 nothing to restart");if(await Y(n.leadBox)){let a=await gt(n.leadBox,{lines:Tt}).catch(()=>"");if(Os(r.agent,a))throw new Error("lead is mid-turn \u2014 retry when it goes idle");await oe(n.leadBox),r.state="stopped"}let s=await wu(n);if(await z(),!s)throw new Error("rehydrate failed \u2014 see the track's rehydrate-failed event");return an(n)}var Qg=(t,e,n)=>({name:t.name,agent:t.agent,role:e,state:n,cwd:t.cwd,sessionId:t.sessionId,pinned:t.pinned===!0,hookTokenHash:zr(t.hookToken),createdAt:Date.now()});function ty(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 Go=new Map,Xo=t=>Go.get(t)||(we?nt(we,t)?.handoff:null)||null;async function Da(t,e){let n=await q(),r=nt(n,t);r&&(r.handoff={...r.handoff||{},...e},await z())}function tI(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 ey(){let t=await q(),e=!1;for(let n of t.tracks||[]){let r=n.handoff,o=tI(n);if(!o)continue;e=!0;let s=!!r.toBox&&r.toBox===r.fromBox;if(s&&o.step==="error"){let c=await Y(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=ty(l),d=f[f.length-1],m=d.agent,h=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:Ua,at:u},ct(n,"lead-handoff",{from:r.fromBox,to:r.fromBox,fromAgent:m,toAgent:r.toAgent,fromSessionId:h,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 oe(i).catch(()=>{}),a&&await oe(a).catch(()=>{})}return e&&await z(),e}async function gu(t,e,n=!0,r=null){let o=Go.get(t);if(o&&!o.finishedAt)throw new Error("a lead handoff is already running for this Track");let s=nt(await q(),t);if(!s)throw new Error(`track "${t}" not found`);let a=await Fa(e,n),i=await Y(s.leadBox);if(!i)throw new Error(`lead box "${s.leadBox}" is not running`);let c={trackId:t,step:"checkpoint",fromBox:s.leadBox,toAgent:e,skipPermissions:n,reason:Sg(r),toBox:s.leadBox,startedAt:Date.now(),finishedAt:null,error:null};return Go.set(t,c),await Da(t,{step:c.step,fromBox:c.fromBox,toAgent:e,toBox:c.toBox,skipPermissions:n,startedAt:c.startedAt,finishedAt:null,error:null}),eI(c,s,i,a,e).catch(async l=>{c.error=l?.message||String(l),c.finishedAt=Date.now(),console.error(`[lead-handoff ${t}] FAILED at step "${c.step}": ${c.error}`),await Da(t,{step:c.step,error:c.error,finishedAt:c.finishedAt}).catch(()=>{}),G({flow:"lead-handoff",phase:"failed",trackId:t,step:c.step,error:c.error,fromBox:c.fromBox,toAgent:c.toAgent,toBox:c.toBox}).catch(()=>{})}),G({flow:"lead-handoff",phase:"started",trackId:t,fromBox:c.fromBox,toAgent:e,cwd:i.cwd}).catch(()=>{}),c}async function eI(t,e,n,r,o){let s=async(A,C={})=>{t.step=A,console.log(`[lead-handoff ${t.trackId}] step \u2192 ${A}`),await Da(t.trackId,{step:A,toBox:t.toBox}),await G({flow:"lead-handoff",phase:A,trackId:t.trackId,fromBox:t.fromBox,toBox:t.toBox,toAgent:o,sessionName:C.sessionName,...C})};console.log(`[lead-handoff ${t.trackId}] ${t.fromBox}: ${n.agent} \u2192 ${o} IN PLACE`);let a=await q(),i=nt(a,t.trackId)?.boxes.find(A=>A.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 jf(t.fromBox),await s("swap",{cwd:n.cwd,agentCmd:r});let f=await jc(t.fromBox,{agentCmd:r,agent:o});await s("hydrate",{sessionName:t.fromBox}),await He({cwd:n.cwd,sessionId:f.sessionId,sessionName:t.fromBox,agent:o,agentCmd:Jo(o)});let d=await gt(t.fromBox,{lines:Tt}).catch(()=>"");if(!Ms(o,d)){await s("rollback",{sessionName:t.fromBox,fromAgent:c});let A=xg(c,l,{skipPermissions:t.skipPermissions});await jc(t.fromBox,{agentCmd:A,agent:c,resumeSessionId:l,pinned:u});let C=await q(),V=nt(C,t.trackId);if(V){let O=V.boxes.find(F=>F.name===t.fromBox);O&&(O.state="live"),ct(V,"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 z()}throw new Error(`incoming ${o} CLI never drew its composer \u2014 rolled back to ${c} (--resume, context intact)`)}await s("commit");let m=await q(),h=nt(m,t.trackId);if(!h)throw new Error(`track "${t.trackId}" not found during handoff commit \u2014 was it deleted or archived mid-flight?`);let w=h.boxes.find(A=>A.name===t.fromBox);if(!w)throw new Error(`lead box row "${t.fromBox}" missing during handoff commit`);let g=Date.now(),b=ty(w),S=b[b.length-1];S.endedAt=g,t.reason&&(S.endReason=t.reason),b.push({agent:o,sessionId:f.sessionId,pinned:f.pinned,startedAt:g,endedAt:null}),w.agent=o,w.sessionId=f.sessionId,w.pinned=f.pinned,w.state="live",w.role="lead",h.pendingHandoffHydrate={artifact:Ua,at:g},ct(h,"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 z(),await s("done",{sessionName:t.fromBox}),await Da(t.trackId,{finishedAt:Date.now(),error:null}),t.finishedAt=Date.now()}var nI=Number(process.env.BOSTRAT_SUBTASK_TIMEOUT_MS)||18e5,rI=Number(process.env.BOSTRAT_SUBTASK_POLL_MS)||3e3,Mr=H.join(Me,"worktrees"),$r=[Wo,Ba],Qt=new Map;var Pr=t=>{let e=new Map([...Qt.values()].filter(r=>r.trackId===t).map(r=>[r.id,r])),n=we?nt(we,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)},yu=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 oI(t,e,n){let r=await q(),o=nt(r,t);o&&(o.subtasks=o.subtasks||{},o.subtasks[e]={...o.subtasks[e]||{},...n},await z())}var Cr=t=>oI(t.trackId,t.id,yu(t));async function Or(t,e){t.step=e,await Cr(t)}function sI(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 aI(t){return!!t&&!t.finishedAt&&!t.canceled&&t.step==="working"&&!!t.box&&!!t.dir&&!!t.base&&!!t.branch}async function iI(t,e,n){if(!aI(n)||Qt.has(e)||!await Y(n.box).catch(()=>null))return!1;try{await U.access(n.dir)}catch{return!1}let o=await Y(t.leadBox).catch(()=>null);if(!o?.cwd)return!1;let s={...n,id:e,leadBox:t.leadBox,step:"working",canceled:!1,forceMerge:!1};return Qt.set(e,s),ay(s,o).catch(a=>oy(s,a)),!0}async function ny(){let t=await q(),e=!1;for(let n of t.tracks||[]){for(let[r,o]of Object.entries(n.subtasks||{})){if(Qt.has(r))continue;if(await iI(n,r,o)){e=!0,ct(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=sI(o);if(!a)continue;e=!0,n.subtasks[r]=a;let i=n.boxes.find(c=>c.subtaskId===r);i&&!Ln.has(i.state)&&(i.state="canceled"),ct(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 bg(n,o.box,o.dir)}for(let r of n.boxes){if(r.role!=="subtask"||Ln.has(r.state)||Qt.has(r.subtaskId)||n.subtasks?.[r.subtaskId])continue;e=!0,r.state="canceled";let o=r.subtaskId?H.join(Mr,`${n.id}-${r.subtaskId}`):null;await G({flow:"subtask",phase:"reaped-legacy-orphan",trackId:n.id,subtaskId:r.subtaskId,box:r.name}).catch(()=>{}),await bg(n,r.name,o)}}return e=await cI(t)||e,e&&await z(),e}async function bg(t,e,n){if(e&&await oe(e).catch(()=>{}),!n)return;if(t.workspace?.kind==="multi"){for(let o of Nt(t.workspace)){let s=H.join(n,o.name);await Gt(o.base,s).then(()=>!0,()=>!1)||await Gt(o.dir,s).catch(()=>{})}await U.rm(n,{recursive:!0,force:!0}).catch(()=>{});return}let r=await Y(t.leadBox).catch(()=>null);r?.cwd&&await Gt(r.cwd,n).catch(()=>{})}async function cI(t){let e;try{e=await U.readdir(Mr)}catch{return!1}let n=new Set;for(let o of t.tracks||[]){for(let s of Object.keys(o.subtasks||{}))n.add(`${o.id}-${s}`);for(let s of o.boxes||[])s.subtaskId&&n.add(`${o.id}-${s.subtaskId}`)}for(let o of Qt.values())o.dir&&n.add(H.basename(o.dir));let r=!1;for(let o of e){if(n.has(o))continue;let s=H.join(Mr,o),a=(t.tracks||[]).find(i=>o.startsWith(`${i.id}-`));if(a?.workspace?.kind==="multi"){for(let i of Nt(a.workspace))await Gt(i.base,H.join(s,i.name)).catch(()=>{});await U.rm(s,{recursive:!0,force:!0}).catch(()=>{})}else{let i=a?await Y(a.leadBox).catch(()=>null):null;i?.cwd?await Gt(i.cwd,s).catch(()=>{}):await U.rm(s,{recursive:!0,force:!0}).catch(()=>{})}await G({flow:"subtask",phase:"swept-orphan-worktree",dir:s}).catch(()=>{}),r=!0}return r}var bu=t=>t.trim().replace(/\s+/g,"-").replace(/[^A-Za-z0-9._-]/g,"").toLowerCase()||"track",Vo=class extends Error{};async function ry(t,{agent:e,task:n,skipPermissions:r=!0,reason:o=null}={}){if(typeof n!="string"||!xr(n))throw new Error("task (string) required");let s=await Fa(e,r),a=nt(await q(),t);if(!a)throw new Error(`track "${t}" not found`);let i=await Y(a.leadBox);if(!i)throw new Error(`lead box "${a.leadBox}" is not running`);if(!i.cwd)throw new Error("can't resolve the lead's working directory");let c=a.workspace?.kind==="multi",l=null;if(c){let d=Nt(a.workspace);if(!d.length)throw new Error("this multi-repo Track has no tagged repos to delegate into");for(let m of d)if(!m?.dir||!await ga(m.dir))throw new Error(`tagged checkout "${m?.name??"?"}" is missing from the lead workspace (${m?.dir??"no dir"})`);l=d.map(m=>({name:m.name,leadDir:m.dir,base:null}))}else if(!await ga(i.cwd))throw new Error(`subtask delegation needs the lead's workspace to be a git repo (got ${i.cwd})`);let u=zo().slice(0,8),f={id:u,trackId:t,trackName:a.name,agent:e,task:xr(n),skipPermissions:r,reason:Sg(o),step:"snapshot",leadBox:a.leadBox,box:null,branch:`bostrat/${bu(a.name)}-${u}`,base:null,repos:l,dir:H.join(Mr,`${t}-${u}`),stat:null,conflicts:[],applied:null,startedAt:Date.now(),finishedAt:null,error:null,canceled:!1,forceMerge:!1};return Qt.set(u,f),await Cr(f),lI(f,i,s).catch(d=>oy(f,d)),f}async function oy(t,e){t.failedStep=t.step,t.error=e instanceof Vo?"canceled":e?.message||String(e),t.step=e instanceof Vo?"canceled":"error",t.finishedAt=Date.now(),await xu(t).catch(()=>{}),await Cr(t).catch(()=>{}),await pI(t).catch(()=>{})}function ku(t){let e=Qt.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 sy(t){let e=Qt.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 lI(t,e,n){if(await Or(t,"snapshot"),t.repos){for(let s of t.repos)s.base=await Mo(s.leadDir,`bostrat: snapshot for subtask ${t.id}`);await Cr(t)}else t.base=await Mo(e.cwd,`bostrat: snapshot for subtask ${t.id}`);if(await Or(t,"spawn"),await U.mkdir(Mr,{recursive:!0}),t.repos){await U.mkdir(t.dir,{recursive:!0});for(let a of t.repos)await Sr(a.leadDir,H.join(t.dir,a.name),t.branch,a.base);let s=Nt((nt(await q(),t.trackId)||{}).workspace);await Wa(t.dir,t.repos.map(a=>{let i=s.find(c=>c?.name===a.name);return{name:a.name,repo:i?.repo??null,baseRef:null}}),t.branch)}else await Sr(e.cwd,t.dir,t.branch,t.base),await cu(t.dir);await uI(t,e);let r=t.trackName.slice(0,38).trim(),o=await gn(`${r} ${t.agent} sub ${t.id}`,t.dir,{agentCmd:n,agent:t.agent});t.box=o.name,await dI(t,o),await vg(t.trackId,o.name,h_(t.task,!!t.repos),{kind:"subtask"}),await ay(t,e)}async function ay(t,e){await Or(t,"working");let n=H.join(t.dir,Ba);if(await hI(n,t),t.canceled)throw new Vo;if(await Or(t,"merge"),t.repos){let o={files:0,insertions:0,deletions:0},s=[],a=[],i=!0;for(let c of t.repos){let l=H.join(t.dir,c.name);await wl(l,`bostrat: subtask ${t.id} result (auto-committed)`),c.stat=await kl(c.leadDir,c.base,t.branch,$r),c.files=await xl(c.leadDir,c.base,t.branch,$r);let u=await Sl(c.leadDir,c.base,t.branch,$r);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=>H.join(c.name,f))),a.push(...c.conflicts.map(f=>f.startsWith("patch did not apply cleanly")?`${c.name}: ${f}`:H.join(c.name,f))),u.applied||(i=!1)}t.stat=o,t.files=s,t.conflicts=a,t.applied=i}else{await wl(t.dir,`bostrat: subtask ${t.id} result (auto-committed)`),t.stat=await kl(e.cwd,t.base,t.branch,$r),t.files=await xl(e.cwd,t.base,t.branch,$r);let o=await Sl(e.cwd,t.base,t.branch,$r);t.applied=o.applied,t.conflicts=o.conflicts}await Cr(t);let r=await mI(t,e,n);await Or(t,"resume"),await vg(t.trackId,t.leadBox,w_(t.task,t.stat,t.conflicts,r),{kind:"subtask-resume"}),await Or(t,"reap"),await xu(t),t.step="done",t.finishedAt=Date.now(),await fI(t),await Cr(t)}async function uI(t,e){let n=H.join(t.dir,H.dirname(Wo));await U.mkdir(n,{recursive:!0});let r="";try{r=await Gd(e.cwd,e.sessionId)}catch{}if(!r)try{r=await U.readFile(H.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}
|
|
349
371
|
|
|
350
372
|
**Delegated task:** ${t.task}
|
|
351
373
|
|
|
@@ -356,48 +378,49 @@ ${a}`}async function lu(t){if(t.archivedAt)return!1;let e=t.boxes.find(r=>r.name
|
|
|
356
378
|
## Conversation so far (lead \u2192 you)
|
|
357
379
|
|
|
358
380
|
${r}
|
|
359
|
-
`;await U.writeFile(H.join(t.dir,
|
|
360
|
-
${n}`}},{type:"section",text:{type:"mrkdwn",text:r}},{type:"context",elements:[{type:"mrkdwn",text:`<${
|
|
381
|
+
`;await U.writeFile(H.join(t.dir,Wo),o)}async function dI(t,e){let n=await q(),r=nt(n,t.trackId);r&&(r.boxes.push({name:e.name,agent:e.agent,role:"subtask",state:"live",cwd:e.cwd,sessionId:e.sessionId,hookTokenHash:zr(e.hookToken),createdAt:Date.now(),subtaskId:t.id,branch:t.branch,task:t.task}),ct(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]||{},...yu(t)},await z())}async function fI(t){let e=await q(),n=nt(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),ct(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 z()}async function pI(t){let e=await q(),n=nt(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"),ct(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]||{},...yu(t)},await z()}async function mI(t,e,n){let r=`.bostrat/handoff/subtask-${t.id}.md`;try{let o=H.join(e.cwd,r);await U.mkdir(H.dirname(o),{recursive:!0}),await U.copyFile(n,o)}catch{}return r}async function xu(t){if(t.box&&await oe(t.box).catch(()=>{}),t.repos&&t.dir){for(let e of t.repos)await Gt(e.leadDir,H.join(t.dir,e.name)).catch(()=>{});await U.rm(t.dir,{recursive:!0,force:!0}).catch(()=>{})}else if(t.dir){let n=(await Y(t.leadBox).catch(()=>null))?.cwd;n&&await Gt(n,t.dir).catch(()=>{})}}async function hI(t,e){let n=(e.startedAt||Date.now())+nI;for(;;){if(e.canceled||e.forceMerge)return;try{await U.access(t);return}catch{}if(Date.now()>n)throw new Error("subtask timed out without signaling completion (no result digest written)");await Br(rI)}}function Br(t){return new Promise(e=>setTimeout(e,t))}import Yo from"node:fs/promises";import Ga from"node:path";import kI from"node:os";import wI from"ws";var iy=()=>process.env.BOSTRAT_SLACK_API_BASE||"https://slack.com/api";async function qa(t,e,n={}){let r=await fetch(`${iy()}/${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 Su=(t,e)=>qa("chat.postMessage",t,e),Dr=(t,e)=>qa("chat.update",t,e);async function Pt(t,e){await fetch(t,{method:"POST",headers:{"content-type":"application/json; charset=utf-8"},body:JSON.stringify(e)}).catch(()=>{})}async function gI(t,e,n={}){let r=await fetch(`${iy()}/${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 cy(t,{filename:e,buffer:n,title:r,channelId:o,threadTs:s,initialComment:a}){let{upload_url:i,file_id:c}=await gI("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 qa("files.completeUploadExternal",t,{files:[{id:c,title:r}],channel_id:o,thread_ts:s,...a?{initial_comment:a}:{}})}var yI=2200;function ly({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 qa("apps.connections.open",u))}catch(u){return n(`connections.open failed: ${u?.message||u}`),c()}o=new wI(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{}},m=!1,h=setTimeout(()=>{m=!0,d()},yI);Promise.resolve().then(()=>e(f)).then(w=>{clearTimeout(h),!m&&(m=!0,d(f.accepts_response_payload?w:void 0))},w=>{clearTimeout(h),m||(m=!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 fy=()=>process.env.BOSTRAT_STATE_DIR||Ga.join(process.env.HOME||kI.tmpdir(),".bostrat"),_u=()=>Ga.join(fy(),"slack.json"),Ka=null,vu=null,Tu=Promise.resolve();async function Ru(){return Ka||(vu??=Yo.readFile(_u(),"utf8").then(t=>JSON.parse(t)?.threads??{}).catch(()=>({})).then(t=>Ka=t).finally(()=>vu=null),vu)}async function uy(){let t=JSON.stringify({threads:Ka},null,2),e=`${_u()}.${process.pid}.tmp`;await Yo.mkdir(fy(),{recursive:!0}),await Yo.writeFile(e,t),await Yo.rename(e,_u())}async function Ce(){Tu=Tu.then(uy,uy),await Tu.catch(()=>{})}async function $u(t){return(await Ru())[t]??null}async function xI(t,e){let n=await Ru();for(let[r,o]of Object.entries(n))if(o.channel===t&&o.ts===e)return{trackId:r,rec:o};return null}var X=t=>String(t??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");function Zo(t,e){let n=String(t??"").trim();return n.length<=e?n:`${n.slice(0,e-1).trimEnd()}\u2026`}function SI(t,e=500){let n=String(t??"").trim().split(/\n\s*\n/)[0]??"";return Zo(n.replace(/\n+/g," "),e)}function py(t){let e=`*${X(t.name)}*${t.archivedAt?" \xB7 _archived_":""}`,n=`${X(t.repoLabel)} \xB7 \`${X(t.branch||"?")}\` \xB7 lead *${X(t.lead)}*`,r=t.kickoff?`> ${X(Zo(t.kickoff,300))}`:"_no kickoff yet \u2014 reply in this thread to steer the lead_";return[{type:"section",text:{type:"mrkdwn",text:`${e}
|
|
382
|
+
${n}`}},{type:"section",text:{type:"mrkdwn",text:r}},{type:"context",elements:[{type:"mrkdwn",text:`<${na(t.trackId)}|open the Track> \xB7 reachable on the operator's private network only`}]}]}async function Iu(t,e){await Dr(t.botToken,{channel:e.channel,ts:e.ts,text:`Track: ${e.name}`,blocks:py(e)})}async function Un(t,e,n,r=void 0){return Su(t.botToken,{channel:e.channel,thread_ts:e.ts,text:n,...r?{blocks:r}:{},unfurl_links:!1})}var dy=new Set;function Fn(t){dy.add(t),t.catch(()=>{}).finally(()=>dy.delete(t))}var pt=t=>({response_type:"ephemeral",text:t}),vI="*/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._",TI=["claude","cursor","codex"];function Au(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 Eu=(t,e)=>pt(`No live Track matches \u201C${X(e)}\u201D. Live: ${t.map(n=>`\`${X(n.name)}\``).join(" \xB7 ")||"none"}`);async function my(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 Pt(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 Pt(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?Ga.basename(c.base):"fresh workspace"),lead:"claude"},u=await Su(e.botToken,{channel:e.channel,text:`Track started: ${l.name}`,blocks:py(l),unfurl_links:!1});l.ts=u.ts,(await Ru())[i.id]=l,await Ce(),await Pt(o,{...pt(`\u2713 *${X(l.name)}* started \u2014 root message posted.`),replace_original:!0})}catch(a){await Pt(o,{...pt(`Start failed: ${a?.message||a}`),replace_original:!0})}}async function AI(t,e,{track:n,responseUrl:r}){let o=t.deps;try{await o.archiveTrack(n.id);let s=await $u(n.id);s&&(s.archivedAt=Date.now(),await Ce(),await Iu(e,s).catch(()=>{})),await Pt(r,{...pt(`\u2713 *${X(n.name)}* archived.`),replace_original:!0})}catch(s){await Pt(r,{...pt(`Archive failed: ${s?.message||s}`),replace_original:!0})}}async function EI(t,e,{track:n,agent:r,responseUrl:o}){try{await t.deps.startLeadHandoff(n.id,r),await Pt(o,{...pt(`\u2713 Handing *${X(n.name)}* to ${X(r)} \u2014 the root message updates when the baton lands.`),replace_original:!0})}catch(s){await Pt(o,{...pt(`Handoff failed: ${s?.message||s}`),replace_original:!0})}}async function _I(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 Pt(r,{...pt(`\u2713 Stopped *${X(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 Pt(r,{...pt(`Stop failed: ${s?.message||s}`),replace_original:!0})}}function II(t,e){let n=[...e.slice(0,99).map(r=>({text:{type:"plain_text",text:Zo(`${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 *${X(t)}*:`},accessory:{type:"static_select",action_id:"bostrat-start-repo",placeholder:{type:"plain_text",text:"Choose a repo"},options:n}}]}async function RI(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?Ga.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 *${X(s.name)}* \u2014 ${X(i)}${a.branch?` \`${X(a.branch)}\``:""} \xB7 lead ${X(c)} \xB7 ${l}`}),o=e.length-n.length;return r.join(`
|
|
361
383
|
`)+(o?`
|
|
362
|
-
_${o} archived_`:"")}async function
|
|
363
|
-
\u2713 *${X(a)}*`}}]}).catch(()=>{})}}catch(c){await Pt(n.response_url,
|
|
364
|
-
\u2713 _answered_`}}]})}async function
|
|
365
|
-
_runs on the Track's branch; reachable on the operator's private network only_`}}]);return}case"mock.state.changed":return
|
|
366
|
-
`)){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 fI(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 pI(t,e){return t?.transcript_path?t.transcript_path:e?.cwd&&e?.sessionId?xe(e.cwd,e.sessionId):e?.cwd?Ci(e.cwd):null}async function zg(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 O(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 mI(r,o,t,i);break;default:break}return a}async function mI(t,e,n,r){let o=[],s=pI(n,e);if(s)try{o=dI(await uI.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=fI(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",m=u.get(d)||{tokens:{input:0,output:0,cacheWrite:0,cacheRead:0},messageIds:[]};for(let h of Object.keys(m.tokens))m.tokens[h]+=f.tokens[h];m.messageIds.push(f.messageId),u.set(d,m)}for(let[f,d]of u)await r({type:"cost.recorded",actor:"system",source:"transcript",payload:{model:f,usd:oe(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 gI from"node:fs";import Tu from"node:fs/promises";import yI from"node:http";import Cr from"node:path";import Wa from"node:os";import{createRequire as bI}from"node:module";import Au from"ws";import hI from"node:path";import wI from"node:os";var Vg=process.env.BOSTRAT_STATE_DIR||hI.join(process.env.HOME||wI.tmpdir(),".bostrat");function Jg(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 Xg(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 Ha=(()=>{try{return bI(import.meta.url)("../package.json").version||null}catch{return null}})(),kI=process.env.BOSTRAT_STATE_DIR||Cr.join(process.env.HOME||Wa.tmpdir(),".bostrat");function xI(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 SI({home:t=process.env.HOME||Wa.homedir(),env:e=process.env}={}){let n=r=>{try{return gI.statSync(r),!0}catch{return!1}};return{claude:n(Cr.join(t,".claude",".credentials.json")),codex:n(Cr.join(e.CODEX_HOME||Cr.join(t,".codex"),"auth.json"))}}var It=1,Yg=1e3,vI=3e4,TI=6e4,AI=1500,_I=500,Zg=100,EI=2e3,II=25e3;function Qg({url:t,token:e,nodeId:n=Wa.hostname(),name:r=Wa.hostname(),listTracks:o,detectAgents:s=null,selfUrl:a=`http://127.0.0.1:${process.env.PORT||3362}`,log:i=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=Xg(t);let c=a.replace(/^http/,"ws"),l=new Map,u=new Map,f=Cr.join(kI,"plane-link","cursor.json"),d={},m=new Map,h=null,w=!1,g=!1,b=Yg,v=null,A=null,M=null,z=null,$=!1;async function F(){try{d=JSON.parse(await Tu.readFile(f,"utf8"))}catch{d={}}}function T(){$=!0,!M&&(M=setTimeout(async()=>{if(M=null,!!$){$=!1;try{await Tu.mkdir(Cr.dirname(f),{recursive:!0}),await Tu.writeFile(f,JSON.stringify(d))}catch(I){i.error("[plane-link] cursor flush failed:",I?.message||I)}}},EI),M.unref?.())}function C(I){return!h||h.readyState!==Au.OPEN?!1:(h.send(JSON.stringify(I)),!0)}function W(I){for(let{trackId:R,event:E}of I)m.set(E.id,{trackId:R,seq:E.seq});return C({v:It,ch:"events",type:"append",events:I})}async function ot(){let I;try{I=await o()}catch(R){i.error("[plane-link] backfill listTracks failed:",R?.message||R);return}C({v:It,ch:"ctl",type:"tracks",ids:I.map(R=>R.id).filter(R=>typeof R=="string")});for(let R of I){if(!w)return;let E;try{E=await L(R)}catch{continue}let P=d[R.id]??0,et=E.filter(wt=>wt.seq>P&&typeof wt.id=="string");for(let wt=0;wt<et.length;wt+=Zg){let bt=et.slice(wt,wt+Zg).map(Rt=>({trackId:R.id,event:Rt}));if(!W(bt))return}}}function y(I){!w||!I?.trackId||typeof I.id!="string"||W([{trackId:I.trackId,event:I}])}let p=new Map;async function S(I){let{reqId:R,method:E,path:P,headers:et={},body:wt}=I,bt=Object.fromEntries(Object.entries(et).filter(([,Ht])=>Ht!=null)),Rt=new AbortController;p.set(R,Rt);try{if(I.host){await _(I,bt,Rt);return}let Ht=await fetch(a+P,{method:E,headers:bt,body:wt?Buffer.from(wt,"base64"):void 0,signal:Rt.signal}),zt=Object.fromEntries(Ht.headers);for(let k of["connection","keep-alive","transfer-encoding","upgrade","content-encoding","content-length"])delete zt[k];if(zt["content-type"]||(zt["content-type"]="application/octet-stream"),C({v:It,ch:"control",type:"http_head",reqId:R,status:Ht.status,headers:zt}),Ht.body){for await(let k of Ht.body)if(!C({v:It,ch:"control",type:"http_data",reqId:R,data:Buffer.from(k).toString("base64")}))break}C({v:It,ch:"control",type:"http_end",reqId:R,error:null})}finally{p.delete(R)}}function _(I,R,E){let{reqId:P,method:et,path:wt,body:bt}=I,Rt=new URL(a);return new Promise(Ht=>{let zt=yI.request({host:Rt.hostname,port:Rt.port,method:et,path:wt,headers:{...R,host:I.host},signal:E.signal},k=>{let{connection:K,"keep-alive":Ln,"transfer-encoding":os,upgrade:Fn,...si}=k.headers;C({v:It,ch:"control",type:"http_head",reqId:P,status:k.statusCode||502,headers:si}),k.on("data",Cb=>{C({v:It,ch:"control",type:"http_data",reqId:P,data:Buffer.from(Cb).toString("base64")})||k.destroy()}),k.on("end",()=>{C({v:It,ch:"control",type:"http_end",reqId:P,error:null}),Ht()}),k.on("error",()=>{C({v:It,ch:"control",type:"http_end",reqId:P,error:"view upstream error"}),Ht()})});zt.on("error",k=>{C({v:It,ch:"control",type:"http_end",reqId:P,error:k?.message||"view request failed"}),Ht()}),bt&&zt.write(Buffer.from(bt,"base64")),zt.end()})}function rt(){if(g)return;h=new Au(t,{headers:{authorization:`Bearer ${e}`}}),h.on("open",async()=>{let R=null;try{R=await s?.()||null}catch{R=null}C({v:It,ch:"ctl",type:"hello",nodeId:n,name:r,platform:process.platform,version:Ha,providers:SI(),...R?{agents:R}:{}})}),h.on("message",R=>{let E;try{E=JSON.parse(R)}catch{return}if(E.ch==="ctl"&&E.type==="hello_ok"){w=!0,b=Yg,v=null,i.log(`[plane-link] connected to plane as ${n}`),E.minNodeVersion&&Ha&&xI(Ha,E.minNodeVersion)&&i.error(`[plane-link] this node is bostrat-node ${Ha}; the plane asks for \u2265${E.minNodeVersion} \u2014 update with: npm install -g bostrat-node`),z&&clearInterval(z),z=setInterval(()=>C({v:It,ch:"ctl",type:"ping"}),II),z.unref?.(),ot().catch(P=>i.error("[plane-link] backfill failed:",P?.message||P));return}if(E.ch==="events"&&E.type==="ack"&&Array.isArray(E.ids)){for(let P of E.ids){let et=m.get(P);et&&(m.delete(P),(d[et.trackId]??0)<et.seq&&(d[et.trackId]=et.seq))}T();return}if(E.ch==="control"&&E.type==="http_open"){S(E).catch(P=>C({v:It,ch:"control",type:"http_end",reqId:E.reqId,error:P?.message||"tunnel error"}));return}if(E.ch==="control"&&E.type==="http_cancel"){p.get(E.reqId)?.abort();return}if(E.ch==="attach"&&E.type==="open"){let P=new Au(c+E.path,Array.isArray(E.protocols)?E.protocols:[],E.host?{headers:{host:E.host}}:void 0);l.set(E.streamId,P),u.set(E.streamId,[]),P.on("open",()=>{let wt=u.get(E.streamId)||[];u.delete(E.streamId);for(let{buf:bt,bin:Rt}of wt)P.send(bt,{binary:Rt})}),P.on("message",(wt,bt)=>{let Rt=bt?wt:Buffer.from(String(wt));C({v:It,ch:"attach",type:"data",streamId:E.streamId,data:Rt.toString("base64"),...bt?{bin:!0}:{}})});let et=()=>{u.delete(E.streamId),l.delete(E.streamId)&&C({v:It,ch:"attach",type:"close",streamId:E.streamId})};P.on("close",et),P.on("error",et);return}if(E.ch==="attach"&&E.type==="data"){let P=l.get(E.streamId);if(!P)return;let et=Buffer.from(E.data,"base64"),wt=E.bin===!0,bt=u.get(E.streamId);bt?bt.push({buf:et,bin:wt}):P.send(et,{binary:wt});return}if(E.ch==="attach"&&E.type==="close"){let P=l.get(E.streamId);l.delete(E.streamId),u.delete(E.streamId),P?.close();return}});let I=()=>{w&&i.log("[plane-link] disconnected from plane"),w=!1,z&&(clearInterval(z),z=null),m.clear();for(let P of l.values())P.close();l.clear(),u.clear();for(let P of p.values())P.abort();if(p.clear(),g||A)return;v==null&&(v=Date.now());let R=Date.now()-v<TI,E=R?AI+Math.random()*_I:b;A=setTimeout(()=>{A=null,rt()},E),A.unref?.(),R||(b=Math.min(b*2,vI))};h.on("close",I),h.on("error",I)}return Wt.on("event",y),F().then(rt),{stop:()=>{g=!0,Wt.off("event",y),A&&clearTimeout(A),M&&clearTimeout(M),z&&clearInterval(z),h?.close()},status:()=>({connected:w,nodeId:n,cursor:{...d},inflight:m.size})}}function Eu(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 ty=()=>({input:0,output:0,cacheRead:0,cacheCreation:0}),ey=(t,e)=>{for(let n of Object.keys(t))t[n]+=e?.[n]||0},_u=t=>Math.round(t*1e4)/1e4;async function ny(t){let e=[],n={},r={},o=0,s=0,a=0,i=0,c=new Set,l=ty(),u=0,f=[];for(let d of t||[]){let m=await L(d),h=se(m,{boxLabels:Eu(d)});if(h.turns===0){d.archivedAt?u++:f.push(d.name||d.id);continue}o+=h.totalUsd,s+=h.turns,a+=h.repricedTurns||0,i+=h.unpricedTurns||0;for(let w of h.unpricedModels||[])c.add(w);ey(l,h.totalTokens);for(let[w,g]of Object.entries(h.byModel))n[w]||={usd:0,events:0,tokens:ty()},n[w].usd=_u(n[w].usd+g.usd),n[w].events+=g.events,ey(n[w].tokens,g.tokens);for(let[w,g]of Object.entries(h.byProvider))r[w]||={usd:0,events:0},r[w].usd=_u(r[w].usd+g.usd),r[w].events+=g.events;e.push({id:d.id,name:d.name,usd:h.totalUsd,rollup:h})}return e.sort((d,m)=>m.usd-d.usd),{generatedAt:new Date().toISOString(),pricingBasis:"api-equivalent (subscription usage priced at public API rates)",fleet:{totalUsd:_u(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 Ko=t=>`$${t.toFixed(4)}`,Nr=(t,e)=>String(t).padEnd(e);function ry(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 ${Ko(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(` ${Nr(a,26)} ${Nr(Ko(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(` ${Nr(a.name||a.id,30)} ${Ko(a.usd)}`);let i=Object.values(a.rollup.byBox).sort((c,l)=>l.usd-c.usd);for(let c of i)if(r.push(` ${Nr(c.label,40)} ${Nr(Ko(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))}${Nr(Ko(u.usd),10)} ${u.models.join(",")||"?"}`)}return r.join(`
|
|
367
|
-
`)}import CI from"node:fs/promises";import NI from"node:path";var qa=t=>t.payload?.ts??t.ts,RI=12e4,sn=t=>Math.round(t*1e4)/1e4,$I=()=>({usd:0,activeMs:0,turns:0,toolCalls:0,models:{},providers:{},byTrack:new Map});function OI(t,{since:e}){let{track:n,events:r,brief:o}=t,s=v=>qa(v)>=e,a=Xn([eo(n?.name),eo(n?.workspace?.branch),o].filter(Boolean).join(" ")),i=new Map;for(let v of r)if(v.type==="subtask.delegated"&&v.boxId&&v.payload?.task){let A=Xn(v.payload.task);A&&i.set(v.boxId,A)}let c=new Map,l=v=>{let A=v||"unattributed";return c.has(A)||c.set(A,{segments:[u(null)],lastTs:null,provider:null}),c.get(A)},u=v=>({textCat:v,tools:{},usd:0,tokens:{input:0,output:0,cacheRead:0,cacheWrite:0},models:{},provider:null,activeMs:0,turns:0,toolCalls:0}),f=[...r].sort((v,A)=>qa(v)-qa(A)),d=0,m=0,h=0,w={turns:0};for(let v of f){let A=l(v.boxId),M=A.segments[A.segments.length-1],z=qa(v);switch(A.lastTs!=null&&z>A.lastTs&&s(v)&&(M.activeMs+=Math.min(z-A.lastTs,RI)),A.lastTs=z,v.provider&&(A.provider=v.provider),v.type){case"turn.user":{let $=v.payload?.text;if(v.source==="hook"||!String($||"").trim())break;let F=u(Xn($));A.segments.push(F),s(v)&&(F.turns+=1);break}case"tool.called":{if(v.source==="transcript")break;let $=v.payload?.tool;$&&(M.tools[$]=(M.tools[$]||0)+1),s(v)&&(M.toolCalls+=1);break}case"cost.recorded":{if(!s(v))break;let $=v.payload||{},F={input:$.tokens?.input||0,output:$.tokens?.output||0,cacheRead:$.tokens?.cacheRead||0,cacheWrite:$.tokens?.cacheWrite??$.tokens?.cacheCreation??0},T=Number($.usd)||0;!T&&$.tokens&&(T=oe($.model,F)),!T&&$.tokens&&!Ns($.model)&&(w.turns+=1),M.usd+=T,M.tokens.input+=F.input,M.tokens.output+=F.output,M.tokens.cacheRead+=F.cacheRead,M.tokens.cacheWrite+=F.cacheWrite;let C=$.model||"unknown";M.models[C]=(M.models[C]||0)+T,v.provider&&(M.provider=v.provider);break}case"handoff.done":s(v)&&(d+=1);break;case"subtask.merged":s(v)&&(m+=1);break;case"error":s(v)&&v.payload?.kind==="subtask-failed"&&(h+=1);break;default:break}}let g=new Map,b=!1;for(let[v,A]of c)for(let M of A.segments){if(!(M.usd>0||M.activeMs>0||M.turns>0||M.toolCalls>0))continue;let $=M.textCat?.category??i.get(v)?.category??nf(M.tools)?.category??a?.category??"uncategorized";g.has($)||g.set($,{usd:0,activeMs:0,turns:0,toolCalls:0,models:{},providers:{}});let F=g.get($);F.usd+=M.usd,F.activeMs+=M.activeMs,F.turns+=M.turns,F.toolCalls+=M.toolCalls;for(let[C,W]of Object.entries(M.models))F.models[C]=(F.models[C]||0)+W;let T=M.provider||A.provider;T&&(F.providers[T]=(F.providers[T]||0)+M.usd),T==="cursor"&&M.usd===0&&(b=!0)}return{perCategory:g,handoffs:d,merged:m,failed:h,sawCursorActivity:b,unpricedTurns:w.turns}}function sy(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:g,events:b}=w;if(!b?.length){a+=1;continue}let{perCategory:v,handoffs:A,merged:M,failed:z,sawCursorActivity:$,unpricedTurns:F}=OI(w,{since:r});$&&(i=!0),c+=F;let T={trackId:g?.id,name:g?.name||g?.id,archived:!!g?.archivedAt,usd:0,activeMs:0,turns:0,categories:{},models:{},providers:{},handoffs:A,subtasksMerged:M,subtasksFailed:z};for(let[C,W]of v){T.usd+=W.usd,T.activeMs+=W.activeMs,T.turns+=W.turns,T.categories[C]={usd:sn(W.usd),activeMs:W.activeMs};for(let[p,S]of Object.entries(W.models))T.models[p]=sn((T.models[p]||0)+S);for(let[p,S]of Object.entries(W.providers))T.providers[p]=sn((T.providers[p]||0)+S);o.has(C)||o.set(C,$I());let ot=o.get(C);ot.usd+=W.usd,ot.activeMs+=W.activeMs,ot.turns+=W.turns,ot.toolCalls+=W.toolCalls;for(let[p,S]of Object.entries(W.models))ot.models[p]=(ot.models[p]||0)+S;for(let[p,S]of Object.entries(W.providers))ot.providers[p]=(ot.providers[p]||0)+S;let y=ot.byTrack.get(g?.id)||{usd:0,activeMs:0};y.usd+=W.usd,y.activeMs+=W.activeMs,ot.byTrack.set(g?.id,y)}(T.usd>0||T.activeMs>0||T.turns>0)&&(T.usd=sn(T.usd),T.dominantCategory=MI(T.categories),s.push(T))}let l=[...o.values()].reduce((w,g)=>w+g.usd,0),u=[...o.values()].reduce((w,g)=>w+g.activeMs,0),f=l>0?"usd":"time",d=f==="usd"?l:u,m=new Map((t||[]).map(w=>[w.track?.id,w.track?.name||w.track?.id])),h=[...o.entries()].map(([w,g])=>({category:w,sharePct:d>0?Math.round((f==="usd"?g.usd:g.activeMs)/d*100):0,shareBasis:f,usd:sn(g.usd),activeMs:g.activeMs,turns:g.turns,toolCalls:g.toolCalls,models:oy(g.models),providers:oy(g.providers),topWorkstreams:[...g.byTrack.entries()].sort((b,v)=>v[1].usd-b[1].usd||v[1].activeMs-b[1].activeMs).slice(0,2).map(([b,v])=>({trackId:b,name:m.get(b),usd:sn(v.usd),activeMs:v.activeMs}))})).sort((w,g)=>g.usd-w.usd||g.activeMs-w.activeMs);return s.sort((w,g)=>g.usd-w.usd||g.activeMs-w.activeMs),{since:r,capturedAt:n,totalUsd:sn(l),activeMs:u,turns:[...o.values()].reduce((w,g)=>w+g.turns,0),bullets:h,workstreams:s,gaps:{tracksWithoutEvents:a,cursorCostUnknown:i,unpricedTurns:c}}}var MI=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},oy=t=>Object.fromEntries(Object.entries(t).map(([e,n])=>[e,sn(n)]));var Pr={"24h":24*36e5,"7d":168*36e5,"30d":720*36e5,all:null};function PI(t){let e=String(t||"7d");return e in Pr?{key:e,ms:Pr[e]}:{key:"7d",ms:Pr["7d"]}}function BI(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=ha(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 DI(t){if(!t?.cwd)return null;try{let n=(await CI.readFile(NI.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 ay(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}:PI(e),f=[];for(let d of t||[]){let m=await L(d),h=BI(d,m);i&&c<o&&(h=h.filter(w=>(w.ts??0)<=c)),f.push({track:d,events:h,brief:await DI(d)})}return{window:l,...sy(f,{windowMs:u,now:c})}}var an=36e5,uy=24*an,jI=400,LI=12,Br=t=>Math.round(t*1e4)/1e4;function FI({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 Pr?String(t||"7d"):"7d",i=Pr[a];return{key:a,since:i==null?null:r-i,until:r}}var UI=(t,e)=>t!=null&&e-t<=48*an?an:uy;function iy(t,e){if(e===an)return Math.floor(t/an)*an;let n=new Date(t);return n.setHours(0,0,0,0),n.getTime()}function HI(t,e){if(e===an)return t+an;let n=new Date(t);return n.setDate(n.getDate()+1),n.setHours(0,0,0,0),n.getTime()}var cy=()=>({input:0,output:0,cacheRead:0,cacheCreation:0}),ly=(t,e)=>{for(let n of Object.keys(t))t[n]+=e?.[n]||0},WI=t=>Object.fromEntries(Object.entries(t).map(([e,n])=>[e,Br(n)]));async function dy(t,e={}){let n=e.now??Date.now(),r=FI({...e,now:n}),o=r.since==null?uy:UI(r.since,r.until),s=T=>(r.since==null||T>=r.since)&&T<r.until,a=r.since!=null?r.until-r.since:null,i=T=>a!=null&&T>=r.since-a&&T<r.since,c={},l={},u=cy(),f=0,d=0,m=0,h=0,w=0,g=new Set,b=new Map,v=[],A=[],M=[],z=null;for(let T of t||[]){let W=(await L(T)).filter(p=>p.type==="cost.recorded");if(!W.length){T.archivedAt||M.push(T.name||T.id);continue}let ot=[];for(let p of W){let S=p.ts??0;s(S)?ot.push(p):i(S)&&(w+=Bs(p).usd)}if(!ot.length)continue;let y=se(ot,{boxLabels:Eu(T)});f+=y.totalUsd,d+=y.turns,m+=y.repricedTurns||0,h+=y.unpricedTurns||0;for(let p of y.unpricedModels||[])g.add(p);ly(u,y.totalTokens);for(let[p,S]of Object.entries(y.byModel))c[p]||={usd:0,events:0,tokens:cy()},c[p].usd=Br(c[p].usd+S.usd),c[p].events+=S.events,ly(c[p].tokens,S.tokens);for(let[p,S]of Object.entries(y.byProvider))l[p]||={usd:0,events:0},l[p].usd=Br(l[p].usd+S.usd),l[p].events+=S.events;for(let p of ot){let{usd:S}=Bs(p);if(!S)continue;let _=iy(p.ts??0,o),rt=b.get(_)??{t:_,total:0,byProvider:{}};rt.total+=S;let I=p.provider||"unknown";rt.byProvider[I]=(rt.byProvider[I]||0)+S,b.set(_,rt),(z==null||_<z)&&(z=_)}v.push({id:T.id,name:T.name||T.id,archived:!!T.archivedAt,usd:y.totalUsd,turns:y.turns,providers:Object.keys(y.byProvider)});for(let[p,S]of Object.entries(y.byBox))A.push({trackId:T.id,trackName:T.name||T.id,boxId:p,...S})}let $=r.since!=null?iy(r.since,o):z,F=[];for(let T=$,C=0;T!=null&&T<r.until&&C<jI;T=HI(T,o),C++){let W=b.get(T);F.push(W?{t:T,total:Br(W.total),byProvider:WI(W.byProvider)}:{t:T,total:0,byProvider:{}})}return v.sort((T,C)=>C.usd-T.usd),A.sort((T,C)=>C.usd-T.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:F,fleet:{totalUsd:Br(f),turns:d,repricedTurns:m,unpricedTurns:h,unpricedModels:[...g],totalTokens:u,byModel:c,byProvider:l,tracksWithCost:v.length,tracksSilentLive:M},prior:a!=null?{totalUsd:Br(w),since:r.since-a,until:r.since}:null,tracks:v.slice(0,50),boxes:A.slice(0,LI)}}import jr from"node:fs/promises";import qI from"node:fs";import pt from"node:path";import KI from"node:os";import{execFile as GI}from"node:child_process";import{promisify as zI}from"node:util";var VI=zI(GI),fy=process.env.BOSTRAT_STATE_DIR||pt.join(process.env.HOME||KI.tmpdir(),".bostrat"),Iu=!1;async function Go(t,e){let n=await jr.stat(t).catch(()=>null);if(!n?.isFile())return!1;let r=await jr.stat(e).catch(()=>null);return r&&r.size===n.size&&r.mtimeMs>=n.mtimeMs?!1:(await jr.mkdir(pt.dirname(e),{recursive:!0}),await jr.copyFile(t,e),!0)}var JI=new Set([".claude",".codex",".credentials.json","auth.json",".claude.json","integrations.json"]);async function Dr(t,e){let n=0,r=await jr.readdir(t,{withFileTypes:!0}).catch(()=>[]);for(let o of r){if(JI.has(o.name))continue;let s=pt.join(t,o.name);o.isDirectory()?n+=await Dr(s,pt.join(e,o.name)):o.isFile()&&(n+=await Go(s,pt.join(e,o.name))?1:0)}return n}async function py({stateDir:t=fy,dest:e=process.env.BOSTRAT_SYNC_DIR||`${fy}-sync`,remote:n=process.env.BOSTRAT_STATE_REMOTE||null,tracks:r=null}={}){try{await jr.mkdir(e,{recursive:!0});let o=0;o+=await Go(pt.join(t,"tracks.json"),pt.join(e,"tracks.json"))?1:0,o+=await Go(pt.join(t,"mocks.json"),pt.join(e,"mocks.json"))?1:0,o+=await Go(pt.join(t,"runbooks.json"),pt.join(e,"runbooks.json"))?1:0,o+=await Dr(pt.join(t,"runbooks"),pt.join(e,"runbooks")),o+=await Dr(pt.join(t,"mock-archive"),pt.join(e,"mock-archive")),o+=await Go(pt.join(t,"events.jsonl"),pt.join(e,"events.jsonl"))?1:0,o+=await Dr(pt.join(t,"events"),pt.join(e,"events")),o+=await Dr(pt.join(t,"memory"),pt.join(e,"memory"));for(let l of r||[])!l?.cwd||!l?.id||(o+=await Dr(pt.join(l.cwd,".bostrat"),pt.join(e,"workspaces",l.id,".bostrat")));let s=(...l)=>VI("git",["-C",e,...l]);qI.existsSync(pt.join(e,".git"))||(await s("init","-q"),await s("config","user.email","agent@bostrat.local"),await s("config","user.name","bostrat state sync")),n&&/^https:\/\/github\.com\//i.test(n)&&(await s("config","--get","credential.useHttpPath").then(()=>!0).catch(()=>!1)||await pi(e).catch(()=>{}));let a=!1,{stdout:i}=await s("status","--porcelain");i.trim()&&(await s("add","-A"),await s("commit","-q","-m",`state sync ${new Date().toISOString()}`),a=!0);let c=!1;if(n&&(a||Iu))try{await s("push","-q",n,"HEAD:refs/heads/main"),c=!0,Iu=!1}catch{Iu=!0}return{staged:o,committed:a,pushed:c}}catch(o){return{staged:0,committed:!1,pushed:!1,error:o?.message||String(o)}}}import{execFile as XI}from"node:child_process";import{promisify as YI}from"node:util";import{randomUUID as ZI}from"node:crypto";import QI from"node:fs";import tR from"node:os";import eR from"node:path";var ln=YI(XI),Ka=t=>new Promise(e=>setTimeout(e,t)),un=Ai,Ru=process.env.BOSTRAT_USAGE_PROBE_CWD||eR.join(tR.homedir(),".bostrat","usage-probe"),nR=Number(process.env.BOSTRAT_USAGE_IDLE_REAP_MS)||5*6e4,rR=Number(process.env.BOSTRAT_USAGE_PANEL_TIMEOUT_MS)||15e3,oR=Number(process.env.BOSTRAT_USAGE_READY_TIMEOUT_MS)||45e3,my=Number(process.env.BOSTRAT_USAGE_CACHE_MS)||2e4,cn=null,zo=null,Vo=null;async function sR(){try{return await ln(N,B("has-session","-t",un)),!0}catch{return!1}}function aR(){cn&&clearTimeout(cn),cn=setTimeout(()=>iR().catch(()=>{}),nR),cn.unref&&cn.unref()}async function iR(){cn&&(clearTimeout(cn),cn=null);try{await ln(N,B("kill-session","-t",un))}catch{}}async function cR(){let t=Date.now()+oR;for(;;){let e=await $u().catch(()=>"");if(/for agents|for shortcuts|bypass permissions on/i.test(e)){await Ka(500);return}if(Date.now()>t)throw new Error("usage probe didn't reach the prompt (trust/onboarding may be blocking)");await Ka(500)}}async function lR(){if(await sR())return;if(!await De())throw new Error("host tmux server not running \u2014 start globo-tmux.service");QI.mkdirSync(Ru,{recursive:!0}),await Ne(Ru);let t=ZI();await ln(N,B("new-session","-d","-s",un,"-c",Ru,"-x","200","-y","60"));let e=`${Ti} --session-id ${t}`;await ln(N,B("send-keys","-t",un,e,"Enter")),await cR()}async function $u(){let{stdout:t}=await ln(N,B("capture-pane","-t",un,"-p"));return t}async function uR(){await lR(),await ln(N,B("send-keys","-t",un,"-l","/usage")),await ln(N,B("send-keys","-t",un,"Enter"));let t=Date.now()+rR,e="";for(;await Ka(400),e=await $u(),!(/Current\s+(session|week)/i.test(e)||/\d+%\s*used/i.test(e)||Date.now()>t););return await Ka(900),e=await $u(),await ln(N,B("send-keys","-t",un,"Escape")).catch(()=>{}),aR(),e}function dR(t){let e=String(t||"").split(`
|
|
368
|
-
`)
|
|
369
|
-
`).map(s=>s.replace(/\s+$/,"")),n=
|
|
370
|
-
`)
|
|
371
|
-
|
|
384
|
+
_${o} archived_`:"")}async function $I(t,e){let n=await Tn();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?(Fn(my(t,n,{name:u,repo:l.path,responseUrl:e.response_url})),pt(`Starting *${X(u)}* on ${X(l.name)}\u2026`)):{response_type:"ephemeral",text:`Pick a repo for \u201C${u}\u201D`,blocks:II(u,i)}}case"archive":{if(!a.length)return pt("Usage: `/bostrat archive <track>`");let{match:i,live:c}=Au(await r.listTracks(),a.join(" "));return i?(Fn(AI(t,n,{track:i,responseUrl:e.response_url})),pt(`Archiving *${X(i.name)}*\u2026`)):Eu(c,a.join(" "))}case"handoff":{let i=r.knownAgents??TI,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}=Au(await r.listTracks(),l);return u?(Fn(EI(t,n,{track:u,agent:c,responseUrl:e.response_url})),pt(`Handing *${X(u.name)}* to ${X(c)}\u2026`)):Eu(f,l)}case"stop":{if(!a.length)return pt("Usage: `/bostrat stop <track>`");let{match:i,live:c}=Au(await r.listTracks(),a.join(" "));return i?(Fn(_I(t,n,{track:i,responseUrl:e.response_url})),pt(`Stopping *${X(i.name)}*\u2026`)):Eu(c,a.join(" "))}case"list":return pt(await RI(r));default:return pt(vI)}}async function OI(t,e,n,r){let{trackId:o,promptId:s,label:a}=r,i=await $u(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 Ce(),await Dr(e.botToken,{channel:i.channel,ts:c,text:`\u2713 ${a}`,blocks:[{type:"section",text:{type:"mrkdwn",text:`~${X(l||"question")}~
|
|
385
|
+
\u2713 *${X(a)}*`}}]}).catch(()=>{})}}catch(c){await Pt(n.response_url,pt(`Couldn't answer: ${c?.message||c}`))}}async function MI(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,Sa(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 Pt(n.response_url,pt(`Couldn't act on the offer: ${c?.message||c}`))}}async function CI(t,e){let n=e.actions?.[0];if(!n?.action_id)return;let r=await Tn();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 Pt(e.response_url,{...pt(`Starting *${X(s)}*\u2026`),replace_original:!0}),Fn(my(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"?Fn(OI(t,r,e,o)):n.action_id==="bostrat-ship"&&Fn(MI(t,r,e,o))}async function NI(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 xI(e.channel,e.thread_ts);if(!n)return;let r=await Tn();if(r)try{await t.deps.sayToLead(n.trackId,e.text.trim())}catch(o){await Un(r,n.rec,`Couldn't deliver that to the lead: ${o?.message||o}`).catch(()=>{})}}function PI(t){return async e=>{if(e.type==="slash_commands")return $I(t,e.payload);if(e.type==="interactive"){await CI(t,e.payload);return}if(e.type==="events_api"){await NI(t,e.payload?.event);return}}}var BI=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 DI(t,e){let n=[{type:"section",text:{type:"mrkdwn",text:`\u2753 ${X(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:Zo(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 jI(t,e,n){let r=n.payload||{};if(!r.promptId||e.prompt?.promptId===r.promptId)return;e.prompt&&await Dr(t.botToken,{channel:e.channel,ts:e.prompt.ts,text:"superseded",blocks:[{type:"section",text:{type:"mrkdwn",text:`~${X(e.prompt.question||"question")}~ _superseded_`}}]}).catch(()=>{});let o=await Un(t,e,r.question||"The lead needs an answer",DI(n.trackId,r));e.prompt={promptId:r.promptId,ts:o.ts,question:r.question||null},await Ce()}async function LI(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 Ce(),await Dr(t.botToken,{channel:e.channel,ts:o,text:"answered",blocks:[{type:"section",text:{type:"mrkdwn",text:`~${X(s||"question")}~
|
|
386
|
+
\u2713 _answered_`}}]})}async function FI(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 Un(t,e,r.excerpt||"Ship it?",[{type:"section",text:{type:"mrkdwn",text:`\u{1F6A2} ${X(Zo(r.excerpt||"The lead offered to ship.",300))}`}},{type:"actions",elements:s}]);e.offers=e.offers||{},e.offers[r.offerId]=a.ts,await Ce()}async function UI(t,e,n){let r=n.payload||{},o=e.offers?.[r.offerId];if(!o)return;delete e.offers[r.offerId],await Ce();let s=r.action==="dismiss"?"dismissed":r.action==="pr"?"\u2713 opening a PR":"\u2713 pushing";await Dr(t.botToken,{channel:e.channel,ts:o,text:s,blocks:[{type:"section",text:{type:"mrkdwn",text:`\u{1F6A2} ${s}`}}]})}async function HI(t,e,n,r){let o=n.payload||{};if(o.change==="ready"&&o.mockId){let s=await(r.readMockPng??WI)(o.mockId,o.version??null);if(!s)return;let a=(o.version??1)>1?` (v${o.version})`:"";await cy(t.botToken,{filename:s.filename,buffer:s.buffer,title:`${o.name}${a}`,channelId:e.channel,threadTs:e.ts,initialComment:`\u{1F3A8} mock *${X(o.name)}*${a}${o.group?` (group \`${X(o.group)}\`)`:""}`})}else if(o.change==="picked"){let s=(o.siblings??[]).map(a=>`*${X(a.name)}*`).join(", ");await Un(t,e,`Design decision: ${o.name} picked`,[{type:"section",text:{type:"mrkdwn",text:`\u{1F3A8} design decision: *${X(o.name)}* picked${s?` over ${s}`:""}`}}])}}async function WI(t,e){let n=await We(t);if(!n)return null;let r=Te(n,e);if(!r)return null;let o=await Yo.readFile(Kt(n,r.v).png).catch(()=>null);return o?{filename:`${n.name.replace(/[^\w.-]+/g,"-")}.v${r.v}.png`,buffer:o}:null}async function qI(t,e){if(!BI.has(e.type))return;let n=await $u(e.trackId);if(!n)return;let r=await Tn();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 Ce(),await Iu(r,n);return;case"turn.agent":{if(!o.text)return;let s=SI(o.text);if(!s)return;await Un(r,n,s,[{type:"section",text:{type:"mrkdwn",text:X(s)}},{type:"context",elements:[{type:"mrkdwn",text:`<${na(e.trackId)}|full turn \u2192>`}]}]);return}case"prompt.raised":return jI(r,n,e);case"prompt.answered":return LI(r,n,e);case"pr.offered":return FI(r,n,e);case"pr.offer.answered":return UI(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 Un(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 Ce(),await Iu(r,n);return;case"view.state.changed":{if(o.change!=="created"||o.restarted||!o.slug)return;await Un(r,n,`Live preview ${o.name}: ${sa(o.slug)}`,[{type:"section",text:{type:"mrkdwn",text:`\u{1F50D} live preview *${X(o.name)}*: ${sa(o.slug)}
|
|
387
|
+
_runs on the Track's branch; reachable on the operator's private network only_`}}]);return}case"mock.state.changed":return HI(r,n,e,t.deps)}}function hy(t){let e={deps:t};Wt.on("event",s=>{qI(e,s).catch(()=>{})});let n=null,r=null,o=async()=>{n||!await Tn()||(r&&clearInterval(r),r=null,n=ly({getToken:async()=>(await Tn())?.appToken,onEnvelope:PI(e),log:s=>console.log(`[slack] ${s}`)}))};return r=setInterval(()=>o().catch(()=>{}),3e4),r.unref?.(),o().catch(()=>{}),()=>{r&&clearInterval(r),n?.()}}import KI from"node:fs/promises";function GI(t){let e=new Map;for(let n of String(t||"").split(`
|
|
388
|
+
`)){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 VI(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 zI(t,e){return t?.transcript_path?t.transcript_path:e?.cwd&&e?.sessionId?xe(e.cwd,e.sessionId):e?.cwd?Li(e.cwd):null}async function wy(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,{...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 JI(r,o,t,i);break;default:break}return a}async function JI(t,e,n,r){let o=[],s=zI(n,e);if(s)try{o=GI(await KI.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=VI(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",m=u.get(d)||{tokens:{input:0,output:0,cacheWrite:0,cacheRead:0},messageIds:[]};for(let h of Object.keys(m.tokens))m.tokens[h]+=f.tokens[h];m.messageIds.push(f.messageId),u.set(d,m)}for(let[f,d]of u)await r({type:"cost.recorded",actor:"system",source:"transcript",payload:{model:f,usd:se(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 ZI from"node:fs";import Ou from"node:fs/promises";import QI from"node:http";import jr from"node:path";import za from"node:os";import{createRequire as tR}from"node:module";import Mu from"ws";import XI from"node:path";import YI from"node:os";var gy=process.env.BOSTRAT_STATE_DIR||XI.join(process.env.HOME||YI.tmpdir(),".bostrat");function yy(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 by(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 Va=(()=>{try{return tR(import.meta.url)("../package.json").version||null}catch{return null}})(),eR=process.env.BOSTRAT_STATE_DIR||jr.join(process.env.HOME||za.tmpdir(),".bostrat");function nR(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 rR({home:t=process.env.HOME||za.homedir(),env:e=process.env}={}){let n=r=>{try{return ZI.statSync(r),!0}catch{return!1}};return{claude:n(jr.join(t,".claude",".credentials.json")),codex:n(jr.join(e.CODEX_HOME||jr.join(t,".codex"),"auth.json"))}}var _t=1,ky=1e3,oR=3e4,sR=6e4,aR=1500,iR=500,xy=100,cR=2e3,lR=25e3;function Sy({url:t,token:e,nodeId:n=za.hostname(),name:r=za.hostname(),listTracks:o,detectAgents:s=null,selfUrl:a=`http://127.0.0.1:${process.env.PORT||3362}`,log:i=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=by(t);let c=a.replace(/^http/,"ws"),l=new Map,u=new Map,f=jr.join(eR,"plane-link","cursor.json"),d={},m=new Map,h=null,w=!1,g=!1,b=ky,S=null,A=null,C=null,V=null,O=!1;async function F(){try{d=JSON.parse(await Ou.readFile(f,"utf8"))}catch{d={}}}function T(){O=!0,!C&&(C=setTimeout(async()=>{if(C=null,!!O){O=!1;try{await Ou.mkdir(jr.dirname(f),{recursive:!0}),await Ou.writeFile(f,JSON.stringify(d))}catch(I){i.error("[plane-link] cursor flush failed:",I?.message||I)}}},cR),C.unref?.())}function M(I){return!h||h.readyState!==Mu.OPEN?!1:(h.send(JSON.stringify(I)),!0)}function W(I){for(let{trackId:R,event:_}of I)m.set(_.id,{trackId:R,seq:_.seq});return M({v:_t,ch:"events",type:"append",events:I})}async function st(){let I;try{I=await o()}catch(R){i.error("[plane-link] backfill listTracks failed:",R?.message||R);return}M({v:_t,ch:"ctl",type:"tracks",ids:I.map(R=>R.id).filter(R=>typeof R=="string")});for(let R of I){if(!w)return;let _;try{_=await L(R)}catch{continue}let P=d[R.id]??0,et=_.filter(wt=>wt.seq>P&&typeof wt.id=="string");for(let wt=0;wt<et.length;wt+=xy){let bt=et.slice(wt,wt+xy).map(Rt=>({trackId:R.id,event:Rt}));if(!W(bt))return}}}function y(I){!w||!I?.trackId||typeof I.id!="string"||W([{trackId:I.trackId,event:I}])}let p=new Map;async function v(I){let{reqId:R,method:_,path:P,headers:et={},body:wt}=I,bt=Object.fromEntries(Object.entries(et).filter(([,Ht])=>Ht!=null)),Rt=new AbortController;p.set(R,Rt);try{if(I.host){await E(I,bt,Rt);return}let Ht=await fetch(a+P,{method:_,headers:bt,body:wt?Buffer.from(wt,"base64"):void 0,signal:Rt.signal}),zt=Object.fromEntries(Ht.headers);for(let k of["connection","keep-alive","transfer-encoding","upgrade","content-encoding","content-length"])delete zt[k];if(zt["content-type"]||(zt["content-type"]="application/octet-stream"),M({v:_t,ch:"control",type:"http_head",reqId:R,status:Ht.status,headers:zt}),Ht.body){for await(let k of Ht.body)if(!M({v:_t,ch:"control",type:"http_data",reqId:R,data:Buffer.from(k).toString("base64")}))break}M({v:_t,ch:"control",type:"http_end",reqId:R,error:null})}finally{p.delete(R)}}function E(I,R,_){let{reqId:P,method:et,path:wt,body:bt}=I,Rt=new URL(a);return new Promise(Ht=>{let zt=QI.request({host:Rt.hostname,port:Rt.port,method:et,path:wt,headers:{...R,host:I.host},signal:_.signal},k=>{let{connection:K,"keep-alive":Hn,"transfer-encoding":ds,upgrade:Wn,...fi}=k.headers;M({v:_t,ch:"control",type:"http_head",reqId:P,status:k.statusCode||502,headers:fi}),k.on("data",ak=>{M({v:_t,ch:"control",type:"http_data",reqId:P,data:Buffer.from(ak).toString("base64")})||k.destroy()}),k.on("end",()=>{M({v:_t,ch:"control",type:"http_end",reqId:P,error:null}),Ht()}),k.on("error",()=>{M({v:_t,ch:"control",type:"http_end",reqId:P,error:"view upstream error"}),Ht()})});zt.on("error",k=>{M({v:_t,ch:"control",type:"http_end",reqId:P,error:k?.message||"view request failed"}),Ht()}),bt&&zt.write(Buffer.from(bt,"base64")),zt.end()})}function rt(){if(g)return;h=new Mu(t,{headers:{authorization:`Bearer ${e}`}}),h.on("open",async()=>{let R=null;try{R=await s?.()||null}catch{R=null}M({v:_t,ch:"ctl",type:"hello",nodeId:n,name:r,platform:process.platform,version:Va,providers:rR(),...R?{agents:R}:{}})}),h.on("message",R=>{let _;try{_=JSON.parse(R)}catch{return}if(_.ch==="ctl"&&_.type==="hello_ok"){w=!0,b=ky,S=null,i.log(`[plane-link] connected to plane as ${n}`),_.minNodeVersion&&Va&&nR(Va,_.minNodeVersion)&&i.error(`[plane-link] this node is bostrat-node ${Va}; the plane asks for \u2265${_.minNodeVersion} \u2014 update with: npm install -g bostrat-node`),V&&clearInterval(V),V=setInterval(()=>M({v:_t,ch:"ctl",type:"ping"}),lR),V.unref?.(),st().catch(P=>i.error("[plane-link] backfill failed:",P?.message||P));return}if(_.ch==="events"&&_.type==="ack"&&Array.isArray(_.ids)){for(let P of _.ids){let et=m.get(P);et&&(m.delete(P),(d[et.trackId]??0)<et.seq&&(d[et.trackId]=et.seq))}T();return}if(_.ch==="control"&&_.type==="http_open"){v(_).catch(P=>M({v:_t,ch:"control",type:"http_end",reqId:_.reqId,error:P?.message||"tunnel error"}));return}if(_.ch==="control"&&_.type==="http_cancel"){p.get(_.reqId)?.abort();return}if(_.ch==="attach"&&_.type==="open"){let P=new Mu(c+_.path,Array.isArray(_.protocols)?_.protocols:[],_.host?{headers:{host:_.host}}:void 0);l.set(_.streamId,P),u.set(_.streamId,[]),P.on("open",()=>{let wt=u.get(_.streamId)||[];u.delete(_.streamId);for(let{buf:bt,bin:Rt}of wt)P.send(bt,{binary:Rt})}),P.on("message",(wt,bt)=>{let Rt=bt?wt:Buffer.from(String(wt));M({v:_t,ch:"attach",type:"data",streamId:_.streamId,data:Rt.toString("base64"),...bt?{bin:!0}:{}})});let et=()=>{u.delete(_.streamId),l.delete(_.streamId)&&M({v:_t,ch:"attach",type:"close",streamId:_.streamId})};P.on("close",et),P.on("error",et);return}if(_.ch==="attach"&&_.type==="data"){let P=l.get(_.streamId);if(!P)return;let et=Buffer.from(_.data,"base64"),wt=_.bin===!0,bt=u.get(_.streamId);bt?bt.push({buf:et,bin:wt}):P.send(et,{binary:wt});return}if(_.ch==="attach"&&_.type==="close"){let P=l.get(_.streamId);l.delete(_.streamId),u.delete(_.streamId),P?.close();return}});let I=()=>{w&&i.log("[plane-link] disconnected from plane"),w=!1,V&&(clearInterval(V),V=null),m.clear();for(let P of l.values())P.close();l.clear(),u.clear();for(let P of p.values())P.abort();if(p.clear(),g||A)return;S==null&&(S=Date.now());let R=Date.now()-S<sR,_=R?aR+Math.random()*iR:b;A=setTimeout(()=>{A=null,rt()},_),A.unref?.(),R||(b=Math.min(b*2,oR))};h.on("close",I),h.on("error",I)}return Wt.on("event",y),F().then(rt),{stop:()=>{g=!0,Wt.off("event",y),A&&clearTimeout(A),C&&clearTimeout(C),V&&clearInterval(V),M({v:_t,ch:"ctl",type:"bye",reason:"shutdown"}),h?.close()},status:()=>({connected:w,nodeId:n,cursor:{...d},inflight:m.size})}}function Nu(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 vy=()=>({input:0,output:0,cacheRead:0,cacheCreation:0}),Ty=(t,e)=>{for(let n of Object.keys(t))t[n]+=e?.[n]||0},Cu=t=>Math.round(t*1e4)/1e4;async function Ay(t){let e=[],n={},r={},o=0,s=0,a=0,i=0,c=new Set,l=vy(),u=0,f=[];for(let d of t||[]){let m=await L(d),h=ae(m,{boxLabels:Nu(d)});if(h.turns===0){d.archivedAt?u++:f.push(d.name||d.id);continue}o+=h.totalUsd,s+=h.turns,a+=h.repricedTurns||0,i+=h.unpricedTurns||0;for(let w of h.unpricedModels||[])c.add(w);Ty(l,h.totalTokens);for(let[w,g]of Object.entries(h.byModel))n[w]||={usd:0,events:0,tokens:vy()},n[w].usd=Cu(n[w].usd+g.usd),n[w].events+=g.events,Ty(n[w].tokens,g.tokens);for(let[w,g]of Object.entries(h.byProvider))r[w]||={usd:0,events:0},r[w].usd=Cu(r[w].usd+g.usd),r[w].events+=g.events;e.push({id:d.id,name:d.name,usd:h.totalUsd,rollup:h})}return e.sort((d,m)=>m.usd-d.usd),{generatedAt:new Date().toISOString(),pricingBasis:"api-equivalent (subscription usage priced at public API rates)",fleet:{totalUsd:Cu(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 Qo=t=>`$${t.toFixed(4)}`,Lr=(t,e)=>String(t).padEnd(e);function Ey(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 ${Qo(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(` ${Lr(a,26)} ${Lr(Qo(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(` ${Lr(a.name||a.id,30)} ${Qo(a.usd)}`);let i=Object.values(a.rollup.byBox).sort((c,l)=>l.usd-c.usd);for(let c of i)if(r.push(` ${Lr(c.label,40)} ${Lr(Qo(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))}${Lr(Qo(u.usd),10)} ${u.models.join(",")||"?"}`)}return r.join(`
|
|
389
|
+
`)}import mR from"node:fs/promises";import hR from"node:path";var Ja=t=>t.payload?.ts??t.ts,uR=12e4,cn=t=>Math.round(t*1e4)/1e4,dR=()=>({usd:0,activeMs:0,turns:0,toolCalls:0,models:{},providers:{},byTrack:new Map});function fR(t,{since:e}){let{track:n,events:r,brief:o}=t,s=S=>Ja(S)>=e,a=Qn([co(n?.name),co(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 A=Qn(S.payload.task);A&&i.set(S.boxId,A)}let c=new Map,l=S=>{let A=S||"unattributed";return c.has(A)||c.set(A,{segments:[u(null)],lastTs:null,provider:null}),c.get(A)},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,A)=>Ja(S)-Ja(A)),d=0,m=0,h=0,w={turns:0};for(let S of f){let A=l(S.boxId),C=A.segments[A.segments.length-1],V=Ja(S);switch(A.lastTs!=null&&V>A.lastTs&&s(S)&&(C.activeMs+=Math.min(V-A.lastTs,uR)),A.lastTs=V,S.provider&&(A.provider=S.provider),S.type){case"turn.user":{let O=S.payload?.text;if(S.source==="hook"||!String(O||"").trim())break;let F=u(Qn(O));A.segments.push(F),s(S)&&(F.turns+=1);break}case"tool.called":{if(S.source==="transcript")break;let O=S.payload?.tool;O&&(C.tools[O]=(C.tools[O]||0)+1),s(S)&&(C.toolCalls+=1);break}case"cost.recorded":{if(!s(S))break;let O=S.payload||{},F={input:O.tokens?.input||0,output:O.tokens?.output||0,cacheRead:O.tokens?.cacheRead||0,cacheWrite:O.tokens?.cacheWrite??O.tokens?.cacheCreation??0},T=Number(O.usd)||0;!T&&O.tokens&&(T=se(O.model,F)),!T&&O.tokens&&!Us(O.model)&&(w.turns+=1),C.usd+=T,C.tokens.input+=F.input,C.tokens.output+=F.output,C.tokens.cacheRead+=F.cacheRead,C.tokens.cacheWrite+=F.cacheWrite;let M=O.model||"unknown";C.models[M]=(C.models[M]||0)+T,S.provider&&(C.provider=S.provider);break}case"handoff.done":s(S)&&(d+=1);break;case"subtask.merged":s(S)&&(m+=1);break;case"error":s(S)&&S.payload?.kind==="subtask-failed"&&(h+=1);break;default:break}}let g=new Map,b=!1;for(let[S,A]of c)for(let C of A.segments){if(!(C.usd>0||C.activeMs>0||C.turns>0||C.toolCalls>0))continue;let O=C.textCat?.category??i.get(S)?.category??lf(C.tools)?.category??a?.category??"uncategorized";g.has(O)||g.set(O,{usd:0,activeMs:0,turns:0,toolCalls:0,models:{},providers:{}});let F=g.get(O);F.usd+=C.usd,F.activeMs+=C.activeMs,F.turns+=C.turns,F.toolCalls+=C.toolCalls;for(let[M,W]of Object.entries(C.models))F.models[M]=(F.models[M]||0)+W;let T=C.provider||A.provider;T&&(F.providers[T]=(F.providers[T]||0)+C.usd),T==="cursor"&&C.usd===0&&(b=!0)}return{perCategory:g,handoffs:d,merged:m,failed:h,sawCursorActivity:b,unpricedTurns:w.turns}}function Iy(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:g,events:b}=w;if(!b?.length){a+=1;continue}let{perCategory:S,handoffs:A,merged:C,failed:V,sawCursorActivity:O,unpricedTurns:F}=fR(w,{since:r});O&&(i=!0),c+=F;let T={trackId:g?.id,name:g?.name||g?.id,archived:!!g?.archivedAt,usd:0,activeMs:0,turns:0,categories:{},models:{},providers:{},handoffs:A,subtasksMerged:C,subtasksFailed:V};for(let[M,W]of S){T.usd+=W.usd,T.activeMs+=W.activeMs,T.turns+=W.turns,T.categories[M]={usd:cn(W.usd),activeMs:W.activeMs};for(let[p,v]of Object.entries(W.models))T.models[p]=cn((T.models[p]||0)+v);for(let[p,v]of Object.entries(W.providers))T.providers[p]=cn((T.providers[p]||0)+v);o.has(M)||o.set(M,dR());let st=o.get(M);st.usd+=W.usd,st.activeMs+=W.activeMs,st.turns+=W.turns,st.toolCalls+=W.toolCalls;for(let[p,v]of Object.entries(W.models))st.models[p]=(st.models[p]||0)+v;for(let[p,v]of Object.entries(W.providers))st.providers[p]=(st.providers[p]||0)+v;let y=st.byTrack.get(g?.id)||{usd:0,activeMs:0};y.usd+=W.usd,y.activeMs+=W.activeMs,st.byTrack.set(g?.id,y)}(T.usd>0||T.activeMs>0||T.turns>0)&&(T.usd=cn(T.usd),T.dominantCategory=pR(T.categories),s.push(T))}let l=[...o.values()].reduce((w,g)=>w+g.usd,0),u=[...o.values()].reduce((w,g)=>w+g.activeMs,0),f=l>0?"usd":"time",d=f==="usd"?l:u,m=new Map((t||[]).map(w=>[w.track?.id,w.track?.name||w.track?.id])),h=[...o.entries()].map(([w,g])=>({category:w,sharePct:d>0?Math.round((f==="usd"?g.usd:g.activeMs)/d*100):0,shareBasis:f,usd:cn(g.usd),activeMs:g.activeMs,turns:g.turns,toolCalls:g.toolCalls,models:_y(g.models),providers:_y(g.providers),topWorkstreams:[...g.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:m.get(b),usd:cn(S.usd),activeMs:S.activeMs}))})).sort((w,g)=>g.usd-w.usd||g.activeMs-w.activeMs);return s.sort((w,g)=>g.usd-w.usd||g.activeMs-w.activeMs),{since:r,capturedAt:n,totalUsd:cn(l),activeMs:u,turns:[...o.values()].reduce((w,g)=>w+g.turns,0),bullets:h,workstreams:s,gaps:{tracksWithoutEvents:a,cursorCostUnknown:i,unpricedTurns:c}}}var pR=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},_y=t=>Object.fromEntries(Object.entries(t).map(([e,n])=>[e,cn(n)]));var Fr={"24h":24*36e5,"7d":168*36e5,"30d":720*36e5,all:null};function wR(t){let e=String(t||"7d");return e in Fr?{key:e,ms:Fr[e]}:{key:"7d",ms:Fr["7d"]}}function gR(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=xa(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 yR(t){if(!t?.cwd)return null;try{let n=(await mR.readFile(hR.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 Ry(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}:wR(e),f=[];for(let d of t||[]){let m=await L(d),h=gR(d,m);i&&c<o&&(h=h.filter(w=>(w.ts??0)<=c)),f.push({track:d,events:h,brief:await yR(d)})}return{window:l,...Iy(f,{windowMs:u,now:c})}}var ln=36e5,Cy=24*ln,bR=400,kR=12,Ur=t=>Math.round(t*1e4)/1e4;function xR({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 Fr?String(t||"7d"):"7d",i=Fr[a];return{key:a,since:i==null?null:r-i,until:r}}var SR=(t,e)=>t!=null&&e-t<=48*ln?ln:Cy;function $y(t,e){if(e===ln)return Math.floor(t/ln)*ln;let n=new Date(t);return n.setHours(0,0,0,0),n.getTime()}function vR(t,e){if(e===ln)return t+ln;let n=new Date(t);return n.setDate(n.getDate()+1),n.setHours(0,0,0,0),n.getTime()}var Oy=()=>({input:0,output:0,cacheRead:0,cacheCreation:0}),My=(t,e)=>{for(let n of Object.keys(t))t[n]+=e?.[n]||0},TR=t=>Object.fromEntries(Object.entries(t).map(([e,n])=>[e,Ur(n)]));async function Ny(t,e={}){let n=e.now??Date.now(),r=xR({...e,now:n}),o=r.since==null?Cy:SR(r.since,r.until),s=T=>(r.since==null||T>=r.since)&&T<r.until,a=r.since!=null?r.until-r.since:null,i=T=>a!=null&&T>=r.since-a&&T<r.since,c={},l={},u=Oy(),f=0,d=0,m=0,h=0,w=0,g=new Set,b=new Map,S=[],A=[],C=[],V=null;for(let T of t||[]){let W=(await L(T)).filter(p=>p.type==="cost.recorded");if(!W.length){T.archivedAt||C.push(T.name||T.id);continue}let st=[];for(let p of W){let v=p.ts??0;s(v)?st.push(p):i(v)&&(w+=Ws(p).usd)}if(!st.length)continue;let y=ae(st,{boxLabels:Nu(T)});f+=y.totalUsd,d+=y.turns,m+=y.repricedTurns||0,h+=y.unpricedTurns||0;for(let p of y.unpricedModels||[])g.add(p);My(u,y.totalTokens);for(let[p,v]of Object.entries(y.byModel))c[p]||={usd:0,events:0,tokens:Oy()},c[p].usd=Ur(c[p].usd+v.usd),c[p].events+=v.events,My(c[p].tokens,v.tokens);for(let[p,v]of Object.entries(y.byProvider))l[p]||={usd:0,events:0},l[p].usd=Ur(l[p].usd+v.usd),l[p].events+=v.events;for(let p of st){let{usd:v}=Ws(p);if(!v)continue;let E=$y(p.ts??0,o),rt=b.get(E)??{t:E,total:0,byProvider:{}};rt.total+=v;let I=p.provider||"unknown";rt.byProvider[I]=(rt.byProvider[I]||0)+v,b.set(E,rt),(V==null||E<V)&&(V=E)}S.push({id:T.id,name:T.name||T.id,archived:!!T.archivedAt,usd:y.totalUsd,turns:y.turns,providers:Object.keys(y.byProvider)});for(let[p,v]of Object.entries(y.byBox))A.push({trackId:T.id,trackName:T.name||T.id,boxId:p,...v})}let O=r.since!=null?$y(r.since,o):V,F=[];for(let T=O,M=0;T!=null&&T<r.until&&M<bR;T=vR(T,o),M++){let W=b.get(T);F.push(W?{t:T,total:Ur(W.total),byProvider:TR(W.byProvider)}:{t:T,total:0,byProvider:{}})}return S.sort((T,M)=>M.usd-T.usd),A.sort((T,M)=>M.usd-T.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:F,fleet:{totalUsd:Ur(f),turns:d,repricedTurns:m,unpricedTurns:h,unpricedModels:[...g],totalTokens:u,byModel:c,byProvider:l,tracksWithCost:S.length,tracksSilentLive:C},prior:a!=null?{totalUsd:Ur(w),since:r.since-a,until:r.since}:null,tracks:S.slice(0,50),boxes:A.slice(0,kR)}}import qr from"node:fs/promises";import AR from"node:fs";import dt from"node:path";import ER from"node:os";import{execFile as _R}from"node:child_process";import{promisify as IR}from"node:util";var RR=IR(_R),Py=process.env.BOSTRAT_STATE_DIR||dt.join(process.env.HOME||ER.tmpdir(),".bostrat"),Pu=!1;async function Hr(t,e){let n=await qr.stat(t).catch(()=>null);if(!n?.isFile())return!1;let r=await qr.stat(e).catch(()=>null);return r&&r.size===n.size&&r.mtimeMs>=n.mtimeMs?!1:(await qr.mkdir(dt.dirname(e),{recursive:!0}),await qr.copyFile(t,e),!0)}var $R=new Set([".claude",".codex",".credentials.json","auth.json",".claude.json","integrations.json"]);async function Wr(t,e){let n=0,r=await qr.readdir(t,{withFileTypes:!0}).catch(()=>[]);for(let o of r){if($R.has(o.name))continue;let s=dt.join(t,o.name);o.isDirectory()?n+=await Wr(s,dt.join(e,o.name)):o.isFile()&&(n+=await Hr(s,dt.join(e,o.name))?1:0)}return n}async function By({stateDir:t=Py,dest:e=process.env.BOSTRAT_SYNC_DIR||`${Py}-sync`,remote:n=process.env.BOSTRAT_STATE_REMOTE||null,tracks:r=null}={}){try{await qr.mkdir(e,{recursive:!0});let o=0;o+=await Hr(dt.join(t,"tracks.json"),dt.join(e,"tracks.json"))?1:0,o+=await Hr(dt.join(t,"mocks.json"),dt.join(e,"mocks.json"))?1:0,o+=await Hr(dt.join(t,"flows.json"),dt.join(e,"flows.json"))?1:0,o+=await Hr(dt.join(t,"runbooks.json"),dt.join(e,"runbooks.json"))?1:0,o+=await Wr(dt.join(t,"runbooks"),dt.join(e,"runbooks")),o+=await Wr(dt.join(t,"mock-archive"),dt.join(e,"mock-archive")),o+=await Hr(dt.join(t,"events.jsonl"),dt.join(e,"events.jsonl"))?1:0,o+=await Wr(dt.join(t,"events"),dt.join(e,"events")),o+=await Wr(dt.join(t,"memory"),dt.join(e,"memory"));for(let l of r||[])!l?.cwd||!l?.id||(o+=await Wr(dt.join(l.cwd,".bostrat"),dt.join(e,"workspaces",l.id,".bostrat")));let s=(...l)=>RR("git",["-C",e,...l]);AR.existsSync(dt.join(e,".git"))||(await s("init","-q"),await s("config","user.email","agent@bostrat.local"),await s("config","user.name","bostrat state sync")),n&&/^https:\/\/github\.com\//i.test(n)&&(await s("config","--get","credential.useHttpPath").then(()=>!0).catch(()=>!1)||await ki(e).catch(()=>{}));let a=!1,{stdout:i}=await s("status","--porcelain");i.trim()&&(await s("add","-A"),await s("commit","-q","-m",`state sync ${new Date().toISOString()}`),a=!0);let c=!1;if(n&&(a||Pu))try{await s("push","-q",n,"HEAD:refs/heads/main"),c=!0,Pu=!1}catch{Pu=!0}return{staged:o,committed:a,pushed:c}}catch(o){return{staged:0,committed:!1,pushed:!1,error:o?.message||String(o)}}}import{execFile as OR}from"node:child_process";import{promisify as MR}from"node:util";var CR=MR(OR),NR=12e4,PR=6e4;function BR(t){return`refs/bostrat/wip/${t}`}function Ly(t=process.env){let e=String(t.BOSTRAT_WIP_SYNC_MS??"").trim().toLowerCase();return e!=="0"&&e!=="off"&&e!=="false"}function DR(t=process.env){let e=Number(t.BOSTRAT_WIP_SYNC_MS);return Number.isFinite(e)&&e>0?e:NR}var Xa=(t,e,n=15e3)=>CR("git",["-C",t,...e],{timeout:n,maxBuffer:4*1024*1024}),Dy=new Map,jy=new Set;async function jR(t,{log:e=console}={}){let{id:n,cwd:r}=t??{};if(!n||!r)return{pushed:!1,reason:"no-workspace"};try{await Xa(r,["rev-parse","--git-dir"])}catch{return{pushed:!1,reason:"not-a-repo"}}try{await Xa(r,["remote","get-url","origin"])}catch{return jy.has(r)||(jy.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 Mo(r,"bostrat: wip snapshot"),{stdout:s}=await Xa(r,["rev-parse",`${o}^{tree}`]),a=s.trim();if(Dy.get(r)===a)return{pushed:!1,reason:"unchanged"};let i=BR(n);return await Xa(r,["push","--force","origin",`${o}:${i}`],PR),Dy.set(r,a),await $({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(`
|
|
390
|
+
`)[0]}`),{pushed:!1,reason:"push-failed"}}}function Fy({listTracks:t,sweepMs:e=DR(),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 jR(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 LR}from"node:child_process";import{promisify as FR}from"node:util";import{randomUUID as UR}from"node:crypto";import HR from"node:fs";import WR from"node:os";import qR from"node:path";var dn=FR(LR),Ya=t=>new Promise(e=>setTimeout(e,t)),fn=Oi,Bu=process.env.BOSTRAT_USAGE_PROBE_CWD||qR.join(WR.homedir(),".bostrat","usage-probe"),KR=Number(process.env.BOSTRAT_USAGE_IDLE_REAP_MS)||5*6e4,GR=Number(process.env.BOSTRAT_USAGE_PANEL_TIMEOUT_MS)||15e3,VR=Number(process.env.BOSTRAT_USAGE_READY_TIMEOUT_MS)||45e3,Uy=Number(process.env.BOSTRAT_USAGE_CACHE_MS)||2e4,un=null,ts=null,es=null;async function zR(){try{return await dn(N,B("has-session","-t",fn)),!0}catch{return!1}}function JR(){un&&clearTimeout(un),un=setTimeout(()=>XR().catch(()=>{}),KR),un.unref&&un.unref()}async function XR(){un&&(clearTimeout(un),un=null);try{await dn(N,B("kill-session","-t",fn))}catch{}}async function YR(){let t=Date.now()+VR;for(;;){let e=await Du().catch(()=>"");if(/for agents|for shortcuts|bypass permissions on/i.test(e)){await Ya(500);return}if(Date.now()>t)throw new Error("usage probe didn't reach the prompt (trust/onboarding may be blocking)");await Ya(500)}}async function ZR(){if(await zR())return;if(!await je())throw new Error("host tmux server not running \u2014 start globo-tmux.service");HR.mkdirSync(Bu,{recursive:!0}),await Pe(Bu);let t=UR();await dn(N,B("new-session","-d","-s",fn,"-c",Bu,"-x","200","-y","60"));let e=`${$i} --session-id ${t}`;await dn(N,B("send-keys","-t",fn,e,"Enter")),await YR()}async function Du(){let{stdout:t}=await dn(N,B("capture-pane","-t",fn,"-p"));return t}async function QR(){await ZR(),await dn(N,B("send-keys","-t",fn,"-l","/usage")),await dn(N,B("send-keys","-t",fn,"Enter"));let t=Date.now()+GR,e="";for(;await Ya(400),e=await Du(),!(/Current\s+(session|week)/i.test(e)||/\d+%\s*used/i.test(e)||Date.now()>t););return await Ya(900),e=await Du(),await dn(N,B("send-keys","-t",fn,"Escape")).catch(()=>{}),JR(),e}function t$(t){let e=String(t||"").split(`
|
|
391
|
+
`).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 e$(t){let e=String(t||"").split(`
|
|
392
|
+
`).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 Hy({fresh:t=!1}={}){return!t&&ts&&Date.now()-ts.at<Uy?ts.data:es||(es=(async()=>{try{let e=await QR(),n=t$(e),r={...n,contributors:e$(e),subscription:!!(n.session||n.week||n.weekModels),capturedAt:Date.now()};return ts={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 ts={at:Date.now()-(Uy-3e3),data:n},n}finally{es=null}})(),es)}var n$=262144;function r$(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 Wy(t){if(!t||typeof t!="object")return null;let e=Number(t.used_percent);return Number.isFinite(e)?{pct:Math.round(e),resets:r$(t.resets_in_seconds)}:null}function o$(t){let e=String(t||"").split(`
|
|
393
|
+
`),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:Wy(i.primary),week:Wy(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 ju(t){let e={provider:"codex",supported:!0,contributors:null};try{let n=await Rn(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 Ot(n.file,n$),o=o$(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 qy(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 Za({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 ju(i);if(c.subscription){a=c;break}(!a||a.error&&!c.error)&&(a=c)}r.push(a||await ju(null))}return n.has("cursor")&&r.push(await Za({agent:"cursor"})),{providers:r,capturedAt:Date.now()}}async function Za(t,{fresh:e=!1}={}){let n=t?.agent||"claude";return n==="codex"?ju(t):n==="cursor"?{provider:"cursor",supported:!1,subscription:!1,session:null,week:null,contributors:null,capturedAt:Date.now()}:{provider:"claude",supported:!0,...await Hy({fresh:e})}}function s$(t=process.env){return!!(be(t)&&t.BOSTRAT_PLANE_TOKEN)}var a$=t=>({authorization:`Bearer ${t.BOSTRAT_PLANE_TOKEN||""}`}),Qa=(t,e)=>Object.assign(new Error(t),{status:e});async function Ky(t,e){if(!s$(e))throw Qa("node is not plane-linked \u2014 Gmail unavailable",503);let n;try{n=await fetch(`${be(e)}${t}`,{headers:a$(e)})}catch(o){throw Qa(`plane unreachable \u2014 Gmail unavailable (${o?.message||o})`,503)}let r=await n.json().catch(()=>({}));if(!n.ok)throw Qa(r?.error||`plane replied ${n.status}`,n.status);return r}async function Gy(t,{max:e=10,env:n=process.env}={}){let r=new URLSearchParams({q:t||""});return e&&r.set("max",String(e)),(await Ky(`/v1/gmail/messages?${r.toString()}`,n)).messages||[]}async function Vy(t,{env:e=process.env}={}){if(!t)throw Qa("message id required",400);return(await Ky(`/v1/gmail/messages/${encodeURIComponent(t)}`,e)).message}function zy(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 i$(t){let[e,n]=t.split("/"),r=zy(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 c$(t){return(t||"").split(",").map(e=>e.trim()).filter(Boolean).map(i$).filter(Boolean)}function l$(t){let e=t.headers["fly-client-ip"];return typeof e=="string"&&e.trim()?e.trim():(t.socket&&t.socket.remoteAddress||"").replace(/^::ffff:/,"")}function Jy(t){let e=c$(t),n=String(t||"").trim().toLowerCase()==="open";return{open:n,clientIp:l$,allowed(r){if(n)return!0;let o=zy((r||"").replace(/^::ffff:/,""));return o==null?!1:e.some(s=>s(o))}}}import Xy from"node:crypto";import ti from"node:fs";import u$ from"node:path";function Yy(t=process.env,{stateDir:e=gy,log:n=console}={}){let r=String(t.BOSTRAT_NODE_API_TOKEN||"").trim();if(r)return r;let o=u$.join(e,"api-token");try{let a=ti.readFileSync(o,"utf8").trim();if(a)return a}catch{}let s=Xy.randomBytes(32).toString("base64url");try{ti.mkdirSync(e,{recursive:!0}),ti.writeFileSync(o,s+`
|
|
394
|
+
`,{mode:384}),ti.chmodSync(o,384)}catch(a){n.error?.(`[bostrat-agent] could not persist the node API token: ${a?.message||a}`)}return s}function ns(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 Zy(t,e){if(!t||!e)return!1;let n=Buffer.from(String(t)),r=Buffer.from(String(e));return n.length!==r.length?!1:Xy.timingSafeEqual(n,r)}function Qy(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 d$=[/^\/api\/github\/token\b/],f$=t=>d$.some(e=>e.test(t));function tb({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=Qy(o);return Zy(c,t)?a():f$(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||ns(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 eb(t,e,{enforce:n=!0}={}){return!n||Zy(Qy(t),e)?!0:ns(t)}import p$ from"node:http";import m$ from"node:net";function nb(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 rb(t){let e=nb(t.headers.host);return e?{slug:e,view:dc(e)}:null}var h$=t=>`<!doctype html><meta charset="utf-8">
|
|
372
395
|
<meta http-equiv="refresh" content="3">
|
|
373
396
|
<title>${t.name} \u2014 starting</title>
|
|
374
397
|
<body style="font-family:system-ui;background:#0b0e14;color:#dce3f0;display:grid;place-items:center;height:100vh;margin:0">
|
|
375
398
|
<div style="text-align:center"><p style="font-size:2rem;margin:0">\u23F3</p>
|
|
376
|
-
<p><b>${t.name}</b> is waking up\u2026</p><p style="opacity:.6">auto-refreshing</p></div>`;function
|
|
399
|
+
<p><b>${t.name}</b> is waking up\u2026</p><p style="opacity:.6">auto-refreshing</p></div>`;function ob(t,e,n){let r=rb(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(h$(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=p$.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 sb(t,e,n){let r=rb(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
|
|
377
400
|
\r
|
|
378
401
|
bostrat: no live view "${s}"\r
|
|
379
|
-
`),e.destroy(),!0;let a=
|
|
402
|
+
`),e.destroy(),!0;let a=m$.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
|
|
380
403
|
`)+`\r
|
|
381
404
|
\r
|
|
382
|
-
`),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
|
|
383
|
-
`)){let s=o.trim();if(!s)continue;if(s.startsWith("#")){n==null&&(n=s.replace(/^#+\s*/,"").trim().slice(0,IR)||null);continue}let a=/^[-*]\s+(.*)$/.exec(s);if(!a)continue;let i=a[1].trim(),c=null,l=new RegExp(`^\\[([^\\]]{1,${RR}})\\]\\s+(.*)$`).exec(i);if(l&&(c=l[1].trim(),i=l[2].trim()),!!i){if(i.length>Py)throw new Error(`bullet ${r.length+1} is ${i.length} chars \u2014 over the ${Py}-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>Ny)throw new Error(`${r.length} bullets \u2014 over the ${Ny}-bullet cap; group related changes into one line`);return{title:n||null,bullets:r}}async function Dy(t,{repos:e=Mu()}={}){let n=new Set(e);return(await pe()).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 jy(t,{md:e,day:n=null}){let r=MR(e),o=n==null||n===""?OR():String(n);if(!$R.test(o)||Number.isNaN(Date.parse(o)))throw new Error(`invalid day "${n}" \u2014 expected YYYY-MM-DD`);let s=await O({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 CR}from"node:child_process";import{promisify as NR}from"node:util";var Nu=NR(CR),Ly=3e4,PR=30*6e4,BR=Ly,DR=10*6e4,jR=10080*6e4,LR=60*6e4,FR=8,UR=1440*6e4,HR=3,Fy="number,url,title,headRefName,headRefOid,state,mergedAt,closedAt",Cu=60*6e4,WR=15*6e4,Ja=new Map,Xa=new Map,qR=/could not resolve|not found|no pull requests?/i,KR=t=>qR.test(String(t?.message||t||""));function GR(t,e){return[t.ciFix?.delegatedAt,t.reconcile?.delegatedAt,t.ciFixAwait?.since,t.reconcileAwait?.since].some(n=>n&&e-n<Cu)}var Uy={fast:BR,slow:DR,decay:LR};function Hy(t,e){return t.armedAt&&e-t.armedAt<PR||GR(t,e)?"fast":e-(t.firstSeen||0)>jR?"decay":"slow"}function zR(t,e){return Uy[Hy(t,e)]}function Qo(t,e,n,r){let o=t.lanes?.get(dt(e.repo,e.number))||{};return _a({change:n,repo:e.repo,number:Number(e.number),githubAt:r||null,lane:o.lane||null,intervalMs:o.intervalMs||null,source:"watch"})}async function VR(t,e){Re("watch");try{let{stdout:n}=await Nu("gh",["pr","view",String(e),"--repo",t,"--json",Fy],{timeout:3e4,maxBuffer:4194304,env:{...process.env,...await ee({slug:t})}});return JSON.parse(n||"null")}catch(n){throw Ye(n)&&Ze(n),n}}async function JR(t){Re("watch");try{let{stdout:e}=await Nu("gh",["pr","list","--repo",t,"--state","all","--limit","100","--json",Fy],{timeout:3e4,maxBuffer:8388608,env:{...process.env,...await ee({slug:t})}}),n=new Map;for(let r of JSON.parse(e||"[]"))n.set(Number(r.number),r);return n}catch(e){throw Ye(e)&&Ze(e),e}}async function XR(){let t=new Map;for(let e of await ct().catch(()=>[]))t.set(e.id,e);return t}async function YR(t,e){await O({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 ZR(t,e){let n=await Nn(t);return n?Sr(n,e):null}async function QR(t,e,n){let r=await Nn(t);if(!r)throw new Error(`no local checkout for ${t}`);return vr(r,e,{method:n})}async function t$(t,e){let n=await Nn(t);if(!n)throw new Error(`no local checkout for ${t}`);return Mo(n,e)}async function e$(t,e){await O({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 Lr(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 n$=10*6e4;async function r$(t,e,n){let r=t.autoMerge?.armedAt||0;if(r&&(n.now??Date.now())-r>UR){await Lr(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 Lr(t,o,e,n,`check${o.checks.failed===1?"":"s"} failed: ${a}`);return}if(o.mergeable==="CONFLICTING"){await Lr(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<n$)return;await Lr(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 Lr(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 Lr(t,o,e,n,String(a?.message||a).trim()||"merge failed");return}await Wy(t,{...o,state:"MERGED"},e,n,{change:"auto-merged",githubAt:o.checks.settledAt||null})}}async function Xo(t,e,n){await O({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 o$(t,e){try{return await Nu("git",["-C",t.cwd,"cat-file","-e",`${e}^{commit}`],{timeout:1e4}),!0}catch{return!1}}async function Yo(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 Zo=(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 s$(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 na(t.repo,t.number,{headSha:s}).catch(()=>{}),t.reconcile?.headSha&&s!==t.reconcile.headSha&&await oa(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<WR;await dm(t.repo,t.number,s).catch(()=>{}),t.baseFoldAt&&await um(t.repo,t.number).catch(()=>{}),!i&&!c&&!l&&a&&!await r.localHasCommit(a,s).catch(()=>!1)&&!await Yo(a,"pushed",t.number,f=>f.payload?.sha===s)&&await Xo(a,"pushed",{...Zo(t,e),sha:s}).catch(()=>{})}if(t.ciFixAwait)if(o-(t.ciFixAwait.since||0)>Cu)await ra(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 ra(t.repo,t.number).catch(()=>{});else if(i.checks.total>0&&i.checks.pending===0){let c=Qo(r,t,"ci-fixed",i.checks.settledAt||null),l=t.ciFixAwait.since||0;a&&!await Yo(a,"ci-fixed",t.number,u=>(u.payload?.ts||0)>=l)&&await Xo(a,"ci-fixed",{...Zo(t,e),passed:i.checks.passed,githubAt:c.githubAt,latencyMs:c.latencyMs,lane:c.lane}).catch(()=>{}),await ra(t.repo,t.number).catch(()=>{})}}}if(t.reconcileAwait)if(o-(t.reconcileAwait.since||0)>Cu)await sa(t.repo,t.number).catch(()=>{});else{let i=await r.detail(t.repo,t.number).catch(()=>null);if(i){if(i.mergeable==="CONFLICTING")await sa(t.repo,t.number).catch(()=>{});else if(i.mergeable==="MERGEABLE"){Qo(r,t,"reconciled",null);let c=t.reconcileAwait.since||0;a&&!await Yo(a,"reconciled",t.number,l=>(l.payload?.ts||0)>=c)&&await Xo(a,"reconciled",{...Zo(t,e),base:i.baseRefName||null}).catch(()=>{}),await sa(t.repo,t.number).catch(()=>{})}}}}async function a$(t,e,n,r){let o=Qo(r,t,"closed",Date.parse(e?.closedAt||"")||null),s=n.get(t.trackId);s&&!await Yo(s,"closed",t.number)&&await Xo(s,"closed",{...Zo(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 i$(t,e,n){Qo(n,t,"lost",null);let r=e.get(t.trackId);r&&!await Yo(r,"lost",t.number)&&await Xo(r,"lost",Zo(t,null)).catch(()=>{}),await n.notifyIfResolved(t.repo,t.number,"lost").catch(()=>{}),await n.mark(t.repo,t.number,"lost").catch(()=>{})}async function Wy(t,e,n,r,o={}){let s=Qo(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 c$(t={}){let{now:e=Date.now(),auth:n=Dl,view:r=VR,list:o=JR,links:s=pm,trackMap:a=XR,emitMerged:i=YR,mark:c=ze,archiveIfArmed:l=tn,notifyIfArmed:u=en,notifyIfResolved:f=cu,batchThreshold:d=HR,detail:m=ZR,merge:h=QR,updateBranch:w=t$,lookup:g=Et,disarm:b=yo,emitDisarmed:v=e$,localHasCommit:A=o$}=t;if(!(await n().catch(()=>null))?.authenticated||ba()>0)return 0;let $=(await s().catch(()=>[])).filter(p=>e-(Ja.get(dt(p.repo,p.number))||0)>=zR(p,e));if(!$.length)return 0;let F=Date.now(),T=new Map,C=new Map;for(let p of $){let S=Hy(p,e);T.set(dt(p.repo,p.number),{lane:S,intervalMs:Uy[S]}),Ja.set(dt(p.repo,p.number),e),C.has(p.repo)||C.set(p.repo,[]),C.get(p.repo).push(p)}let W=await a().catch(()=>new Map),ot={now:e,lanes:T,emitMerged:i,mark:c,archiveIfArmed:l,notifyIfArmed:u,notifyIfResolved:f,detail:m,merge:h,updateBranch:w,lookup:g,disarm:b,emitDisarmed:v,localHasCommit:A};for(let[p,S]of C){let _,rt=new Set;if(S.length>d)_=await o(p).catch(()=>null);else{_=new Map;for(let I of S)try{let R=await r(p,I.number);R?.number!=null&&_.set(Number(R.number),R)}catch(R){KR(R)&&rt.add(dt(I.repo,I.number))}}if(_)for(let I of S){let R=dt(I.repo,I.number),E=_.get(Number(I.number));if(!E){if(rt.has(R)){let P=(Xa.get(R)||0)+1;Xa.set(R,P),P>=FR&&(await i$(I,W,ot),Xa.delete(R),Ja.delete(R))}continue}Xa.delete(R),E.state==="MERGED"||E.state==="CLOSED"?(E.state==="MERGED"?await Wy(I,E,W,ot):await a$(I,E,W,ot),Ja.delete(R)):(await s$(I,E,W,ot).catch(()=>{}),I.autoMerge&&await r$(I,W,ot).catch(()=>{}))}}let y={};for(let{lane:p}of T.values())y[p]=(y[p]||0)+1;return console.log(`[pr-watch] probed ${$.length} link(s) (${Object.entries(y).map(([p,S])=>`${p} ${S}`).join(", ")}) in ${Date.now()-F}ms`),$.length}function qy(){let t=!1;setInterval(async()=>{if(!t){t=!0;try{await c$()}catch{}finally{t=!1}}},Ly)}import Ce from"node:fs/promises";import Fr from"node:path";import Yy from"node:os";import Ky from"node:fs/promises";import Ya from"node:path";import{execFile as l$}from"node:child_process";import{promisify as u$}from"node:util";import{fileURLToPath as d$}from"node:url";var f$=u$(l$),zy=Ya.dirname(d$(import.meta.url)),p$=Ya.resolve(zy,"..","..","client","dist"),m$="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",h$=new Map([...m$].map((t,e)=>[t,e]));function w$(t){let e=[],n=0,r=0;for(let o of t){let s=h$.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 g$(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=w$(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 y$(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 Gy=new Map;async function b$(t){let e;try{e=await Ky.stat(t)}catch{return null}let n=Gy.get(t);if(n&&n.mtimeMs===e.mtimeMs)return n;try{let r=JSON.parse(await Ky.readFile(t,"utf8")),o={mtimeMs:e.mtimeMs,sources:r.sources||[],lines:g$(r.mappings||"")};return Gy.set(t,o),o}catch{return null}}var Pu=null;function k$(){return Pu||(Pu=process.env.BOSTRAT_RELEASE?Promise.resolve(process.env.BOSTRAT_RELEASE):f$("git",["rev-parse","--short","HEAD"],{cwd:Ya.resolve(zy,"..","..")}).then(t=>t.stdout.trim()).catch(()=>null)),Pu}var x$=/(?:https?:\/\/[^\s()]*)?\/assets\/([^\s()/]+\.js):(\d+):(\d+)/g;async function S$(t,{distDir:e=p$}={}){let n=[],r=0,o=!1;for(let s of t.matchAll(x$)){let[a,i,c,l]=s,u=await b$(Ya.join(e,"assets",`${i}.map`)),f=u?y$(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 Vy(t,e={}){try{if(!t?.error?.stack||!t.release||t.release==="unknown")return t;let n=e.release??await k$();if(!n||t.release!==n)return t;let{stack:r,symbolicated:o}=await S$(t.error.stack,e);o&&(t.error.stack=r,t.error.symbolicated=!0)}catch{}return t}var v$=()=>process.env.BOSTRAT_STATE_DIR||Fr.join(process.env.HOME||Yy.tmpdir(),".bostrat"),dn=()=>Fr.join(v$(),"telemetry"),Zy=()=>Fr.join(dn(),".relay-offsets.json"),T$=50,A$=14,_$=3e4,Jy=500,Ut=(t,e)=>typeof t=="string"?t.slice(0,e):"";function E$(t,e){if(!t||typeof t!="object")return null;let n=Ut(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:Ut(t.anonId,64),sessionId:Ut(t.sessionId,64),userId:Ut(t.userId,64),release:Ut(t.release,64),appVersion:Ut(t.appVersion,32),route:Ut(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:Ut(t.error.kind,256),message:Ut(t.error.message,8192),stack:Ut(t.error.stack,32768),fingerprint:Ut(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:Ut(s?.n,64),r:Ut(s?.r,128)}))),o}var Qy=t=>new Date(t).toISOString().slice(0,10),Xy=0;async function tb(t,e=Date.now()){let r=(Array.isArray(t?.events)?t.events.slice(0,T$):[]).map(s=>E$(s,e)).filter(Boolean);if(!r.length)return 0;for(let s of r)s.error?.stack&&await Vy(s);await Ce.mkdir(dn(),{recursive:!0});let o=Fr.join(dn(),`${Qy(e)}.ndjson`);return await Ce.appendFile(o,r.map(s=>JSON.stringify(s)+`
|
|
384
|
-
`).join(""),"utf8"),e-
|
|
385
|
-
`).filter(Boolean),f=[];for(let d of u)try{f.push(JSON.parse(d))}catch{}for(let d=0;d<f.length;d+=Jy){let m=f.slice(d,d+Jy).map(w=>R$(w)),h=await t(`${e.url.replace(/\/$/,"")}/v1/logs`,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${e.ingestKey}`},body:JSON.stringify({events:m})}).catch(()=>null);if(!h||!h.ok)return{sent:o,disabled:!1};o+=m.length}r[s]=i+l+1,await rb(r)}return{sent:o,disabled:!1}}var Bu=null,Du=!1;function ob(){Bu||(Bu=setInterval(async()=>{if(!Du){Du=!0;try{await $$()}catch(t){console.error("[telemetry] relay failed:",t?.message||t)}finally{Du=!1}}},_$),Bu.unref?.())}import ti from"node:fs/promises";import lb from"node:path";import O$ from"node:os";var ub=()=>process.env.BOSTRAT_STATE_DIR||lb.join(process.env.HOME||O$.tmpdir(),".bostrat"),db=()=>lb.join(ub(),"dictation-glossary.json"),M$=100,C$=500,N$=["bostrat","globopi","codex","tmux","worktree","Deepgram","Linear","Firecracker","Tigris","WireGuard","misous","postyx","cedar","gerbil","Anthropic","Opus","Haiku","Sonnet","keyterm","monorepo","dogfood"],ei=null,ju=null,Lu=Promise.resolve();function sb(){let t={},e=Date.now();for(let n of N$)t[n.toLowerCase()]={term:n,count:1,lastUsed:e,source:"seed"};return t}async function fb(){return ei||(ju??=ti.readFile(db(),"utf8").then(t=>JSON.parse(t)?.terms??sb()).catch(()=>sb()).then(t=>ei=t).finally(()=>ju=null),ju)}async function ab(){let t=JSON.stringify({terms:ei},null,2),e=db(),n=`${e}.${process.pid}.tmp`;await ti.mkdir(ub(),{recursive:!0}),await ti.writeFile(n,t),await ti.rename(n,e)}function P$(){return Lu=Lu.then(ab,ab),Lu.catch(()=>{})}async function Fu(){return Object.values(await fb()).sort((e,n)=>n.count-e.count||n.lastUsed-e.lastUsed)}var B$=t=>Math.max(1,Math.ceil(t.length/4));async function D$(){let t=[],e=0;for(let{term:n}of await Fu()){let r=B$(n);if(!(e+r>C$)&&(t.push(n),e+=r,t.length>=M$))break}return t}async function j$(t,e){let n=await fb(),r=t.toLowerCase(),o=n[r];n[r]={term:t,count:(o?.count??0)+1,lastUsed:Date.now(),source:o?.source??e}}var ib=t=>(t||"").split(/\s+/).map(e=>e.replace(/^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu,"")).filter(Boolean),Qa=t=>t.toLowerCase();function cb(t){return!new RegExp("\\p{L}","u").test(t)||t.length<3?!1:/^[a-z]+$/.test(t)?t.length>=4:!0}async function pb(t,e){let n=ib(t),r=ib(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]=Qa(n[u])===Qa(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;)Qa(n[c])===Qa(r[l])?(c++,l++):a[c+1][l]>=a[c][l+1]?c++:(cb(r[l])&&i.add(r[l]),l++);for(;l<s;l++)cb(r[l])&&i.add(r[l]);if(!i.size)return[];for(let u of i)await j$(u,"learned");return await P$(),[...i]}var ts=null;function L$(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 mb(){if(!ts)return null;let t=await fo();if(!t||t.apiKey.slice(-6)!==ts.keyTail)return null;let{keyTail:e,...n}=ts;return n}async function hb(t,e){let n=await fo();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 D$())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=L$(o.status,i);c.persistent&&(ts={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 ts=null,{transcript:(await o.json())?.results?.channels?.[0]?.alternatives?.[0]?.transcript??""}}var Uu={"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 F$(t){if(!t)return null;let e=String(t).trim();if(Uu[e])return e;let n=null;for(let r of Object.keys(Uu))e.startsWith(r)&&(!n||r.length>n.length)&&(n=r);return n}function U$(t,e={}){let n=Uu[F$(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 H$(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 es=null;async function W$(){if(!es)return null;let t=await Vs();if(!t||t.apiKey.slice(-6)!==es.keyTail)return null;let{keyTail:e,...n}=es;return n}async function q$(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=H$(e.status,o);s.persistent&&(es={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}es=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 K$={openai:q$};async function wb(){let t=await Vs();return t?{configured:!0,provider:t.provider,model:t.model,voice:t.voice,problem:await W$()}:{configured:!1}}async function gb(){let t=await Vs();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 K$[t.provider](t);return{provider:t.provider,model:t.model,voice:t.voice,...e}}async function yb(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=U$(e,s),i=await O(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 Tb=ut.dirname(z$(import.meta.url)),bb=Number(process.env.PORT)||3362,ni=Jg(process.env),Ab=Number(process.env.BOSTRAT_AUTO_TRIGGER_PCT)||80,Ur=Sy(process.env.BOSTRAT_ALLOW_IPS),_b=Ty(),Eb=process.env.BOSTRAT_ALLOW_LAN_TOKENLESS!=="1",x=ge();x.disable("x-powered-by");x.use((t,e,n)=>{if(Jo(t)||Ur.allowed(Ur.clientIp(t)))return n();e.status(403).type("text/plain").send("bostrat: access restricted to the allowlist")});x.use(Oy);x.use(Ey({token:_b,enforce:Eb}));x.use("/api/mocks",ge.json({limit:"1mb"}));x.use("/api/mocks",ge.raw({type:"image/png",limit:"24mb"}));x.use("/api/runbooks",ge.json({limit:"1mb"}));x.use("/api/telemetry",ge.json({limit:"256kb"}));x.use("/api/hook",ge.json({limit:"2mb"}));x.use("/api/dictation",ge.raw({type:["audio/*","video/mp4","application/octet-stream"],limit:"32mb"}));x.use("/api/tracks/:id/upload",ge.raw({type:()=>!0,limit:"25mb"}));x.use(ge.json({limit:"64kb"}));x.get("/api/health",async(t,e)=>{e.json({ok:!0,tmuxSocket:jt,hostTmuxAlive:await De(),autoTriggerPct:Ab})});x.get("/api/sessions",async(t,e)=>{e.json({sessions:await mn()})});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 hn(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 Y(n))return e.status(404).json({error:"session not found"});try{await re(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 Y(n);if(!r)return e.status(404).json({error:"session not found"});let o=await Qr(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 Rf(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=Cs(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 kb=10;x.get("/api/tracks",async(t,e)=>{let n=await ct(),r=n.filter(s=>s.archivedAt),o=n;if(t.query.archived!=="all"&&r.length>kb){let s=new Set([...r].sort((a,i)=>(i.archivedAt||0)-(a.archivedAt||0)).slice(0,kb).map(a=>a.id));o=n.filter(a=>!a.archivedAt||s.has(a.id))}e.json({tracks:o,archivedTotal:r.length,agents:Na,agentAvailability:await nu(),tickets:await Zs()})});x.get("/api/cost",async(t,e)=>{try{let n=await ct();t.query.track&&(n=n.filter(o=>o.id===t.query.track));let r=await ny(n);if(r.cursorUsage=Cl(),t.query.format==="text")return e.type("text/plain").send(ry(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 ay(await ct(),{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 dy(await ct(),{window:t.query.window,from:t.query.from,to:t.query.to});n.cursorUsage=Cl(),n.subscriptionMonthlyUsd=Number(process.env.BOSTRAT_SUBSCRIPTION_MONTHLY_USD)||null,e.json(n)}catch(n){e.status(500).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 gy(await ct(),{fresh:n}))}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/arcs",async(t,e)=>{try{e.json(await ym(await ct()))}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/arcs/:key",async(t,e)=>{try{let n=await bm(await ct(),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 Ag()})}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 Yu(t.get(as),{byToken:rg,legacy:()=>ng(t.body?.session_id,t.body?.cwd)});if(n==="refused")return is("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&&is("mismatch",{track:r.track.id,box:r.box.name}),is(n,{track:r?.track?.id,hook:t.body?.hook_event_name});let o=await zg(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(!eb(Ur.clientIp(t)))return e.json({ok:!0,dropped:!0});let n=await tb(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 ye(),r=new Set((await Promise.all(n.map(a=>tt(a.path)))).filter(Boolean).map(a=>a.toLowerCase())),s=((await Gr()).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 ds(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:us(),calls:Qh(),latency:Aw()})});x.post("/api/github/refresh",(t,e)=>{id(),e.json({ok:!0})});x.get("/api/gmail/search",async(t,e)=>{try{let n=await by(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 ky(String(t.params.id))})}catch(n){e.status(n?.status||502).json({error:n?.message||String(n)})}});function zu(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 ri({repo:t=null,number:e=null,branch:n=null}){let r=t?await tt(t):null;if(r&&e!=null){let s=await Et(r,e),a=s&&await Q(s.trackId);if(a)return a}return n?(await ct()).find(s=>s.workspace?.branch===n&&zu(s,t,r))??null:null}async function Ib({repo:t=null,number:e=null,branch:n=null}){let r=await ri({repo:t,number:e,branch:n}).catch(()=>null);if(r&&!r.archivedAt)return r;if(!n)return null;let o=t?await tt(t).catch(()=>null):null;return(await ct()).find(a=>!a.archivedAt&&a.workspace?.branch===n&&zu(a,t,o))??null}async function te(t,{number:e,url:n=null,title:r=null,branch:o=null,repo:s=null,track:a=null,extra:i={}}){let c=a||await ri({repo:s,number:e,branch:o}).catch(()=>null);if(!c)return;let l=i.repo??(s?await tt(s).catch(()=>null):null);await O({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 Y$(t){let e=(await ct()).filter(n=>!n.archivedAt);for(let n of t??[]){if(!n.prs?.length)continue;let r=await tt(n.path);if(r)for(let o of n.prs){if(await Et(r,o.number))continue;let s=e.find(a=>a.workspace?.branch===o.headRefName&&zu(a,n.path,r));s&&await Lt({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 Mw();await Y$(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(!Oo.has(w))throw new Error(`invalid merge method: ${w}`);let g=Number(r);if(!Number.isInteger(g)||g<=0)throw new Error("invalid PR number");let b=await tt((await $e(n)).path);if(!b)throw new Error("repo has no GitHub remote");let v=await ri({repo:n,number:g,branch:l}).catch(()=>null);if(!v)throw new Error("auto-merge needs a Track to watch this PR \u2014 none owns it");if(a&&await Mo(n,g),await Lt({repo:b,number:g,trackId:v.id,url:f,headRef:l,title:u,source:"matched"}).catch(()=>{}),!await go(b,g,{merge:{method:w}}))throw new Error("couldn't record the auto-merge arm");return await lr(b,g,i===!0),await aa(b,g,c===!0&&i!==!0),he(),await te("auto-armed",{number:g,url:f,title:u,branch:l,repo:n,track:v,...a?{extra:{updated:!0}}:{}}),e.json({ok:!0,number:g,auto:!0})}let d=await vr(n,r,{method:o||"squash"});await te("merged",{number:d.number,url:f,title:u,branch:l,repo:n});let m=await tt(n).catch(()=>null),h=!1;if(m&&(i===!0&&await lr(m,d.number,!0).catch(()=>{}),c===!0&&i!==!0&&await aa(m,d.number,!0).catch(()=>{}),await en(m,d.number).catch(()=>{}),h=await tn(m,d.number).catch(()=>!1),await ze(m,d.number,"merged").catch(()=>{})),i===!0&&!h){let w=await ri({repo:n,number:d.number,branch:l}).catch(()=>null);w&&!w.archivedAt&&await Rr(w.id).catch(()=>{})}e.json(d)}catch(d){let m=d?.message||String(d),h=/invalid|unknown repo|required|needs a Track|no GitHub remote/i.test(m)?400:502;e.status(h).json({error:m})}});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 tt((await $e(n)).path).catch(()=>null),l=c?await yo(c,i).catch(()=>null):null;try{await Fl(n,i)}catch(u){if(!l)throw u}he(),await te("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 tt(n).catch(()=>null),s=await Bw(n,{method:r||"merge",onMerged:a=>{te("merged",{number:a.number,url:a.url,title:a.title,branch:a.headRefName,repo:n}),o&&en(o,a.number).catch(()=>{}).then(()=>tn(o,a.number)).catch(()=>{}).then(()=>ze(o,a.number,"merged")).catch(()=>{})}});e.json(s)}catch(o){let s=o?.message||String(o),a=/already running/i.test(s)?409:/invalid|unknown repo|no PRs ready/i.test(s)?400:502;e.status(a).json({error:s})}});x.get("/api/git/prs/merge-all",(t,e)=>{e.json({jobs:Pw()})});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(!Oo.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 pe()).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 lr(d.repo,d.number,o===!0).catch(()=>{}),await aa(d.repo,d.number,s===!0&&o!==!0).catch(()=>{});let u=[],f=[];for(let d of l){let m={repo:d.repo,number:d.number,ok:!0,action:"armed"};u.push(m);try{let h=await Nn(d.repo);if(!h)throw new Error(`no local checkout for ${d.repo}`);let w=await Sr(h,d.number);f.push({r:m,l:d,repoPath:h,pr:w,plan:Cw(w,{update:r!==!1})})}catch(h){m.ok=!1,m.action="failed",m.reason=h?.message||String(h)}}for(let{r:d,l:m,repoPath:h,pr:w,plan:g}of Nw(f))try{if(g.action==="skip")d.action="skipped",d.reason=g.reason;else if(g.action==="merge")await vr(h,m.number,{method:i}),await te("merged",{number:m.number,url:w.url,title:w.title,branch:w.headRefName,repo:h,track:c}),await en(m.repo,m.number).catch(()=>{}),await tn(m.repo,m.number).catch(()=>{}),await ze(m.repo,m.number,"merged").catch(()=>{}),d.action="merged";else{if(g.action==="update-arm"&&(await Mo(h,m.number).catch(()=>{}),d.action="updated-armed"),!await go(m.repo,m.number,{merge:{method:i}}))throw new Error("couldn't record the auto-merge arm");await te("auto-armed",{number:m.number,url:w.url,title:w.title,branch:w.headRefName,repo:h,track:c,...d.action==="updated-armed"?{extra:{updated:!0}}:{}})}}catch(b){d.ok=!1,d.action="failed",d.reason=b?.message||String(b)}if(he(),!u.some(d=>d.ok))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 pe()).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 Nn(a.repo).catch(()=>null);c&&await Fl(c,a.number).catch(()=>{}),i&&(await te("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}))}he(),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=Nt(i.workspace),f,d=Array.isArray(a)?a.filter(Boolean):[];if(d.length)f=d.map(w=>{let g=u.find(b=>b.name===w||b.repo===w||b.base===w||b.dir===w);if(!g)throw new Error(`invalid repo "${w}" \u2014 not tagged on this Track`);return g});else if(f=await Eo(i),!f.length)return e.status(409).json({error:"no tagged repo has work to ship"});let m=[];for(let w of f)try{let g=await Hl(w.dir,{title:r,body:o,base:s}),b=w.repo||await tt(w.dir).catch(()=>null);b&&g.number&&await Lt({repo:b,number:g.number,trackId:i.id,url:g.url,headRef:i.workspace?.branch??null,title:r,source:"api"}),await te("opened",{number:g.number,url:g.url,title:r,track:i,branch:i.workspace?.branch,extra:{repo:b??w.name}}),m.push({repo:w.name,slug:b??null,ok:!0,url:g.url,number:g.number})}catch(g){m.push({repo:w.name,slug:w.repo??null,ok:!1,error:g?.message||String(g)})}let h=m.filter(w=>w.ok);return h.length?e.json({ok:!0,multi:!0,results:m,url:h[0].url,number:h[0].number}):e.status(502).json({error:m.map(w=>`${w.repo}: ${w.error}`).join("; "),results:m})}let c=await Hl(i.cwd,{title:r,body:o,base:s}),l=await tt(i.cwd).catch(()=>null);l&&c.number&&await Lt({repo:l,number:c.number,trackId:i.id,url:c.url,headRef:i.workspace?.branch??null,title:r,source:"api"}),await te("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 Eo(n),o=new Map(r.map(a=>[a.base,a])),s=Nt(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 wg(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 gg(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 Sr(n,r),s=await tt(n).catch(()=>null),a=await Ib({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=Nt(a.workspace).find(b=>b.base===n||s&&b.repo===s)??null,c=a.workspace?.kind==="multi",l=await Dw(n,o.number);if(!l.length)return e.status(409).json({error:"no failing checks on this PR"});let u=c&&i?ut.join("ci",i.name):"ci",f=ut.join(a.cwd,".bostrat",u),d=ut.join(".bostrat",u,`pr-${o.number}-fail.log`);await Bt.promises.mkdir(f,{recursive:!0}),await Bt.promises.writeFile(ut.join(a.cwd,d),l.map(b=>`\u2550\u2550\u2550\u2550 ${b.name}${b.link?` \xB7 ${b.link}`:""} \u2550\u2550\u2550\u2550
|
|
405
|
+
`),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 ab(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}.${w$(n.host)}/`}function w$(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 nb(e)&&dc(n[0])?n.slice(1).join("."):e}var ib=10,cb=200,g$=120,y$=24,b$=/^\d{4}-\d{2}-\d{2}$/;function Lu(t=process.env){return(t.CHANGELOG_REPOS||"globocodes/bostrat").split(",").map(e=>e.trim()).filter(Boolean)}var k$=(t=Date.now())=>new Date(t).toISOString().slice(0,10);function lb(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 x$(t){if(typeof t!="string"||!t.trim())throw new Error("md (markdown string) required");let e=go(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(`
|
|
406
|
+
`)){let s=o.trim();if(!s)continue;if(s.startsWith("#")){n==null&&(n=s.replace(/^#+\s*/,"").trim().slice(0,g$)||null);continue}let a=/^[-*]\s+(.*)$/.exec(s);if(!a)continue;let i=a[1].trim(),c=null,l=new RegExp(`^\\[([^\\]]{1,${y$}})\\]\\s+(.*)$`).exec(i);if(l&&(c=l[1].trim(),i=l[2].trim()),!!i){if(i.length>cb)throw new Error(`bullet ${r.length+1} is ${i.length} chars \u2014 over the ${cb}-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>ib)throw new Error(`${r.length} bullets \u2014 over the ${ib}-bullet cap; group related changes into one line`);return{title:n||null,bullets:r}}async function ub(t,{repos:e=Lu()}={}){let n=new Set(e);return(await pe()).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 db(t,{md:e,day:n=null}){let r=x$(e),o=n==null||n===""?k$():String(n);if(!b$.test(o)||Number.isNaN(Date.parse(o)))throw new Error(`invalid day "${n}" \u2014 expected YYYY-MM-DD`);let s=await $({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 S$}from"node:child_process";import{promisify as v$}from"node:util";var Uu=v$(S$),fb=3e4,T$=30*6e4,A$=fb,E$=10*6e4,_$=10080*6e4,I$=60*6e4,R$=8,$$=1440*6e4,O$=3,pb="number,url,title,headRefName,headRefOid,state,mergedAt,closedAt",Fu=60*6e4,M$=15*6e4,ei=new Map,ni=new Map,C$=/could not resolve|not found|no pull requests?/i,N$=t=>C$.test(String(t?.message||t||""));function P$(t,e){return[t.ciFix?.delegatedAt,t.reconcile?.delegatedAt,t.ciFixAwait?.since,t.reconcileAwait?.since].some(n=>n&&e-n<Fu)}var mb={fast:A$,slow:E$,decay:I$};function hb(t,e){return t.armedAt&&e-t.armedAt<T$||P$(t,e)?"fast":e-(t.firstSeen||0)>_$?"decay":"slow"}function B$(t,e){return mb[hb(t,e)]}function as(t,e,n,r){let o=t.lanes?.get(ft(e.repo,e.number))||{};return Ma({change:n,repo:e.repo,number:Number(e.number),githubAt:r||null,lane:o.lane||null,intervalMs:o.intervalMs||null,source:"watch"})}async function D$(t,e){$e("watch");try{let{stdout:n}=await Uu("gh",["pr","view",String(e),"--repo",t,"--json",pb],{timeout:3e4,maxBuffer:4194304,env:{...process.env,...await ne({slug:t})}});return JSON.parse(n||"null")}catch(n){throw Ze(n)&&Qe(n),n}}async function j$(t){$e("watch");try{let{stdout:e}=await Uu("gh",["pr","list","--repo",t,"--state","all","--limit","100","--json",pb],{timeout:3e4,maxBuffer:8388608,env:{...process.env,...await ne({slug:t})}}),n=new Map;for(let r of JSON.parse(e||"[]"))n.set(Number(r.number),r);return n}catch(e){throw Ze(e)&&Qe(e),e}}async function L$(){let t=new Map;for(let e of await ot().catch(()=>[]))t.set(e.id,e);return t}async function F$(t,e){await $({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 U$(t,e){let n=await Bn(t);return n?_r(n,e):null}async function H$(t,e,n){let r=await Bn(t);if(!r)throw new Error(`no local checkout for ${t}`);return Ir(r,e,{method:n})}async function W$(t,e){let n=await Bn(t);if(!n)throw new Error(`no local checkout for ${t}`);return Lo(n,e)}async function q$(t,e){await $({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 Kr(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 K$=10*6e4;async function G$(t,e,n){let r=t.autoMerge?.armedAt||0;if(r&&(n.now??Date.now())-r>$$){await Kr(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 Kr(t,o,e,n,`check${o.checks.failed===1?"":"s"} failed: ${a}`);return}if(o.mergeable==="CONFLICTING"){await Kr(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<K$)return;await Kr(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 Kr(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 Kr(t,o,e,n,String(a?.message||a).trim()||"merge failed");return}await wb(t,{...o,state:"MERGED"},e,n,{change:"auto-merged",githubAt:o.checks.settledAt||null})}}async function rs(t,e,n){await $({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 V$(t,e){try{return await Uu("git",["-C",t.cwd,"cat-file","-e",`${e}^{commit}`],{timeout:1e4}),!0}catch{return!1}}async function os(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 ss=(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 z$(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 ca(t.repo,t.number,{headSha:s}).catch(()=>{}),t.reconcile?.headSha&&s!==t.reconcile.headSha&&await ua(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<M$;await ym(t.repo,t.number,s).catch(()=>{}),t.baseFoldAt&&await gm(t.repo,t.number).catch(()=>{}),!i&&!c&&!l&&a&&!await r.localHasCommit(a,s).catch(()=>!1)&&!await os(a,"pushed",t.number,f=>f.payload?.sha===s)&&await rs(a,"pushed",{...ss(t,e),sha:s}).catch(()=>{})}if(t.ciFixAwait)if(o-(t.ciFixAwait.since||0)>Fu)await la(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 la(t.repo,t.number).catch(()=>{});else if(i.checks.total>0&&i.checks.pending===0){let c=as(r,t,"ci-fixed",i.checks.settledAt||null),l=t.ciFixAwait.since||0;a&&!await os(a,"ci-fixed",t.number,u=>(u.payload?.ts||0)>=l)&&await rs(a,"ci-fixed",{...ss(t,e),passed:i.checks.passed,githubAt:c.githubAt,latencyMs:c.latencyMs,lane:c.lane}).catch(()=>{}),await la(t.repo,t.number).catch(()=>{})}}}if(t.reconcileAwait)if(o-(t.reconcileAwait.since||0)>Fu)await da(t.repo,t.number).catch(()=>{});else{let i=await r.detail(t.repo,t.number).catch(()=>null);if(i){if(i.mergeable==="CONFLICTING")await da(t.repo,t.number).catch(()=>{});else if(i.mergeable==="MERGEABLE"){as(r,t,"reconciled",null);let c=t.reconcileAwait.since||0;a&&!await os(a,"reconciled",t.number,l=>(l.payload?.ts||0)>=c)&&await rs(a,"reconciled",{...ss(t,e),base:i.baseRefName||null}).catch(()=>{}),await da(t.repo,t.number).catch(()=>{})}}}}async function J$(t,e,n,r){let o=as(r,t,"closed",Date.parse(e?.closedAt||"")||null),s=n.get(t.trackId);s&&!await os(s,"closed",t.number)&&await rs(s,"closed",{...ss(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 X$(t,e,n){as(n,t,"lost",null);let r=e.get(t.trackId);r&&!await os(r,"lost",t.number)&&await rs(r,"lost",ss(t,null)).catch(()=>{}),await n.notifyIfResolved(t.repo,t.number,"lost").catch(()=>{}),await n.mark(t.repo,t.number,"lost").catch(()=>{})}async function wb(t,e,n,r,o={}){let s=as(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 Y$(t={}){let{now:e=Date.now(),auth:n=Hl,view:r=D$,list:o=j$,links:s=km,trackMap:a=L$,emitMerged:i=F$,mark:c=ze,archiveIfArmed:l=en,notifyIfArmed:u=nn,notifyIfResolved:f=hu,batchThreshold:d=O$,detail:m=U$,merge:h=H$,updateBranch:w=W$,lookup:g=It,disarm:b=To,emitDisarmed:S=q$,localHasCommit:A=V$}=t;if(!(await n().catch(()=>null))?.authenticated||Aa()>0)return 0;let O=(await s().catch(()=>[])).filter(p=>e-(ei.get(ft(p.repo,p.number))||0)>=B$(p,e));if(!O.length)return 0;let F=Date.now(),T=new Map,M=new Map;for(let p of O){let v=hb(p,e);T.set(ft(p.repo,p.number),{lane:v,intervalMs:mb[v]}),ei.set(ft(p.repo,p.number),e),M.has(p.repo)||M.set(p.repo,[]),M.get(p.repo).push(p)}let W=await a().catch(()=>new Map),st={now:e,lanes:T,emitMerged:i,mark:c,archiveIfArmed:l,notifyIfArmed:u,notifyIfResolved:f,detail:m,merge:h,updateBranch:w,lookup:g,disarm:b,emitDisarmed:S,localHasCommit:A};for(let[p,v]of M){let E,rt=new Set;if(v.length>d)E=await o(p).catch(()=>null);else{E=new Map;for(let I of v)try{let R=await r(p,I.number);R?.number!=null&&E.set(Number(R.number),R)}catch(R){N$(R)&&rt.add(ft(I.repo,I.number))}}if(E)for(let I of v){let R=ft(I.repo,I.number),_=E.get(Number(I.number));if(!_){if(rt.has(R)){let P=(ni.get(R)||0)+1;ni.set(R,P),P>=R$&&(await X$(I,W,st),ni.delete(R),ei.delete(R))}continue}ni.delete(R),_.state==="MERGED"||_.state==="CLOSED"?(_.state==="MERGED"?await wb(I,_,W,st):await J$(I,_,W,st),ei.delete(R)):(await z$(I,_,W,st).catch(()=>{}),I.autoMerge&&await G$(I,W,st).catch(()=>{}))}}let y={};for(let{lane:p}of T.values())y[p]=(y[p]||0)+1;return console.log(`[pr-watch] probed ${O.length} link(s) (${Object.entries(y).map(([p,v])=>`${p} ${v}`).join(", ")}) in ${Date.now()-F}ms`),O.length}function gb(){let t=!1;setInterval(async()=>{if(!t){t=!0;try{await Y$()}catch{}finally{t=!1}}},fb)}import Ne from"node:fs/promises";import Gr from"node:path";import Tb from"node:os";import yb from"node:fs/promises";import ri from"node:path";import{execFile as Z$}from"node:child_process";import{promisify as Q$}from"node:util";import{fileURLToPath as tO}from"node:url";var eO=Q$(Z$),kb=ri.dirname(tO(import.meta.url)),nO=ri.resolve(kb,"..","..","client","dist"),rO="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",oO=new Map([...rO].map((t,e)=>[t,e]));function sO(t){let e=[],n=0,r=0;for(let o of t){let s=oO.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 aO(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=sO(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 iO(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 bb=new Map;async function cO(t){let e;try{e=await yb.stat(t)}catch{return null}let n=bb.get(t);if(n&&n.mtimeMs===e.mtimeMs)return n;try{let r=JSON.parse(await yb.readFile(t,"utf8")),o={mtimeMs:e.mtimeMs,sources:r.sources||[],lines:aO(r.mappings||"")};return bb.set(t,o),o}catch{return null}}var Hu=null;function lO(){return Hu||(Hu=process.env.BOSTRAT_RELEASE?Promise.resolve(process.env.BOSTRAT_RELEASE):eO("git",["rev-parse","--short","HEAD"],{cwd:ri.resolve(kb,"..","..")}).then(t=>t.stdout.trim()).catch(()=>null)),Hu}var uO=/(?:https?:\/\/[^\s()]*)?\/assets\/([^\s()/]+\.js):(\d+):(\d+)/g;async function dO(t,{distDir:e=nO}={}){let n=[],r=0,o=!1;for(let s of t.matchAll(uO)){let[a,i,c,l]=s,u=await cO(ri.join(e,"assets",`${i}.map`)),f=u?iO(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 xb(t,e={}){try{if(!t?.error?.stack||!t.release||t.release==="unknown")return t;let n=e.release??await lO();if(!n||t.release!==n)return t;let{stack:r,symbolicated:o}=await dO(t.error.stack,e);o&&(t.error.stack=r,t.error.symbolicated=!0)}catch{}return t}var fO=()=>process.env.BOSTRAT_STATE_DIR||Gr.join(process.env.HOME||Tb.tmpdir(),".bostrat"),pn=()=>Gr.join(fO(),"telemetry"),Ab=()=>Gr.join(pn(),".relay-offsets.json"),pO=50,mO=14,hO=3e4,Sb=500,Ut=(t,e)=>typeof t=="string"?t.slice(0,e):"";function wO(t,e){if(!t||typeof t!="object")return null;let n=Ut(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:Ut(t.anonId,64),sessionId:Ut(t.sessionId,64),userId:Ut(t.userId,64),release:Ut(t.release,64),appVersion:Ut(t.appVersion,32),route:Ut(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:Ut(t.error.kind,256),message:Ut(t.error.message,8192),stack:Ut(t.error.stack,32768),fingerprint:Ut(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:Ut(s?.n,64),r:Ut(s?.r,128)}))),o}var Eb=t=>new Date(t).toISOString().slice(0,10),vb=0;async function _b(t,e=Date.now()){let r=(Array.isArray(t?.events)?t.events.slice(0,pO):[]).map(s=>wO(s,e)).filter(Boolean);if(!r.length)return 0;for(let s of r)s.error?.stack&&await xb(s);await Ne.mkdir(pn(),{recursive:!0});let o=Gr.join(pn(),`${Eb(e)}.ndjson`);return await Ne.appendFile(o,r.map(s=>JSON.stringify(s)+`
|
|
407
|
+
`).join(""),"utf8"),e-vb>6*3600*1e3&&(vb=e,gO(e).catch(()=>{})),r.length}async function gO(t=Date.now()){let e=Eb(t-mO*24*3600*1e3),n=[];try{n=await Ne.readdir(pn())}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 Ne.rm(Gr.join(pn(),s),{force:!0});let o=await Rb();for(let s of r)delete o[s];await $b(o)}var oi=new Map;function Ib(t,e=Date.now()){let n=t||"unknown",r=oi.get(n);return r||(oi.size>512&&oi.clear(),r={tokens:40,at:e},oi.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 Rb(){try{return JSON.parse(await Ne.readFile(Ab(),"utf8"))||{}}catch{return{}}}async function $b(t){await Ne.mkdir(pn(),{recursive:!0}),await Ne.writeFile(Ab(),JSON.stringify(t),"utf8")}function yO(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||Tb.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 bO({fetchImpl:t=fetch}={}){let e=await Fp();if(!e)return{sent:0,disabled:!0};let n;try{n=(await Ne.readdir(pn())).filter(s=>s.endsWith(".ndjson")).sort()}catch{return{sent:0,disabled:!1}}let r=await Rb(),o=0;for(let s of n){let a=Gr.join(pn(),s),i=r[s]||0,c;try{let d=await Ne.open(a,"r");try{let m=(await d.stat()).size;if(m<=i)continue;c=Buffer.alloc(m-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(`
|
|
408
|
+
`).filter(Boolean),f=[];for(let d of u)try{f.push(JSON.parse(d))}catch{}for(let d=0;d<f.length;d+=Sb){let m=f.slice(d,d+Sb).map(w=>yO(w)),h=await t(`${e.url.replace(/\/$/,"")}/v1/logs`,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${e.ingestKey}`},body:JSON.stringify({events:m})}).catch(()=>null);if(!h||!h.ok)return{sent:o,disabled:!1};o+=m.length}r[s]=i+l+1,await $b(r)}return{sent:o,disabled:!1}}var Wu=null,qu=!1;function Ob(){Wu||(Wu=setInterval(async()=>{if(!qu){qu=!0;try{await bO()}catch(t){console.error("[telemetry] relay failed:",t?.message||t)}finally{qu=!1}}},hO),Wu.unref?.())}import ai from"node:fs/promises";import Bb from"node:path";import kO from"node:os";var Db=()=>process.env.BOSTRAT_STATE_DIR||Bb.join(process.env.HOME||kO.tmpdir(),".bostrat"),jb=()=>Bb.join(Db(),"dictation-glossary.json"),xO=100,SO=500,vO=["bostrat","globopi","codex","tmux","worktree","Deepgram","Linear","Firecracker","Tigris","WireGuard","misous","postyx","cedar","gerbil","Anthropic","Opus","Haiku","Sonnet","keyterm","monorepo","dogfood"],ii=null,Ku=null,Gu=Promise.resolve();function Mb(){let t={},e=Date.now();for(let n of vO)t[n.toLowerCase()]={term:n,count:1,lastUsed:e,source:"seed"};return t}async function Lb(){return ii||(Ku??=ai.readFile(jb(),"utf8").then(t=>JSON.parse(t)?.terms??Mb()).catch(()=>Mb()).then(t=>ii=t).finally(()=>Ku=null),Ku)}async function Cb(){let t=JSON.stringify({terms:ii},null,2),e=jb(),n=`${e}.${process.pid}.tmp`;await ai.mkdir(Db(),{recursive:!0}),await ai.writeFile(n,t),await ai.rename(n,e)}function TO(){return Gu=Gu.then(Cb,Cb),Gu.catch(()=>{})}async function Vu(){return Object.values(await Lb()).sort((e,n)=>n.count-e.count||n.lastUsed-e.lastUsed)}var AO=t=>Math.max(1,Math.ceil(t.length/4));async function EO(){let t=[],e=0;for(let{term:n}of await Vu()){let r=AO(n);if(!(e+r>SO)&&(t.push(n),e+=r,t.length>=xO))break}return t}async function _O(t,e){let n=await Lb(),r=t.toLowerCase(),o=n[r];n[r]={term:t,count:(o?.count??0)+1,lastUsed:Date.now(),source:o?.source??e}}var Nb=t=>(t||"").split(/\s+/).map(e=>e.replace(/^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu,"")).filter(Boolean),si=t=>t.toLowerCase();function Pb(t){return!new RegExp("\\p{L}","u").test(t)||t.length<3?!1:/^[a-z]+$/.test(t)?t.length>=4:!0}async function Fb(t,e){let n=Nb(t),r=Nb(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]=si(n[u])===si(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;)si(n[c])===si(r[l])?(c++,l++):a[c+1][l]>=a[c][l+1]?c++:(Pb(r[l])&&i.add(r[l]),l++);for(;l<s;l++)Pb(r[l])&&i.add(r[l]);if(!i.size)return[];for(let u of i)await _O(u,"learned");return await TO(),[...i]}var is=null;function IO(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 Ub(){if(!is)return null;let t=await yo();if(!t||t.apiKey.slice(-6)!==is.keyTail)return null;let{keyTail:e,...n}=is;return n}async function Hb(t,e){let n=await yo();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 EO())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=IO(o.status,i);c.persistent&&(is={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 is=null,{transcript:(await o.json())?.results?.channels?.[0]?.alternatives?.[0]?.transcript??""}}var zu={"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 RO(t){if(!t)return null;let e=String(t).trim();if(zu[e])return e;let n=null;for(let r of Object.keys(zu))e.startsWith(r)&&(!n||r.length>n.length)&&(n=r);return n}function $O(t,e={}){let n=zu[RO(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 OO(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 cs=null;async function MO(){if(!cs)return null;let t=await ta();if(!t||t.apiKey.slice(-6)!==cs.keyTail)return null;let{keyTail:e,...n}=cs;return n}async function CO(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=OO(e.status,o);s.persistent&&(cs={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}cs=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 NO={openai:CO};async function Wb(){let t=await ta();return t?{configured:!0,provider:t.provider,model:t.model,voice:t.voice,problem:await MO()}:{configured:!1}}async function qb(){let t=await ta();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 NO[t.provider](t);return{provider:t.provider,model:t.model,voice:t.voice,...e}}async function Kb(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=$O(e,s),i=await $(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 Yb=lt.dirname(BO(import.meta.url)),Gb=Number(process.env.PORT)||3362,ci=yy(process.env),Zb=Number(process.env.BOSTRAT_AUTO_TRIGGER_PCT)||80,Vr=Jy(process.env.BOSTRAT_ALLOW_IPS),Qb=Yy(),tk=process.env.BOSTRAT_ALLOW_LAN_TOKENLESS!=="1",x=ge();x.disable("x-powered-by");x.use((t,e,n)=>{if(ns(t)||Vr.allowed(Vr.clientIp(t)))return n();e.status(403).type("text/plain").send("bostrat: access restricted to the allowlist")});x.use(ob);x.use(tb({token:Qb,enforce:tk}));x.use("/api/mocks",ge.json({limit:"1mb"}));x.use("/api/mocks",ge.raw({type:"image/png",limit:"24mb"}));x.use("/api/runbooks",ge.json({limit:"1mb"}));x.use("/api/telemetry",ge.json({limit:"256kb"}));x.use("/api/hook",ge.json({limit:"2mb"}));x.use("/api/dictation",ge.raw({type:["audio/*","video/mp4","application/octet-stream"],limit:"32mb"}));x.use("/api/tracks/:id/upload",ge.raw({type:()=>!0,limit:"25mb"}));x.use(ge.json({limit:"64kb"}));x.get("/api/health",async(t,e)=>{e.json({ok:!0,tmuxSocket:jt,hostTmuxAlive:await je(),autoTriggerPct:Zb})});x.get("/api/sessions",async(t,e)=>{e.json({sessions:await wn()})});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 gn(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 Y(n))return e.status(404).json({error:"session not found"});try{await oe(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 Y(n);if(!r)return e.status(404).json({error:"session not found"});let o=await ao(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 Bf(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=Fs(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 Vb=10;x.get("/api/tracks",async(t,e)=>{let n=await ot(),r=n.filter(s=>s.archivedAt),o=n;if(t.query.archived!=="all"&&r.length>Vb){let s=new Set([...r].sort((a,i)=>(i.archivedAt||0)-(a.archivedAt||0)).slice(0,Vb).map(a=>a.id));o=n.filter(a=>!a.archivedAt||s.has(a.id))}e.json({tracks:o,archivedTotal:r.length,agents:La,agentAvailability:await lu(),tickets:await oa()})});x.get("/api/cost",async(t,e)=>{try{let n=await ot();t.query.track&&(n=n.filter(o=>o.id===t.query.track));let r=await Ay(n);if(r.cursorUsage=jl(),t.query.format==="text")return e.type("text/plain").send(Ey(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 Ry(await ot(),{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 Ny(await ot(),{window:t.query.window,from:t.query.from,to:t.query.to});n.cursorUsage=jl(),n.subscriptionMonthlyUsd=Number(process.env.BOSTRAT_SUBSCRIPTION_MONTHLY_USD)||null,e.json(n)}catch(n){e.status(500).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 qy(await ot(),{fresh:n}))}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/arcs",async(t,e)=>{try{e.json(await Am(await ot()))}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/arcs/:key",async(t,e)=>{try{let n=await Em(await ot(),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 zg()})}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 rd(t.get(ps),{byToken:Eg,legacy:()=>Ag(t.body?.session_id,t.body?.cwd)});if(n==="refused")return ms("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&&ms("mismatch",{track:r.track.id,box:r.box.name}),ms(n,{track:r?.track?.id,hook:t.body?.hook_event_name});let o=await wy(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(!Ib(Vr.clientIp(t)))return e.json({ok:!0,dropped:!0});let n=await _b(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 ye(),r=new Set((await Promise.all(n.map(a=>tt(a.path)))).filter(Boolean).map(a=>a.toLowerCase())),s=((await Zr()).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 ys(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:gs(),calls:aw(),latency:Mw()})});x.post("/api/github/refresh",(t,e)=>{pd(),e.json({ok:!0})});x.get("/api/gmail/search",async(t,e)=>{try{let n=await Gy(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 Vy(String(t.params.id))})}catch(n){e.status(n?.status||502).json({error:n?.message||String(n)})}});function Qu(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 li({repo:t=null,number:e=null,branch:n=null}){let r=t?await tt(t):null;if(r&&e!=null){let s=await It(r,e),a=s&&await Q(s.trackId);if(a)return a}return n?(await ot()).find(s=>s.workspace?.branch===n&&Qu(s,t,r))??null:null}async function ek({repo:t=null,number:e=null,branch:n=null}){let r=await li({repo:t,number:e,branch:n}).catch(()=>null);if(r&&!r.archivedAt)return r;if(!n)return null;let o=t?await tt(t).catch(()=>null):null;return(await ot()).find(a=>!a.archivedAt&&a.workspace?.branch===n&&Qu(a,t,o))??null}async function ee(t,{number:e,url:n=null,title:r=null,branch:o=null,repo:s=null,track:a=null,extra:i={}}){let c=a||await li({repo:s,number:e,branch:o}).catch(()=>null);if(!c)return;let l=i.repo??(s?await tt(s).catch(()=>null):null);await $({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 FO(t){let e=(await ot()).filter(n=>!n.archivedAt);for(let n of t??[]){if(!n.prs?.length)continue;let r=await tt(n.path);if(r)for(let o of n.prs){if(await It(r,o.number))continue;let s=e.find(a=>a.workspace?.branch===o.headRefName&&Qu(a,n.path,r));s&&await Lt({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 Lw();await FO(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(!jo.has(w))throw new Error(`invalid merge method: ${w}`);let g=Number(r);if(!Number.isInteger(g)||g<=0)throw new Error("invalid PR number");let b=await tt((await Oe(n)).path);if(!b)throw new Error("repo has no GitHub remote");let S=await li({repo:n,number:g,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 Lo(n,g),await Lt({repo:b,number:g,trackId:S.id,url:f,headRef:l,title:u,source:"matched"}).catch(()=>{}),!await vo(b,g,{merge:{method:w}}))throw new Error("couldn't record the auto-merge arm");return await pr(b,g,i===!0),await fa(b,g,c===!0&&i!==!0),he(),await ee("auto-armed",{number:g,url:f,title:u,branch:l,repo:n,track:S,...a?{extra:{updated:!0}}:{}}),e.json({ok:!0,number:g,auto:!0})}let d=await Ir(n,r,{method:o||"squash"});await ee("merged",{number:d.number,url:f,title:u,branch:l,repo:n});let m=await tt(n).catch(()=>null),h=!1;if(m&&(i===!0&&await pr(m,d.number,!0).catch(()=>{}),c===!0&&i!==!0&&await fa(m,d.number,!0).catch(()=>{}),await nn(m,d.number).catch(()=>{}),h=await en(m,d.number).catch(()=>!1),await ze(m,d.number,"merged").catch(()=>{})),i===!0&&!h){let w=await li({repo:n,number:d.number,branch:l}).catch(()=>null);w&&!w.archivedAt&&await Nr(w.id).catch(()=>{})}e.json(d)}catch(d){let m=d?.message||String(d),h=/invalid|unknown repo|required|needs a Track|no GitHub remote/i.test(m)?400:502;e.status(h).json({error:m})}});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 tt((await Oe(n)).path).catch(()=>null),l=c?await To(c,i).catch(()=>null):null;try{await Kl(n,i)}catch(u){if(!l)throw u}he(),await ee("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 tt(n).catch(()=>null),s=await Ww(n,{method:r||"merge",onMerged:a=>{ee("merged",{number:a.number,url:a.url,title:a.title,branch:a.headRefName,repo:n}),o&&nn(o,a.number).catch(()=>{}).then(()=>en(o,a.number)).catch(()=>{}).then(()=>ze(o,a.number,"merged")).catch(()=>{})}});e.json(s)}catch(o){let s=o?.message||String(o),a=/already running/i.test(s)?409:/invalid|unknown repo|no PRs ready/i.test(s)?400:502;e.status(a).json({error:s})}});x.get("/api/git/prs/merge-all",(t,e)=>{e.json({jobs:Hw()})});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(!jo.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 pe()).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 pr(d.repo,d.number,o===!0).catch(()=>{}),await fa(d.repo,d.number,s===!0&&o!==!0).catch(()=>{});let u=[],f=[];for(let d of l){let m={repo:d.repo,number:d.number,ok:!0,action:"armed"};u.push(m);try{let h=await Bn(d.repo);if(!h)throw new Error(`no local checkout for ${d.repo}`);let w=await _r(h,d.number);f.push({r:m,l:d,repoPath:h,pr:w,plan:Fw(w,{update:r!==!1})})}catch(h){m.ok=!1,m.action="failed",m.reason=h?.message||String(h)}}for(let{r:d,l:m,repoPath:h,pr:w,plan:g}of Uw(f))try{if(g.action==="skip")d.action="skipped",d.reason=g.reason;else if(g.action==="merge")await Ir(h,m.number,{method:i}),await ee("merged",{number:m.number,url:w.url,title:w.title,branch:w.headRefName,repo:h,track:c}),await nn(m.repo,m.number).catch(()=>{}),await en(m.repo,m.number).catch(()=>{}),await ze(m.repo,m.number,"merged").catch(()=>{}),d.action="merged";else{if(g.action==="update-arm"&&(await Lo(h,m.number).catch(()=>{}),d.action="updated-armed"),!await vo(m.repo,m.number,{merge:{method:i}}))throw new Error("couldn't record the auto-merge arm");await ee("auto-armed",{number:m.number,url:w.url,title:w.title,branch:w.headRefName,repo:h,track:c,...d.action==="updated-armed"?{extra:{updated:!0}}:{}})}}catch(b){d.ok=!1,d.action="failed",d.reason=b?.message||String(b)}if(he(),!u.some(d=>d.ok))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 pe()).filter(a=>a.trackId===r.id&&a.state!=="merged"&&a.state!=="closed"),s=[];for(let a of o){let i=await To(a.repo,a.number).catch(()=>null),c=await Bn(a.repo).catch(()=>null);c&&await Kl(c,a.number).catch(()=>{}),i&&(await ee("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}))}he(),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=Nt(i.workspace),f,d=Array.isArray(a)?a.filter(Boolean):[];if(d.length)f=d.map(w=>{let g=u.find(b=>b.name===w||b.repo===w||b.base===w||b.dir===w);if(!g)throw new Error(`invalid repo "${w}" \u2014 not tagged on this Track`);return g});else if(f=await No(i),!f.length)return e.status(409).json({error:"no tagged repo has work to ship"});let m=[];for(let w of f)try{let g=await Vl(w.dir,{title:r,body:o,base:s}),b=w.repo||await tt(w.dir).catch(()=>null);b&&g.number&&await Lt({repo:b,number:g.number,trackId:i.id,url:g.url,headRef:i.workspace?.branch??null,title:r,source:"api"}),await ee("opened",{number:g.number,url:g.url,title:r,track:i,branch:i.workspace?.branch,extra:{repo:b??w.name}}),m.push({repo:w.name,slug:b??null,ok:!0,url:g.url,number:g.number})}catch(g){m.push({repo:w.name,slug:w.repo??null,ok:!1,error:g?.message||String(g)})}let h=m.filter(w=>w.ok);return h.length?e.json({ok:!0,multi:!0,results:m,url:h[0].url,number:h[0].number}):e.status(502).json({error:m.map(w=>`${w.repo}: ${w.error}`).join("; "),results:m})}let c=await Vl(i.cwd,{title:r,body:o,base:s}),l=await tt(i.cwd).catch(()=>null);l&&c.number&&await Lt({repo:l,number:c.number,trackId:i.id,url:c.url,headRef:i.workspace?.branch??null,title:r,source:"api"}),await ee("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 No(n),o=new Map(r.map(a=>[a.base,a])),s=Nt(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 Lg(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 Fg(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 _r(n,r),s=await tt(n).catch(()=>null),a=await ek({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=Nt(a.workspace).find(b=>b.base===n||s&&b.repo===s)??null,c=a.workspace?.kind==="multi",l=await qw(n,o.number);if(!l.length)return e.status(409).json({error:"no failing checks on this PR"});let u=c&&i?lt.join("ci",i.name):"ci",f=lt.join(a.cwd,".bostrat",u),d=lt.join(".bostrat",u,`pr-${o.number}-fail.log`);await Bt.promises.mkdir(f,{recursive:!0}),await Bt.promises.writeFile(lt.join(a.cwd,d),l.map(b=>`\u2550\u2550\u2550\u2550 ${b.name}${b.link?` \xB7 ${b.link}`:""} \u2550\u2550\u2550\u2550
|
|
386
409
|
${b.log??"(no fetchable log \u2014 see link)"}
|
|
387
410
|
`).join(`
|
|
388
|
-
`));let m=l.map(b=>b.name).join(", "),h=c&&i?` in the ${i.name}/ checkout`:"",w=await Qt(a.id,`CI is red on PR #${o.number} "${o.title}" (${o.url}) \u2014 branch ${o.headRefName}${h}. Failing check${l.length===1?"":"s"}: ${m}. 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"}),g=s;g&&(await Lt({repo:g,number:o.number,trackId:a.id,url:o.url,headRef:o.headRefName,title:o.title,source:"matched"}),await im(g,o.number,{headSha:o.headSha,trackId:a.id}).catch(()=>{})),await te("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 Sr(n,r);if(o.mergeable!=="CONFLICTING")return e.status(409).json({error:"this PR does not conflict with its base branch"});let s=await tt(n).catch(()=>null),a=await Ib({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=Nt(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 Qt(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 Lt({repo:u,number:o.number,trackId:a.id,url:o.url,headRef:o.headRefName,title:o.title,source:"matched"}),await cm(u,o.number,{headSha:o.headSha,trackId:a.id}).catch(()=>{})),await te("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=Fh.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 Qt(a.id,wa(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 O({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&&cg(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 ns=t=>e=>({...e,url:Cy(e,t.headers)});x.get("/api/views",async(t,e)=>{try{let n=await Ws({trackId:t.query.track||void 0});e.json({views:n.map(ns(t)),maxLive:We})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/views/:id",async(t,e)=>{let n=await fp(t.params.id);if(!n)return e.status(404).json({error:"view not found"});e.json({view:ns(t)(n),session:er(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 ct()).find(u=>u.cwd&&(s===u.cwd||s.startsWith(u.cwd+ut.sep)))||null),!i)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});let c=await ac({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:ns(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:ns(t)(await pp(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:ns(t)(await mp(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 hp(t.params.id))}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});var Z$=[["linear",xn,mc],["jira",zs,fc]],Hu=new Map;x.get("/api/integrations/ticketing",async(t,e)=>{try{let n=await dc(),r={};for(let[o,s,a]of Z$){let i=await s();if(!i){Hu.delete(o),r[o]={configured:!1};continue}let c=Hu.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},Hu.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 dc()})}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 Mp(o),i=n?await Q(n):null;!i&&r&&(i=(await ct()).find(l=>l.cwd&&(r===l.cwd||r.startsWith(l.cwd+ut.sep)))||null),i&&await O({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 ct()).find(c=>c.cwd&&(r===c.cwd||r.startsWith(c.cwd+ut.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 Q(r):null;if(!o&&n&&(o=(await ct()).find(a=>a.cwd&&(n===a.cwd||String(n).startsWith(a.cwd+ut.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 Vp(),ticket:await fe(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 fe(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 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 Ac(t.params.id)})});var oi=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 Zp(t.params.id))}catch(n){let r=n?.message||String(n);e.status(oi(r)).json({error:r})}});x.post("/api/tracks/:id/ticket/state",async(t,e)=>{try{e.json({ok:!0,...await _c(t.params.id,(t.body||{}).stateId)})}catch(n){let r=n?.message||String(n);e.status(oi(r)).json({error:r})}});x.post("/api/tracks/:id/ticket/comment",async(t,e)=>{try{e.json({ok:!0,...await Qp(t.params.id,(t.body||{}).body)})}catch(n){let r=n?.message||String(n);e.status(oi(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 tm(o,n,r===!0)})}catch(o){let s=o?.message||String(o);e.status(oi(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 ct()).find(i=>i.cwd&&(r===i.cwd||r.startsWith(i.cwd+ut.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 nm(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 em()})}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 rm(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 js({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 He(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 He(t.params.id);if(!n)return e.status(404).json({error:"mock not found"});let r=yn(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(qt(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 He(t.params.id);if(!n)return e.status(404).json({error:"mock not found"});let r=yn(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(qt(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 ct()).find(m=>m.cwd&&(a===m.cwd||a.startsWith(m.cwd+ut.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 zf({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 Vf(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 Xf(t.params.id),o=r.length?` over ${r.map(s=>`"${s.name}"`).join(", ")}`:"";await Qt(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{e.json(await Yf(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 ct(),o=String(t.query.cwd);if(n=r.find(s=>s.cwd&&(o===s.cwd||o.startsWith(s.cwd+ut.sep)))?.id,!n)return e.json({runbooks:[]})}e.json({runbooks:await qs({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 lc(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 lc(t.params.id);if(!n)return e.status(404).json({error:"runbook not found"});let r=kp(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(nr(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 ct()).find(u=>u.cwd&&(s===u.cwd||s.startsWith(u.cwd+ut.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 vp({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 Tp(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 Ap(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 _p(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=By(t.query.since||null);e.json({since:n,repos:Mu(),prs:await Dy(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 ct()).find(c=>c.cwd&&(s===c.cwd||String(s).startsWith(c.cwd+ut.sep)))||null),!a)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});e.json({ok:!0,entry:await jy(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 Q$=120,Wu=400,Gu=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>Wu)return e.status(400).json({error:`message is ${i.length} chars \u2014 over the ${Wu} cap; a notification is a nudge, put the detail in chat`});let c=uo(`${n||""}
|
|
389
|
-
${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
|
|
411
|
+
`));let m=l.map(b=>b.name).join(", "),h=c&&i?` in the ${i.name}/ checkout`:"",w=await te(a.id,`CI is red on PR #${o.number} "${o.title}" (${o.url}) \u2014 branch ${o.headRefName}${h}. Failing check${l.length===1?"":"s"}: ${m}. 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"}),g=s;g&&(await Lt({repo:g,number:o.number,trackId:a.id,url:o.url,headRef:o.headRefName,title:o.title,source:"matched"}),await mm(g,o.number,{headSha:o.headSha,trackId:a.id}).catch(()=>{})),await ee("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 _r(n,r);if(o.mergeable!=="CONFLICTING")return e.status(409).json({error:"this PR does not conflict with its base branch"});let s=await tt(n).catch(()=>null),a=await ek({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=Nt(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 te(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 Lt({repo:u,number:o.number,trackId:a.id,url:o.url,headRef:o.headRefName,title:o.title,source:"matched"}),await hm(u,o.number,{headSha:o.headSha,trackId:a.id}).catch(()=>{})),await ee("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=Vh.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 te(a.id,Sa(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 $({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&&Og(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 ls=t=>e=>({...e,url:ab(e,t.headers)});x.get("/api/views",async(t,e)=>{try{let n=await Js({trackId:t.query.track||void 0});e.json({views:n.map(ls(t)),maxLive:qe})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/views/:id",async(t,e)=>{let n=await bp(t.params.id);if(!n)return e.status(404).json({error:"view not found"});e.json({view:ls(t)(n),session:sr(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 ot()).find(u=>u.cwd&&(s===u.cwd||s.startsWith(u.cwd+lt.sep)))||null),!i)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});let c=await fc({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:ls(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:ls(t)(await kp(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:ls(t)(await xp(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 Sp(t.params.id))}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});var UO=[["linear",vn,kc],["jira",Qs,yc]],Ju=new Map;x.get("/api/integrations/ticketing",async(t,e)=>{try{let n=await gc(),r={};for(let[o,s,a]of UO){let i=await s();if(!i){Ju.delete(o),r[o]={configured:!1};continue}let c=Ju.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},Ju.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 gc()})}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 Lp(o),i=n?await Q(n):null;!i&&r&&(i=(await ot()).find(l=>l.cwd&&(r===l.cwd||r.startsWith(l.cwd+lt.sep)))||null),i&&await $({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 ot()).find(c=>c.cwd&&(r===c.cwd||r.startsWith(c.cwd+lt.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 fr(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 ot()).find(a=>a.cwd&&(n===a.cwd||String(n).startsWith(a.cwd+lt.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 em(),ticket:await fe(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 fe(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 fr(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 Oc(t.params.id)})});var di=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 sm(t.params.id))}catch(n){let r=n?.message||String(n);e.status(di(r)).json({error:r})}});x.post("/api/tracks/:id/ticket/state",async(t,e)=>{try{e.json({ok:!0,...await Mc(t.params.id,(t.body||{}).stateId)})}catch(n){let r=n?.message||String(n);e.status(di(r)).json({error:r})}});x.post("/api/tracks/:id/ticket/comment",async(t,e)=>{try{e.json({ok:!0,...await am(t.params.id,(t.body||{}).body)})}catch(n){let r=n?.message||String(n);e.status(di(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 im(o,n,r===!0)})}catch(o){let s=o?.message||String(o);e.status(di(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 ot()).find(i=>i.cwd&&(r===i.cwd||r.startsWith(i.cwd+lt.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 lm(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 cm()})}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 um(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 kn({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 We(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 We(t.params.id);if(!n)return e.status(404).json({error:"mock not found"});let r=Te(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(Kt(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 We(t.params.id);if(!n)return e.status(404).json({error:"mock not found"});let r=Te(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(Kt(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 ot()).find(m=>m.cwd&&(a===m.cwd||a.startsWith(m.cwd+lt.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 tp({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 ep(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 rp(t.params.id),o=r.length?` over ${r.map(s=>`"${s.name}"`).join(", ")}`:"";await te(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 sg(t.params.id)).map(r=>({id:r.id,name:r.name}));e.json({...await op(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 tg({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 eg(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 ot()).find(u=>u.cwd&&(s===u.cwd||s.startsWith(u.cwd+lt.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 ng({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 rg(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 og(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 ot(),o=String(t.query.cwd);if(n=r.find(s=>s.cwd&&(o===s.cwd||o.startsWith(s.cwd+lt.sep)))?.id,!n)return e.json({runbooks:[]})}e.json({runbooks:await Xs({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 hc(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 hc(t.params.id);if(!n)return e.status(404).json({error:"runbook not found"});let r=_p(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(ar(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 ot()).find(u=>u.cwd&&(s===u.cwd||s.startsWith(u.cwd+lt.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 $p({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 Op(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 Mp(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 Cp(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=lb(t.query.since||null);e.json({since:n,repos:Lu(),prs:await ub(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 ot()).find(c=>c.cwd&&(s===c.cwd||String(s).startsWith(c.cwd+lt.sep)))||null),!a)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});e.json({ok:!0,entry:await db(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 HO=120,Xu=400,ui=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>Xu)return e.status(400).json({error:`message is ${i.length} chars \u2014 over the ${Xu} cap; a notification is a nudge, put the detail in chat`});let c=go(`${n||""}
|
|
412
|
+
${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 ot()).find(h=>h.cwd&&(s===h.cwd||s.startsWith(h.cwd+lt.sep)))||null),!l)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});let u=(l.boxes||[]).find(m=>m.name===l.leadBox),f=(m,h)=>String(m).replace(/\s+/g," ").trim().slice(0,h),d={title:n?f(n,HO):l.name,body:f(i,Xu),urgency:o==="high"?"high":"normal",trackName:l.name,ts:Date.now()};await $(l,{type:"notify.raised",actor:"agent",boxId:l.leadBox??null,provider:u?.agent||null,payload:d}),e.json({ok:!0,notification:d,delivered:ui?ui.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 Hb(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 yo(),terms:await Vu(),problem:await Ub()})});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 Fb(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 Wb())});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 qb())}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 Kb(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 mu({name:n,cwd:r,repo:o,repos:s,agent:a,skipPermissions:i!==!1}),u=null;if(c)try{await fr(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:nk(Xo(t.params.id)),subtasks:Pr(t.params.id).map(us)})});x.delete("/api/tracks/:id",async(t,e)=>{try{let n=await Gg(t.params.id,{cascadeMocks:t.query.mocks!=="0",cascadeRunbooks:t.query.runbooks==="1"});await Oc(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 Nr(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 Xg(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 uu(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}
|
|
390
413
|
data: ${JSON.stringify(i)}
|
|
391
414
|
|
|
392
|
-
`),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=
|
|
415
|
+
`),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=yf(n.id,r),a=setInterval(()=>e.write(`: hb
|
|
393
416
|
|
|
394
|
-
`),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=
|
|
395
|
-
`);x.post("/api/tracks/:id/memory/consolidate",async(t,e)=>{try{e.json({ok:!0,...await
|
|
417
|
+
`),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=lo(await L(n)),a=Ns(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 _d()})});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 Id(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 WO=["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(`
|
|
418
|
+
`);x.post("/api/tracks/:id/memory/consolidate",async(t,e)=>{try{e.json({ok:!0,...await te(t.params.id,WO)})}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 Zg(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 te(t.params.id,n,{attachments:r}))}catch(o){let s=o?.message||String(o);e.status(/not found/i.test(s)?404:/archived/i.test(s)?409:/required|not running/i.test(s)?400:500).json({error:s})}});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 ug(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=lt.basename(String(t.params.file||""));if(!r||!Ql(lt.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(lt.join(Zl(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 qO=/^[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(!qO.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=xe(a.cwd,a.sessionId);if(!i)return e.status(404).json({error:"transcript not found"});let c=`"${o}"`,l=null,u=PO.createInterface({input:Bt.createReadStream(i,{encoding:"utf8"}),crlfDelay:1/0});for await(let d of u){if(!d.includes(c))continue;let m;try{m=JSON.parse(d)}catch{continue}if(m.uuid===o){l=m,u.close();break}}let f=l?Fi(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 pu(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",(t,e)=>{e.json({ok:!0,removed:Ha(t.params.id).filter(n=>n.kind!=="ship-notify")})});x.post("/api/tracks/:id/answer-prompt",async(t,e)=>{let{promptId:n,answers:r,text:o}=t.body||{};try{e.json(await du(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 qg({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 Ig(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}=t.body||{};try{e.json(await Ng(t.params.id,typeof n=="string"?n:""))}catch(r){let o=r?.message||String(r),s=/not found/i.test(o)?404:/required/i.test(o)?400:500;e.status(s).json({error:o})}});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 Za(r,{fresh:o}))});x.post("/api/tracks/:id/model",async(t,e)=>{let{model:n}=t.body||{};try{e.json(await Pg(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 ry(t.params.id,{agent:n,task:r,skipPermissions:o!==!1,reason:s});e.json({ok:!0,subtask:us(a)})}catch(a){let i=a?.message||String(a),c=/not found/i.test(i)?404:/required|unknown agent|git repo|not running|can't resolve/i.test(i)?400:500;e.status(c).json({error:i})}});x.get("/api/tracks/:id/subtasks",(t,e)=>{e.json({subtasks:Pr(t.params.id).map(us)})});x.get("/api/tracks/:id/subtask/:sid/messages",async(t,e)=>{let n=await _g(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:us(ku(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:us(sy(t.params.sid))})}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:409).json({error:r})}});function us(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 gu(t.params.id,n,r!==!1,o);e.json({ok:!0,step:s.step,toAgent:s.toAgent,fromBox:s.fromBox,skipPermissions:s.skipPermissions})}catch(s){let a=s?.message||String(s);e.status(/already running/i.test(a)?409:/not found/i.test(a)?404:400).json({error:a})}});x.post("/api/tracks/:id/spin-up-view",async(t,e)=>{try{e.json(await Mg(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(nk(Xo(t.params.id))??{running:!1})});function nk(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:Ss(),paths:Cd,trace:await Md({limit:n})})});var Yu=lt.resolve(Yb,"..","..","client","dist");Bt.existsSync(Yu)&&(x.use(ge.static(Yu)),x.get(/^(?!\/api\/).*/,(t,e)=>e.sendFile(lt.join(Yu,"index.html"))));var rk=DO.createServer(x),KO=new LO({noServer:!0}),ok=(t,e,n)=>{if(!(ns(t)||Vr.allowed(Vr.clientIp(t)))){e.write(`HTTP/1.1 403 Forbidden\r
|
|
396
419
|
\r
|
|
397
|
-
`),e.destroy();return}if(
|
|
420
|
+
`),e.destroy();return}if(sb(t,e,n))return;if(!eb(t,Qb,{enforce:tk})){e.write(`HTTP/1.1 401 Unauthorized\r
|
|
398
421
|
\r
|
|
399
|
-
`),e.destroy();return}let r=
|
|
422
|
+
`),e.destroy();return}let r=Hd(t.url);if(!r.ok){e.write(`HTTP/1.1 ${r.status} ${r.status===400?"Bad Request":"Not Found"}\r
|
|
400
423
|
\r
|
|
401
424
|
`),e.destroy();return}let o=r.sessionName;if(!yt(o)){e.write(`HTTP/1.1 400 Bad Request\r
|
|
402
425
|
\r
|
|
403
|
-
`),e.destroy();return}let{cols:s,rows:a}=r;
|
|
426
|
+
`),e.destroy();return}let{cols:s,rows:a}=r;KO.handleUpgrade(t,e,n,i=>Ld(i,o,{cols:s,rows:a}))};rk.on("upgrade",ok);var sk=process.env.BOSTRAT_CERT_DIR||lt.join(process.env.HOME||Zu.tmpdir(),".bostrat","certs"),zb=Number(process.env.BOSTRAT_TLS_PORT)||3443,Jb=lt.join(sk,"bostrat.globopi.key"),Xb=lt.join(sk,"bostrat.globopi.crt");if(Bt.existsSync(Jb)&&Bt.existsSync(Xb)){let t=jO.createServer({key:Bt.readFileSync(Jb),cert:Bt.readFileSync(Xb)},x);t.on("upgrade",ok),t.on("error",e=>{console.error(`[bostrat-agent] https listener failed (${e?.code||e?.message}) \u2014 continuing HTTP-only`)}),t.listen(zb,ci,()=>{console.log(`[bostrat-agent] https listening on ${ci||"0.0.0.0"}:${zb}`)})}rk.listen(Gb,ci,()=>{console.log(`[bostrat-agent] listening on ${ci||"0.0.0.0"}:${Gb}`),console.log(`[bostrat-agent] tmux socket: ${jt}`),console.log(`[bostrat-agent] auto-trigger default: ${Zb}%`),console.log(`[bostrat-agent] ip allowlist: ${Vr.open?"OPEN (BOSTRAT_ALLOW_IPS unset)":process.env.BOSTRAT_ALLOW_IPS}`),ey().then(t=>t&&console.log("[bostrat-agent] reconciled interrupted handoff(s)")).catch(t=>console.error("[bostrat-agent] handoff reconcile failed:",t?.message||t)),ny().then(t=>t&&console.log("[bostrat-agent] reconciled interrupted subtask(s)")).catch(t=>console.error("[bostrat-agent] subtask reconcile failed:",t?.message||t)),Yg().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)),Wg().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)),JO(),zO(),Ly()&&Fy({listTracks:ot}),gb(),dm({resolveTrack:Q}),hy({listTracks:ot,createTrack:mu,archiveTrack:Nr,listRepos:ye,resolveTrack:Q,sayToLead:te,answerPrompt:du,startLeadHandoff:gu,interruptLead:pu,listSubtaskJobs:Pr,cancelSubtask:ku,readEvents:L,appendEvent:$,knownAgents:La}),GO(),oo().then(t=>{t||console.warn(`[boot] no host tmux server on ${jt} and auto-start failed \u2014 boxes cannot spawn until one runs`)}),Ob(),process.env.BOSTRAT_PLANE_URL&&process.env.BOSTRAT_PLANE_TOKEN&&(ui=Sy({url:process.env.BOSTRAT_PLANE_URL,token:process.env.BOSTRAT_PLANE_TOKEN,nodeId:process.env.BOSTRAT_NODE_ID||Zu.hostname(),name:process.env.BOSTRAT_NODE_NAME||Zu.hostname(),listTracks:ot,detectAgents:lu}),console.log(`[bostrat-agent] plane link \u2192 ${process.env.BOSTRAT_PLANE_URL}`))});process.on("SIGTERM",()=>{try{ui?.stop()}catch{}setTimeout(()=>process.exit(0),150)});function GO(){VO(),zs().catch(()=>{}),setInterval(()=>zs().catch(()=>{}),1e4)}function VO(){for(let t of["bostrat-view","bostrat-mock","bostrat-flow","bostrat-ticket","bostrat-runbook","bostrat-notify","bostrat-gmail","bostrat-changelog","gh"])try{let e=lt.resolve(Yb,"..","bin",t),n=lt.join(process.env.HOME||"",".local","bin");if(!process.env.HOME||!Bt.existsSync(e))continue;Bt.mkdirSync(n,{recursive:!0});let r=lt.join(n,t);(Bt.existsSync(r)?Bt.readlinkSync(r):null)!==e&&(Bt.rmSync(r,{force:!0}),Bt.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 zO(){let t=!1,e=async()=>{if(!t){t=!0;try{let n=await ot(),r=await By({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 JO(){let t=!1;setInterval(async()=>{if(!t){t=!0;try{let e=await ot();for(let n of e)n.archivedAt||!n.boxes?.some(r=>r.state==="live")||(await uu(n.id).catch(()=>{}),await Kw(n).catch(()=>{}))}catch{}finally{t=!1}}},4e3)}
|