bostrat-node 0.2.16 → 0.2.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +33 -33
- package/dist/index.js +147 -149
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,121 +1,121 @@
|
|
|
1
|
-
var
|
|
2
|
-
`).includes(a));if(!s.length)return;await
|
|
1
|
+
var gx=Object.defineProperty;var Ud=(t,e)=>{for(var n in e)gx(t,n,{get:e[n],enumerable:!0})};import ft from"node:path";import Gt from"node:fs";import IC from"node:readline";import{fileURLToPath as RC}from"node:url";import Ee from"express";import $C from"node:http";import OC from"node:https";import jd from"node:os";import{WebSocketServer as MC}from"ws";import{execFile as B0}from"node:child_process";import{promisify as D0}from"node:util";import Js from"node:fs";import{randomUUID as j0}from"node:crypto";import Vt from"node:fs/promises";import Kd from"node:os";import Lt from"node:path";import{execFile as Sx}from"node:child_process";import{promisify as vx}from"node:util";import{createHash as yx,randomBytes as bx,timingSafeEqual as kx}from"node:crypto";var Ns="BOSTRAT_HOOK_TOKEN",Ps="x-bostrat-hook";function Hd(){return bx(32).toString("hex")}function uo(t){let e=typeof t=="string"?t.trim():"";return e?yx("sha256").update(e).digest("hex"):null}function Wd(t,e){return typeof t!="string"||typeof e!="string"||t.length!==e.length||!t?!1:kx(Buffer.from(t),Buffer.from(e))}var qi=new Map,Ki=new Map,xx=6e4;function Bs(t,e={},{now:n=Date.now(),log:r=console.warn}={}){if(qi.set(t,(qi.get(t)||0)+1),Ki.has(t)&&n-Ki.get(t)<xx)return!1;Ki.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=${qi.get(t)}${o?` ${o}`:""}`),!0}async function qd(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 Ds(){return/^(1|true|yes|on)$/i.test(process.env.BOSTRAT_CLAUDE_USE_API_KEY||"")&&process.env.ANTHROPIC_API_KEY?.trim()||null}function Tx(){return process.env.CLAUDE_CONFIG_DIR?Lt.join(process.env.CLAUDE_CONFIG_DIR,".claude.json"):Lt.join(Kd.homedir(),".claude.json")}function Ax(){return process.env.CLAUDE_CONFIG_DIR?Lt.join(process.env.CLAUDE_CONFIG_DIR,"settings.json"):Lt.join(Kd.homedir(),".claude","settings.json")}async function Ye(t){if(!t||typeof t!="string")return;let e=Lt.resolve(t),n=Tx(),r=Ax(),o={};try{o=JSON.parse(await Vt.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 Vt.mkdir(Lt.dirname(n),{recursive:!0}),await Vt.writeFile(n,JSON.stringify(o,null,2));let i={};try{i=JSON.parse(await Vt.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}: $${Ns}" --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 Vt.mkdir(Lt.dirname(r),{recursive:!0}),await Vt.writeFile(r,JSON.stringify(i,null,2)),await Ex(e)}async function Ex(t){try{let e=Lt.resolve(t),n=Lt.join(e,".bostrat","memory","claude"),r=Lt.join(e,".claude","settings.local.json"),o={};try{o=JSON.parse(await Vt.readFile(r,"utf8"))}catch{}let s=o.autoMemoryDirectory,a=typeof s=="string"&&s.includes(Lt.join(".bostrat","memory"));if(s&&!a||s===n)return;o.autoMemoryDirectory=n,await Vt.mkdir(n,{recursive:!0}),await Vt.mkdir(Lt.dirname(r),{recursive:!0}),await Vt.writeFile(r,JSON.stringify(o,null,2)),await _x(e,["/.claude/settings.local.json"])}catch{}}async function _x(t,e){try{let{stdout:n}=await vx(Sx)("git",["-C",t,"rev-parse","--git-path","info/exclude"]),r=Lt.resolve(t,n.trim()),o=await Vt.readFile(r,"utf8").catch(()=>""),s=e.filter(a=>!o.split(`
|
|
2
|
+
`).includes(a));if(!s.length)return;await Vt.mkdir(Lt.dirname(r),{recursive:!0}),await Vt.appendFile(r,(o&&!o.endsWith(`
|
|
3
3
|
`)?`
|
|
4
4
|
`:"")+s.join(`
|
|
5
5
|
`)+`
|
|
6
|
-
`)}catch{}}import
|
|
6
|
+
`)}catch{}}import Gi from"node:fs/promises";import Ix from"node:os";import js from"node:path";function Rx(){let t=process.env.CODEX_HOME||js.join(Ix.homedir(),".codex");return js.join(t,"config.toml")}function Gd(t){return`[projects."${t}"]`}function $x(t,e){let n=Gd(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 ir(t){if(!t||typeof t!="string")return;let e=js.resolve(t),n=Rx(),r="";try{r=await Gi.readFile(n,"utf8")}catch{}if($x(r,e))return;let o=Gd(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
|
|
13
|
+
`:"")}try{await Gi.mkdir(js.dirname(n),{recursive:!0}),await Gi.writeFile(n,r)}catch(s){console.warn(`[codex-config] could not seed folder trust for ${e}: ${s?.message||s}`)}}import Vd from"node:fs/promises";import Ox from"node:os";import fo from"node:path";function Mx(t){let n=fo.resolve(t).slice(1).replace(/\//g,"-").replace(/\./g,""),r=process.env.CURSOR_CONFIG_DIR||fo.join(Ox.homedir(),".cursor");return fo.join(r,"projects",n)}async function cr(t){if(!t||typeof t!="string")return;let e=fo.resolve(t),n=Mx(e),r=fo.join(n,".workspace-trusted");await Vd.mkdir(n,{recursive:!0}),await Vd.writeFile(r,`${JSON.stringify({trustedAt:new Date().toISOString(),workspacePath:e},null,2)}
|
|
14
|
+
`,"utf8")}import j from"node:fs/promises";import s0 from"node:os";import Y from"node:path";import{execFile as a0}from"node:child_process";import{promisify as i0}from"node:util";import Wx from"node:fs";import Jd from"node:fs/promises";import mo from"node:path";import qx from"node:os";import{fileURLToPath as Kx}from"node:url";import{execFile as Gx}from"node:child_process";import{promisify as Vx}from"node:util";import zi from"node:fs/promises";import Ze from"node:path";import{execFile as Cx}from"node:child_process";import{promisify as Nx}from"node:util";var Px=Nx(Cx),zd=process.env.HOME||"/home/globo",Bx=Ze.join(process.env.BOSTRAT_STATE_DIR||Ze.join(zd,".bostrat"),"repos"),Dx=[...(process.env.BOSTRAT_REPOS_ROOT||Ze.join(zd,"Documents")).split(","),Bx].map(t=>t.trim()).filter(Boolean),jx=Number(process.env.BOSTRAT_REPOS_DEPTH)||3,Lx=new Set(["node_modules",".git",".cache","dist","build",".next","vendor","__pycache__","venv",".venv","target"]);async function Fx(t){try{return(await zi.stat(Ze.join(t,".git"))).isDirectory()?"repo":"worktree"}catch{return null}}async function Ux(t){let e=i=>zi.readFile(Ze.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 _e(){let t=new Map;async function e(o,s){if(t.has(o))return;let a=await Fx(o);if(a==="worktree")return;if(a==="repo"){t.set(o,{path:o,name:Ze.basename(o),branch:await Ux(o)});return}if(s>=jx)return;let i;try{i=await zi.readdir(o,{withFileTypes:!0})}catch{return}for(let c of i)!c.isDirectory()||c.name.startsWith(".")||Lx.has(c.name)||await e(Ze.join(o,c.name),s+1)}for(let o of Dx)await e(Ze.resolve(o),0);let n=new Set,r=[];for(let o of t.values()){let s=(await ot(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 Hx(t){let e=String(t||"").trim().match(/github\.com[:/]+([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/i);return e?`${e[1]}/${e[2]}`:null}var Vi=new Map;async function ot(t){if(!t)return null;if(Vi.has(t))return Vi.get(t);let e=null;try{let{stdout:n}=await Px("git",["-C",t,"config","--get","remote.origin.url"],{timeout:5e3});e=Hx(n)}catch{e=null}return Vi.set(t,e),e}async function po(t){let e=String(t||"").toLowerCase();if(!e)return null;for(let n of await _e())if((await ot(n.path))?.toLowerCase()===e)return n.path;return null}var Xd=Vx(Gx),zx=process.env.BOSTRAT_STATE_DIR||mo.join(process.env.HOME||qx.tmpdir(),".bostrat"),Jx=mo.join(zx,"repos"),Yd=/^[^/\s]+\/[^/\s]+$/,Xx=5*6e4,Yx=6e4,Zx=10*6e4,Zd=fetch,Ji=new Map,Cn=null;function Qt(t=process.env){let e=t.BOSTRAT_PLANE_URL||"";if(!e)return null;try{let n=new URL(e);return n.protocol=n.protocol==="ws:"?"http:":"https:",n.origin}catch{return null}}function Ls(t=process.env){return!!(Qt(t)&&t.BOSTRAT_PLANE_TOKEN)}var Qd=t=>({authorization:`Bearer ${t.BOSTRAT_PLANE_TOKEN||""}`}),lr=(t,e)=>Object.assign(new Error(t),{status:e});async function Fs(t,{env:e=process.env}={}){let n=String(t||"").toLowerCase();if(!Yd.test(n))throw lr("repo must be owner/name",400);let r=Ji.get(n);if(r&&r.expiresAt-Date.now()>Xx)return r;if(!Ls(e))throw lr("node is not plane-linked \u2014 GitHub auth unavailable",503);let o;try{o=await Zd(`${Qt(e)}/v1/github/token?repo=${encodeURIComponent(t)}`,{headers:Qd(e)})}catch(i){if(r&&r.expiresAt>Date.now())return r;throw lr(`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 lr(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 Ji.set(n,a),a}async function ho({env:t=process.env,force:e=!1}={}){if(!Ls(t))return{configured:!1,repos:[],username:null};if(!e&&Cn&&Date.now()-Cn.at<Yx)return Cn.data;try{let n=await Zd(`${Qt(t)}/v1/github/repos`,{headers:Qd(t)});if(!n.ok)throw new Error(`plane replied ${n.status}`);let r=await n.json();return Cn={at:Date.now(),data:r},r}catch(n){return Cn?Cn.data:{configured:!0,repos:[],username:null,error:n?.message||String(n)}}}function tf(){Ji.clear(),Cn=null}async function ue({cwd:t=null,slug:e=null,env:n=process.env}={}){try{let r=e||(t?await ot(t):null);if(!r)return{};let{token:o}=await Fs(r,{env:n});return o?{GH_TOKEN:o}:{}}catch{return{}}}async function Xi({env:t=process.env}={}){return(await ho({env:t})).username??null}var Qx=()=>Kx(new URL("../bin/bostrat-git-credential",import.meta.url)),t0=(t=process.env)=>`http://127.0.0.1:${Number(t.PORT)||3362}`;function e0(t){let[e,n]=String(t).split("/");return mo.join(Jx,e,n)}async function Yi(t,{env:e=process.env}={}){let n=(...r)=>Xd("git",["-C",t,...r],{timeout:1e4});await n("config","--replace-all","credential.helper",""),await n("config","--add","credential.helper",`!"${Qx()}" ${t0(e)}`),await n("config","credential.useHttpPath","true")}async function Zi(t,{env:e=process.env,baseUrl:n="https://github.com"}={}){if(!Yd.test(String(t||"")))throw lr(`bad repo slug "${t}"`,400);let r=e0(t);if(Wx.existsSync(mo.join(r,".git")))return r;let{token:o}=await Fs(t,{env:e});await Jd.mkdir(mo.dirname(r),{recursive:!0});let s="!f() { echo username=x-access-token; echo password=$BOSTRAT_GIT_TOKEN; }; f";try{await Xd("git",["-c","credential.helper=","-c",`credential.helper=${s}`,"clone","--no-tags","-q",`${n}/${t}.git`,r],{timeout:Zx,env:{...e,BOSTRAT_GIT_TOKEN:o,GIT_TERMINAL_PROMPT:"0"}})}catch(a){throw await Jd.rm(r,{recursive:!0,force:!0}).catch(()=>{}),lr(`clone of ${t} failed: ${a?.stderr?.trim?.()||a?.message||a}`,502)}return await Yi(r,{env:e}),r}var n0=6e4,r0=5e3,Qi=new Map,o0=(...t)=>fetch(...t);function ef(t=process.env){return!!(Qt(t)&&t.BOSTRAT_PLANE_TOKEN)}var nf=t=>({authorization:`Bearer ${t.BOSTRAT_PLANE_TOKEN||""}`});async function rf(t,e,n){let r=Qt(n),o=new AbortController,s=setTimeout(()=>o.abort(),r0);try{return await o0(`${r}${t}`,{...e,signal:o.signal})}finally{clearTimeout(s)}}async function of(t,{env:e=process.env,force:n=!1}={}){if(!ef(e))return{facts:[],source:"unconfigured"};let r=Qi.get(t);if(!n&&r&&Date.now()-r.at<n0)return{facts:r.facts,source:"cache"};try{let o=await rf(`/v1/memory/${encodeURIComponent(t)}`,{headers:nf(e)},e);if(!o.ok)throw new Error(`plane ${o.status}`);let s=await o.json(),a=Array.isArray(s?.facts)?s.facts:[];return Qi.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 Us(t,e,{env:n=process.env}={}){if(!ef(n))return{pushed:0,source:"unconfigured"};if(!e?.length)return{pushed:0,source:"empty"};try{let r=await rf(`/v1/memory/${encodeURIComponent(t)}`,{method:"POST",headers:{...nf(n),"content-type":"application/json"},body:JSON.stringify({facts:e})},n);if(!r.ok)throw new Error(`plane ${r.status}`);let o=await r.json(),s=Array.isArray(o?.results)?o.results:[];return Qi.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 c0=i0(a0),tc=process.env.BOSTRAT_STATE_DIR||Y.join(process.env.HOME||s0.tmpdir(),".bostrat"),sf=200,l0=25*1024;function Hs(){return Y.join(tc,"memory","repos")}var Ie="operator";function Nn(t){return t===Ie?Y.join(tc,"memory",Ie):Y.join(Hs(),t)}var uf=".tombstones.json";async function wo(t){try{return JSON.parse(await j.readFile(Y.join(t,uf),"utf8"))}catch{return{}}}async function ec(t,e){let n=await wo(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(Y.join(t,uf),JSON.stringify(n))}function af(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 cf(t){try{let{stdout:e}=await c0("git",["-C",t,"config","--get","remote.origin.url"]);return e.trim()||null}catch{return null}}async function nc(t){if(!t)return[];let e=af(await cf(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=af(await cf(Y.join(t,o.name)));s&&!n.includes(s)&&n.push(s)}return n}function Ws(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 Qe(t,e){let n=new RegExp(`^\\s*${e}:\\s*(.+?)\\s*$`,"m").exec(t||"");return n?n[1].replace(/^"|"$/g,""):null}async function de(t,e=null){let n=e??await j.readFile(t,"utf8").catch(()=>""),{fm:r}=Ws(n),o=Qe(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 df(t,e){let n=Qe(t,"scope"),r=/^repo:([a-z0-9][a-z0-9._-]*)$/i.exec(n||"");if(r)return r[1].toLowerCase();if((n||"").toLowerCase()===Ie){let o=Qe(t,"trust");if(!o||/^operator/i.test(o))return Ie}return e}var ur=t=>/\]\(([^)]+\.md)\)/.exec(t)?.[1]||null;function rc(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=ur(c);l&&c.trim().startsWith("-")&&s.set(l,c)}let a=new Set,i=o.map(c=>{let l=ur(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 ff(t,e){let n=Qe(e,"name")||t.replace(/\.md$/,""),r=Qe(e,"description")||"";return`- [${n}](${t})${r?` \u2014 ${r}`:""}`}async function go(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 pf(t,e){let n=Y.join(t,".bostrat","memory"),r=await go(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=Y.join(n,l),f;try{f=await j.readFile(u,"utf8")}catch{continue}let{fm:d}=Ws(f),p=df(d,e);if(!p)continue;let m=Nn(p),w=Y.join(m,l),y=await de(u,f);if(i.has(m)||i.set(m,await wo(m)),(i.get(m)[l]||0)>=y)continue;let b=await de(w).catch(()=>0);if(await j.access(w).then(()=>!0).catch(()=>!1)&&b>=y)continue;await j.mkdir(m,{recursive:!0}),await j.writeFile(w,f),a++;let _=o.get(m);_||o.set(m,_={won:new Set,generated:new Map}),_.won.add(l),_.generated.set(l,ff(l,d)),s.has(p)||s.set(p,new Set),s.get(p).add(l)}let c=await j.readFile(Y.join(n,"MEMORY.md"),"utf8").catch(()=>"");for(let[l,u]of o){let f=Y.join(l,"MEMORY.md"),d=await j.readFile(f,"utf8").catch(()=>"");await j.writeFile(f,rc(d,c,u.won,u.generated))}return{harvested:a,changedByKey:s}}async function u0(){let t=Y.join(tc,"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=Y.join(t,o.name);if(!await j.access(Y.join(s,".bostrat","memory")).then(()=>!0).catch(()=>!1))continue;let[i]=await nc(s),c=await pf(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 d0=/^[A-Za-z0-9][A-Za-z0-9._-]*\.md$/,f0=t=>{let e=new Map;for(let n of(t||"").split(`
|
|
20
|
+
`)){let r=ur(n);r&&n.trim().startsWith("-")&&e.set(r,n)}return e};async function p0(t,e){let n=Nn(t),r=await wo(n),o=new Set,s=new Map,a=[],i=0;for(let c of e||[]){let l=String(c?.file||"");if(!d0.test(l)||l==="MEMORY.md")continue;let u=Y.join(n,l),f=await j.access(u).then(()=>!0).catch(()=>!1),d=f?await de(u):0,p=Number(c.modified)||0;if(c.deleted){p>d&&(f&&await j.rm(u,{force:!0}),await ec(n,[[l,p]]),f&&(a.push(l),i++));continue}f&&d>=p||(r[l]||0)>=p||(await j.mkdir(n,{recursive:!0}),await j.writeFile(u,String(c.content??"")),o.add(l),typeof c.indexLine=="string"&&c.indexLine.trim()&&s.set(l,c.indexLine),i++)}if(o.size||a.length){let c=Y.join(n,"MEMORY.md"),l=await j.readFile(c,"utf8").catch(()=>""),u=rc(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
|
|
24
|
-
`);e.length>
|
|
25
|
-
`);for(;Buffer.byteLength(n,"utf8")>
|
|
26
|
-
`);return n}var
|
|
22
|
+
`).filter(d=>!f.has(ur(d))).join(`
|
|
23
|
+
`)}await j.mkdir(n,{recursive:!0}),await j.writeFile(c,u)}return{changed:i,tombstoned:a.length}}async function oc(t,e=null){let n=Nn(t),r=e?.length?e:await go(n);if(!r.length)return{pushed:0,source:"empty"};let o=f0(await j.readFile(Y.join(n,"MEMORY.md"),"utf8").catch(()=>"")),s=[];for(let a of r){let i=Y.join(n,a),c;try{c=await j.readFile(i,"utf8")}catch{continue}s.push({file:a,content:c,modified:await de(i,c),indexLine:o.get(a)??null})}return Us(t,s)}async function m0(t){let{facts:e,source:n}=await of(t);e.length?await p0(t,e):n==="plane"&&await oc(t)}function h0(t){let e=t.split(`
|
|
24
|
+
`);e.length>sf&&(e=e.slice(0,sf));let n=e.join(`
|
|
25
|
+
`);for(;Buffer.byteLength(n,"utf8")>l0&&e.length;)e.pop(),n=e.join(`
|
|
26
|
+
`);return n}var w0=`# 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 g0(t,e){if(!t||!e?.length)return{materialized:!1,reason:"no-repo-key"};let n=Y.join(t,".bostrat","memory"),r=0,o=0,s=[];for(let u of e){let f=Nn(u),d=await wo(f);for(let[y,b]of Object.entries(d)){let S=Y.join(n,y);await j.access(S).then(()=>!0).catch(()=>!1)&&await de(S)<=b&&(await j.rm(S,{force:!0}),o++)}let p=await go(f);if(!p.length)continue;await j.mkdir(n,{recursive:!0});for(let y of p){let b=Y.join(f,y),S=Y.join(n,y),_=await de(b),$=await de(S).catch(()=>0);await j.access(S).then(()=>!0).catch(()=>!1)&&$>=_||(await j.copyFile(b,S),r++)}let m=await j.readFile(Y.join(f,"MEMORY.md"),"utf8").catch(()=>""),w=u===Ie?"## operator memory":`## repo: ${u}`;m.trim()&&s.push(e.length>1?`${w}
|
|
32
32
|
|
|
33
33
|
${m.trim()}`:m.trim())}if(!s.length)return o?{materialized:!1,reason:"empty-store",cleaned:o}:{materialized:!1,reason:"empty-store"};let i="_Shared Track memory (point-in-time facts). Verify file/line citations against the current code before acting on them; correct a wrong fact in place (bump `modified`, use `supersedes`) rather than working around it._\n\n"+s.join(`
|
|
34
34
|
|
|
35
35
|
`)+`
|
|
36
|
-
`,c=
|
|
37
|
-
`).filter(p=>!s.has(
|
|
38
|
-
`);await
|
|
39
|
-
`).filter(l=>
|
|
40
|
-
`)).catch(()=>{});let c=await
|
|
41
|
-
`,n!=null?{mode:n}:void 0),await
|
|
42
|
-
`,"utf8");let n=(await
|
|
43
|
-
`).filter(Boolean);n.length>
|
|
36
|
+
`,c=h0(i);await j.mkdir(n,{recursive:!0}),await j.writeFile(Y.join(n,"MEMORY.md"),c);let l=Y.join(n,"claude");return await j.mkdir(l,{recursive:!0}),await j.writeFile(Y.join(l,"MEMORY.md"),w0,{flag:"wx"}).catch(()=>{}),{materialized:!0,facts:r,capped:c!==i}}var lf=0,y0=3e4;async function mf(t){try{let e=await nc(t);if(!e.length)return{materialized:!1,reason:"no-repo-key"};let n=[...e,Ie];if(Date.now()-lf>y0){lf=Date.now(),await b0();let{changedByKey:r}=await u0();for(let[o,s]of r)await oc(o,[...s])}for(let r of n)await m0(r);return await g0(t,n)}catch(e){return{materialized:!1,reason:"error",error:e?.message||String(e)}}}async function b0(){let t=0,e=[];try{e=await j.readdir(Hs(),{withFileTypes:!0})}catch{return{moved:t}}for(let n of e){if(!n.isDirectory())continue;let r=Y.join(Hs(),n.name),o=Nn(Ie),s=new Set,a=new Map;for(let p of await go(r)){let m=Y.join(r,p),w;try{w=await j.readFile(m,"utf8")}catch{continue}let{fm:y}=Ws(w);if(df(y,n.name)!==Ie)continue;let b=Y.join(o,p),S=await de(b).catch(()=>0),_=await de(m,w);await j.mkdir(o,{recursive:!0}),_>S&&await j.writeFile(b,w),await j.rm(m,{force:!0}),s.add(p),a.set(p,ff(p,y)),t++}if(!s.size)continue;let i=Date.now();await ec(r,[...s].map(p=>[p,i])),await Us(n.name,[...s].map(p=>({file:p,deleted:!0,modified:i})));let c=Y.join(r,"MEMORY.md"),l=await j.readFile(c,"utf8").catch(()=>""),u=l.split(`
|
|
37
|
+
`).filter(p=>!s.has(ur(p))).join(`
|
|
38
|
+
`);await j.writeFile(c,u).catch(()=>{});let f=Y.join(o,"MEMORY.md"),d=await j.readFile(f,"utf8").catch(()=>"");await j.writeFile(f,rc(d,l,s,a))}return{moved:t}}async function hf(){let t=[],e=[Ie];try{for(let n of await j.readdir(Hs(),{withFileTypes:!0}))n.isDirectory()&&e.push(n.name)}catch{}for(let n of e){let r=Nn(n),o=await go(r),s=await wo(r);if(!o.length&&!Object.keys(s).length)continue;let a=[];for(let i of o){let c=await j.readFile(Y.join(r,i),"utf8").catch(()=>""),{fm:l}=Ws(c);a.push({file:i,name:Qe(l,"name")||i.replace(/\.md$/,""),description:Qe(l,"description")||"",scope:Qe(l,"scope")||null,modified:await de(Y.join(r,i),c)})}t.push({key:n,facts:a,tombstones:Object.keys(s).length})}return t}async function wf(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=Nn(t),r=Y.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 ec(n,[[e,s]]);let a=Y.join(n,"MEMORY.md"),i=await j.readFile(a,"utf8").catch(()=>"");await j.writeFile(a,i.split(`
|
|
39
|
+
`).filter(l=>ur(l)!==e).join(`
|
|
40
|
+
`)).catch(()=>{});let c=await Us(t,[{file:e,deleted:!0,modified:s}]);return{deleted:e,key:t,plane:c.source}}async function gf(t){try{let e=await nc(t),{harvested:n,changedByKey:r}=await pf(t,e[0]||null);for(let[o,s]of r)await oc(o,[...s]);return{harvested:n}}catch(e){return{harvested:0,error:e?.message||String(e)}}}import cc from"node:fs/promises";import Ef from"node:path";import kf from"node:fs/promises";import xf from"node:os";import Sf from"node:path";import sc from"node:fs/promises";import k0 from"node:path";var x0=0;async function Ft(t,e,{mode:n}={}){await sc.mkdir(k0.dirname(t),{recursive:!0});let r=`${t}.${process.pid}.${++x0}.tmp`;await sc.writeFile(r,JSON.stringify(e,null,2)+`
|
|
41
|
+
`,n!=null?{mode:n}:void 0),await sc.rename(r,t)}var S0=1e4,v0=3e4,yf=2e3,T0=()=>process.env.BOSTRAT_STATE_DIR||Sf.join(process.env.HOME||xf.tmpdir(),".bostrat"),vf=()=>Sf.join(T0(),"content-outbox.json"),A0=()=>process.env.BOSTRAT_NODE_ID||xf.hostname(),E0=(...t)=>fetch(...t),_0=v0;function ht(t=process.env){return!!(Qt(t)&&t.BOSTRAT_PLANE_TOKEN)}var bo=(t=process.env)=>({authorization:`Bearer ${t.BOSTRAT_PLANE_TOKEN||""}`});async function ko(t,e={},n=process.env){let r=new AbortController,o=setTimeout(()=>r.abort(),S0);try{return await E0(`${Qt(n)}${t}`,{...e,signal:r.signal})}finally{clearTimeout(o)}}var Ks=null,ac=null,qs=null,yo=null;async function Tf(){return Ks||(ac||=kf.readFile(vf(),"utf8").then(t=>JSON.parse(t)).catch(()=>({ops:[]})).then(t=>Ks={ops:Array.isArray(t?.ops)?t.ops:[]}).finally(()=>ac=null),ac)}async function Af(){await Ft(vf(),Ks).catch(()=>{})}function bf(t){return t.op==="blob"?`blob ${t.family} ${t.id} ${t.v} ${t.kind}`:`${t.op} ${t.family} ${t.id}`}async function ic(t){let e=await Tf(),n=bf(t);if(t.op==="delete")e.ops=e.ops.filter(r=>!(r.family===t.family&&r.id===t.id)),e.ops.push(t);else{e.ops=e.ops.filter(o=>!(o.op==="delete"&&o.family===t.family&&o.id===t.id));let r=e.ops.findIndex(o=>bf(o)===n);r>=0?e.ops[r]=t:e.ops.push(t)}e.ops.length>yf&&(e.ops=e.ops.slice(-yf)),await Af()}function I0(){qs||(qs=setTimeout(()=>{qs=null,dr().catch(()=>{})},_0),qs.unref?.())}async function R0(t,e=process.env){if(t.op==="record"){let n=await ko(`/v1/content/${encodeURIComponent(t.family)}`,{method:"POST",headers:{...bo(e),"content-type":"application/json"},body:JSON.stringify({records:[{id:t.id,trackId:t.trackId??null,data:t.data,updatedAt:t.updatedAt,originNode:A0()}]})},e);if(!n.ok)throw new Error(`plane ${n.status}`);let o=(await n.json().catch(()=>null))?.results?.[0];return o?.outcome==="rejected"?{delivered:!1,rejected:o.reason}:{delivered:!0}}if(t.op==="blob"){let n=await kf.readFile(t.path).catch(()=>null);if(!n)return{delivered:!0,dropped:"source file gone"};let r=await ko(`/v1/content/${encodeURIComponent(t.family)}/${encodeURIComponent(t.id)}/blob?v=${t.v}&kind=${encodeURIComponent(t.kind)}`,{method:"POST",headers:{...bo(e),"content-type":"application/octet-stream"},body:n},e);if(!r.ok&&r.status!==400)throw new Error(`plane ${r.status}`);return r.status===400?{delivered:!1,rejected:(await r.json().catch(()=>null))?.error||"rejected"}:{delivered:!0}}if(t.op==="delete"){let n=await ko(`/v1/content/${encodeURIComponent(t.family)}/${encodeURIComponent(t.id)}?deletedAt=${t.deletedAt}`,{method:"DELETE",headers:bo(e)},e);if(!n.ok)throw new Error(`plane ${n.status}`);return{delivered:!0}}return{delivered:!0,dropped:"unknown op"}}async function dr({env:t=process.env,log:e=console}={}){return ht(t)?yo||(yo=(async()=>{let n=await Tf(),r=0;for(;n.ops.length;){let o=n.ops[0],s;try{s=await R0(o,t)}catch(a){return I0(),{flushed:r,pending:n.ops.length,error:a?.message||String(a)}}s.rejected&&e.error?.(`[content-plane] ALARM plane rejected ${o.op} ${o.family}/${o.id}: ${s.rejected}`),n.ops.shift(),r++,await Af()}return{flushed:r,pending:0}})().finally(()=>{yo=null}),yo):{flushed:0,source:"unconfigured"}}async function Nt(t,e,{trackId:n=null,env:r=process.env}={}){return ht(r)?(await ic({op:"record",family:t,id:e.id,trackId:n??e.trackId??null,data:e,updatedAt:Number(e.updatedAt)||Date.now()}),dr({env:r})):{queued:!1,source:"unconfigured"}}async function tn(t,e,n,r,o,{env:s=process.env}={}){return ht(s)?(await ic({op:"blob",family:t,id:e,v:Number(n),kind:r,path:o}),dr({env:s})):{queued:!1,source:"unconfigured"}}async function re(t,e,{deletedAt:n=Date.now(),env:r=process.env}={}){return ht(r)?(await ic({op:"delete",family:t,id:e,deletedAt:n}),dr({env:r})):{queued:!1,source:"unconfigured"}}async function Rt(t,{env:e=process.env}={}){if(!ht(e))return{records:[],source:"unconfigured"};try{let n=await ko(`/v1/content/${encodeURIComponent(t)}?tombstones=1`,{headers:bo(e)},e);if(!n.ok)throw new Error(`plane ${n.status}`);let r=await n.json();return{records:Array.isArray(r?.records)?r.records:[],source:"plane"}}catch(n){return{records:[],source:"unreachable",error:n?.message}}}async function Gs(t,e,n,r,{env:o=process.env}={}){if(!ht(o))return null;try{let s=await ko(`/v1/content/${encodeURIComponent(t)}/${encodeURIComponent(e)}/blob?v=${Number(n)}&kind=${encodeURIComponent(r)}`,{headers:bo(o)},o);if(!s.ok)return null;let a=Buffer.from(await s.arrayBuffer());return r==="png"?a:a.toString("utf8")}catch{return null}}var $0=".bostrat/handoff",O0="latest.md",M0=t=>/workspaces[/\\]([0-9a-f]{8})$/.exec(t||"")?.[1]??null;async function _f(t,e,{box:n=null,agent:r=null}={}){if(!t||!ht())return{pushed:!1};try{let o=Date.now();return await Nt("checkpoints",{id:t,trackId:t,v:o,box:n,agent:r,updatedAt:o},{trackId:t}),await tn("checkpoints",t,o,"md",e),{pushed:!0,v:o}}catch{return{pushed:!1}}}async function If(t){try{let e=M0(t);if(!e||!ht())return{restored:!1};let n=Ef.join(t,$0,O0);if(await cc.stat(n).then(()=>!0,()=>!1))return{restored:!1};let{records:r,source:o}=await Rt("checkpoints");if(o!=="plane")return{restored:!1};let s=r.find(i=>!i.deletedAt&&i.data?.id===e);if(!s?.data?.v)return{restored:!1};let a=await Gs("checkpoints",e,s.data.v,"md");return typeof a!="string"||!a?{restored:!1}:(await cc.mkdir(Ef.dirname(n),{recursive:!0}),await cc.writeFile(n,a),{restored:!0,v:s.data.v})}catch{return{restored:!1}}}import xo from"node:fs/promises";import C0 from"node:os";import $f from"node:path";var N0=[/[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 So(t){let e=String(t??"");for(let n of N0)e=e.replace(n,"\u2039redacted\u203A");return e}var lc=process.env.BOSTRAT_DEBUG_DIR||$f.join(C0.homedir(),".bostrat","debug"),fr=$f.join(lc,"handoff.jsonl"),Rf=Number(process.env.BOSTRAT_HANDOFF_DEBUG_MAX)||500;function Vs(){let t=process.env.BOSTRAT_HANDOFF_DEBUG;return t==="1"||t==="true"}async function P0(t){try{await xo.mkdir(lc,{recursive:!0}),await xo.appendFile(fr,`${JSON.stringify(t)}
|
|
42
|
+
`,"utf8");let n=(await xo.readFile(fr,"utf8")).split(`
|
|
43
|
+
`).filter(Boolean);n.length>Rf&&await xo.writeFile(fr,`${n.slice(-Rf).join(`
|
|
44
44
|
`)}
|
|
45
|
-
`,"utf8")}catch(e){console.warn(`[handoff-debug] failed to write ${
|
|
46
|
-
`).filter(Boolean).slice(-t).map(r=>{try{let o=JSON.parse(r);return typeof o?.paneTail=="string"&&(o.paneTail=
|
|
47
|
-
`).filter(Boolean).map(r=>{let[o,s,a,i,c,l,u,f,d]=r.split("|"),p=Number(i);return{name:o,title:u||null,windows:Number(s),attached:Number(a),lastActivityMs:Number.isFinite(p)?n-p*1e3:null,cwd:c||null,isBostrat:l==="1",sessionId:f||null,agent:d||(l==="1"?"claude":null)}}).filter(r=>!
|
|
48
|
-
`).map(n=>n.trim()).includes(t)}catch{return!1}}var
|
|
49
|
-
`).filter(Boolean).length}catch{return 0}}async function
|
|
50
|
-
`);for(let n=e.length-1;n>=0;n--){let r=e[n].trim();if(!r||r[0]!=="{")continue;let o;try{o=JSON.parse(r)}catch{continue}let s=o?.message?.usage;if(!s)continue;let a=(s.input_tokens||0)+(s.cache_creation_input_tokens||0)+(s.cache_read_input_tokens||0);if(a>0)return{occupancy:a,model:o.message.model||o.model||null}}return null}async function
|
|
51
|
-
`).trim():""}function
|
|
52
|
-
`)){let i=a.trim();if(!i||i[0]!=="{")continue;let c;try{c=JSON.parse(i)}catch{continue}if(c.type==="attachment"){let m=c.attachment;m?.type==="queued_command"&&m.commandMode==="task-notification"&&typeof m.prompt=="string"&&m.prompt.trim()&&o.push({role:"user",text:m.prompt.trim(),ts:c.timestamp?Date.parse(c.timestamp):null,model:null});continue}let l=c.message;if(!l)continue;let u=l.role||c.type;if(u!=="user"&&u!=="assistant"||u==="assistant"&&l.model==="<synthetic>"&&!c.isApiErrorMessage)continue;if(u==="assistant"&&Array.isArray(l.content))for(let m of l.content){let
|
|
53
|
-
|
|
54
|
-
`):""}import
|
|
55
|
-
`);if(!
|
|
56
|
-
`);for(let r=n.length-1;r>=0;r--)if(
|
|
57
|
-
`);return e}function
|
|
58
|
-
`),o=[],s=-1;for(let l=0;l<r.length;l++){let u=r[l].match(
|
|
59
|
-
`),o=r.findIndex(i=>
|
|
60
|
-
`).map(a=>a.trim()),o=r.findIndex(a=>/Press any key to log in/.test(a)),s=r.slice(0,o<0?r.length:o).filter(a=>a&&!/^Cursor Agent$/i.test(a)&&!/^v?\d{4}\.\d{2}\.\d{2}/.test(a)).join(" ");return{provider:"cursor",box:e,multiSelect:!1,cursorIndex:0,answerMode:"keys",promptId:"cursor:signin-start",questions:[{question:(s?`${s} `:"")+"Cursor isn't signed in on this box \u2014 start the sign-in to get a login link you can open from any device.",header:"Sign in",multiSelect:!1,options:[{label:"Start sign-in",description:"Shows a login link (and a QR option) \u2014 your login goes to cursor.com, never through bostrat"}]}]}}var
|
|
61
|
-
`),o="",s=r.findIndex(a=>/https:\/\//.test(a));if(s>=0){o=r[s].slice(r[s].indexOf("https://")).trim();for(let a=s+1;a<r.length;a++){let i=r[a].trim();if(!i||!/^[A-Za-z0-9~%&=?/_.-]+$/.test(i))break;o+=i}}return{provider:"cursor",box:e,multiSelect:!1,cursorIndex:0,promptId:`cursor:signin-link:${(o.match(/[?&]challenge=([\w-]+)/)||[])[1]?.slice(0,16)||o.slice(-24)||"pending"}`,questions:[{question:o?`Cursor is waiting for you to finish signing in: open ${o} on any device and log in. Your login goes straight to cursor.com \u2014 bostrat never sees it. This card clears once sign-in completes.`:"Cursor is waiting for you to finish signing in \u2014 open the login link shown in the Terminal tab. This card clears once sign-in completes.",header:"Sign in",multiSelect:!1,options:[]}]}}function
|
|
62
|
-
`))
|
|
63
|
-
`).slice(-
|
|
64
|
-
`);return
|
|
65
|
-
`))if(a.trim())try{r.push(JSON.parse(a))}catch{}}catch{}return
|
|
66
|
-
${o}`.trim()||e.slice(0,2e3)}function
|
|
67
|
-
`):""}async function
|
|
45
|
+
`,"utf8")}catch(e){console.warn(`[handoff-debug] failed to write ${fr}: ${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&&Vs())try{e.paneTail=So((await mt(e.sessionName,{lines:35})).slice(-4e3))}catch{e.paneTail=null}(Vs()||e.error||/^(checkpoint|spawn|hydrate|commit|done|summarize|clear|resume|ready|send|failed|launch)/.test(String(e.phase||e.step||"")))&&await P0(e)}async function Of({limit:t=80}={}){try{return(await xo.readFile(fr,"utf8")).split(`
|
|
46
|
+
`).filter(Boolean).slice(-t).map(r=>{try{let o=JSON.parse(r);return typeof o?.paneTail=="string"&&(o.paneTail=So(o.paneTail)),o}catch{return{raw:So(r)}}})}catch{return[]}}var Mf={DEBUG_DIR:lc,LOG_FILE:fr};var st=D0(B0);function hr(t){return new Promise(e=>setTimeout(e,t))}function zs(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 M=process.env.BOSTRAT_TMUX_BIN||"/usr/bin/tmux";function Nf(t){return`/tmp/tmux-${t}/default`}function L0({explicit:t,legacyExists:e,runtimeDir:n,uid:r}){if(t)return t;let o=Nf(r);return e?o:n?`${n}/bostrat/tmux-default`:o}var Cf=typeof process.getuid=="function"?process.getuid():0,zt=L0({explicit:process.env.BOSTRAT_TMUX_SOCKET,legacyExists:(()=>{try{return Js.existsSync(Nf(Cf))}catch{return!1}})(),runtimeDir:process.env.XDG_RUNTIME_DIR,uid:Cf}),F0=process.env.BOSTRAT_DEFAULT_CWD||process.env.HOME||"/",dc=process.env.BOSTRAT_AGENT_CMD||"claude --dangerously-skip-permissions",U0="CLAUDE_CODE_DISABLE_ALTERNATE_SCREEN";function fc(){return process.env.BOSTRAT_CLAUDE_ALT_SCREEN==="1"?[]:["-e",`${U0}=1`]}var pc="bostrat-usage-probe",H0=new Set([pc]);function mc(t,e){let n=Pn(t),r=/(^|\s)(--session-id|--resume|--continue|-r|-c)(\s|=|$)/.test(t);return!n||r?t:`${t} --session-id ${e}`}function Pn(t){return/(^|\/|\s)claude(\s|$)/.test(String(t||""))}function en(t){return/(^|\/|\s)claude(\s|$)/.test(t)?"claude":/cursor/.test(t)?"cursor":/codex/.test(t)?"codex":/gemini/.test(t)?"gemini":"agent"}function W0(t){let e=String(t||"").trim();return!e||/fake-|\.sh(\s|$)/.test(e)?!1:Pn(e)||/(^|\/|\s)codex(\s|$)/.test(e)||/cursor-agent/.test(e)||/(^|\/|\s)gemini(\s|$)/.test(e)}async function Pf(t,{loginShell:e=!1,failOpen:n=!1}={}){let r=await Bf(t,{loginShell:e});return r===Xs?!0:r===pr?!1:n}var Xs="present",pr="absent",mr="unknown",uc=5e3,q0=[250,1e3,3e3],K0=t=>new Promise(e=>setTimeout(e,t));async function Bf(t,{loginShell:e=!1,timeoutMs:n=uc}={}){if(!t)return pr;let r=e?process.env.SHELL||"/bin/sh":"sh",o=e?"-lic":"-c";try{return await st(r,[o,'command -v "$1" >/dev/null 2>&1',"_",t],{timeout:n}),Xs}catch(s){return s?.killed||s?.signal?mr:s?.code===1||s?.code===127?pr:mr}}async function Ys(t,{retries:e=q0,sleep:n=K0,probe:r=Bf}={}){let o=String(t||"").trim().split(/\s+/)[0],s=await r(o,{timeoutMs:uc});for(let a=0;s===mr&&a<e.length;a++)await n(e[a]),s=await r(o,{timeoutMs:uc*2});return s}var hc={claude:"npm install -g @anthropic-ai/claude-code",codex:"npm install -g @openai/codex","cursor-agent":"curl https://cursor.com/install -fsS | bash",gh:"apt install gh (Debian: enable cli.github.com/packages) / brew install gh \u2014 see https://github.com/cli/cli#installation"};function P(...t){return["-S",zt,...t]}async function nn(){if(!Js.existsSync(zt))return!1;try{return await st(M,P("list-sessions")),!0}catch(t){let e=t&&t.stderr?String(t.stderr):"";return!/no server running/i.test(e)}}async function vo(){if(await nn())return!0;try{Js.mkdirSync(zt.replace(/\/[^/]+$/,""),{recursive:!0}),await st(M,P("new-session","-d","-s","keeper","sleep 2147483647"))}catch{}return await nn()}var G0=/^[A-Za-z0-9_.-]{1,64}$/;function yt(t){return typeof t=="string"&&G0.test(t)}async function Re(){if(!Js.existsSync(zt))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 st(M,P("list-sessions","-F",t)),n=Date.now();return e.trim().split(`
|
|
47
|
+
`).filter(Boolean).map(r=>{let[o,s,a,i,c,l,u,f,d]=r.split("|"),p=Number(i);return{name:o,title:u||null,windows:Number(s),attached:Number(a),lastActivityMs:Number.isFinite(p)?n-p*1e3:null,cwd:c||null,isBostrat:l==="1",sessionId:f||null,agent:d||(l==="1"?"claude":null)}}).filter(r=>!H0.has(r.name)&&!r.name.startsWith("bostrat-view-"))}catch{return[]}}async function nt(t){return(await Re()).find(n=>n.name===t)||null}var V0=/^[A-Za-z0-9 ._↻-]+$/,z0=64;function J0(t){if(typeof t!="string")return!1;let e=t.trim();return e.length>=1&&e.length<=z0&&V0.test(e)}function wc(t){return t.trim().replace(/\s+/g,"-").toLowerCase().replace(/[^A-Za-z0-9_.-]/g,"").replace(/^-+|-+$/g,"")}async function Df(t){try{let{stdout:e}=await st(M,P("list-sessions","-F","#{session_name}"));return e.split(`
|
|
48
|
+
`).map(n=>n.trim()).includes(t)}catch{return!1}}var X0=()=>Number(process.env.BOSTRAT_LAUNCH_VERIFY_MS)||12e3,Y0=1e3,Z0=/not found|Unknown command/i;function Q0(t){let e=String(t||"").trim().split(/\s+/)[0]||"";if(e.includes("/"))return null;let n=[];for(let o=1;o<=e.length-3;o++)n.push(e.slice(o));if(!n.length)return null;let r=n.map(o=>o.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"));return new RegExp(`\\b(?:${r.join("|")})`)}async function tS(t,e){let n=Date.now()+X0(),r=Q0(e),o=!1;for(;;){if(await hr(Y0),!await Df(t)||!await wr(t))return;let s=await mt(t,{lines:15});if(!o&&Z0.test(s)&&r?.test(s)){o=!0,await G({flow:"spawn",phase:"launch-retry",sessionName:t,agent:en(e),promptHead:e.slice(0,120)}).catch(()=>{}),await st(M,P("send-keys","-t",t,"C-u")),await st(M,P("send-keys","-t",t,"-l",e)),await st(M,P("send-keys","-t",t,"Enter"));continue}if(Date.now()>n){await G({flow:"spawn",phase:"launch-failed",sessionName:t,agent:en(e),retried:o,error:"agent CLI never started after launch was typed"}).catch(()=>{});return}}}async function Bn(t,e=F0,n={}){if(!J0(t))throw new Error("displayName must be 1\u201364 chars (letters, digits, space . _ -)");let r=t.trim(),o=wc(r);if(!yt(o))throw new Error("resulting tmux session name is invalid");if(!await vo())throw new Error(`host tmux server not running on ${zt} and auto-start failed \u2014 start one: ${M} -S ${zt} new-session -d -s keeper "sleep 2147483647"`);if(await Df(o))throw new Error(`tmux session "${o}" already exists`);let s=n.agentCmd||dc,a=n.agent||en(s),i=!!n.resumeSessionId,c=n.resumeSessionId||j0(),l=i?s:mc(s,c),u=i?n.pinned===!0:/(^|\s)--session-id(\s|=)/.test(l),f=a==="claude"&&Pn(s),d=n.firstRun===!0;d||(f&&await Ye(e),a==="codex"&&await ir(e),a==="cursor"&&await cr(e),await mf(e),await If(e));let p=f&&!d?Ds():null,m=Hd(),w=["-e",`${Ns}=${m}`];p&&w.push("-e",`ANTHROPIC_API_KEY=${p}`),f&&w.push(...fc());try{await st(M,P("new-session","-d","-s",o,"-c",e,...w))}catch(y){throw zs(p?zs(y,p):y,m)}return await Dn(o),await st(M,P("set-option","-t",o,"@bostrat-session","1")),await st(M,P("set-option","-t",o,"@bostrat-title",r)),await st(M,P("set-option","-t",o,"@bostrat-sid",c)),await st(M,P("set-option","-t",o,"@bostrat-agent",a)),await st(M,P("send-keys","-t",o,l,"Enter")),W0(l)&&tS(o,l).catch(()=>{}),{name:o,title:r,cwd:e,sessionId:c,agent:a,pinned:u,hookToken:m}}async function fe(t){if(!yt(t))throw new Error("invalid session name");await st(M,P("kill-session","-t",t))}async function jf(t,e){if(yt(t))try{let n=e?P("set-option","-t",t,"@bostrat-sid",e):P("set-option","-u","-t",t,"@bostrat-sid");await st(M,n)}catch{}}async function mt(t,{lines:e=40}={}){if(!yt(t))return"";try{let n=P("capture-pane","-t",t,"-p");Number.isFinite(e)&&e>0&&n.push("-S",`-${Math.floor(e)}`);let{stdout:r}=await st(M,n);return String(r||"")}catch{return""}}var eS=Number(process.env.BOSTRAT_PROMPT_CAPTURE_COLS)||100,nS=Number(process.env.BOSTRAT_PROMPT_CAPTURE_ROWS)||40,Tt=Number(process.env.BOSTRAT_PROMPT_CAPTURE_LINES)||80,rS=Number(process.env.BOSTRAT_BOX_COLS)||120,oS=Number(process.env.BOSTRAT_BOX_ROWS)||40;async function Lf(t){if(!yt(t))return 0;try{let{stdout:e}=await st(M,P("list-clients","-t",t,"-F","1"));return String(e||"").trim().split(`
|
|
49
|
+
`).filter(Boolean).length}catch{return 0}}async function Dn(t,{cols:e=rS,rows:n=oS}={}){if(yt(t)&&!await Lf(t))try{let r=await Ff(t);if(r.cols===e&&r.rows===n)return;await st(M,P("resize-window","-t",t,"-x",String(e),"-y",String(n))),await st(M,P("set-window-option","-t",t,"window-size","latest")),await hr(80)}catch{}}async function sS(t){if(!yt(t))return 0;try{let{stdout:e}=await st(M,P("display-message","-t",t,"-p","#{pane_pid}"));return Number(String(e||"").trim())||0}catch{return 0}}async function wr(t){let e=await sS(t);if(!e)return!1;try{return await st("pgrep",["-P",String(e)]),!1}catch(n){return n?.code===1}}async function Ff(t){if(!yt(t))return{cols:0,rows:0};try{let{stdout:e}=await st(M,P("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 Zs(t,{cols:e=eS,rows:n=nS}={}){if(yt(t)&&!await Lf(t))try{let r=await Ff(t),o=Math.max(r.cols,e),s=Math.max(r.rows,n);(o>r.cols||s>r.rows)&&(await st(M,P("resize-window","-t",t,"-x",String(o),"-y",String(s))),await st(M,P("set-window-option","-t",t,"window-size","latest")),await hr(80))}catch{}}async function rn(t){return await Zs(t),mt(t,{lines:Tt})}var aS=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=>aS.has(s));for(let s=0;s<o.length;s++)await st(M,P("send-keys","-t",t,o[s])),n>0&&s<o.length-1&&await hr(n);return G({flow:"tmux",phase:"send-keys",sessionName:t,kind:r||"keys",keys:o.join(" ")}).catch(()=>{}),o.length}async function Qs(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 st(M,P("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 ta(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 st(M,P("send-keys","-t",t,"-l",s)),n>0&&await hr(n);G({flow:"tmux",phase:"type",sessionName:t,kind:r||"type",textLen:o.length,textHead:String(e).slice(0,120)}).catch(()=>{})}async function At(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 st(M,P("send-keys","-t",t,"-l",e)),n){let s=Number(r);Number.isFinite(s)&&s>0&&await hr(s),await st(M,P("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 iS from"node-pty";async function Hf(t,e,n={}){if(!yt(e)){ea(t,`invalid session name: ${e}`),t.close(1008,"invalid session name");return}if(!await nn()){ea(t,`host tmux server not running on ${zt}`),t.close(1011,"tmux server unavailable");return}if(!await nt(e)){ea(t,`session "${e}" not found`),t.close(1008,"session not found");return}let r=Uf(n.cols,120,Wf),o=Uf(n.rows,32,qf),s;try{s=iS.spawn(M,["-S",zt,"attach-session","-d","-t",e],{name:"xterm-256color",cols:r,rows:o,cwd:process.env.HOME||"/",env:{...process.env,TERM:"xterm-256color"}})}catch(a){ea(t,`failed to spawn tmux attach: ${a.message}`),t.close(1011,"spawn failed");return}s.onData(a=>{t.readyState===t.OPEN&&gc(t,{type:"data",data:a})}),s.onExit(({exitCode:a,signal:i})=>{t.readyState===t.OPEN&&(gc(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"&&cS(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{}Dn(e).catch(()=>{})})}var Wf=20,qf=6;function cS(t,e){return Number.isFinite(Number(t))&&Number.isFinite(Number(e))&&Number(t)>=Wf&&Number(e)>=qf}function Uf(t,e,n){let r=Number(t);return!Number.isFinite(r)||r<n?e:Math.min(500,r|0)}function gc(t,e){try{t.send(JSON.stringify(e))}catch{}}function ea(t,e){gc(t,{type:"error",message:e})}function Kf(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 To from"node:fs";import lS from"node:fs/promises";import gr from"node:path";import uS from"node:os";var Gf={"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},dS=2e5,yc=process.env.BOSTRAT_CLAUDE_PROJECTS||gr.join(uS.homedir(),".claude","projects");function bc(t){return t.replace(/[^A-Za-z0-9]/g,"-")}function kc(t){let e=gr.join(yc,bc(t)),n;try{n=To.readdirSync(e).filter(o=>o.endsWith(".jsonl"))}catch{return null}let r=null;for(let o of n){let s=gr.join(e,o);try{let{mtimeMs:a}=To.statSync(s);(!r||a>r.mtimeMs)&&(r={full:s,mtimeMs:a})}catch{}}return r?r.full:null}function $e(t,e){if(!e)return null;let n=gr.join(yc,bc(t),`${e}.jsonl`);return To.existsSync(n)?n:null}function Vf(t){if(!t)return[];let e=gr.join(yc,bc(t)),n;try{n=To.readdirSync(e).filter(o=>o.endsWith(".jsonl"))}catch{return[]}let r=[];for(let o of n)try{let s=To.statSync(gr.join(e,o));r.push({id:o.slice(0,-6),birthMs:s.birthtimeMs||s.ctimeMs||s.mtimeMs})}catch{}return r}async function Ut(t,e=131072){let n=await lS.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 fS(t){let e=t.split(`
|
|
50
|
+
`);for(let n=e.length-1;n>=0;n--){let r=e[n].trim();if(!r||r[0]!=="{")continue;let o;try{o=JSON.parse(r)}catch{continue}let s=o?.message?.usage;if(!s)continue;let a=(s.input_tokens||0)+(s.cache_creation_input_tokens||0)+(s.cache_read_input_tokens||0);if(a>0)return{occupancy:a,model:o.message.model||o.model||null}}return null}async function Ao(t,e=null,{fallbackNewest:n=!0}={}){if(!t)return null;let r,o;if(e){if(r=$e(t,e),o=!0,!r)return null}else if(n){if(r=kc(t),o=!1,!r)return null}else return null;let s;try{s=fS(await Ut(r))}catch{return null}if(!s)return null;let a=Object.prototype.hasOwnProperty.call(Gf,s.model),i=Gf[s.model]||dS;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 pS(t,e){return!t||!e?null:$e(t,e)}function mS(t){return typeof t=="string"?t.trim():Array.isArray(t)?t.filter(e=>e&&e.type==="text"&&typeof e.text=="string").map(e=>e.text).join(`
|
|
51
|
+
`).trim():""}function hS(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 wS(t){return Array.isArray(t)&&t.length>0&&t.every(e=>e&&e.type==="tool_result")}function xc(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 zf(t,e){if(!t.uuid)return null;let n=xc(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 gS=/\.(png|jpe?g|gif|webp|bmp|svg|tiff?|avif|heic|heif|ico)$/i;async function Sc(t,{maxBytes:e=524288,limit:n=80}={}){let r;try{r=await Ut(t,e)}catch{return[]}let o=[],s=new Map;for(let a of r.split(`
|
|
52
|
+
`)){let i=a.trim();if(!i||i[0]!=="{")continue;let c;try{c=JSON.parse(i)}catch{continue}if(c.type==="attachment"){let m=c.attachment;m?.type==="queued_command"&&m.commandMode==="task-notification"&&typeof m.prompt=="string"&&m.prompt.trim()&&o.push({role:"user",text:m.prompt.trim(),ts:c.timestamp?Date.parse(c.timestamp):null,model:null});continue}let l=c.message;if(!l)continue;let u=l.role||c.type;if(u!=="user"&&u!=="assistant"||u==="assistant"&&l.model==="<synthetic>"&&!c.isApiErrorMessage)continue;if(u==="assistant"&&Array.isArray(l.content))for(let m of l.content){let w=m?.type==="tool_use"?m.input?.file_path||m.input?.path:null;typeof w=="string"&&gS.test(w)&&s.set(m.id,w.split("/").pop())}if(wS(l.content)){let m=zf(c,s);m&&o.push({role:"assistant",text:"",images:m,ts:c.timestamp?Date.parse(c.timestamp):null,model:null});continue}let f=mS(l.content),d=u==="assistant"?hS(l.content):null,p=u==="user"?zf(c,s):null;!f&&!d&&!p||o.push({role:u,text:f,tools:d||void 0,images:p||void 0,ts:c.timestamp?Date.parse(c.timestamp):null,model:l.model||null})}return o.slice(-n)}var yS=(t,e)=>t.length>e?`${t.slice(0,e).trimEnd()}\u2026`:t;async function Jf(t,e,{turns:n=16}={}){let r=pS(t,e);if(!r)return"";let o=await Sc(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}:** ${yS(s.text||c,700)} ${i}`.trim()}).join(`
|
|
53
|
+
|
|
54
|
+
`):""}import mv from"node:fs";import xr from"node:fs/promises";import Sr from"node:path";import bS from"node:fs/promises";import{execFile as kS}from"node:child_process";import{promisify as xS}from"node:util";var vc=xS(kS),SS=Number(process.env.BOSTRAT_CODEX_UPDATE_TIMEOUT_MS)||5*6e4,Xf=new Set,Yf=Promise.resolve(),Tc=null;function Qf(t){Tc=typeof t=="function"?t:null}function vS(t){let e=String(t||""),n=e.indexOf("/lib/node_modules/");return n>0?e.slice(0,n):null}function tp({sessionName:t,from:e,to:n}={}){let r=String(n||"").trim();!r||Xf.has(r)||(Xf.add(r),Yf=Yf.then(()=>TS({sessionName:t,from:e,to:r})).catch(()=>{}))}async function TS({sessionName:t,from:e,to:n}){await G({flow:"codex-update",phase:"start",sessionName:t,from:e,to:n}).catch(()=>{});try{let r=(await vc("/bin/sh",["-c","command -v codex"])).stdout.trim();if(!r)throw new Error("codex not on PATH");let o=await bS.realpath(r),s=vS(o),a=s?{...process.env,PATH:`${s}/bin:${process.env.PATH||""}`}:process.env;await vc("npm",["install","-g","@openai/codex"],{env:a,timeout:SS});let i=(await vc(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 Zf({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 Zf({sessionName:t,from:e,to:n,status:"failed",error:o})}}async function Zf(t){if(Tc)try{await Tc(t)}catch{}}var AS=/^\s*[›>*\-❯]?\s*(\d+)[.)]\s+(.+?)\s*$/,ep=/\b(allow|approve|apply (this )?patch|run (this )?command|do you want to run|grant)\b/i;function ES(t,{provider:e,box:n}){let r=String(t).replace(/\r/g,"").split(`
|
|
55
|
+
`);if(!ep.test(t))return null;let o=[],s=!1;for(let l=r.length-1;l>=0;l--){let u=r[l].match(AS);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"&&ep.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 na=/(^|\n)\s*›/,op=/^\s*Welcome to Codex\b/;function yr(t){let e=String(t??"").replace(/\r/g,""),n=e.split(`
|
|
56
|
+
`);for(let r=n.length-1;r>=0;r--)if(op.test(n[r]))return n.slice(r).join(`
|
|
57
|
+
`);return e}function _S(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 IS(t){return/device code/i.test(t)?"works from the device you're reading this on":/sign in with chatgpt/i.test(t)?"opens a browser link on the node itself \u2014 can't finish from another device":""}var np=/^\s*(>)?\s*(\d+)\.\s+(.+?)\s*$/;function RS(t,e=""){let n=yr(t);if(na.test(n)||!/Sign in with ChatGPT/.test(n)||!/Press enter to continue/.test(n))return null;let r=n.split(`
|
|
58
|
+
`),o=[],s=-1;for(let l=0;l<r.length;l++){let u=r[l].match(np);if(!u)continue;s<0&&(s=l);let f="";for(let d=l+1;d<Math.min(l+3,r.length);d++){let p=r[d].trim();if(p){if(np.test(r[d])||/^Press enter/.test(p))break;f=p;break}}o.push({n:Number(u[2]),cursor:!!u[1],label:u[3].trim(),desc:f})}let a=o.sort((l,u)=>l.n-u.n);if(a.length<2||a.some((l,u)=>l.n!==u+1)||!a.some(l=>/Sign in with ChatGPT/.test(l.label)))return null;let i=[];for(let l=0;l<s;l++){let u=r[l].trim();u&&!op.test(r[l])&&_S(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=IS(l.label);return{label:l.label,description:[l.desc,u].filter(Boolean).join(" \xB7 ")}})}]}}var rp=/Enter this one-time code/,$S=/^\s*([A-Z0-9]{2,10}-[A-Z0-9]{2,10})\s*$/;function OS(t,e=""){let n=yr(t);if(na.test(n)||!rp.test(n))return null;let r=n.split(`
|
|
59
|
+
`),o=r.findIndex(i=>rp.test(i)),s="";for(let i=o+1;i<r.length;i++){let c=r[i].match($S);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 MS(t,e=""){let n=yr(t);return na.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 CS(t,e=""){let n=yr(t);return na.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 Ac=/Run Everything|Add a follow-up|Plan, search, build anything/;function NS(t,e=""){let n=String(t);if(!/Press any key to log in/.test(n)||Ac.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
60
|
+
`).map(a=>a.trim()),o=r.findIndex(a=>/Press any key to log in/.test(a)),s=r.slice(0,o<0?r.length:o).filter(a=>a&&!/^Cursor Agent$/i.test(a)&&!/^v?\d{4}\.\d{2}\.\d{2}/.test(a)).join(" ");return{provider:"cursor",box:e,multiSelect:!1,cursorIndex:0,answerMode:"keys",promptId:"cursor:signin-start",questions:[{question:(s?`${s} `:"")+"Cursor isn't signed in on this box \u2014 start the sign-in to get a login link you can open from any device.",header:"Sign in",multiSelect:!1,options:[{label:"Start sign-in",description:"Shows a login link (and a QR option) \u2014 your login goes to cursor.com, never through bostrat"}]}]}}var PS=/click this link to log in|Signing in with the browser/;function BS(t,e=""){let n=String(t);if(!PS.test(n)||Ac.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
61
|
+
`),o="",s=r.findIndex(a=>/https:\/\//.test(a));if(s>=0){o=r[s].slice(r[s].indexOf("https://")).trim();for(let a=s+1;a<r.length;a++){let i=r[a].trim();if(!i||!/^[A-Za-z0-9~%&=?/_.-]+$/.test(i))break;o+=i}}return{provider:"cursor",box:e,multiSelect:!1,cursorIndex:0,promptId:`cursor:signin-link:${(o.match(/[?&]challenge=([\w-]+)/)||[])[1]?.slice(0,16)||o.slice(-24)||"pending"}`,questions:[{question:o?`Cursor is waiting for you to finish signing in: open ${o} on any device and log in. Your login goes straight to cursor.com \u2014 bostrat never sees it. This card clears once sign-in completes.`:"Cursor is waiting for you to finish signing in \u2014 open the login link shown in the Terminal tab. This card clears once sign-in completes.",header:"Sign in",multiSelect:!1,options:[]}]}}function DS(t,e=""){let n=String(t);return!/Scan this QR code to log in/.test(n)||Ac.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 sp(t,e,n=""){if(t!=="cursor"&&t!=="codex")return null;if(t==="codex"){let r=OS(e,n)||MS(e,n)||CS(e,n)||RS(e,n);if(r)return r}if(t==="cursor"){let r=BS(e,n)||DS(e,n)||NS(e,n);if(r)return r}return ES(e,{provider:t,box:n})}function ap(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 jS=()=>Number(process.env.BOSTRAT_AGENT_READY_TIMEOUT_MS)||9e4,Ec=Number(process.env.BOSTRAT_AGENT_READY_POLL_MS)||500,Eo=Number(process.env.BOSTRAT_AGENT_READY_SETTLE_MS)||800,$t={claude:{up:/shift\+tab to cycle|⏵⏵ bypass permissions on|⏸ manual mode on/,busy:/esc to interrupt/,busyAlt:/^[·✢✳✶✻✽✺✹✸✷]\s+[A-Z][\w '’-]{1,40}…/m,auth:/Select login method:|Paste code here if prompted|OAuth error:|Choose the text style that looks best|Quick safety check: Is this a project you created/,block:/Press\s+Enter\s+to\s+continue\s*[.…]{0,3}\s*$/m},codex:{up:/(^|\n)\s*›/,busy:/esc to interrupt|\bWorking \(/,block:/Update available![\s\S]*\b(?:Update now|Skip until next version)\b[\s\S]*Press enter to continue/,auth:/Sign in with ChatGPT[\s\S]*Press enter to continue|Finish signing in via your browser|Paste or type your API key/},cursor:{up:/Run Everything|Add a follow-up|Plan, search, build anything/,busy:/ctrl\+c to stop|^\s*[⠁-⣿]+\s+\S/m,block:/Workspace Trust Required|Trust this workspace|\[a\]\s*Trust|Trusting workspace/,auth:/Press any key to log in|click this link to log in|Scan this QR code to log in|Signing in with the browser/}},LS=/Trust this workspace|\[a\]\s*Trust/,FS=/Update available!\s*([\d][\w.-]*)\s*->\s*([\d][\w.-]*)/;function US(t){let e=String(t||"");if(!$t.codex.block.test(e))return null;let n=yr(e);if(!$t.codex.block.test(n))return null;let r=n.match(FS);return{from:r?.[1]||null,to:r?.[2]||null}}function on(t){return new Promise(e=>setTimeout(e,t))}var HS=()=>Number(process.env.BOSTRAT_GATE_RESEND_MS)||3e3,ra=new Map;function ip(t,e){let n=Date.now(),r=HS();for(let[s,a]of ra)n-a>=r&&ra.delete(s);let o=`${t}::${e}`;return ra.has(o)?!1:(ra.set(o,n),!0)}var WS=68,qS=/^\s*[─━]{10,}\s*$/;function KS(t){let e=0;for(let n of String(t).split(`
|
|
62
|
+
`))qS.test(n)&&(e=Math.max(e,n.trimEnd().length));return e}function _c(t,e){let n=$t[t];if(!n?.busy)return"idle";if(Ic(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=KS(e);return t==="claude"&&r>0&&r<WS?"unknown":"idle"}var GS=/\bWaiting for \d+\b[^\n]*\bto finish\b|\/workflows to (?:stop|monitor)\b/,VS=15;function lp(t,e){if(t!=="claude")return!1;let n=String(e||"").split(`
|
|
63
|
+
`).slice(-VS).join(`
|
|
64
|
+
`);return GS.test(n)}function Oe(t,e){return _c(t,e)==="busy"}function oa(t,e){return _c(t,e)!=="idle"}function sa(t,e){let n=$t[t];return n?.up?n.up.test(e):!0}function zS(t,e){let n=$t[t];return n?Ic(t,e)?!1:n.up.test(e)&&_c(t,e)==="idle":!0}function Ic(t,e){let n=$t[t];return n?n.auth?.test(e)&&!n.up.test(e)?!0:n.block?.test(e)?t==="codex"?!0:!n.up.test(e):!1:!1}async function cp(t,e,n){let r=Date.now()+n,o=0;for(await Dn(t).catch(()=>{});;){let s=await mt(t);if(zS(e,s)){await on(Eo);return}if(e==="cursor"&&LS.test(s)&&!$t.cursor.up.test(s)&&o<3){if(!ip(t,"cursor-trust")){await on(Ec);continue}o+=1,await G({flow:"agent-ready",phase:"gate-accept",sessionName:t,agent:e,attempt:o}).catch(()=>{}),await At(t,"a",{enter:!1,kind:"cursor-trust-accept"}),await on(Eo);continue}if(e==="codex"&&o<3){let a=US(s);if(a){if(!ip(t,`codex-update:${a.from}->${a.to}`)){await on(Ec);continue}o+=1,await G({flow:"agent-ready",phase:"gate-accept",sessionName:t,agent:e,attempt:o,update:`${a.from} -> ${a.to}`}).catch(()=>{}),await At(t,"3",{enter:!1,kind:"codex-update-skip"}),tp({sessionName:t,from:a.from,to:a.to}),await on(Eo);continue}}if(Date.now()>r){if(Ic(e,s)){let a=`${e} is blocked on an interactive prompt`;throw e==="codex"?a=$t.codex.auth.test(s)?"codex isn't signed in on this box \u2014 answer the Sign in card in chat (Device Code works from any device) or finish signing in from the Terminal tab, then send again":"codex is stuck on its update prompt (auto-skip didn't clear it) \u2014 open the terminal and choose Skip, then send again":e==="cursor"&&$t.cursor.auth.test(s)?a="cursor isn't signed in on this box \u2014 answer the Sign in card in chat (the login link works from any device) or finish signing in from the Terminal tab, then send again":e==="claude"&&$t.claude.auth.test(s)?a="claude isn't signed in on this box \u2014 answer the setup cards in chat (or finish signing in from the Terminal tab), then send again":e==="claude"&&$t.claude.block.test(s)&&(a='claude is waiting on its "Press Enter to continue" screen \u2014 answer the Continue card in chat (or press Enter in the Terminal tab), then send again'),G({flow:"agent-ready",phase:"blocked",sessionName:t,agent:e,timeoutMs:n,gateAccepts:o,error:a}).catch(()=>{}),new Error(a)}if(await wr(t)){let a=`${e} isn't running in this box \u2014 its pane is sitting at a bare shell (the launch command was likely swallowed at shell startup, or the CLI exited). Relaunch it from the Terminal tab, or stop and reopen the box, then send again.`;throw G({flow:"agent-ready",phase:"blocked",reason:"cli-not-running",sessionName:t,agent:e,timeoutMs:n,gateAccepts:o,error:a}).catch(()=>{}),new Error(a)}console.warn(`[agent-ready] ${e} readiness unconfirmed in ${t} after ${n}ms \u2014 driving the box anyway.`),G({flow:"agent-ready",phase:"timeout",sessionName:t,agent:e,timeoutMs:n,gateAccepts:o,paneBlocked:!!$t[e]?.block?.test(s),paneUp:!!$t[e]?.up?.test(s),paneBusy:!!$t[e]?.busy?.test(s)}).catch(()=>{}),await on(Eo);return}await on(Ec)}}function up({agent:t,agentCmd:e}={}){let n=e?en(e):en(t||"");return{label:n,realClaude:e?Pn(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 aa({cwd:t,sessionName:e,agent:n,agentCmd:r}={}){let{realCodex:o,realCursor:s}=up({agent:n,agentCmd:r});return!e||!o&&!s?null:sn({cwd:t,sessionName:e,agent:n,agentCmd:r}).catch(()=>{})}async function sn({cwd:t,sessionId:e,sessionName:n,agent:r,agentCmd:o,timeoutMs:s=jS()}={}){let{label:a,realClaude:i,realCodex:c,realCursor:l}=up({agent:r,agentCmd:o});if(i&&t&&await Ye(t),c&&t&&await ir(t),l&&t&&await cr(t),(i||c||l)&&n){await cp(n,a,s);return}let u=$t[r]?r:$t[a]?a:null;if(process.env.BOSTRAT_FAKE_POLL_READY==="1"&&n&&u){await cp(n,u,s);return}await on(Eo)}var JS=(()=>{let t=Number(process.env.BOSTRAT_CODEX_SUBMIT_SETTLE_MS??120);return Number.isFinite(t)&&t>=0?t:120})(),XS=(()=>{let t=Number(process.env.BOSTRAT_CLAUDE_SUBMIT_SETTLE_MS??120);return Number.isFinite(t)&&t>=0?t:120})();function Me(t){return t==="codex"?{enter:!0,submitDelayMs:JS}:t==="claude"?{enter:!0,submitDelayMs:XS}:{enter:!0}}import ca from"node:fs/promises";import wp from"node:path";var YS=["bug-fix","orchestration","design","ops-infra","docs-writing","research","feature-build"],ZS={research:[/\bresearch\b/,/\binvestigat/,/\bverif/,/\bcompar(e|ing|ison)/,/\bcompetit/,/\bdeep[ -]?dive\b/,/\banaly[sz]/,/\bassess/,/\bdiligence\b/,/\bmarket\b/,/\bstrateg/,/\broadmap\b/,/\bnorth star/,/\bwhat('| i)?s the (state|status)\b/,/\bstatus of\b/,/\bexplore\b/,/\bfeasib/,/\bevaluat/,/\bscope out\b/,/\btalk through\b/],"bug-fix":[/\bfix(e[sd]|ing)?\b/,/\bbugs?\b/,/\bbroken\b/,/\bregression\b/,/\bcrash/,/\bincident\b/,/\bdebug/,/\bdoesn'?t (work|render|load|start)/,/\bnot working\b/,/\btroubleshoot/,/\bflaky?\b/,/\brepro(duce|duction)?\b/,/\brace condition\b/,/\bleak\b/],"feature-build":[/\bimplement/,/\bbuild(ing)?\b/,/\badd(ing)?\b/,/\bcreate\b/,/\bwire (it |them )?(up|in)\b/,/\bship(ping)?\b/,/\bintegrat/,/\bsupport for\b/,/\bfeature\b/,/\bendpoint\b/,/\badapter\b/,/\bprototype\b/],design:[/\bdesign/,/\bui\b/,/\bux\b/,/\bstyl(e|es|ing)\b/,/\bcss\b/,/\blayout\b/,/\bmock-?up/,/\brender(ing|s)?\b/,/\bpolish/,/\btheme\b/,/\bfonts?\b/,/\bresponsive\b/,/\banimation/,/\bvisual/,/\bpopover\b/],orchestration:[/\bhand[ -]?(off|over)/,/\bhand (it|the lead|this) (over|off)\b/,/\bdelegat/,/\bsubtask/,/\bmerge[ -]?back\b/,/\borchestrat/,/\b(switch|change) (the )?model\b/,/\bmodel switch/],"docs-writing":[/\bdocs?\b/,/\bdocument/,/\bmemo\b/,/\breadme\b/,/\bwrite-?up\b/,/\bchangelog\b/,/\bclaude\.md\b/,/\bblog/,/\bproofread/,/\bcopy(edit|writ)/],"ops-infra":[/\bdeploy/,/\bsystemd\b/,/\bdaemon\b/,/\btmux\b/,/\bdns\b/,/\bwireguard\b/,/\brestart/,/\bnginx\b/,/\bprovision/,/\binstall/,/\bupgrade/,/\bcron\b/,/\bbackups?\b/,/\bcertificat/,/\bssh\b/,/\bsecrets?\b/]};function br(t){let e=String(t||"").toLowerCase();if(!e.trim())return null;let n=null;for(let r of YS){let o=0;for(let s of ZS[r])s.test(e)&&o++;o>0&&(!n||o>n.score)&&(n={category:r,score:o})}return n}var QS=new Set(["Read","Grep","Glob","LS","WebSearch","WebFetch","NotebookRead"]),tv=new Set(["Edit","Write","MultiEdit","NotebookEdit"]);function dp(t){let e=0,n=0;for(let[r,o]of Object.entries(t||{}))QS.has(r)&&(e+=o||0),tv.has(r)&&(n+=o||0);return e>=5&&n===0?{category:"research",score:1}:n>=3?{category:"feature-build",score:1}:null}function _o(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 fp=1.5,pp=.75,ev=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 hp(t){let e=_o(String(t||"")).toLowerCase(),n=[];for(let r of e.split(/[^a-z0-9]+/))r.length<2||ev.has(r)||n.push(r);return n}function nv(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(mp(n,"user",o))}else if(n.type==="turn.agent"){if(!o)continue;e.push(mp(n,"assistant",o))}}return e}function mp(t,e,n){let r=br(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 Io(t){let e=nv(t),n=new Map,r=new Array(e.length).fill(0),o=0;e.forEach((a,i)=>{let c=hp(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 ia(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(hp(e))];if(!i.length)return[];let c=new Map;for(let u of i){let f=t.postings.get(u);if(!f)continue;let d=f.size,p=Math.log(1+(t.N-d+.5)/(d+.5));for(let[m,w]of f){let y=t.lengths[m],b=w+fp*(1-pp+pp*(y/(t.avgdl||1))),S=p*(w*(fp+1)/b);c.set(m,(c.get(m)||0)+S)}}let l=[];for(let[u,f]of c){let d=t.chunks[u];o&&d.role!==o||s&&!d.tags.includes(s)||a&&d.boxId!==a||l.push({chunk:d,score:f})}return l.sort((u,f)=>f.score-u.score||f.chunk.seq-u.chunk.seq),l.slice(0,r)}var gp="Relevant prior discussion (verbatim, retrieved";async function rv(t){let e=wp.join(t,".bostrat","events"),n;try{n=await ca.readdir(e)}catch{return Io([])}let r=[];for(let o of n)if(o.endsWith(".ndjson"))try{let s=await ca.readFile(wp.join(e,o),"utf8");for(let a of s.split(`
|
|
65
|
+
`))if(a.trim())try{r.push(JSON.parse(a))}catch{}}catch{}return Io(r)}function ov(t){let e=String(t||""),n=a=>{let i=e.match(a);return i?i[1].trim():""},r=n(/#+\s*Objective\s*\n([\s\S]*?)(?:\n#+\s|$)/i),o=n(/#+\s*Next steps?[^\n]*\n([\s\S]*?)(?:\n#+\s|$)/i);return`${r}
|
|
66
|
+
${o}`.trim()||e.slice(0,2e3)}function sv(t,e,{k:n=6,maxChars:r=4e3,exclude:o=null}={}){let s=ia(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?["",`## ${gp}, not a summary)`,"_Auto-selected from the Track's event log by lexical relevance to the Objective / Next steps. Verbatim excerpts, not re-summarization (docs/memory-segmentation-indexing-scope.md \xA71.3)._","",...a,""].join(`
|
|
67
|
+
`):""}async function yp(t,e,n={}){let{k:r=6,maxChars:o=4e3,exclude:s=null}=n;try{let a=await ca.readFile(e,"utf8");if(a.includes(gp))return{appended:!1,reason:"already-enriched"};let i=await rv(t);if(!i.N)return{appended:!1,reason:"empty-index"};let c=sv(i,ov(a),{k:r,maxChars:o,exclude:s});return c?(await ca.appendFile(e,`
|
|
68
68
|
`+c),{appended:!0,chars:c.length,chunks:c.split(`
|
|
69
|
-
- `).length-1,indexed:i.N}):{appended:!1,reason:"no-hits",indexed:i.N}}catch(a){return{appended:!1,reason:"error",error:a?.message||String(a)}}}import
|
|
70
|
-
`),
|
|
71
|
-
`))if(a.trim())try{s.push(JSON.parse(a))}catch{}return s}function
|
|
69
|
+
- `).length-1,indexed:i.N}):{appended:!1,reason:"no-hits",indexed:i.N}}catch(a){return{appended:!1,reason:"error",error:a?.message||String(a)}}}import Rc from"node:fs/promises";import kr from"node:path";import av from"node:os";import{EventEmitter as iv}from"node:events";import{randomUUID as cv}from"node:crypto";var lv=new Set(["session.ready","turn.user","turn.agent","turn.retracted","turn.interrupted","tool.called","tool.result","prompt.raised","prompt.answered","context.usage","cost.recorded","memory.seed","handoff.artifact","code.pushed","handoff.started","handoff.step","handoff.done","handoff.failed","subtask.delegated","subtask.merged","job.started","pr.state.changed","pr.offered","pr.offer.answered","box.state.changed","view.state.changed","view.spinup.failed","mock.state.changed","flow.state.changed","runbook.state.changed","ticket.linked","ticket.sync.failed","ticket.move.offered","ticket.move.answered","ticket.state.changed","ticket.commented","ticket.flow.changed","ticket.create.offered","ticket.create.answered","ticket.created","notify.raised","provider.connected","track.summary.recorded","changelog.published","stall.detected","error"]),uv=new Set(["user","agent","orchestrator","system"]),dv=new Set(["hook","otel","statusline","transcript","pane","orchestrator","vendor"]),te=new iv;te.setMaxListeners(0);var $c=process.env.BOSTRAT_STATE_DIR||kr.join(process.env.HOME||av.tmpdir(),".bostrat");function ua(t){return t?.cwd?kr.join(t.cwd,".bostrat","events"):kr.join($c,"events")}var bp=(t,e)=>kr.join(t,`${e}.ndjson`),la=new Map;async function fv(t,e){if(!la.has(e)){let o=(await L(t,e)).reduce((s,a)=>a.seq>s?a.seq:s,0);la.set(e,o)}let n=la.get(e)+1;return la.set(e,n),n}function pv(t,e){let n=Array.isArray(t?.epochs)?t.epochs:null;if(!(!n||n.length===0)){if(!Number.isFinite(e))return n.length-1;for(let r=n.length-1;r>=0;r--){let o=n[r].startedAt;if(o==null||o<=e)return r}return 0}}async function R(t,e){let n=typeof t=="string"?t:t?.id;if(!n)throw new Error("appendEvent: a track (or trackId) is required");if(!e||!lv.has(e.type))throw new Error(`appendEvent: unknown event type "${e?.type}"`);if(e.actor&&!uv.has(e.actor))throw new Error(`appendEvent: bad actor "${e.actor}"`);if(e.source&&!dv.has(e.source))throw new Error(`appendEvent: bad source "${e.source}"`);let r=typeof t=="string"?kr.join($c,"events"):ua(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=pv(c,Number(e.payload?.ts??e.ts)||Date.now())}let s=await fv(r,n),a=e.userId??process.env.BOSTRAT_OWNER??null,i={id:e.id||cv(),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 Rc.mkdir(r,{recursive:!0}),await Rc.appendFile(bp(r,n),JSON.stringify(i)+`
|
|
70
|
+
`),te.emit("event",i),te.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"?kr.join($c,"events"):ua(t));let o;try{o=await Rc.readFile(bp(n,r),"utf8")}catch{return[]}let s=[];for(let a of o.split(`
|
|
71
|
+
`))if(a.trim())try{s.push(JSON.parse(a))}catch{}return s}function kp(t,e){let n=`track:${t}`;return te.on(n,e),()=>te.off(n,e)}var Tp=".bostrat/handoff",Ap="latest.md",xp=32*1024;function hv(t){let e=So(String(t??"")),n=Buffer.byteLength(e,"utf8");return n<=xp?{content:e,bytes:n,truncated:!1}:{content:`${Buffer.from(e,"utf8").subarray(0,xp).toString("utf8").replace(/�+$/,"")}
|
|
72
72
|
|
|
73
|
-
[\u2026 truncated by bostrat at 32 KiB \u2014 full artifact in the workspace]`,bytes:n,truncated:!0}}var mp="Checkpoint: write a work-in-progress handoff",hp="HANDOFF WRITTEN",wp="Resume from .bostrat/handoff/latest.md",tv="/clear",ev=t=>`${mp} 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 ${hp} and take no further action.`,nv=`${wp}: read it, restate the Objective and the single next step in one line, then continue the work from 'Next steps'.`,gp="Beat: write a",yp="BEAT WRITTEN",rv=".bostrat/arc",ov="beat.txt",up=200,bp=4,kp=5,sv=36,av=t=>`${gp} 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 ${bp} further lines, each starting with "- " and naming ONE piece in at most ${kp} 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 ${yp} and take no further action.`,dp=/^(?:[-–—*•·]|\d+[.)])\s+/,iv=(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 cv(t){let e=String(t||"").split(/\r?\n/).map(s=>s.trim()).filter(Boolean),n=[],r=[];for(let s of e)if(dp.test(s)){if(r.length>=bp)continue;let a=iv(s.replace(dp,"").replace(/[.;,:]+$/,""),kp,sv);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>up&&(o=`${o.slice(0,up-1).trimEnd()}\u2026`),{text:o,items:r.length>1?r:[]}}async function xp(t,e,{timeoutMs:n=9e4}={}){let r=wr.join(e,rv),o=wr.join(r,ov);await hr.mkdir(r,{recursive:!0});let s=await na(o),a=await Z(t);if(!a)throw new Error(`session "${t}" not found`);return await ra(t,a,av(o)),await Rp(o,s,n),cv(await hr.readFile(o,"utf8"))}function Sp(t,e){let n=(e||"").trim();return n?t==="assistant"?n===hp||n===yp:n.startsWith(mp)||n===tv||n.startsWith(wp)||n.startsWith(gp):!1}var lv=6e5;function vp(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-lv;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 Tp(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 Ap(t,{windows:e=[]}={}){if(Sp(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 uv=3500,dv=3e5,Sc=new Map;function oa(t){return Sc.get(t)||null}async function Ep(t){let e=Sc.get(t);if(e&&!e.finishedAt)throw new Error("a handoff is already running for this session");let n=await Z(t);if(!n)throw new Error(`session "${t}" not found`);if(!n.cwd)throw new Error(`can't resolve cwd for "${t}"`);let r={name:t,step:"summarize",startedAt:Date.now(),finishedAt:null,error:null,file:wr.join(n.cwd,fp,pp)};return Sc.set(t,r),fv(r,n.cwd).catch(o=>{r.error=o?.message||String(o),r.finishedAt=Date.now()}),r}async function fv(t,e){t.step="summarize",await _p(t.name,e),t.step="clear";let n=await Z(t.name);await ra(t.name,n,"/clear"),await $p(uv),t.step="resume",await ra(t.name,n,nv),t.step="done",t.finishedAt=Date.now()}async function _p(t,e){let n=wr.join(e,fp),r=wr.join(n,pp);await hr.mkdir(n,{recursive:!0});let o=await na(r),s=await Z(t);await U({flow:"checkpoint",phase:"summarize-start",sessionName:t,cwd:e,file:r,beforeMtime:o}),await ra(t,s,ev(r)),await Rp(r,o,dv),await U({flow:"checkpoint",phase:"summarize-done",sessionName:t,file:r,afterMtime:await na(r)});let a=await ap(e,r,{exclude:l=>Sp(l.role,l.text)});await U({flow:"checkpoint",phase:"seed-enriched",sessionName:t,file:r,...a});let i=/workspaces[/\\]([0-9a-f]{8})$/.exec(e||"")?.[1];if(i){await R({id:i,cwd:e},{type:"memory.seed",actor:"orchestrator",payload:{...a,tokensEst:a.chars?Math.round(a.chars/4):0,ts:Date.now()}}).catch(()=>{});try{let l=await hr.readFile(r,"utf8");await R({id:i,cwd:e},{type:"handoff.artifact",actor:"orchestrator",payload:{...QS(l),ts:Date.now()}})}catch{}}await af(e);let c=await pv(n,r);return i&&await gf(i,c||r,{box:t,agent:s?.agent??null}),r}async function Ip(t){let e=await Z(t);if(!e)throw new Error(`session "${t}" not found`);if(!e.cwd)throw new Error(`can't resolve cwd for "${t}"`);return _p(t,e.cwd)}async function na(t){try{return(await hr.stat(t)).mtimeMs}catch{return 0}}async function Rp(t,e,n){let r=Date.now()+n;for(;;){if(await na(t)>e)return;if(Date.now()>r)throw new Error(`timed out waiting for the handoff file to be written (${t})`);await $p(2e3)}}async function pv(t,e){try{if(!ZS.existsSync(e))return null;let n=new Date().toISOString().replace(/[:.]/g,"-"),r=wr.join(t,`${n}.md`);return await hr.copyFile(e,r),r}catch{return null}}function $p(t){return new Promise(e=>setTimeout(e,t))}async function ra(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 tn({cwd:e.cwd,sessionId:e.sessionId,sessionName:t,agent:e.agent,agentCmd:o})}let r=await Z(t);if(!r)throw new Error(`session "${t}" disappeared before the orchestration prompt could be sent`);await St(t,n,{kind:"orchestration-drive",..._e(r.agent)})}import L from"node:fs/promises";import F from"node:path";import{randomUUID as cs}from"node:crypto";import{execFile as TI}from"node:child_process";import{promisify as AI}from"node:util";import hh from"node:path";var vc={"claude-opus-5":{input:5,output:25,cacheWrite:6.25,cacheRead:.5},"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}},Op={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 Cp(t){if(!t)return null;let e=String(t).trim();return vc[e]?e:Op[e]?Op[e]:Object.keys(vc).find(r=>e.startsWith(r))||null}var sa=t=>Cp(t)!==null;function le(t,e={}){let n=vc[Cp(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 Mp=()=>({input:0,output:0,cacheRead:0,cacheCreation:0});function mv(t){return t?{input:t.input||0,output:t.output||0,cacheRead:t.cacheRead||0,cacheWrite:t.cacheWrite??t.cacheCreation??0}:null}function Np(t,e){e&&(t.input+=e.input,t.output+=e.output,t.cacheRead+=e.cacheRead,t.cacheCreation+=e.cacheWrite)}function ia(t){let e=t.payload?.model||"unknown",n=mv(t.payload?.tokens),r=Number(t.payload?.usd)||0,o=!1;!r&&n&&(r=le(e,n),r>0&&(o=!0));let s=n?n.input+n.output+n.cacheRead+n.cacheWrite:0,a=!r&&s>0&&!sa(e);return{usd:r,model:e,tokens:n,repriced:o,unpriced:a}}function ue(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=Mp();for(let f of n){let{usd:d,model:p,tokens:m,repriced:g,unpriced:y}=ia(f),b=f.provider||"unknown",S=f.boxId||"unattributed";if(g&&i++,y&&(c++,l.add(p)),a+=d,Np(u,m),r[p]||={usd:0,tokens:Mp(),events:0},r[p].usd+=d,r[p].events+=1,Np(r[p].tokens,m),o[b]||={usd:0,events:0},o[b].usd+=d,o[b].events+=1,s[S]||={usd:0,events:0,provider:b,models:new Set,label:e[S]||S},s[S].usd+=d,s[S].events+=1,p!=="unknown"&&s[S].models.add(p),f.epoch!=null){let E=(s[S].epochs||={})[f.epoch]||={usd:0,events:0,provider:b,models:new Set};E.usd+=d,E.events+=1,E.provider=b,p!=="unknown"&&E.models.add(p)}}return{totalUsd:vo(a),totalTokens:u,turns:n.length,repricedTurns:i,unpricedTurns:c,unpricedModels:[...l],byModel:aa(r,f=>({...f,usd:vo(f.usd)})),byProvider:aa(o,f=>({...f,usd:vo(f.usd)})),byBox:aa(s,f=>({usd:vo(f.usd),events:f.events,provider:f.provider,label:f.label,models:[...f.models],...f.epochs?{epochs:aa(f.epochs,d=>({usd:vo(d.usd),events:d.events,provider:d.provider,models:[...d.models]}))}:{}}))}}var vo=t=>Math.round(t*1e4)/1e4,aa=(t,e)=>Object.fromEntries(Object.entries(t).map(([n,r])=>[n,e(r)]));import kt from"node:fs/promises";import Pt from"node:path";import To from"node:os";import{randomUUID as hv}from"node:crypto";import{execFile as wv}from"node:child_process";import{promisify as gv}from"node:util";import{chromium as yv}from"playwright-core";var Bp=gv(wv),Dp=process.env.BOSTRAT_STATE_DIR||Pt.join(process.env.HOME||To.tmpdir(),".bostrat"),jp=Pt.join(Dp,"mocks.json"),bv=process.env.BOSTRAT_MOCK_ENGINE||"chromium",kv=process.env.BOSTRAT_CHROMIUM_BIN||"/usr/bin/chromium",xv=process.env.BOSTRAT_FIREFOX_BIN||"firefox",Sv=1300,gr=2,vv=32,Lp=1500,yr=Number(process.env.BOSTRAT_MOCK_TIMEOUT_MS)||75e3,Tv=700*1024*1024;async function Av(){if(process.platform!=="darwin")return To.freemem();try{let{stdout:t}=await Bp("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:To.freemem()}catch{return To.freemem()}}var te=class extends Error{constructor(e,n=""){super(n?`${e}
|
|
73
|
+
[\u2026 truncated by bostrat at 32 KiB \u2014 full artifact in the workspace]`,bytes:n,truncated:!0}}var Ep="Checkpoint: write a work-in-progress handoff",_p="HANDOFF WRITTEN",Ip="Resume from .bostrat/handoff/latest.md",wv="/clear",gv=t=>`${Ep} 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 ${_p} and take no further action.`,yv=`${Ip}: read it, restate the Objective and the single next step in one line, then continue the work from 'Next steps'.`,Rp="Beat: write a",$p="BEAT WRITTEN",bv=".bostrat/arc",kv="beat.txt",Sp=200,Op=4,Mp=5,xv=36,Sv=t=>`${Rp} 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 ${Op} further lines, each starting with "- " and naming ONE piece in at most ${Mp} 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 ${$p} and take no further action.`,vp=/^(?:[-–—*•·]|\d+[.)])\s+/,vv=(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 Tv(t){let e=String(t||"").split(/\r?\n/).map(s=>s.trim()).filter(Boolean),n=[],r=[];for(let s of e)if(vp.test(s)){if(r.length>=Op)continue;let a=vv(s.replace(vp,"").replace(/[.;,:]+$/,""),Mp,xv);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>Sp&&(o=`${o.slice(0,Sp-1).trimEnd()}\u2026`),{text:o,items:r.length>1?r:[]}}async function Cp(t,e,{timeoutMs:n=9e4}={}){let r=Sr.join(e,bv),o=Sr.join(r,kv);await xr.mkdir(r,{recursive:!0});let s=await da(o),a=await nt(t);if(!a)throw new Error(`session "${t}" not found`);return await fa(t,a,Sv(o)),await Up(o,s,n),Tv(await xr.readFile(o,"utf8"))}function Np(t,e){let n=(e||"").trim();return n?t==="assistant"?n===_p||n===$p:n.startsWith(Ep)||n===wv||n.startsWith(Ip)||n.startsWith(Rp):!1}var Av=6e5;function Pp(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-Av;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 Bp(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 Dp(t,{windows:e=[]}={}){if(Np(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 Ev=3500,_v=3e5,Oc=new Map;function pa(t){return Oc.get(t)||null}async function jp(t){let e=Oc.get(t);if(e&&!e.finishedAt)throw new Error("a handoff is already running for this session");let n=await nt(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:Sr.join(n.cwd,Tp,Ap)};return Oc.set(t,r),Iv(r,n.cwd).catch(o=>{r.error=o?.message||String(o),r.finishedAt=Date.now()}),r}async function Iv(t,e){t.step="summarize",await Lp(t.name,e),t.step="clear";let n=await nt(t.name);await fa(t.name,n,"/clear"),await Hp(Ev),t.step="resume",await fa(t.name,n,yv),t.step="done",t.finishedAt=Date.now()}async function Lp(t,e){let n=Sr.join(e,Tp),r=Sr.join(n,Ap);await xr.mkdir(n,{recursive:!0});let o=await da(r),s=await nt(t);await G({flow:"checkpoint",phase:"summarize-start",sessionName:t,cwd:e,file:r,beforeMtime:o}),await fa(t,s,gv(r)),await Up(r,o,_v),await G({flow:"checkpoint",phase:"summarize-done",sessionName:t,file:r,afterMtime:await da(r)});let a=await yp(e,r,{exclude:l=>Np(l.role,l.text)});await G({flow:"checkpoint",phase:"seed-enriched",sessionName:t,file:r,...a});let i=/workspaces[/\\]([0-9a-f]{8})$/.exec(e||"")?.[1];if(i){await R({id:i,cwd:e},{type:"memory.seed",actor:"orchestrator",payload:{...a,tokensEst:a.chars?Math.round(a.chars/4):0,ts:Date.now()}}).catch(()=>{});try{let l=await xr.readFile(r,"utf8");await R({id:i,cwd:e},{type:"handoff.artifact",actor:"orchestrator",payload:{...hv(l),ts:Date.now()}})}catch{}}await gf(e);let c=await Rv(n,r);return i&&await _f(i,c||r,{box:t,agent:s?.agent??null}),r}async function Fp(t){let e=await nt(t);if(!e)throw new Error(`session "${t}" not found`);if(!e.cwd)throw new Error(`can't resolve cwd for "${t}"`);return Lp(t,e.cwd)}async function da(t){try{return(await xr.stat(t)).mtimeMs}catch{return 0}}async function Up(t,e,n){let r=Date.now()+n;for(;;){if(await da(t)>e)return;if(Date.now()>r)throw new Error(`timed out waiting for the handoff file to be written (${t})`);await Hp(2e3)}}async function Rv(t,e){try{if(!mv.existsSync(e))return null;let n=new Date().toISOString().replace(/[:.]/g,"-"),r=Sr.join(t,`${n}.md`);return await xr.copyFile(e,r),r}catch{return null}}function Hp(t){return new Promise(e=>setTimeout(e,t))}async function fa(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 sn({cwd:e.cwd,sessionId:e.sessionId,sessionName:t,agent:e.agent,agentCmd:o})}let r=await nt(t);if(!r)throw new Error(`session "${t}" disappeared before the orchestration prompt could be sent`);await At(t,n,{kind:"orchestration-drive",...Me(r.agent)})}import F from"node:fs/promises";import U from"node:path";import{randomUUID as hs}from"node:crypto";import{execFile as WI}from"node:child_process";import{promisify as qI}from"node:util";import _h from"node:path";var Mc={"claude-opus-5":{input:5,output:25,cacheWrite:6.25,cacheRead:.5},"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}},Wp={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 qp(t){if(!t)return null;let e=String(t).trim();return Mc[e]?e:Wp[e]?Wp[e]:Object.keys(Mc).find(r=>e.startsWith(r))||null}var ma=t=>qp(t)!==null;function pe(t,e={}){let n=Mc[qp(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 Kp=()=>({input:0,output:0,cacheRead:0,cacheCreation:0});function $v(t){return t?{input:t.input||0,output:t.output||0,cacheRead:t.cacheRead||0,cacheWrite:t.cacheWrite??t.cacheCreation??0}:null}function Gp(t,e){e&&(t.input+=e.input,t.output+=e.output,t.cacheRead+=e.cacheRead,t.cacheCreation+=e.cacheWrite)}function wa(t){let e=t.payload?.model||"unknown",n=$v(t.payload?.tokens),r=Number(t.payload?.usd)||0,o=!1;!r&&n&&(r=pe(e,n),r>0&&(o=!0));let s=n?n.input+n.output+n.cacheRead+n.cacheWrite:0,a=!r&&s>0&&!ma(e);return{usd:r,model:e,tokens:n,repriced:o,unpriced:a}}function me(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=Kp();for(let f of n){let{usd:d,model:p,tokens:m,repriced:w,unpriced:y}=wa(f),b=f.provider||"unknown",S=f.boxId||"unattributed";if(w&&i++,y&&(c++,l.add(p)),a+=d,Gp(u,m),r[p]||={usd:0,tokens:Kp(),events:0},r[p].usd+=d,r[p].events+=1,Gp(r[p].tokens,m),o[b]||={usd:0,events:0},o[b].usd+=d,o[b].events+=1,s[S]||={usd:0,events:0,provider:b,models:new Set,label:e[S]||S},s[S].usd+=d,s[S].events+=1,p!=="unknown"&&s[S].models.add(p),f.epoch!=null){let _=(s[S].epochs||={})[f.epoch]||={usd:0,events:0,provider:b,models:new Set};_.usd+=d,_.events+=1,_.provider=b,p!=="unknown"&&_.models.add(p)}}return{totalUsd:Ro(a),totalTokens:u,turns:n.length,repricedTurns:i,unpricedTurns:c,unpricedModels:[...l],byModel:ha(r,f=>({...f,usd:Ro(f.usd)})),byProvider:ha(o,f=>({...f,usd:Ro(f.usd)})),byBox:ha(s,f=>({usd:Ro(f.usd),events:f.events,provider:f.provider,label:f.label,models:[...f.models],...f.epochs?{epochs:ha(f.epochs,d=>({usd:Ro(d.usd),events:d.events,provider:d.provider,models:[...d.models]}))}:{}}))}}var Ro=t=>Math.round(t*1e4)/1e4,ha=(t,e)=>Object.fromEntries(Object.entries(t).map(([n,r])=>[n,e(r)]));import vt from"node:fs/promises";import Ht from"node:path";import zp from"node:os";import{randomUUID as Nv}from"node:crypto";import{execFile as Pv}from"node:child_process";import{promisify as Bv}from"node:util";import{chromium as Dv}from"playwright-core";import Cc from"node:os";import{execFile as Ov}from"node:child_process";import{promisify as Mv}from"node:util";var Cv=Mv(Ov);async function ga(){if(process.platform!=="darwin")return Cc.freemem();try{let{stdout:t}=await Cv("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:Cc.freemem()}catch{return Cc.freemem()}}var jv=Bv(Pv),Jp=process.env.BOSTRAT_STATE_DIR||Ht.join(process.env.HOME||zp.tmpdir(),".bostrat"),Xp=Ht.join(Jp,"mocks.json"),Lv=process.env.BOSTRAT_MOCK_ENGINE||"chromium",Fv=process.env.BOSTRAT_CHROMIUM_BIN||"/usr/bin/chromium",Uv=process.env.BOSTRAT_FIREFOX_BIN||"firefox",Hv=1300,vr=2,Wv=32,Yp=1500,Tr=Number(process.env.BOSTRAT_MOCK_TIMEOUT_MS)||75e3,qv=700*1024*1024,oe=class extends Error{constructor(e,n=""){super(n?`${e}
|
|
74
74
|
${n.trim().split(`
|
|
75
75
|
`).slice(-6).join(`
|
|
76
|
-
`)}`:e),this.name="MockRenderError"}},Cn=null,Tc=null;async function fe(){return Cn||(Tc||=(async()=>{try{Cn=JSON.parse(await kt.readFile(jp,"utf8"))}catch{Cn={mocks:[]}}finally{Tc=null}return Cn.mocks||=[],Cn})(),Tc)}async function de(){await Mt(jp,Cn)}var en=t=>Rt("mocks",{...t},{trackId:t.trackId}).catch(()=>{}),Mn=(t,e,n)=>Je("mocks",t.id,e,n,Ht(t,e)[n]).catch(()=>{});function ee(t,e,n={}){return R({id:t.trackId,cwd:t.cwd},{type:"mock.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box||null,provider:t.agent||null,payload:{ts:Date.now(),change:e,mockId:t.id,name:t.name,...t.group?{group:t.group}:{},...n}}).catch(()=>{})}var ca=t=>Pt.join(t,".bostrat","mocks");function Ht(t,e){let n=ca(t.cwd);return{html:Pt.join(n,`${t.id}.v${e}.html`),png:Pt.join(n,`${t.id}.v${e}.png`)}}function Ie(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 Ev=Buffer.from([137,80,78,71,13,10,26,10]);function Fp(t){return Buffer.isBuffer(t)&&t.length>24&&t.subarray(0,8).equals(Ev)}async function _v(t){let e=Buffer.alloc(24),n=await kt.open(t,"r");try{await n.read(e,0,24,0)}finally{await n.close()}return e}function Iv({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 Rv(t,e){return t.evaluate(async n=>{let r=new Image;await new Promise((m,g)=>{r.onload=m,r.onerror=()=>g(new Error("decode failed")),r.src=`data:image/png;base64,${n}`});let o=r.naturalWidth,s=r.naturalHeight;if(!o||!s||o*s>24e6)return null;let a=document.createElement("canvas");a.width=o,a.height=s;let i=a.getContext("2d",{willReadFrequently:!0});i.drawImage(r,0,0);let c=i.getImageData(0,0,o,s).data,l=[c[0],c[1],c[2]],u=o,f=s,d=-1,p=-1;for(let m=0;m<s;m++)for(let g=m*o*4,y=0;y<o;y++,g+=4)Math.abs(c[g]-l[0])+Math.abs(c[g+1]-l[1])+Math.abs(c[g+2]-l[2])>24&&(y<u&&(u=y),y>d&&(d=y),m<f&&(f=m),p=m);return d<0?null:{imgW:o,imgH:s,bg:l,bbox:{left:u,top:f,right:d,bottom:p}}},e.toString("base64"))}async function $v(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 Ov({htmlPath:t,outPath:e,width:n}){let r=Date.now(),o;try{o=await yv.launch({executablePath:kv,args:["--disable-gpu"],timeout:3e4})}catch(s){throw new te("chromium failed to launch",s?.message||"")}try{let a=await(await o.newContext({viewport:{width:n,height:800},deviceScaleFactor:gr})).newPage();a.setDefaultTimeout(yr),await a.goto(`file://${t}`,{waitUntil:"load",timeout:yr}),await a.waitForTimeout(250);let i;for(let l=1;;l++)try{i=await a.screenshot({fullPage:!0,timeout:yr});break}catch(u){if(l>=3||!/captureScreenshot|Unable to capture/i.test(u?.message||""))throw u;await a.waitForTimeout(500)}try{let l=await Rv(a,i),u=l&&Iv({...l,pad:vv*gr,align:gr});u&&(i=await $v(a,i,{bg:l.bg,crop:u}))}catch{}if(i.length<Lp)throw new te(`capture produced a ${i.length}-byte image`);let c=Ec(i);if(!c)throw new te("output is not a PNG");return await kt.writeFile(e,i),{width:Math.round(c.width/gr),height:Math.round(c.height/gr),dpr:gr,renderMs:Date.now()-r}}catch(s){throw s instanceof te?s:new te(/timeout/i.test(s?.message||"")?`render timed out after ${yr}ms`:"chromium render failed",s?.message||"")}finally{await o.close().catch(()=>{})}}async function Pp({htmlPath:t,outPath:e,width:n}){let r=await kt.mkdtemp(Pt.join(To.tmpdir(),"bostrat-mock-profile-")),o=Date.now(),s="";try{try{s=(await Bp(xv,["--headless","--no-remote","--profile",r,`--window-size=${n}`,"--screenshot",e,`file://${t}`],{env:{...process.env,MOZ_HEADLESS:"1"},timeout:yr,killSignal:"SIGKILL",maxBuffer:4194304})).stderr||""}catch(c){throw s=c?.stderr||"",new te(c?.killed?`render timed out after ${yr}ms`:"firefox exited non-zero",s)}let a=await kt.stat(e).catch(()=>null);if(!a||a.size<Lp)throw new te(`render produced ${a?`a ${a.size}-byte file`:"no file"} (silent no-op \u2014 another firefox holding the profile?)`,s);let i=Ec(await _v(e));if(!i)throw new te("output is not a PNG",s);return{width:i.width,height:i.height,dpr:1,renderMs:Date.now()-o}}finally{await kt.rm(r,{recursive:!0,force:!0}).catch(()=>{})}}async function Cv(t){if(bv==="firefox")return Pp(t);try{return await Ov(t)}catch(e){if(/failed to launch/i.test(e?.message||""))return Pp(t);throw e}}var Mv=Cv;var Ac=Promise.resolve();function Up(t){return Ac=Ac.then(t,t),Ac}async function Hp(t,e){let n=await fe(),r=n.mocks.find(o=>o.id===t);if(r){r.state="rendering",await de(),await ee(r,"rendering",{version:e});try{let o=await Av();if(o<Tv)throw new te(`box is low on memory (${Math.round(o/1e6)} MB available) \u2014 not spawning a browser`);let s=Ht(r,e),a=await Mv({htmlPath:s.html,outPath:s.png,width:r.width});if(!n.mocks.some(i=>i.id===t)){await kt.rm(s.png,{force:!0}).catch(()=>{});return}Wp(r,{v:e,source:"html",htmlRel:Pt.relative(r.cwd,s.html),pngRel:Pt.relative(r.cwd,s.png),width:a.width,height:a.height,dpr:a.dpr,renderMs:a.renderMs,createdAt:Date.now()}),await de(),await ee(r,"ready",{version:e,width:a.width,height:a.height,dpr:a.dpr}),en(r),Mn(r,e,"png")}catch(o){if(!n.mocks.some(s=>s.id===t))return;r.state="error",r.error=o?.message||String(o),await de(),await ee(r,"error",{version:e,error:r.error}),en(r)}}}function Wp(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 Nv({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?Sv:Number(r);if(!Number.isInteger(o)||o<320||o>4e3)throw new Error("invalid width (320-4000)");return o}async function Nn({trackId:t,group:e}={}){let n=await fe(),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 nn(t){let n=(await fe()).mocks.find(r=>r.id===t);return n?{...n}:null}async function qp({trackId:t,name:e,html:n,image:r,cwd:o,width:s,group:a,box:i,agent:c,prompt:l}){let u=Nv({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&&!Fp(r))throw new Error("image must be a PNG");let d=await fe(),p={id:hv().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 kt.mkdir(ca(o),{recursive:!0}),d.mocks.push(p),f?(await kt.writeFile(Ht(p,1).html,n),await de(),await ee(p,"created"),en(p),Mn(p,1,"html"),Up(()=>Hp(p.id,1))):(await Gp(p,1,r),await de(),await ee(p,"created"),await ee(p,"ready",{version:1,width:p.width,height:p.height,dpr:p.dpr}),en(p),Mn(p,1,"png")),{...p}}async function Kp(t,{html:e,image:n,width:r}={}){let s=(await fe()).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&&!Fp(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 kt.mkdir(ca(s.cwd),{recursive:!0}),a?(s.state="queued",await kt.writeFile(Ht(s,i).html,e),await de(),await ee(s,"updated",{version:i}),en(s),Mn(s,i,"html"),Up(()=>Hp(s.id,i))):(await Gp(s,i,n),await de(),await ee(s,"updated",{version:i}),await ee(s,"ready",{version:i,width:s.width,height:s.height,dpr:s.dpr}),en(s),Mn(s,i,"png")),{...s}}async function Gp(t,e,n){let r=Ec(n);if(!r)throw new Error("image must be a PNG");let o=Ht(t,e);await kt.writeFile(o.png,n),Wp(t,{v:e,source:"image",htmlRel:null,pngRel:Pt.relative(t.cwd,o.png),width:r.width,height:r.height,dpr:1,renderMs:null,createdAt:Date.now()}),t.width=r.width}async function Vp(t){let e=await fe(),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 ee(n,"picked",{version:n.readyVersion,siblings:r}),{mock:{...n},siblings:r}}async function zp(t){let e=await fe(),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=Ht(r,o);await kt.rm(s.html,{force:!0}).catch(()=>{}),await kt.rm(s.png,{force:!0}).catch(()=>{})}return await de(),await ee(r,"deleted"),Qt("mocks",r.id).catch(()=>{}),{ok:!0,id:t,name:r.name}}async function Jp(t){let n=(await fe()).mocks.filter(i=>i.trackId===t.id);if(!n.length)return 0;let r=Pt.join(Dp,"mock-archive",t.id),o=ca(t.cwd),s=Pt.join(r,".bostrat","mocks");await kt.mkdir(s,{recursive:!0});let a=await kt.readdir(o).catch(()=>[]);for(let i of a)await kt.copyFile(Pt.join(o,i),Pt.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 kt.stat(Pt.join(r,l)).catch(()=>null))?.isFile())throw new Error(`mock evacuation incomplete: ${l} missing from the archive`)}i.cwd=r,i.trackDeletedAt=Date.now(),i.trackName=t.name||t.id,i.updatedAt=Date.now(),en(i)}return await de(),n.length}async function Xp(t){let e=await fe(),n=e.mocks.filter(r=>r.trackId===t);if(n.length){e.mocks=e.mocks.filter(r=>r.trackId!==t),await de();for(let r of n)Qt("mocks",r.id).catch(()=>{})}return n.length}async function Yp(){if(!pt())return{pushed:0,source:"unconfigured"};let{records:t,source:e}=await Tt("mocks");if(e!=="plane"||t.length)return{pushed:0,source:e};let n=await fe();for(let r of n.mocks){await en(r);for(let o of r.versions||[])o.htmlRel&&await Mn(r,o.v,"html"),o.pngRel&&await Mn(r,o.v,"png")}return{pushed:n.mocks.length,source:"plane"}}import Eo from"node:fs/promises";import Bt from"node:path";import Pv from"node:os";import Bv from"node:net";import{randomUUID as Dv}from"node:crypto";import{execFile as jv}from"node:child_process";import{promisify as Lv}from"node:util";var me=Lv(jv),Fv=process.env.BOSTRAT_STATE_DIR||Bt.join(process.env.HOME||Pv.tmpdir(),".bostrat"),nm=Bt.join(Fv,"views.json"),[Zp,Qp]=(process.env.BOSTRAT_VIEW_PORTS||"4300-4349").split("-").map(t=>Number(t)||0),rn=Number(process.env.BOSTRAT_VIEW_MAX)||3,Uv="bostrat-view-",br=t=>`${Uv}${t}`,tm=new Set(["bostrat","www","api","views","app"]),la=t=>Object.assign(new Error(t),{status:400}),Hv=new Set(["npm","pnpm","yarn","bun"]),Rc="package.json",Wv=/[|;&`]|\$\(/,qv=new Set(["sh","bash","zsh","dash","env","exec","nohup","time"]),Kv=/(^|\s)(-C|--cwd|--manifest-path|--dir)(=|\s)/,rm=t=>String(t).trim().split(/\s+/).filter(Boolean),Gv=/^[A-Za-z_][A-Za-z0-9_]*=/;function Vv(t){for(let e of rm(t))if(!Gv.test(e))return e;return null}function om(t){let e=rm(t),n=e.indexOf("--");return n===-1?e:e.slice(0,n)}function zv(t,e){let n=om(e);for(let r=0;r<n.length;r++){let o=/^--prefix=(.+)$/.exec(n[r]);if(o)return Bt.resolve(t,o[1]);if(n[r]==="--prefix"&&n[r+1])return Bt.resolve(t,n[r+1])}return t}var Jv=t=>Eo.stat(t).then(e=>e.isDirectory(),()=>!1),sm=t=>Eo.stat(t).then(e=>e.isFile(),()=>!1),ua=t=>Eo.realpath(t).catch(()=>Bt.resolve(t));async function Xv(t,e){if(Bt.resolve(t)===Bt.resolve(e))return!0;let[n,r]=await Promise.all([ua(t),ua(e)]);return n===r}async function Yv(t){let n=(await Eo.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 sm(Bt.join(t,o.name,Rc))?`./${o.name}`:null))).filter(Boolean)}async function Zv(t,e){let[n,r]=await Promise.all([ua(t),ua(e)]),o=Bt.relative(n,r);if(o&&(o.startsWith("..")||Bt.isAbsolute(o)))throw la(`serve directory escapes the Track workspace: ${e}`)}async function Qv(t,e){if(!await Jv(t))throw la(`serve directory does not exist: ${t}`);let n=Vv(e);if(!n)return;if(!n.includes("/")&&!await sc(n,{loginShell:!0,failOpen:!0}))throw la(`\`${n}\` is not on the box's PATH \u2014 install it in the box first`);if(Wv.test(e)||qv.has(n)||!Hv.has(n)||!om(e).includes("run")||Kv.test(e))return;let r=zv(t,e);if(await sm(Bt.join(r,Rc)))return;let o=await Yv(r),s=o.length?` \u2014 found one in ${o.join(", ")}; run bostrat-view from there`:"";throw la(`no ${Rc} in ${r}${s}`)}var pe=null,_c=null;async function Oe(){return pe||(_c||=(async()=>{try{pe=JSON.parse(await Eo.readFile(nm,"utf8"))}catch{pe={views:[],adoptWatermarks:{}}}finally{_c=null}return pe.views||=[],pe.adoptWatermarks||={},pe})(),_c)}async function he(){await Mt(nm,pe)}function $e(t,e,n={}){return R({id:t.trackId,cwd:t.cwd},{type:"view.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box||null,payload:{ts:Date.now(),change:e,viewId:t.id,name:t.name,slug:t.slug,port:t.port,...n}}).catch(()=>{})}var Ao=new Map,Cc=Number(process.env.BOSTRAT_VIEW_JOB_TIMEOUT)||3e5,am=12e3;function Mc(t){return Ao.get(t)||null}function im(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 $c(t,e){let n=Ao.get(t);n&&!n.finishedAt&&!n.viewId&&(n.viewId=e,n.step="booting")}function da(t,{error:e=null}={}){let n=Ao.get(t);return!n||n.finishedAt?null:(n.finishedAt=Date.now(),e&&(n.error=e),n)}function cm(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 Oc(t){return String(t||"").trim().toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,40).replace(/-+$/g,"")||"view"}function tT(t,e){let n=Oc(e),r=new Set(t.views.map(o=>o.slug));if(!r.has(n)&&!tm.has(n))return n;for(let o=2;;o++){let s=`${n}-${o}`;if(!r.has(s)&&!tm.has(s))return s}}function _o(t,{timeoutMs:e=1200}={}){return new Promise(n=>{let r=Bv.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 Nc(t){if(process.platform!=="darwin"){await me("fuser",["-k",`${t}/tcp`]).catch(()=>{});return}let e="";try{({stdout:e}=await me("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 lm(t=process.env){return new Set([Number(t.PORT)||3362,Number(t.BOSTRAT_TLS_PORT)||3443])}async function eT(t,e){let n=new Set(t.views.map(o=>o.port)),r=lm();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=Zp;o<=Qp;o++)if(!(n.has(o)||r.has(o))&&!await _o(o,{timeoutMs:250}))return o;throw new Error(`no free view port in ${Zp}-${Qp}`)}async function nT(t){try{let{stdout:e}=await me(N,P("list-sessions","-F","#{session_name}"));return e.split(`
|
|
77
|
-
`).some(n=>n.trim()===
|
|
76
|
+
`)}`:e),this.name="MockRenderError"}},jn=null,Nc=null;async function we(){return jn||(Nc||=(async()=>{try{jn=JSON.parse(await vt.readFile(Xp,"utf8"))}catch{jn={mocks:[]}}finally{Nc=null}return jn.mocks||=[],jn})(),Nc)}async function he(){await Ft(Xp,jn)}var an=t=>Nt("mocks",{...t},{trackId:t.trackId}).catch(()=>{}),Ln=(t,e,n)=>tn("mocks",t.id,e,n,Jt(t,e)[n]).catch(()=>{});function se(t,e,n={}){return R({id:t.trackId,cwd:t.cwd},{type:"mock.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box||null,provider:t.agent||null,payload:{ts:Date.now(),change:e,mockId:t.id,name:t.name,...t.group?{group:t.group}:{},...n}}).catch(()=>{})}var ya=t=>Ht.join(t,".bostrat","mocks");function Jt(t,e){let n=ya(t.cwd);return{html:Ht.join(n,`${t.id}.v${e}.html`),png:Ht.join(n,`${t.id}.v${e}.png`)}}function Ce(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 Bc(t){return t.length<24||t.readUInt32BE(12)!==1229472850?null:{width:t.readUInt32BE(16),height:t.readUInt32BE(20)}}var Kv=Buffer.from([137,80,78,71,13,10,26,10]);function Zp(t){return Buffer.isBuffer(t)&&t.length>24&&t.subarray(0,8).equals(Kv)}async function Gv(t){let e=Buffer.alloc(24),n=await vt.open(t,"r");try{await n.read(e,0,24,0)}finally{await n.close()}return e}function Vv({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 zv(t,e){return t.evaluate(async n=>{let r=new Image;await new Promise((m,w)=>{r.onload=m,r.onerror=()=>w(new Error("decode failed")),r.src=`data:image/png;base64,${n}`});let o=r.naturalWidth,s=r.naturalHeight;if(!o||!s||o*s>24e6)return null;let a=document.createElement("canvas");a.width=o,a.height=s;let i=a.getContext("2d",{willReadFrequently:!0});i.drawImage(r,0,0);let c=i.getImageData(0,0,o,s).data,l=[c[0],c[1],c[2]],u=o,f=s,d=-1,p=-1;for(let m=0;m<s;m++)for(let w=m*o*4,y=0;y<o;y++,w+=4)Math.abs(c[w]-l[0])+Math.abs(c[w+1]-l[1])+Math.abs(c[w+2]-l[2])>24&&(y<u&&(u=y),y>d&&(d=y),m<f&&(f=m),p=m);return d<0?null:{imgW:o,imgH:s,bg:l,bbox:{left:u,top:f,right:d,bottom:p}}},e.toString("base64"))}async function Jv(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 Xv({htmlPath:t,outPath:e,width:n}){let r=Date.now(),o;try{o=await Dv.launch({executablePath:Fv,args:["--disable-gpu"],timeout:3e4})}catch(s){throw new oe("chromium failed to launch",s?.message||"")}try{let a=await(await o.newContext({viewport:{width:n,height:800},deviceScaleFactor:vr})).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 zv(a,i),u=l&&Vv({...l,pad:Wv*vr,align:vr});u&&(i=await Jv(a,i,{bg:l.bg,crop:u}))}catch{}if(i.length<Yp)throw new oe(`capture produced a ${i.length}-byte image`);let c=Bc(i);if(!c)throw new oe("output is not a PNG");return await vt.writeFile(e,i),{width:Math.round(c.width/vr),height:Math.round(c.height/vr),dpr:vr,renderMs:Date.now()-r}}catch(s){throw s instanceof oe?s:new oe(/timeout/i.test(s?.message||"")?`render timed out after ${Tr}ms`:"chromium render failed",s?.message||"")}finally{await o.close().catch(()=>{})}}async function Vp({htmlPath:t,outPath:e,width:n}){let r=await vt.mkdtemp(Ht.join(zp.tmpdir(),"bostrat-mock-profile-")),o=Date.now(),s="";try{try{s=(await jv(Uv,["--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 oe(c?.killed?`render timed out after ${Tr}ms`:"firefox exited non-zero",s)}let a=await vt.stat(e).catch(()=>null);if(!a||a.size<Yp)throw new oe(`render produced ${a?`a ${a.size}-byte file`:"no file"} (silent no-op \u2014 another firefox holding the profile?)`,s);let i=Bc(await Gv(e));if(!i)throw new oe("output is not a PNG",s);return{width:i.width,height:i.height,dpr:1,renderMs:Date.now()-o}}finally{await vt.rm(r,{recursive:!0,force:!0}).catch(()=>{})}}async function Yv(t){if(Lv==="firefox")return Vp(t);try{return await Xv(t)}catch(e){if(/failed to launch/i.test(e?.message||""))return Vp(t);throw e}}var Zv=Yv;var Pc=Promise.resolve();function Qp(t){return Pc=Pc.then(t,t),Pc}async function tm(t,e){let n=await we(),r=n.mocks.find(o=>o.id===t);if(r){r.state="rendering",await he(),await se(r,"rendering",{version:e});try{let o=await ga();if(o<qv)throw new oe(`box is low on memory (${Math.round(o/1e6)} MB available) \u2014 not spawning a browser`);let s=Jt(r,e),a=await Zv({htmlPath:s.html,outPath:s.png,width:r.width});if(!n.mocks.some(i=>i.id===t)){await vt.rm(s.png,{force:!0}).catch(()=>{});return}em(r,{v:e,source:"html",htmlRel:Ht.relative(r.cwd,s.html),pngRel:Ht.relative(r.cwd,s.png),width:a.width,height:a.height,dpr:a.dpr,renderMs:a.renderMs,createdAt:Date.now()}),await he(),await se(r,"ready",{version:e,width:a.width,height:a.height,dpr:a.dpr}),an(r),Ln(r,e,"png")}catch(o){if(!n.mocks.some(s=>s.id===t))return;r.state="error",r.error=o?.message||String(o),await he(),await se(r,"error",{version:e,error:r.error}),an(r)}}}function em(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 Qv({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?Hv:Number(r);if(!Number.isInteger(o)||o<320||o>4e3)throw new Error("invalid width (320-4000)");return o}async function Fn({trackId:t,group:e}={}){let n=await we(),r=t?n.mocks.filter(o=>o.trackId===t):n.mocks;return e&&(r=r.filter(o=>o.group===e)),r.map(o=>({...o}))}async function cn(t){let n=(await we()).mocks.find(r=>r.id===t);return n?{...n}:null}async function nm({trackId:t,name:e,html:n,image:r,cwd:o,width:s,group:a,box:i,agent:c,prompt:l}){let u=Qv({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&&!Zp(r))throw new Error("image must be a PNG");let d=await we(),p={id:Nv().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 vt.mkdir(ya(o),{recursive:!0}),d.mocks.push(p),f?(await vt.writeFile(Jt(p,1).html,n),await he(),await se(p,"created"),an(p),Ln(p,1,"html"),Qp(()=>tm(p.id,1))):(await om(p,1,r),await he(),await se(p,"created"),await se(p,"ready",{version:1,width:p.width,height:p.height,dpr:p.dpr}),an(p),Ln(p,1,"png")),{...p}}async function rm(t,{html:e,image:n,width:r}={}){let s=(await we()).mocks.find(c=>c.id===t);if(!s)throw new Error("mock not found");let a=typeof e=="string"&&e.trim();if(!a&&!n)throw new Error("html (string) or image (png) required");if(n&&!Zp(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 vt.mkdir(ya(s.cwd),{recursive:!0}),a?(s.state="queued",await vt.writeFile(Jt(s,i).html,e),await he(),await se(s,"updated",{version:i}),an(s),Ln(s,i,"html"),Qp(()=>tm(s.id,i))):(await om(s,i,n),await he(),await se(s,"updated",{version:i}),await se(s,"ready",{version:i,width:s.width,height:s.height,dpr:s.dpr}),an(s),Ln(s,i,"png")),{...s}}async function om(t,e,n){let r=Bc(n);if(!r)throw new Error("image must be a PNG");let o=Jt(t,e);await vt.writeFile(o.png,n),em(t,{v:e,source:"image",htmlRel:null,pngRel:Ht.relative(t.cwd,o.png),width:r.width,height:r.height,dpr:1,renderMs:null,createdAt:Date.now()}),t.width=r.width}async function sm(t){let e=await we(),n=e.mocks.find(o=>o.id===t);if(!n)throw new Error("mock not found");if(n.readyVersion==null)throw new Error(`mock is ${n.state} \u2014 nothing to pick yet`);let r=n.group?e.mocks.filter(o=>o.trackId===n.trackId&&o.group===n.group&&o.id!==t&&o.readyVersion!=null).map(o=>({mockId:o.id,name:o.name})):[];return await se(n,"picked",{version:n.readyVersion,siblings:r}),{mock:{...n},siblings:r}}async function am(t){let e=await we(),n=e.mocks.findIndex(o=>o.id===t);if(n===-1)throw new Error("mock not found");let r=e.mocks[n];e.mocks.splice(n,1);for(let o=1;o<=r.version;o++){let s=Jt(r,o);await vt.rm(s.html,{force:!0}).catch(()=>{}),await vt.rm(s.png,{force:!0}).catch(()=>{})}return await he(),await se(r,"deleted"),re("mocks",r.id).catch(()=>{}),{ok:!0,id:t,name:r.name}}async function im(t){let n=(await we()).mocks.filter(i=>i.trackId===t.id);if(!n.length)return 0;let r=Ht.join(Jp,"mock-archive",t.id),o=ya(t.cwd),s=Ht.join(r,".bostrat","mocks");await vt.mkdir(s,{recursive:!0});let a=await vt.readdir(o).catch(()=>[]);for(let i of a)await vt.copyFile(Ht.join(o,i),Ht.join(s,i));for(let i of n){for(let c of i.versions||[])for(let l of[c.htmlRel,c.pngRel]){if(!l)continue;if(!(await vt.stat(Ht.join(r,l)).catch(()=>null))?.isFile())throw new Error(`mock evacuation incomplete: ${l} missing from the archive`)}i.cwd=r,i.trackDeletedAt=Date.now(),i.trackName=t.name||t.id,i.updatedAt=Date.now(),an(i)}return await he(),n.length}async function cm(t){let e=await we(),n=e.mocks.filter(r=>r.trackId===t);if(n.length){e.mocks=e.mocks.filter(r=>r.trackId!==t),await he();for(let r of n)re("mocks",r.id).catch(()=>{})}return n.length}async function lm(){if(!ht())return{pushed:0,source:"unconfigured"};let{records:t,source:e}=await Rt("mocks");if(e!=="plane"||t.length)return{pushed:0,source:e};let n=await we();for(let r of n.mocks){await an(r);for(let o of r.versions||[])o.htmlRel&&await Ln(r,o.v,"html"),o.pngRel&&await Ln(r,o.v,"png")}return{pushed:n.mocks.length,source:"plane"}}import Oo from"node:fs/promises";import Wt from"node:path";import tT from"node:os";import eT from"node:net";import{randomUUID as nT}from"node:crypto";import{execFile as rT}from"node:child_process";import{promisify as oT}from"node:util";var ye=oT(rT),sT=process.env.BOSTRAT_STATE_DIR||Wt.join(process.env.HOME||tT.tmpdir(),".bostrat"),mm=Wt.join(sT,"views.json"),[um,dm]=(process.env.BOSTRAT_VIEW_PORTS||"4300-4349").split("-").map(t=>Number(t)||0),ln=Number(process.env.BOSTRAT_VIEW_MAX)||3,aT="bostrat-view-",Ar=t=>`${aT}${t}`,fm=new Set(["bostrat","www","api","views","app"]),ba=t=>Object.assign(new Error(t),{status:400}),iT=new Set(["npm","pnpm","yarn","bun"]),Lc="package.json",cT=/[|;&`]|\$\(/,lT=new Set(["sh","bash","zsh","dash","env","exec","nohup","time"]),uT=/(^|\s)(-C|--cwd|--manifest-path|--dir)(=|\s)/,hm=t=>String(t).trim().split(/\s+/).filter(Boolean),dT=/^[A-Za-z_][A-Za-z0-9_]*=/;function fT(t){for(let e of hm(t))if(!dT.test(e))return e;return null}function wm(t){let e=hm(t),n=e.indexOf("--");return n===-1?e:e.slice(0,n)}function pT(t,e){let n=wm(e);for(let r=0;r<n.length;r++){let o=/^--prefix=(.+)$/.exec(n[r]);if(o)return Wt.resolve(t,o[1]);if(n[r]==="--prefix"&&n[r+1])return Wt.resolve(t,n[r+1])}return t}var mT=t=>Oo.stat(t).then(e=>e.isDirectory(),()=>!1),gm=t=>Oo.stat(t).then(e=>e.isFile(),()=>!1),ka=t=>Oo.realpath(t).catch(()=>Wt.resolve(t));async function hT(t,e){if(Wt.resolve(t)===Wt.resolve(e))return!0;let[n,r]=await Promise.all([ka(t),ka(e)]);return n===r}async function wT(t){let n=(await Oo.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 gm(Wt.join(t,o.name,Lc))?`./${o.name}`:null))).filter(Boolean)}async function gT(t,e){let[n,r]=await Promise.all([ka(t),ka(e)]),o=Wt.relative(n,r);if(o&&(o.startsWith("..")||Wt.isAbsolute(o)))throw ba(`serve directory escapes the Track workspace: ${e}`)}async function yT(t,e){if(!await mT(t))throw ba(`serve directory does not exist: ${t}`);let n=fT(e);if(!n)return;if(!n.includes("/")&&!await Pf(n,{loginShell:!0,failOpen:!0}))throw ba(`\`${n}\` is not on the box's PATH \u2014 install it in the box first`);if(cT.test(e)||lT.has(n)||!iT.has(n)||!wm(e).includes("run")||uT.test(e))return;let r=pT(t,e);if(await gm(Wt.join(r,Lc)))return;let o=await wT(r),s=o.length?` \u2014 found one in ${o.join(", ")}; run bostrat-view from there`:"";throw ba(`no ${Lc} in ${r}${s}`)}var ge=null,Dc=null;async function Be(){return ge||(Dc||=(async()=>{try{ge=JSON.parse(await Oo.readFile(mm,"utf8"))}catch{ge={views:[],adoptWatermarks:{}}}finally{Dc=null}return ge.views||=[],ge.adoptWatermarks||={},ge})(),Dc)}async function be(){await Ft(mm,ge)}function Pe(t,e,n={}){return R({id:t.trackId,cwd:t.cwd},{type:"view.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box||null,payload:{ts:Date.now(),change:e,viewId:t.id,name:t.name,slug:t.slug,port:t.port,...n}}).catch(()=>{})}var $o=new Map,Hc=Number(process.env.BOSTRAT_VIEW_JOB_TIMEOUT)||3e5,ym=12e3;function Wc(t){return $o.get(t)||null}function bm(t,{name:e,box:n}={}){let r=$o.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 $o.set(t,o),o}function Fc(t,e){let n=$o.get(t);n&&!n.finishedAt&&!n.viewId&&(n.viewId=e,n.step="booting")}function xa(t,{error:e=null}={}){let n=$o.get(t);return!n||n.finishedAt?null:(n.finishedAt=Date.now(),e&&(n.error=e),n)}function km(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 Uc(t){return String(t||"").trim().toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,40).replace(/-+$/g,"")||"view"}function bT(t,e){let n=Uc(e),r=new Set(t.views.map(o=>o.slug));if(!r.has(n)&&!fm.has(n))return n;for(let o=2;;o++){let s=`${n}-${o}`;if(!r.has(s)&&!fm.has(s))return s}}function Mo(t,{timeoutMs:e=1200}={}){return new Promise(n=>{let r=eT.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 qc(t){if(process.platform!=="darwin"){await ye("fuser",["-k",`${t}/tcp`]).catch(()=>{});return}let e="";try{({stdout:e}=await ye("lsof",["-t","-sTCP:LISTEN","-i",`tcp:${t}`]))}catch{return}for(let n of e.split(/\s+/).filter(Boolean)){let r=Number(n);if(!(!r||r===process.pid))try{process.kill(r,"SIGKILL")}catch{}}}function xm(t=process.env){return new Set([Number(t.PORT)||3362,Number(t.BOSTRAT_TLS_PORT)||3443])}async function kT(t,e){let n=new Set(t.views.map(o=>o.port)),r=xm();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=um;o<=dm;o++)if(!(n.has(o)||r.has(o))&&!await Mo(o,{timeoutMs:250}))return o;throw new Error(`no free view port in ${um}-${dm}`)}async function xT(t){try{let{stdout:e}=await ye(M,P("list-sessions","-F","#{session_name}"));return e.split(`
|
|
77
|
+
`).some(n=>n.trim()===Ar(t))}catch{return!1}}async function Co(t){try{await ye(M,P("kill-session","-t",Ar(t)))}catch{}}async function Kc(t){if(!await vo())throw new Error("host tmux server not running and auto-start failed \u2014 cannot spawn a view server");let e=Ar(t.slug),n=t.serveCwd??t.cwd,r=Wt.dirname(process.execPath);await ye(M,P("new-session","-d","-s",e,"-c",n)),await ye(M,P("send-keys","-t",e,"-l",`export PORT=${t.port} PATH="${r}:$PATH"`)),await ye(M,P("send-keys","-t",e,"Enter")),await ye(M,P("send-keys","-t",e,"-l",t.cmd)),await ye(M,P("send-keys","-t",e,"Enter"))}async function pm(t,e=12){try{let{stdout:n}=await ye(M,P("capture-pane","-t",Ar(t),"-p"));return String(n||"").trimEnd().split(`
|
|
78
78
|
`).filter(Boolean).slice(-e).join(`
|
|
79
|
-
`)}catch{return""}}var Re=new Map,rT=12e4;async function oT(t){if(t.state==="stopped"||t.state==="error")return!1;let e=t.origin!=="detected",n=e?await nT(t.slug):!0;if(e&&!n){let s=t.state==="starting";return t.state=s?"error":"stopped",s&&(t.error=await em(t.slug)||"server exited before listening"),Re.delete(t.id),await $e(t,t.state,t.error?{error:t.error}:{}),!0}if(await _o(t.port))return Re.delete(t.id),t.lastHealthyAt=Date.now(),t.state!=="live"?(t.state="live",t.error=null,await $e(t,"live"),!0):!1;if(t.state==="starting")return Date.now()-t.createdAt>rT?(t.state="error",t.error=await em(t.slug)||"never started listening",await $e(t,"error",{error:t.error}),!0):!1;let o=(Re.get(t.id)||0)+1;return Re.set(t.id,o),o>=3&&t.state==="live"?(t.state="unhealthy",!0):!1}var Ic=!1;async function fa(){if(!Ic){Ic=!0;try{let t=await Oe(),e=!1;for(let n of t.views)await oT(n)&&(e=!0);e&&await he()}finally{Ic=!1}}}async function pa({trackId:t}={}){await fa();let e=await Oe();return(t?e.views.filter(r=>r.trackId===t):e.views).map(r=>({...r}))}async function um(t){let n=(await Oe()).views.find(r=>r.id===t);return n?{...n}:null}function Bc(t){return pe&&pe.views.find(e=>e.slug===t)||null}async function Dc({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=Bt.resolve(r,o??"."),f=i==="detected"?null:async()=>{await Zv(r,u),await Qv(u,n)},d=await Oe();if(i!=="detected"){let g=Oc(e),y=String(n).trim(),b=d.views.find(S=>S.trackId===t&&(S.slug===g||Oc(S.name)===g||S.cmd===y&&Bt.resolve(S.serveCwd??S.cwd)===u));if(b)return sT(d,b,{cmd:y,box:a,branch:c,serveCwd:l?u:void 0,validate:f})}if(f&&await f(),d.views.filter(g=>g.state==="live"||g.state==="starting").length>=rn)throw new Error(`view cap reached (${rn} live) \u2014 stop one first`);let m={id:Dv().slice(0,8),trackId:t,box:a||null,name:e.trim(),slug:tT(d,e),cmd:n?String(n).trim():null,cwd:r,serveCwd:u,branch:c,port:await eT(d,s),origin:i,state:"starting",error:null,createdAt:Date.now(),lastHealthyAt:null};return i!=="detected"&&await Pc(m),d.views.push(m),await he(),await $e(m,"created",{origin:i}),$c(t,m.id),i==="detected"&&await _o(m.port)&&(m.state="live",m.lastHealthyAt=Date.now(),await he(),await $e(m,"live")),{...m}}async function sT(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 Xv(e.serveCwd??e.cwd,s);if((e.state==="live"||e.state==="starting")&&!i&&!c&&await _o(e.port))return await he(),$c(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>=rn)throw new Error(`view cap reached (${rn} live) \u2014 stop one first`);return e.origin==="detected"?(await Nc(e.port),e.origin="agent"):await Io(e.slug),e.cmd=n,s!==void 0&&(e.serveCwd=s),e.state="starting",e.error=null,e.createdAt=Date.now(),Re.delete(e.id),await Pc(e),await he(),await $e(e,"created",{restarted:!0,reused:!0}),$c(e.trackId,e.id),{...e,reused:!0}}async function dm(t){let n=(await Oe()).views.find(r=>r.id===t);if(!n)throw new Error("view not found");return n.origin==="detected"?await Nc(n.port):await Io(n.slug),n.state!=="stopped"&&(n.state="stopped",Re.delete(n.id),await he(),await $e(n,"stopped")),{...n}}async function fm(t){let e=await Oe(),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>=rn)throw new Error(`view cap reached (${rn} live) \u2014 stop one first`);return await Io(n.slug),n.state="starting",n.error=null,n.createdAt=Date.now(),Re.delete(n.id),await Pc(n),await he(),await $e(n,"created",{restarted:!0}),{...n}}async function pm(t){let e=await Oe(),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 Nc(r.port):await Io(r.slug),e.views.splice(n,1),Re.delete(r.id),await he(),await $e(r,"deleted"),{ok:!0,id:t,slug:r.slug}}async function mm(t){let e=await Oe(),n=e.views.filter(r=>r.trackId===t);for(let r of n)r.origin!=="detected"&&await Io(r.slug),Re.delete(r.id);return n.length&&(e.views=e.views.filter(r=>r.trackId!==t),await he()),n.length}var aT=/https?:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0)(?::(\d{2,5}))?(?:\/|\b)/g;async function hm(t,e){if(!t?.id||!Array.isArray(e)||!e.length)return[];let n=await Oe(),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 he()),!o.length)return[];let a=new Set;for(let c of o)for(let l of String(c.text||"").matchAll(aT)){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(!lm().has(c)&&!n.views.some(l=>l.port===c)&&await _o(c))try{i.push(await Dc({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 Ne from"node:fs/promises";import Pe from"node:path";import iT from"node:os";import{randomUUID as cT}from"node:crypto";var Pn=process.env.BOSTRAT_STATE_DIR||Pe.join(process.env.HOME||iT.tmpdir(),".bostrat"),gm=Pe.join(Pn,"runbooks.json"),ma=Pe.join(Pn,"runbooks"),wm=256*1024,lT=[{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 Ro(t){return lT.filter(({re:e})=>e.test(t)).map(({label:e})=>e)}var Ce=null,jc=null;async function Be(){return Ce||(jc||=(async()=>{let t=!0;try{Ce=JSON.parse(await Ne.readFile(gm,"utf8"))}catch{Ce={runbooks:[]},t=!1}finally{jc=null}return Ce.runbooks||=[],!t&&pt()&&await uT().catch(()=>{}),Ce})(),jc)}async function Bn(){await Mt(gm,Ce)}var $o=t=>Promise.resolve(t).catch(()=>{});function ha(t){return $o(Rt("runbooks",{...t},{trackId:t.trackId}))}async function uT(){let{records:t,source:e}=await Tt("runbooks");if(!(e!=="plane"||!t.length)){await Ne.mkdir(ma,{recursive:!0});for(let n of t){if(n.deletedAt||!n.data?.id)continue;let r=n.data;for(let o of r.versions||[]){let s=await js("runbooks",r.id,o.v,"md");typeof s=="string"&&await Ne.writeFile(Me(r,o.v),s).catch(()=>{})}Ce.runbooks.push(r)}Ce.runbooks.length&&await Bn()}}function kr(t,e,n={}){return R({id:t.trackId,cwd:t.cwd},{type:"runbook.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box||null,provider:t.agent||null,payload:{ts:Date.now(),change:e,runbookId:t.id,name:t.name,status:t.status,...n}}).catch(()=>{})}function Me(t,e){return Pe.join(ma,`${t.id}.v${e}.md`)}function ym(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 bm(t){if(typeof t!="string"||!t.trim())throw new Error("md (markdown string) required");let e=Buffer.byteLength(t,"utf8");if(e>wm)throw new Error(`runbook is ${Math.round(e/1024)} KB \u2014 over the ${wm/1024} KB cap; a doc that size belongs in the repo, not a runbook`);let n=Ro(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 km(t,e){if(t.trackDeletedAt)throw new Error(`runbook is orphaned (its Track was deleted) \u2014 cannot ${e} it`)}async function wa({trackId:t,status:e}={}){let n=await Be(),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 Be()).runbooks.find(r=>r.id===t);return n?{...n}:null}async function xm({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=bm(r),c=await Be(),l={id:cT().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 Ne.mkdir(ma,{recursive:!0}),await Ne.writeFile(Me(l,1),r),l.versions.push({v:1,mdRel:Pe.relative(Pn,Me(l,1)),bytes:i,note:null,createdAt:Date.now()}),c.runbooks.push(l),await Bn(),await kr(l,"created",{version:1}),ha(l),$o(Je("runbooks",l.id,1,"md",Me(l,1))),{...l}}async function Sm(t,{md:e,note:n}={}){let o=(await Be()).runbooks.find(i=>i.id===t);if(!o)throw new Error("runbook not found");km(o,"update");let s=bm(e),a=o.version+1;return o.version=a,o.updatedAt=Date.now(),await Ne.mkdir(ma,{recursive:!0}),await Ne.writeFile(Me(o,a),e),o.versions.push({v:a,mdRel:Pe.relative(Pn,Me(o,a)),bytes:s,note:n?String(n).slice(0,200):null,createdAt:Date.now()}),await Bn(),await kr(o,"updated",{version:a,...n?{note:String(n).slice(0,200)}:{}}),ha(o),$o(Je("runbooks",o.id,a,"md",Me(o,a))),{...o}}async function vm(t,e){if(e!=="open"&&e!=="done")throw new Error('status must be "open" or "done"');let r=(await Be()).runbooks.find(o=>o.id===t);if(!r)throw new Error("runbook not found");return km(r,e==="done"?"complete":"reopen"),r.status===e?{...r}:(r.status=e,r.updatedAt=Date.now(),await Bn(),await kr(r,e==="done"?"done":"reopened"),ha(r),{...r})}async function Tm(t){let e=await Be(),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 Ne.rm(Pe.join(Pn,o.mdRel),{force:!0}).catch(()=>{});return await Bn(),r.trackDeletedAt||await kr(r,"deleted"),$o(Qt("runbooks",r.id)),{ok:!0,id:t,name:r.name}}async function Am(t){let n=(await Be()).runbooks.filter(r=>r.trackId===t&&!r.trackDeletedAt);for(let r of n)r.trackDeletedAt=Date.now(),r.updatedAt=Date.now(),await kr(r,"orphaned"),r.cwd=null,ha(r);return n.length&&await Bn(),n.length}async function Em(t){let e=await Be(),n=e.runbooks.filter(r=>r.trackId===t);for(let r of n){await kr(r,"deleted");for(let o of r.versions||[])await Ne.rm(Pe.join(Pn,o.mdRel),{force:!0}).catch(()=>{});$o(Qt("runbooks",r.id))}return n.length&&(e.runbooks=e.runbooks.filter(r=>r.trackId!==t),await Bn()),n.length}async function _m(){if(!pt())return{pushed:0,source:"unconfigured"};let{records:t,source:e}=await Tt("runbooks");if(e!=="plane"||t.length)return{pushed:0,source:e};let n=await Be();for(let r of n.runbooks){await Rt("runbooks",{...r},{trackId:r.trackId}).catch(()=>{});for(let o of r.versions||[])await Je("runbooks",r.id,o.v,"md",Pe.join(Pn,o.mdRel)).catch(()=>{})}return{pushed:n.runbooks.length,source:"plane"}}import Mo from"node:fs/promises";import Um from"node:path";import FT from"node:os";import{randomUUID as Hm}from"node:crypto";import dT from"node:fs/promises";import Im from"node:path";import fT from"node:os";var Rm=()=>Im.join(process.env.BOSTRAT_STATE_DIR||Im.join(process.env.HOME||fT.tmpdir(),".bostrat"),"integrations.json");async function De(){try{return JSON.parse(await dT.readFile(Rm(),"utf8"))||{}}catch{return{}}}var ga=["linked","prOpened","prMerged","create"],Fc=["prompt","auto","off"],pT={create:["prompt","off"]},$m=t=>pT[t]??Fc;function ya(t){let e=t.linear||{},n=e.transitions===!0?"auto":"prompt",r={};for(let o of ga){let s=$m(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 Dn(){let t=await De(),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:ya(t),defaultTeam:n}:null}async function ba(){let t=await De(),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:ya(t)}}async function Uc(){return ya(await De())}async function Om(t){let e=Object.entries(t||{});if(!e.length)throw new Error(`nothing to set \u2014 phases: ${ga.join(", ")}`);for(let[r,o]of e){if(!ga.includes(r))throw new Error(`unknown phase "${r}" \u2014 configurable phases: ${ga.join(", ")}`);let s=$m(r);if(!s.includes(o))throw new Error(s.length<Fc.length?`mode "${o}" not allowed for "${r}" \u2014 ${r} is always confirmed (modes: ${s.join(", ")})`:`unknown mode "${o}" \u2014 modes: ${Fc.join(", ")}`)}let n=await De();return n.ticketFlow={...ya(n),...Object.fromEntries(e)},n.linear&&(delete n.linear.ticketFlow,delete n.linear.transitions),await Mt(Rm(),n,{mode:384}),n.ticketFlow}async function Oo(){let t=(await De()).deepgram||{},e=process.env.DEEPGRAM_API_KEY||t.apiKey;return e?{apiKey:e}:null}async function mT(){let t=(await De()).openai||{},e=process.env.BOSTRAT_OPENAI_API_KEY||t.apiKey;return e?{apiKey:e}:null}var hT={openai:{model:"gpt-realtime-2.1-mini",voice:"marin"}};async function ka(){let e=(await De()).voiceChat||{},n=e.provider||"openai",r=hT[n];if(!r)return null;let o=n==="openai"?await mT():null;return o?{provider:n,model:e.model||r.model,voice:e.voice||r.voice,apiKey:o.apiKey}:null}async function Cm(){let t=(await De()).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 jn(){let t=(await De()).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 Wc={};Od(Wc,{createComment:()=>TT,findIssueUrl:()=>bT,issueStates:()=>_T,mdToAdf:()=>Pm,moveIssue:()=>IT,parseIssueRef:()=>yT,resolveIssue:()=>kT,uploadImage:()=>AT,upsertCard:()=>xT,whoami:()=>Hc});var xr=t=>String(t.baseUrl||"").replace(/\/$/,""),Nm=t=>`Basic ${Buffer.from(`${t.email}:${t.apiToken}`).toString("base64")}`;async function Ln(t,e,n,r){let o=await fetch(`${xr(t)}${n}`,{method:e,headers:{authorization:Nm(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 wT=/\/browse\/([A-Za-z][A-Za-z0-9]{0,9}-\d+)/,gT=/^([A-Z][A-Z0-9]{0,9}-\d+)$/;function yT(t){let e=String(t||"").trim();if(/^https?:\/\//i.test(e)){let r=e.match(wT);return r?r[1].toUpperCase():null}let n=e.match(gT);return n?n[1]:null}function bT(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 kT(t,e){let n;try{n=await Ln(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:`${xr(t)}/browse/${n.key}`,state:n.fields?.status?.name??null}}async function xT(t,e,{url:n,title:r,subtitle:o}){await Ln(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 ST=/(!\[[^\]]*\]\([^)\s]+\)|\*\*[^*\n]+\*\*|`[^`\n]+`|_[^_\n]+_|https?:\/\/[^\s)]+)/g;function vT(t){let e=[],n=0;for(let r of t.matchAll(ST)){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 Pm(t){let e=String(t??"").split(/\n{2,}/).map(n=>n.trim()).filter(Boolean).map(n=>{let r=[];return n.split(`
|
|
80
|
-
`).forEach((o,s)=>{s>0&&r.push({type:"hardBreak"}),r.push(...
|
|
79
|
+
`)}catch{return""}}var Ne=new Map,ST=12e4;async function vT(t){if(t.state==="stopped"||t.state==="error")return!1;let e=t.origin!=="detected",n=e?await xT(t.slug):!0;if(e&&!n){let s=t.state==="starting";return t.state=s?"error":"stopped",s&&(t.error=await pm(t.slug)||"server exited before listening"),Ne.delete(t.id),await Pe(t,t.state,t.error?{error:t.error}:{}),!0}if(await Mo(t.port))return Ne.delete(t.id),t.lastHealthyAt=Date.now(),t.state!=="live"?(t.state="live",t.error=null,await Pe(t,"live"),!0):!1;if(t.state==="starting")return Date.now()-t.createdAt>ST?(t.state="error",t.error=await pm(t.slug)||"never started listening",await Pe(t,"error",{error:t.error}),!0):!1;let o=(Ne.get(t.id)||0)+1;return Ne.set(t.id,o),o>=3&&t.state==="live"?(t.state="unhealthy",!0):!1}var jc=!1;async function Sa(){if(!jc){jc=!0;try{let t=await Be(),e=!1;for(let n of t.views)await vT(n)&&(e=!0);e&&await be()}finally{jc=!1}}}async function va({trackId:t}={}){await Sa();let e=await Be();return(t?e.views.filter(r=>r.trackId===t):e.views).map(r=>({...r}))}async function Sm(t){let n=(await Be()).views.find(r=>r.id===t);return n?{...n}:null}function Gc(t){return ge&&ge.views.find(e=>e.slug===t)||null}async function Vc({trackId:t,name:e,cmd:n,cwd:r,serveCwd:o,port:s,box:a,origin:i="agent",branch:c=null}){if(!t)throw new Error("trackId required");if(typeof e!="string"||!e.trim())throw new Error("name (string) required");if(i!=="detected"&&(typeof n!="string"||!n.trim()))throw new Error("cmd (string) required");if(!r)throw new Error("cwd required");let l=o!=null,u=Wt.resolve(r,o??"."),f=i==="detected"?null:async()=>{await gT(r,u),await yT(u,n)},d=await Be();if(i!=="detected"){let w=Uc(e),y=String(n).trim(),b=d.views.find(S=>S.trackId===t&&(S.slug===w||Uc(S.name)===w||S.cmd===y&&Wt.resolve(S.serveCwd??S.cwd)===u));if(b)return TT(d,b,{cmd:y,box:a,branch:c,serveCwd:l?u:void 0,validate:f})}if(f&&await f(),d.views.filter(w=>w.state==="live"||w.state==="starting").length>=ln)throw new Error(`view cap reached (${ln} live) \u2014 stop one first`);let m={id:nT().slice(0,8),trackId:t,box:a||null,name:e.trim(),slug:bT(d,e),cmd:n?String(n).trim():null,cwd:r,serveCwd:u,branch:c,port:await kT(d,s),origin:i,state:"starting",error:null,createdAt:Date.now(),lastHealthyAt:null};return i!=="detected"&&await Kc(m),d.views.push(m),await be(),await Pe(m,"created",{origin:i}),Fc(t,m.id),i==="detected"&&await Mo(m.port)&&(m.state="live",m.lastHealthyAt=Date.now(),await be(),await Pe(m,"live")),{...m}}async function TT(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 hT(e.serveCwd??e.cwd,s);if((e.state==="live"||e.state==="starting")&&!i&&!c&&await Mo(e.port))return await be(),Fc(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>=ln)throw new Error(`view cap reached (${ln} live) \u2014 stop one first`);return e.origin==="detected"?(await qc(e.port),e.origin="agent"):await Co(e.slug),e.cmd=n,s!==void 0&&(e.serveCwd=s),e.state="starting",e.error=null,e.createdAt=Date.now(),Ne.delete(e.id),await Kc(e),await be(),await Pe(e,"created",{restarted:!0,reused:!0}),Fc(e.trackId,e.id),{...e,reused:!0}}async function vm(t){let n=(await Be()).views.find(r=>r.id===t);if(!n)throw new Error("view not found");return n.origin==="detected"?await qc(n.port):await Co(n.slug),n.state!=="stopped"&&(n.state="stopped",Ne.delete(n.id),await be(),await Pe(n,"stopped")),{...n}}async function Tm(t){let e=await Be(),n=e.views.find(o=>o.id===t);if(!n)throw new Error("view not found");if(!n.cmd)throw new Error("a detected view has no known command \u2014 recreate it as a managed view");if(e.views.filter(o=>o.id!==t&&(o.state==="live"||o.state==="starting")).length>=ln)throw new Error(`view cap reached (${ln} live) \u2014 stop one first`);return await Co(n.slug),n.state="starting",n.error=null,n.createdAt=Date.now(),Ne.delete(n.id),await Kc(n),await be(),await Pe(n,"created",{restarted:!0}),{...n}}async function Am(t){let e=await Be(),n=e.views.findIndex(o=>o.id===t);if(n===-1)throw new Error("view not found");let r=e.views[n];return r.origin==="detected"?await qc(r.port):await Co(r.slug),e.views.splice(n,1),Ne.delete(r.id),await be(),await Pe(r,"deleted"),{ok:!0,id:t,slug:r.slug}}async function Em(t){let e=await Be(),n=e.views.filter(r=>r.trackId===t);for(let r of n)r.origin!=="detected"&&await Co(r.slug),Ne.delete(r.id);return n.length&&(e.views=e.views.filter(r=>r.trackId!==t),await be()),n.length}var AT=/https?:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0)(?::(\d{2,5}))?(?:\/|\b)/g;async function _m(t,e){if(!t?.id||!Array.isArray(e)||!e.length)return[];let n=await Be(),r=n.adoptWatermarks[t.id]??null,o=e.filter(c=>c.role==="assistant"&&c.ts!=null&&(r==null?!1:c.ts>r)),s=e.reduce((c,l)=>l.ts>c?l.ts:c,r??0);if(n.adoptWatermarks[t.id]!==s&&(n.adoptWatermarks[t.id]=s,await be()),!o.length)return[];let a=new Set;for(let c of o)for(let l of String(c.text||"").matchAll(AT)){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(!xm().has(c)&&!n.views.some(l=>l.port===c)&&await Mo(c))try{i.push(await Vc({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 Le from"node:fs/promises";import Fe from"node:path";import ET from"node:os";import{randomUUID as _T}from"node:crypto";var Un=process.env.BOSTRAT_STATE_DIR||Fe.join(process.env.HOME||ET.tmpdir(),".bostrat"),Rm=Fe.join(Un,"runbooks.json"),Ta=Fe.join(Un,"runbooks"),Im=256*1024,IT=[{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 No(t){return IT.filter(({re:e})=>e.test(t)).map(({label:e})=>e)}var De=null,zc=null;async function Ue(){return De||(zc||=(async()=>{let t=!0;try{De=JSON.parse(await Le.readFile(Rm,"utf8"))}catch{De={runbooks:[]},t=!1}finally{zc=null}return De.runbooks||=[],!t&&ht()&&await RT().catch(()=>{}),De})(),zc)}async function Hn(){await Ft(Rm,De)}var Po=t=>Promise.resolve(t).catch(()=>{});function Aa(t){return Po(Nt("runbooks",{...t},{trackId:t.trackId}))}async function RT(){let{records:t,source:e}=await Rt("runbooks");if(!(e!=="plane"||!t.length)){await Le.mkdir(Ta,{recursive:!0});for(let n of t){if(n.deletedAt||!n.data?.id)continue;let r=n.data;for(let o of r.versions||[]){let s=await Gs("runbooks",r.id,o.v,"md");typeof s=="string"&&await Le.writeFile(je(r,o.v),s).catch(()=>{})}De.runbooks.push(r)}De.runbooks.length&&await Hn()}}function Er(t,e,n={}){return R({id:t.trackId,cwd:t.cwd},{type:"runbook.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box||null,provider:t.agent||null,payload:{ts:Date.now(),change:e,runbookId:t.id,name:t.name,status:t.status,...n}}).catch(()=>{})}function je(t,e){return Fe.join(Ta,`${t.id}.v${e}.md`)}function $m(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 Om(t){if(typeof t!="string"||!t.trim())throw new Error("md (markdown string) required");let e=Buffer.byteLength(t,"utf8");if(e>Im)throw new Error(`runbook is ${Math.round(e/1024)} KB \u2014 over the ${Im/1024} KB cap; a doc that size belongs in the repo, not a runbook`);let n=No(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 Mm(t,e){if(t.trackDeletedAt)throw new Error(`runbook is orphaned (its Track was deleted) \u2014 cannot ${e} it`)}async function Ea({trackId:t,status:e}={}){let n=await Ue(),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 Jc(t){let n=(await Ue()).runbooks.find(r=>r.id===t);return n?{...n}:null}async function Cm({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=Om(r),c=await Ue(),l={id:_T().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 Le.mkdir(Ta,{recursive:!0}),await Le.writeFile(je(l,1),r),l.versions.push({v:1,mdRel:Fe.relative(Un,je(l,1)),bytes:i,note:null,createdAt:Date.now()}),c.runbooks.push(l),await Hn(),await Er(l,"created",{version:1}),Aa(l),Po(tn("runbooks",l.id,1,"md",je(l,1))),{...l}}async function Nm(t,{md:e,note:n}={}){let o=(await Ue()).runbooks.find(i=>i.id===t);if(!o)throw new Error("runbook not found");Mm(o,"update");let s=Om(e),a=o.version+1;return o.version=a,o.updatedAt=Date.now(),await Le.mkdir(Ta,{recursive:!0}),await Le.writeFile(je(o,a),e),o.versions.push({v:a,mdRel:Fe.relative(Un,je(o,a)),bytes:s,note:n?String(n).slice(0,200):null,createdAt:Date.now()}),await Hn(),await Er(o,"updated",{version:a,...n?{note:String(n).slice(0,200)}:{}}),Aa(o),Po(tn("runbooks",o.id,a,"md",je(o,a))),{...o}}async function Pm(t,e){if(e!=="open"&&e!=="done")throw new Error('status must be "open" or "done"');let r=(await Ue()).runbooks.find(o=>o.id===t);if(!r)throw new Error("runbook not found");return Mm(r,e==="done"?"complete":"reopen"),r.status===e?{...r}:(r.status=e,r.updatedAt=Date.now(),await Hn(),await Er(r,e==="done"?"done":"reopened"),Aa(r),{...r})}async function Bm(t){let e=await Ue(),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 Le.rm(Fe.join(Un,o.mdRel),{force:!0}).catch(()=>{});return await Hn(),r.trackDeletedAt||await Er(r,"deleted"),Po(re("runbooks",r.id)),{ok:!0,id:t,name:r.name}}async function Dm(t){let n=(await Ue()).runbooks.filter(r=>r.trackId===t&&!r.trackDeletedAt);for(let r of n)r.trackDeletedAt=Date.now(),r.updatedAt=Date.now(),await Er(r,"orphaned"),r.cwd=null,Aa(r);return n.length&&await Hn(),n.length}async function jm(t){let e=await Ue(),n=e.runbooks.filter(r=>r.trackId===t);for(let r of n){await Er(r,"deleted");for(let o of r.versions||[])await Le.rm(Fe.join(Un,o.mdRel),{force:!0}).catch(()=>{});Po(re("runbooks",r.id))}return n.length&&(e.runbooks=e.runbooks.filter(r=>r.trackId!==t),await Hn()),n.length}async function Lm(){if(!ht())return{pushed:0,source:"unconfigured"};let{records:t,source:e}=await Rt("runbooks");if(e!=="plane"||t.length)return{pushed:0,source:e};let n=await Ue();for(let r of n.runbooks){await Nt("runbooks",{...r},{trackId:r.trackId}).catch(()=>{});for(let o of r.versions||[])await tn("runbooks",r.id,o.v,"md",Fe.join(Un,o.mdRel)).catch(()=>{})}return{pushed:n.runbooks.length,source:"plane"}}import jo from"node:fs/promises";import Qm from"node:path";import sA from"node:os";import{randomUUID as th}from"node:crypto";import $T from"node:fs/promises";import Fm from"node:path";import OT from"node:os";var Um=()=>Fm.join(process.env.BOSTRAT_STATE_DIR||Fm.join(process.env.HOME||OT.tmpdir(),".bostrat"),"integrations.json");async function He(){try{return JSON.parse(await $T.readFile(Um(),"utf8"))||{}}catch{return{}}}var _a=["linked","prOpened","prMerged","create"],Xc=["prompt","auto","off"],MT={create:["prompt","off"]},Hm=t=>MT[t]??Xc;function Ia(t){let e=t.linear||{},n=e.transitions===!0?"auto":"prompt",r={};for(let o of _a){let s=Hm(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 Wn(){let t=await He(),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:Ia(t),defaultTeam:n}:null}async function Ra(){let t=await He(),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:Ia(t)}}async function Yc(){return Ia(await He())}async function Wm(t){let e=Object.entries(t||{});if(!e.length)throw new Error(`nothing to set \u2014 phases: ${_a.join(", ")}`);for(let[r,o]of e){if(!_a.includes(r))throw new Error(`unknown phase "${r}" \u2014 configurable phases: ${_a.join(", ")}`);let s=Hm(r);if(!s.includes(o))throw new Error(s.length<Xc.length?`mode "${o}" not allowed for "${r}" \u2014 ${r} is always confirmed (modes: ${s.join(", ")})`:`unknown mode "${o}" \u2014 modes: ${Xc.join(", ")}`)}let n=await He();return n.ticketFlow={...Ia(n),...Object.fromEntries(e)},n.linear&&(delete n.linear.ticketFlow,delete n.linear.transitions),await Ft(Um(),n,{mode:384}),n.ticketFlow}async function Bo(){let t=(await He()).deepgram||{},e=process.env.DEEPGRAM_API_KEY||t.apiKey;return e?{apiKey:e}:null}async function CT(){let t=(await He()).openai||{},e=process.env.BOSTRAT_OPENAI_API_KEY||t.apiKey;return e?{apiKey:e}:null}var NT={openai:{model:"gpt-realtime-2.1-mini",voice:"marin"}};async function $a(){let e=(await He()).voiceChat||{},n=e.provider||"openai",r=NT[n];if(!r)return null;let o=n==="openai"?await CT():null;return o?{provider:n,model:e.model||r.model,voice:e.voice||r.voice,apiKey:o.apiKey}:null}async function qm(){let t=(await He()).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 qn(){let t=(await He()).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 Qc={};Ud(Qc,{createComment:()=>WT,findIssueUrl:()=>jT,issueStates:()=>GT,mdToAdf:()=>Vm,moveIssue:()=>VT,parseIssueRef:()=>DT,resolveIssue:()=>LT,uploadImage:()=>qT,upsertCard:()=>FT,whoami:()=>Zc});var _r=t=>String(t.baseUrl||"").replace(/\/$/,""),Gm=t=>`Basic ${Buffer.from(`${t.email}:${t.apiToken}`).toString("base64")}`;async function Kn(t,e,n,r){let o=await fetch(`${_r(t)}${n}`,{method:e,headers:{authorization:Gm(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 PT=/\/browse\/([A-Za-z][A-Za-z0-9]{0,9}-\d+)/,BT=/^([A-Z][A-Z0-9]{0,9}-\d+)$/;function DT(t){let e=String(t||"").trim();if(/^https?:\/\//i.test(e)){let r=e.match(PT);return r?r[1].toUpperCase():null}let n=e.match(BT);return n?n[1]:null}function jT(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 LT(t,e){let n;try{n=await Kn(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:`${_r(t)}/browse/${n.key}`,state:n.fields?.status?.name??null}}async function FT(t,e,{url:n,title:r,subtitle:o}){await Kn(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 UT=/(!\[[^\]]*\]\([^)\s]+\)|\*\*[^*\n]+\*\*|`[^`\n]+`|_[^_\n]+_|https?:\/\/[^\s)]+)/g;function HT(t){let e=[],n=0;for(let r of t.matchAll(UT)){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 Vm(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(...HT(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 WT(t,e,n){let r=await Kn(t,"POST",`/rest/api/3/issue/${encodeURIComponent(e.key)}/comment`,{body:Vm(n)});return r?.id?`${_r(t)}/browse/${e.key}?focusedCommentId=${r.id}`:null}async function Zc(t){let e=await Kn(t,"GET","/rest/api/3/myself"),n=null;try{n=new URL(_r(t)).host}catch{n=t.baseUrl??null}return{user:e?.displayName??e?.emailAddress??null,org:n}}async function qT(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(`${_r(t)}/rest/api/3/issue/${encodeURIComponent(e)}/attachments`,{method:"POST",headers:{authorization:Gm(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??`${_r(t)}/rest/api/3/attachment/content/${c.id}`}var KT={new:"unstarted",indeterminate:"started",done:"completed"},Km=t=>KT[t?.key]??"started";async function GT(t,e){let n=encodeURIComponent(e.key),[r,o]=await Promise.all([Kn(t,"GET",`/rest/api/3/issue/${n}?fields=status`),Kn(t,"GET",`/rest/api/3/issue/${n}/transitions`)]),s=r?.fields?.status??null,a=s?{id:`status:${s.id}`,name:s.name,type:Km(s.statusCategory)}:null,i=(o?.transitions??[]).map((c,l)=>({id:c.id,name:c.to?.name??c.name,type:Km(c.to?.statusCategory),position:l}));return{current:a,states:i}}async function VT(t,e,n){await Kn(t,"POST",`/rest/api/3/issue/${encodeURIComponent(e.key)}/transitions`,{transition:{id:String(n)}})}var rl={};Ud(rl,{createComment:()=>eA,createIssue:()=>nl,createProject:()=>el,findIssueUrl:()=>ZT,issueStates:()=>rA,moveIssue:()=>oA,parseIssueRef:()=>YT,resolveIssue:()=>QT,teams:()=>Ir,uploadImage:()=>nA,upsertCard:()=>tA,whoami:()=>tl});var zT=()=>process.env.BOSTRAT_LINEAR_API_URL||"https://api.linear.app/graphql";async function ke(t,e,n={}){let r=await fetch(zT(),{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 JT=/linear\.app\/[^/\s]+\/issue\/([A-Za-z][A-Za-z0-9]{0,9}-\d+)/,XT=/^([A-Z][A-Z0-9]{0,9}-\d+)$/;function YT(t){let e=String(t||"").trim(),n=e.match(JT);if(n)return n[1].toUpperCase();let r=e.match(XT);return r?r[1]:null}function ZT(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 QT(t,e){let r=(await ke(t,"query($id: String!) { issue(id: $id) { id identifier title url state { name } team { key } } }",{id:e})).issue;if(!r)throw new Error(`linear: issue "${e}" not found`);return{issueId:r.id,key:r.identifier,title:r.title,url:r.url,state:r.state?.name??null,team:r.team?.key??null}}async function tA(t,e,{url:n,title:r,subtitle:o}){let s=await ke(t,"mutation($input: AttachmentCreateInput!) { attachmentCreate(input: $input) { success attachment { id } } }",{input:{issueId:e.issueId,url:n,title:r,subtitle:o}});if(!s.attachmentCreate?.success)throw new Error("linear: attachmentCreate did not succeed");return s.attachmentCreate.attachment?.id??null}async function eA(t,e,n){let r=await ke(t,"mutation($input: CommentCreateInput!) { commentCreate(input: $input) { success comment { url } } }",{input:{issueId:e.issueId,body:n}});if(!r.commentCreate?.success)throw new Error("linear: commentCreate did not succeed");return r.commentCreate.comment?.url??null}async function tl(t){let e=await ke(t,"query { viewer { name email } organization { name urlKey } }");return{user:e.viewer?.name??e.viewer?.email??null,org:e.organization?.name??e.organization?.urlKey??null}}async function nA(t,{filename:e,contentType:n,buffer:r}){let o=await ke(t,`mutation($contentType: String!, $filename: String!, $size: Int!) {
|
|
81
81
|
fileUpload(contentType: $contentType, filename: $filename, size: $size) {
|
|
82
82
|
success
|
|
83
83
|
uploadFile { uploadUrl assetUrl headers { key value } }
|
|
84
84
|
}
|
|
85
|
-
}`,{contentType:n,filename:e,size:r.length}),s=o.fileUpload?.uploadFile;if(!o.fileUpload?.success||!s?.uploadUrl)throw new Error("linear: fileUpload did not succeed");let a={"content-type":n};for(let c of s.headers||[])a[c.key]=c.value;let i=await fetch(s.uploadUrl,{method:"PUT",headers:a,body:r});if(!i.ok)throw new Error(`linear: asset PUT failed (HTTP ${i.status})`);return s.assetUrl}async function jT(t,e){let n=await we(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 LT(t,e,n){if(!(await we(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 Sr(t){return((await we(t,"query { teams(first: 100) { nodes { id key name } } }")).teams?.nodes??[]).map(n=>({id:n.id,key:n.key,name:n.name}))}var Bm=255;async function Kc(t,{teamId:e,name:n,description:r}){let o=r||null,s=!!o&&o.length>Bm,a=s?`${o.slice(0,Bm-1).trimEnd()}\u2026`:o,i=await we(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 Gc(t,{teamId:e,title:n,description:r,projectId:o,priority:s}){let a=await we(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 Wm={linear:{api:Vc,config:Dn},jira:{api:Wc,config:ba}};async function tl(){let t=[];for(let[e,{api:n,config:r}]of Object.entries(Wm)){let o=await r();o&&t.push({name:e,api:n,cfg:o})}return t}async function Fn(t){let e=Wm[t?.provider];if(!e)return null;let n=await e.config();return n?{name:t.provider,api:e.api,cfg:n}:null}function el(t,e){if(!t||t.name)return t;let n=e.find(r=>r.id===t.id);return n?{...t,name:n.name}:t}var qm=()=>process.env.BOSTRAT_STATE_DIR||Um.join(process.env.HOME||FT.tmpdir(),".bostrat"),Yc=()=>Um.join(qm(),"tickets.json"),Km=()=>(process.env.BOSTRAT_PUBLIC_URL||"http://bostrat.globopi").replace(/\/$/,""),xa=t=>`${Km()}/track/${t}`,on=null,zc=null,Jc=Promise.resolve();async function Po(){return on||(zc??=Mo.readFile(Yc(),"utf8").then(t=>({tickets:JSON.parse(t)?.tickets??{},existed:!0})).catch(()=>({tickets:{},existed:!1})).then(async({tickets:t,existed:e})=>{if(on=t,!e&&pt()){let{records:n,source:r}=await Tt("tickets").catch(()=>({records:[],source:"error"}));if(r==="plane"){for(let o of n){if(o.deletedAt||!o.data?.id)continue;let{id:s,updatedAt:a,...i}=o.data;on[s]=i}Object.keys(on).length&&await Zc().catch(()=>{})}}return on}).finally(()=>zc=null),zc)}async function Zc(){let t=JSON.stringify({tickets:on},null,2),e=`${Yc()}.${process.pid}.tmp`;await Mo.mkdir(qm(),{recursive:!0}),await Mo.writeFile(e,t),await Mo.rename(e,Yc())}async function sn(t=null){if(Jc=Jc.then(Zc,Zc),await Jc.catch(()=>{}),!t)return;let e=on?.[t];e?Rt("tickets",{id:t,updatedAt:Date.now(),...e},{trackId:t}).catch(()=>{}):Qt("tickets",t).catch(()=>{})}async function ge(t){return(await Po())[t]??null}async function Sa(){return{...await Po()}}async function Gm(){return(await tl()).length>0}async function vr(t,e,{source:n="api"}={}){let r=await tl();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 Po())[t.id]=c,await sn(t.id),await R({id:t.id,cwd:t.cwd},{type:"ticket.linked",actor:"user",source:"orchestrator",payload:{ts:Date.now(),provider:c.provider,key:c.key,url:c.url,title:c.title,linkSource:n}}).catch(()=>{}),await No(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 Co(c,`**Bostrat** \u2014 Track **${c.track.name}** linked${l.length?` (${l.join(", ")})`:""}.`),await Jm(t.id,c,"linked"),c}async function nl(t){let e=await Po();return e[t]?(delete e[t],await sn(t),!0):!1}var Qc=t=>`$${t>=100?Math.round(t):t.toFixed(2)}`;async function Vm(t,e){if(!t.track?.cwd)return null;let n=ue(await D({id:e,cwd:t.track.cwd}));return!n.totalUsd||n.totalUsd<.01?null:n}function UT(t){let e=Object.entries(t.byProvider).sort((r,o)=>o[1].usd-r[1].usd).map(([r,o])=>`${r} ${Qc(o.usd)}`).join(" \xB7 "),n=Object.keys(t.byBox).length;return`${Qc(t.totalUsd)}${e?` (${e})`:""}${n>1?` across ${n} boxes`:""}`}function HT(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(Qc(e.totalUsd)),n.join(" \xB7 ")||"linked"}async function No(t,e){let n=await Fn(e);if(!n)return;let r=await Vm(e,t).catch(()=>null);await n.api.upsertCard(n.cfg,e,{url:xa(t),title:`Bostrat Track: ${e.track.name}`,subtitle:HT(e,r)})}function va(t){let e=new URL(Km());return`${e.protocol}//${t}.${e.host}/`}var WT={linked:"linked",opened:"prOpened",merged:"prMerged"};function qT(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 zm(t,e,{from:n,to:r,mode:o,phase:s=null,actor:a="user"}){e.state=r,await sn(t),await R({id:t,cwd:e.track?.cwd},{type:"ticket.state.changed",actor:a,source:"orchestrator",payload:{ts:Date.now(),key:e.key,url:e.url,from:n??null,to:r,mode:o,...s?{phase:s}:{}}}).catch(()=>{})}async function Jm(t,e,n){let r=await Fn(e);if(!r)return;let o=r.cfg.ticketFlow?.[WT[n]]??"prompt";if(o==="off")return;let{current:s,states:a}=await r.api.issueStates(r.cfg,e),i=el(s,a),c=qT(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 zm(t,e,{from:l,to:c.name,mode:"auto",phase:n,actor:"system"});return}await R({id:t,cwd:e.track?.cwd},{type:"ticket.move.offered",actor:"system",source:"orchestrator",payload:{ts:Date.now(),offerId:Hm(),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 Xm(t){let e=await ge(t);if(!e)throw new Error("no ticket linked to this Track");let n=await Fn(e);if(!n)throw new Error("no ticketing integration configured");let{current:r,states:o}=await n.api.issueStates(n.cfg,e),s=el(r,o);return s?.name&&e.state!==s.name&&(e.state=s.name,await sn(t)),{key:e.key,url:e.url,current:s?{id:s.id,name:s.name??null,type:s.type??null}:null,states:[...o].sort((a,i)=>a.position-i.position).map(({id:a,name:i,type:c,position:l})=>({id:a,name:i,type:c,position:l}))}}async function rl(t,e,{mode:n="manual"}={}){if(!e)throw new Error("stateId required");let r=await ge(t);if(!r)throw new Error("no ticket linked to this Track");let o=await Fn(r);if(!o)throw new Error("no ticketing integration configured");let{current:s,states:a}=await o.api.issueStates(o.cfg,r),i=el(s,a),c=a.find(u=>u.id===e);if(!c)throw new Error("unknown state for this ticket's team");let l=i?.name??r.state??null;return i?.id===c.id?(r.state!==c.name&&(r.state=c.name,await sn(t)),{key:r.key,from:l,to:c.name,moved:!1}):(await o.api.moveIssue(o.cfg,r,c.id),await zm(t,r,{from:l,to:c.name,mode:n}),{key:r.key,from:l,to:c.name,moved:!0})}async function Ym(t,e){let n=await ge(t);if(!n)throw new Error("no ticket linked to this Track");let r=await Fn(n);if(!r)throw new Error("no ticketing integration configured");let o=String(e||"").trim();if(!o)throw new Error("comment body required");let s=await r.api.createComment(r.cfg,n,o);return await R({id:t,cwd:n.track?.cwd},{type:"ticket.commented",actor:"user",source:"orchestrator",payload:{ts:Date.now(),key:n.key,url:s??n.url,excerpt:o.length>280?`${o.slice(0,279)}\u2026`:o}}).catch(()=>{}),{key:n.key,url:s??n.url}}async function Zm(t,e,n){if(!e)throw new Error("offerId required");let r=await D({id:t.id,cwd:t.cwd}),o=r.find(a=>a.type==="ticket.move.offered"&&a.payload?.offerId===e);if(!o)throw new Error("unknown offer");if(r.some(a=>a.type==="ticket.move.answered"&&a.payload?.offerId===e))throw new Error("offer already answered");let s=null;return n&&(s=await rl(t.id,o.payload.to?.id,{mode:"offer"})),await R({id:t.id,cwd:t.cwd},{type:"ticket.move.answered",actor:"user",source:"orchestrator",payload:{ts:Date.now(),offerId:e,action:n?"moved":"dismiss",...s?{from:s.from,to:s.to}:{}}}),{offerId:e,action:n?"moved":"dismiss",...s??{}}}var Dm=50;async function Qm(){let t=await Dn();if(!t)throw new Error("no ticketing integration configured");return Sr(t)}function KT(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>Dm)throw new Error(`too many issues (${n.length}); cap is ${Dm} per proposal`);return{epic:r,issues:n,team:e.team?String(e.team).trim():null,linkCurrentTrack:e.linkCurrentTrack===!0}}async function GT(t,e,n,r){let o=r??await Sr(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 ge(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 th(t,e){let n=await Dn();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=KT(e),o=await Sr(n),s=await GT(n,r,t,o),a=s?{id:s.id,key:s.key,name:s.name}:null,i=Hm();return await R({id:t.id,cwd:t.cwd},{type:"ticket.create.offered",actor:"system",source:"orchestrator",payload:{ts:Date.now(),proposalId:i,provider:"linear",epic:r.epic,issues:r.issues,team:a,needsTeam:!s,linkCurrentTrack:r.linkCurrentTrack}}),{proposalId:i,epic:r.epic,issues:r.issues,team:a,needsTeam:!s}}async function eh(t,e,n,{teamId:r=null}={}){if(!e)throw new Error("proposalId required");let o=await D({id:t.id,cwd:t.cwd}),s=o.find(m=>m.type==="ticket.create.offered"&&m.payload?.proposalId===e);if(!s)throw new Error("unknown proposal");if(o.some(m=>m.type==="ticket.create.answered"&&m.payload?.proposalId===e))throw new Error("proposal already answered");if(!n)return await R({id:t.id,cwd:t.cwd},{type:"ticket.create.answered",actor:"user",source:"orchestrator",payload:{ts:Date.now(),proposalId:e,action:"dismiss"}}),{proposalId:e,action:"dismiss"};let a=await Dn();if(!a)throw new Error("no ticketing integration configured");let i=s.payload,c=i.team??null;if(!c&&r){let m=(await Sr(a)).find(g=>g.id===r);if(!m)throw new Error("unknown team");c={id:m.id,key:m.key,name:m.name}}if(!c)throw new Error("a team is required to create \u2014 pick one");let l=[],u=[],f=null;if(i.epic){let m=await Kc(a,{teamId:c.id,name:i.epic.name,description:i.epic.description});f=m.id,l.push({kind:"epic",title:m.name,url:m.url})}for(let m of i.issues||[])try{let g=await Gc(a,{teamId:c.id,title:m.title,description:m.description,projectId:f,priority:m.priority});l.push({kind:"issue",key:g.key,title:g.title,url:g.url})}catch(g){u.push({title:m.title,message:g?.message||String(g)})}await R({id:t.id,cwd:t.cwd},{type:"ticket.created",actor:"user",source:"orchestrator",payload:{ts:Date.now(),proposalId:e,team:{key:c.key,name:c.name},items:l,failed:u}}),await R({id:t.id,cwd:t.cwd},{type:"ticket.create.answered",actor:"user",source:"orchestrator",payload:{ts:Date.now(),proposalId:e,action:"created",createdCount:l.length,failedCount:u.length}});let d=l.filter(m=>m.kind==="issue"),p=null;return i.linkCurrentTrack&&d.length===1&&!await ge(t.id)&&(p=await vr(t,d[0].key,{source:"created"}).catch(()=>null)),{proposalId:e,action:"created",created:l,failed:u,linked:p?p.key:null}}async function Co(t,e){let n=await Fn(t);n&&await n.api.createComment(n.cfg,t,e)}async function VT(t,e,n){let r=await Fn(t);if(!r)return null;let o=await nn(e);if(!o)return null;let s=Ie(o,n);if(!s)return null;let a=await Mo.readFile(Ht(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 jm=()=>Number(process.env.BOSTRAT_TICKET_COST_SYNC_MS??12e4),Xc=new Map;function zT(t,e){if(jm()<=0)return No(t,e);if(Xc.has(t))return;let n=setTimeout(()=>{Xc.delete(t),ge(t).then(r=>r?No(t,r):null).catch(()=>{})},jm());n.unref?.(),Xc.set(t,n)}var Lm=new Map;async function Fm(t,e,n){let r=Lm.get(t)??0;Date.now()-r<600*1e3||(Lm.set(t,Date.now()),await R({id:t,cwd:e},{type:"ticket.sync.failed",actor:"system",source:"orchestrator",payload:{ts:Date.now(),message:n?.message||String(n)}}).catch(()=>{}))}async function JT(t,{resolveTrack:e=null}={}){let n=await ge(t.trackId),r=t.payload||{};if(!n){if(t.type!=="turn.user"||t.actor!=="user")return;let o=null;for(let a of await tl())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 vr(s,o,{source:"chat"})}catch(a){await Fm(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 sn(t.trackId))}try{switch(t.type){case"handoff.done":n.track.leadAgent=r.toAgent??n.track.leadAgent,await sn(t.trackId),await No(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 sn(t.trackId),await No(t.trackId,n),r.change==="opened"||r.change==="merged"){let o=r.change==="merged"?await Vm(n,t.trackId).catch(()=>null):null;await Co(n,`**Bostrat** \u2014 PR ${r.change} for **${n.track.name}**${r.url?`: ${r.url}`:r.number?` (#${r.number})`:""}`+(o?`
|
|
86
|
-
|
|
87
|
-
Agent cost for this Track: ${
|
|
88
|
-
|
|
89
|
-
`)}else if(r.change==="picked"){let o=(r.siblings??[]).map(s=>`**${s.name}**`).join(", ");await
|
|
90
|
-
_(runs on the Track's branch; reachable on the operator's private network only)_`);break}case"cost.recorded":await zT(t.trackId,n);break;default:break}}catch(o){let s=e?await e(t.trackId).catch(()=>null):null;await Fm(t.trackId,s?.cwd??null,o)}}function nh({resolveTrack:t}){Jt.on("event",e=>{JT(e,{resolveTrack:t}).catch(()=>{})})}async function rh(){if(!pt())return{pushed:0,source:"unconfigured"};let{records:t,source:e}=await Tt("tickets");if(e!=="plane"||t.length)return{pushed:0,source:e};let n=await Po(),r=0;for(let[o,s]of Object.entries(n))await Rt("tickets",{id:o,updatedAt:Date.now(),...s},{trackId:o}).catch(()=>{}),r++;return{pushed:r,source:"plane"}}import XT from"node:crypto";import Ta from"node:fs/promises";import oh from"node:path";import YT from"node:os";var sh=process.env.BOSTRAT_STATE_DIR||oh.join(process.env.HOME||YT.tmpdir(),".bostrat"),al=oh.join(sh,"pr-links.json"),H=(t,e)=>`${t}#${Number(e)}`,ah=t=>{let e=t.replace(/[^A-Za-z0-9._-]+/g,"-").replace(/^[^A-Za-z0-9]+/,"").slice(0,55),n=XT.createHash("sha256").update(t).digest("hex").slice(0,8);return`${e||"pr"}-${n}`},an=null,ol=null,sl=Promise.resolve();async function gt(){return an||(ol??=Ta.readFile(al,"utf8").then(t=>({links:JSON.parse(t)?.links??{},existed:!0})).catch(()=>({links:{},existed:!1})).then(async({links:t,existed:e})=>{if(an=t,!e&&pt()){let{records:n,source:r}=await Tt("prlinks").catch(()=>({records:[],source:"error"}));if(r==="plane"){for(let o of n){if(o.deletedAt||!o.data?.repo||o.data?.number==null)continue;let{id:s,updatedAt:a,...i}=o.data;an[H(i.repo,i.number)]=i}Object.keys(an).length&&await il().catch(()=>{})}}return an}).finally(()=>ol=null),ol)}async function il(){let t=JSON.stringify({links:an},null,2),e=`${al}.${process.pid}.tmp`;await Ta.mkdir(sh,{recursive:!0}),await Ta.writeFile(e,t),await Ta.rename(e,al)}async function vt(t){sl=sl.then(il,il),await sl.catch(()=>{});let e=an?.[t];e&&Rt("prlinks",{id:ah(t),updatedAt:Date.now(),...e},{trackId:e.trackId??null}).catch(()=>{})}var ZT=new Set(["api","observed","reconciled"]);async function Wt({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 gt(),c=H(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:ZT.has(a),firstSeen:Date.now()},await vt(H(t,e)),{link:i[c],created:!0})}async function Bo(t,e){if(!t||e==null)return null;let n=(await gt())[H(t,e)];return!n||n.announced?null:(n.announced=!0,await vt(H(t,e)),n)}async function $t(t,e){return!t||e==null?null:(await gt())[H(t,e)]??null}async function cn(t,e,n){if(!t||e==null)return null;let r=(await gt())[H(t,e)];return r?(r.state=n,r.resolvedAt=Date.now(),delete r.armedAt,delete r.autoMerge,delete r.archiveOnMerge,delete r.notifyOnMerge,delete r.notifyPending,await vt(H(t,e)),r):null}async function ih(t,e,{headSha:n=null,trackId:r=null}={}){if(!t||e==null)return null;let o=(await gt())[H(t,e)];return o?(o.ciFix={delegatedAt:Date.now(),headSha:n,trackId:r},await vt(H(t,e)),o):null}async function ch(t,e,{headSha:n=null,trackId:r=null}={}){if(!t||e==null)return null;let o=(await gt())[H(t,e)];return o?(o.reconcile={delegatedAt:Date.now(),headSha:n,trackId:r},await vt(H(t,e)),o):null}async function Aa(t,e,{headSha:n=null}={}){if(!t||e==null)return null;let r=(await gt())[H(t,e)];return!r||!r.ciFix?null:(r.ciFixAwait={since:Date.now(),trackId:r.ciFix.trackId??null,headSha:n},delete r.ciFix,await vt(H(t,e)),r)}async function Ea(t,e){if(!t||e==null)return null;let n=(await gt())[H(t,e)];return!n||!n.ciFixAwait?null:(delete n.ciFixAwait,await vt(H(t,e)),n)}async function _a(t,e,{headSha:n=null}={}){if(!t||e==null)return null;let r=(await gt())[H(t,e)];return!r||!r.reconcile?null:(r.reconcileAwait={since:Date.now(),trackId:r.reconcile.trackId??null,headSha:n},delete r.reconcile,await vt(H(t,e)),r)}async function Ia(t,e){if(!t||e==null)return null;let n=(await gt())[H(t,e)];return!n||!n.reconcileAwait?null:(delete n.reconcileAwait,await vt(H(t,e)),n)}async function lh(t,e,{at:n=Date.now()}={}){if(!t||e==null)return null;let r=(await gt())[H(t,e)];return r?(r.baseFoldAt=n,await vt(H(t,e)),r):null}async function uh(t,e){if(!t||e==null)return null;let n=(await gt())[H(t,e)];return!n||!n.baseFoldAt?null:(delete n.baseFoldAt,await vt(H(t,e)),n)}async function dh(t,e,n){if(!t||e==null||!n)return null;let r=(await gt())[H(t,e)];return r?(r.lastHeadSha=n,await vt(H(t,e)),r):null}async function Do(t,e,{merge:n=null}={}){if(!t||e==null)return null;let r=(await gt())[H(t,e)];return r?(r.armedAt=Date.now(),n&&(r.autoMerge={method:n.method||"merge",armedAt:r.armedAt}),await vt(H(t,e)),r):null}async function Tr(t,e,n=!0){if(!t||e==null)return null;let r=(await gt())[H(t,e)];return r?(n?r.archiveOnMerge=!0:delete r.archiveOnMerge,await vt(H(t,e)),r):null}async function Ra(t,e,n=!0){if(!t||e==null)return null;let r=(await gt())[H(t,e)];return r?(n?r.notifyOnMerge=!0:delete r.notifyOnMerge,await vt(H(t,e)),r):null}async function fh(t,e,n){if(!t||e==null)return null;let r=(await gt())[H(t,e)];if(!r)return null;r.notifyOnMerge=!0;let o=new Map((r.notifyPending||[]).map(s=>[`${s.repo}#${s.number}`,s]));for(let s of n||[])s&&s.number!=null&&o.set(`${s.repo}#${s.number}`,s);return r.notifyPending=[...o.values()],await vt(H(t,e)),r}async function jo(t,e){if(!t||e==null)return null;let n=(await gt())[H(t,e)];return!n||!n.armedAt&&!n.autoMerge?null:(delete n.armedAt,delete n.autoMerge,delete n.archiveOnMerge,delete n.notifyOnMerge,delete n.notifyPending,await vt(H(t,e)),n)}var QT=new Set(["merged","closed","lost"]);async function ph(){return Object.values(await gt()).filter(t=>!QT.has(t.state))}async function ye(){return Object.values(await gt()).sort((t,e)=>e.firstSeen-t.firstSeen)}async function mh(){if(!pt())return{pushed:0,source:"unconfigured"};let{records:t,source:e}=await Tt("prlinks");if(e!=="plane"||t.length)return{pushed:0,source:e};let n=await gt(),r=0;for(let[o,s]of Object.entries(n))await Rt("prlinks",{id:ah(o),updatedAt:Date.now(),...s},{trackId:s.trackId??null}).catch(()=>{}),r++;return{pushed:r,source:"plane"}}var wh=t=>Math.round(t*100)/100;function tA(t){return!t||t.kind==="setup"?[]:Dt(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():hh.basename(e.base)}))}function gh(t){let e=new Map;for(let n of t||[])for(let r of tA(n)){let o=e.get(r.key);o||e.set(r.key,o={...r,tracks:[]}),o.tracks.push(n)}return e}function $a(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 cl=(t,e)=>`${t} ${e}${t===1?"":"s"}`;function eA(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 ll(t,e=[]){let n=[],r=t?.events||[],o=new Map,s=0;for(let u of e){if(u.type!=="pr.state.changed")continue;let f=u.payload||{};!f.repo||f.number==null||(f.change==="merged"?o.set(`${f.repo}#${f.number}`,f):f.change==="opened"&&s++)}if(o.size){let u=[...o.keys()].map(f=>{let[d,p]=f.split("#");return`${d.split("/").pop()}#${p}`});n.push(`Merged ${u.slice(0,4).join(", ")}${u.length>4?` +${u.length-4}`:""}`)}else s&&n.push(cl(s,"PR")+" opened");let a=Object.keys(t?.subtasks||{}).length||r.filter(u=>u.type==="subtask-delegated").length;a&&n.push(cl(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(`${cl(i.length,"handoff")} ${u}${i.length>3?"\u2026":""}`)}let c=ue(e);c.totalUsd>=.01&&n.push(`$${c.totalUsd.toFixed(2)}`);let l=Number.isFinite(t?.archivedAt)&&Number.isFinite(t?.createdAt)?eA(t.archivedAt-t.createdAt):null;return n.length&&l&&n.push(l),n.length?n.join(" \xB7 "):null}function nA(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 yh(t,e,n){let r=new Set(t.tracks.map(d=>d.id)),o=0,s=0,a=0,i=null,c=null,l=0;for(let d of t.tracks){d.archivedAt?s++:o++,d.beat?.text&&a++;let p=$a(d);p&&(!i||p>i)&&(i=p),d.createdAt&&(!c||d.createdAt<c)&&(c=d.createdAt),l+=e.get(d.id)||0}let u=0,f=0;for(let d of n)r.has(d.trackId)&&(t.slug&&d.repo!==t.slug||t.slug&&(d.state==="merged"?f++:d.state!=="closed"&&u++));return{key:t.key,slug:t.slug,base:t.base,name:t.name,tracks:{total:t.tracks.length,live:o,archived:s,withBeat:a},prs:{open:u,merged:f},usd:wh(l),firstActivityAt:c,lastActivityAt:i}}async function bh(t){let e=gh(t),n=await ye().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 D(i).catch(()=>[]);r.set(i.id,ue(c).totalUsd)}let s=[...e.values()].map(a=>yh(a,r,n)).sort((a,i)=>(i.lastActivityAt??0)-(a.lastActivityAt??0));return{generatedAt:Date.now(),arcs:s}}async function kh(t,e){let r=gh(t).get(e);if(!r)return null;let[o,s,a,i,c]=await Promise.all([ye().catch(()=>[]),Nn().catch(()=>[]),pa().catch(()=>[]),wa().catch(()=>[]),Sa().catch(()=>({}))]),l=new Map,u=[],f=(d,p)=>($a(p)||p.createdAt||0)-($a(d)||d.createdAt||0);for(let d of[...r.tracks].sort(f)){let p=await D(d).catch(()=>[]),m=ue(p);l.set(d.id,m.totalUsd);let g=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:nA(d),handoffs:(d.events||[]).filter(y=>y.type==="lead-handoff").length,subtasks:Object.keys(d.subtasks||{}).length,repos:Dt(d.workspace).map(y=>y.repo||hh.basename(y.base||"")),prs:o.filter(y=>y.trackId===d.id&&(!r.slug||y.repo===r.slug)).map(y=>({repo:y.repo,number:y.number,state:y.state??null,title:y.title??null,url:y.url??null})),mocks:s.filter(y=>y.trackId===d.id).length,views:a.filter(y=>y.trackId===d.id).length,runbooks:i.filter(y=>y.trackId===d.id).map(y=>({name:y.name,status:y.status})),ticket:g?{provider:g.provider,key:g.key,url:g.url??null}:null,usd:wh(m.totalUsd),lastActivityAt:$a(d)})}return{...yh(r,l,o),generatedAt:Date.now(),segments:u}}import{execFile as rA}from"node:child_process";import{promisify as oA}from"node:util";import{randomUUID as sA}from"node:crypto";var Ar=oA(rA),Oa=t=>new Promise(e=>setTimeout(e,t)),aA=Number(process.env.BOSTRAT_SWAP_EXIT_TIMEOUT_MS)||15e3,xh=Number(process.env.BOSTRAT_SWAP_SHELL_TIMEOUT_MS)||8e3,iA=250,cA=400,Sh={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 ul(t,e){let n=Date.now()+e;for(;;){if(await ur(t))return!0;if(Date.now()>n)return!1;await Oa(iA)}}async function lA(t,e){let n=Sh[e]||Sh.default;n.escape&&(await bt(t,["Escape"],{kind:"swap-exit"}),await Oa(150)),n.wipe&&await qs(t,400,{kind:"swap-exit"}),await Ks(t,n.text,{perCharDelayMs:90,kind:"swap-exit"});let r=_e(e).submitDelayMs||0;await Oa(Math.max(r,250)),await bt(t,["Enter"],{kind:"swap-exit"})}async function uA(t,e){try{let{stdout:n}=await Ar(N,P("show-environment","-t",t,e));return!String(n||"").trimStart().startsWith("-")}catch{return!1}}async function vh(t){await Ar(N,P("respawn-pane","-k","-t",t))}async function dl(t,e={}){if(!wt(t))throw new Error("invalid session name");let n=String(e.agentCmd||"").trim();if(!n)throw new Error("agentCmd is required");let r=await Z(t);if(!r)throw new Error(`session "${t}" not found`);if(!r.cwd)throw new Error(`can't resolve cwd for "${t}"`);let o=r.agent||"agent",s=e.agent||Xe(n),a=Number(e.exitTimeoutMs)||aA;await U({flow:"swap",phase:"start",sessionName:t,from:o,to:s,cwd:r.cwd});let i=!1;if(!await ur(t)&&(await lA(t,o),!await ul(t,a)&&(i=!0,await U({flow:"swap",phase:"respawn-forced",sessionName:t,from:o}),await vh(t),!await ul(t,xh))))throw new Error(`pane shell did not come back after respawn-pane in "${t}"`);let c=s==="claude"&&In(n),l=c?Rs():null,u=await uA(t,"ANTHROPIC_API_KEY"),f=!1;try{l?(await Ar(N,P("set-environment","-t",t,"ANTHROPIC_API_KEY",l)),f=!0):u&&(await Ar(N,P("set-environment","-u","-t",t,"ANTHROPIC_API_KEY")),f=!0)}catch(y){throw l?Fs(y,l):y}if(f&&!i&&(await vh(t),!await ul(t,xh)))throw new Error(`pane shell did not come back after env respawn in "${t}"`);let d=!!e.resumeSessionId,p=e.resumeSessionId||sA(),m=d?n:oc(n,p),g=d?e.pinned===!0:/(^|\s)--session-id(\s|=)/.test(m);return c&&await Ge(r.cwd),s==="codex"&&await rr(r.cwd),s==="cursor"&&await or(r.cwd),await On(t),await Ar(N,P("set-option","-t",t,"@bostrat-sid",p)),await Ar(N,P("set-option","-t",t,"@bostrat-agent",s)),await Oa(cA),await St(t,m,{kind:"swap-launch"}),await U({flow:"swap",phase:"done",sessionName:t,from:o,to:s,sid:p,forced:i,envRespawned:f}),{name:t,cwd:r.cwd,sessionId:p,agent:s,pinned:g,forced:i,envRespawned:f}}import WA from"node:fs/promises";function dA(t){let e=[],n="",r=null;for(let o=0;o<t.length;o++){let s=t[o];if(r){n+=s,s===r&&t[o-1]!=="\\"&&(r=null);continue}if(s==="'"||s==='"'||s==="`"){r=s,n+=s;continue}if(s==="&"&&t[o+1]==="&"){e.push(n),n="",o++;continue}if(s==="|"&&t[o+1]==="|"){e.push(n),n="",o++;continue}if(s==="|"||s===";"||s===`
|
|
91
|
-
`){e.push(n),n="";continue}n+=s}return e.push(n),e.map(o=>o.trim()).filter(Boolean)}function
|
|
92
|
-
`,n);i!==-1&&(e=e.slice(0,i))}let r
|
|
93
|
-
`).map(n=>n.trim()).find(Boolean)||t;return e.length>88?`${e.slice(0,88).trimEnd()}\u2026`:e}function
|
|
94
|
-
`)){let a=s.trim();if(!a||a[0]!=="{")continue;let i;try{i=JSON.parse(a)}catch{continue}let c=i.message;if(!c)continue;let l=[];if(
|
|
95
|
-
`)){let c=i.trim();if(!c||c[0]!=="{")continue;let l;try{l=JSON.parse(c)}catch{continue}if(l.isSidechain)continue;let u=l.message?.content;if(!Array.isArray(u))continue;let f=l.timestamp?Date.parse(l.timestamp):null;for(let d of u)if(!(!d||typeof d!="object"))if(d.type==="tool_use"&&d.id){if(d.name==="Agent"){let p=d.input||{};o.set(d.id,{id:d.id,kind:"agent",name:String(p.description||"").trim()||null,detail:String(p.subagent_type||"").trim()||null,ts:f,proven:!1})}else if(d.name==="Bash"&&d.input?.run_in_background===!0){let p=String(d.input.command||"").trim();o.set(d.id,{id:d.id,kind:"command",name:String(d.input.description||"").trim()||(p?
|
|
96
|
-
`).trim():"",
|
|
97
|
-
`);if(i!==-1)return o.slice(0,i);if(s+=a,o.length>=e)break}return o}finally{await n.close()}}async function
|
|
98
|
-
`).trim():"";async function
|
|
99
|
-
`)){let p=d.trim();if(!p||p[0]!=="{")continue;let m;try{m=JSON.parse(p)}catch{continue}if((m.type==="turn_context"&&m.payload?.model||m.type==="session_meta"&&m.payload?.model)&&(i=m.payload.model),m.type==="event_msg"){let S=m.payload?.type;S==="task_started"?(l=!0,u=!1,f=m.payload?.turn_id||null):S==="task_complete"&&l&&(l=!1,!u&&m.payload?.last_agent_message==null&&a.push({role:"assistant",failed:!0,text:
|
|
100
|
-
`;else if(s==="t")n+=" ";else if(s==="r")n+="\r";else if(s==="u")if(t[r+1]==="{"){let a=t.indexOf("}",r+2);if(a===-1)return null;n+=String.fromCodePoint(parseInt(t.slice(r+2,a),16)),r=a}else n+=String.fromCharCode(parseInt(t.slice(r+1,r+5),16)),r+=4;else n+=s}else{if(o===e)return n;if(e==="`"&&o==="$"&&t[r+1]==="{")return null;n+=o}}return null}function
|
|
101
|
-
`)){let c=i.trim();if(!c||c[0]!=="{")continue;let l;try{l=JSON.parse(c)}catch{continue}if(l.type!=="response_item")continue;let u=l.payload;if(!u)continue;let f=[];if(u.type==="function_call"&&
|
|
102
|
-
`),o=-1;for(let u=r.length-1;u>=0;u--)if(/^\s*Switch model\?\s*$/.test(r[u])){o=u;break}if(o<0)return null;let s=[],a=[];for(let u=o+1;u<r.length;u++){let f=r[u].match(
|
|
103
|
-
`),r=n.length-1;for(;r>=0&&n[r].trim()==="";)r--;if(r<0||!
|
|
104
|
-
`),o=[];for(let c=0;c<r.length;c++){let l=r[c].match(
|
|
105
|
-
`),o=-1;for(let u=r.length-1;u>=0;u--)if(
|
|
106
|
-
`),o=
|
|
107
|
-
`),o=
|
|
108
|
-
`),o="";for(let s=0;s<r.length;s++)if(/^\s*https:\/\//.test(r[s])){o=
|
|
109
|
-
`)].reverse().find(a=>
|
|
110
|
-
`),o=
|
|
111
|
-
`),o=-1;for(let l=r.length-1;l>=0;l--)if(r[l].trim()!==""){o=l;break}if(o<0)return null;let s=r[o].match(
|
|
112
|
-
`),o=
|
|
113
|
-
`).pop()||"")?!0:
|
|
114
|
-
`).pop()||""):t==="codex"&&/Sign in with ChatGPT|one-time code|Paste or type your API key|link to authenticate/i.test(n)?!0:t==="cursor"||t==="codex"?/\b(allow|approve|apply (this )?patch|run (this )?command|do you want to run|grant)\b/i.test(n):!1:!1}function
|
|
115
|
-
`).map(c=>c.replace(/\s+$/,"")),r=-1;for(let c=n.length-1;c>=0;c--)if(
|
|
85
|
+
}`,{contentType:n,filename:e,size:r.length}),s=o.fileUpload?.uploadFile;if(!o.fileUpload?.success||!s?.uploadUrl)throw new Error("linear: fileUpload did not succeed");let a={"content-type":n};for(let c of s.headers||[])a[c.key]=c.value;let i=await fetch(s.uploadUrl,{method:"PUT",headers:a,body:r});if(!i.ok)throw new Error(`linear: asset PUT failed (HTTP ${i.status})`);return s.assetUrl}async function rA(t,e){let n=await ke(t,"query($id: String!) { issue(id: $id) { state { id name type } team { states { nodes { id name type position } } } } }",{id:e.key});if(!n.issue)throw new Error(`linear: issue "${e.key}" not found`);return{current:n.issue.state??null,states:n.issue.team?.states?.nodes??[]}}async function oA(t,e,n){if(!(await ke(t,"mutation($id: String!, $input: IssueUpdateInput!) { issueUpdate(id: $id, input: $input) { success } }",{id:e.issueId,input:{stateId:n}})).issueUpdate?.success)throw new Error("linear: issueUpdate did not succeed")}async function Ir(t){return((await ke(t,"query { teams(first: 100) { nodes { id key name } } }")).teams?.nodes??[]).map(n=>({id:n.id,key:n.key,name:n.name}))}var zm=255;async function el(t,{teamId:e,name:n,description:r}){let o=r||null,s=!!o&&o.length>zm,a=s?`${o.slice(0,zm-1).trimEnd()}\u2026`:o,i=await ke(t,"mutation($input: ProjectCreateInput!) { projectCreate(input: $input) { success project { id url name } } }",{input:{teamIds:[e],name:n,...a?{description:a}:{},...s?{content:o}:{}}}),c=i.projectCreate?.project;if(!i.projectCreate?.success||!c)throw new Error("linear: projectCreate did not succeed");return{id:c.id,url:c.url,name:c.name}}async function nl(t,{teamId:e,title:n,description:r,projectId:o,priority:s}){let a=await ke(t,"mutation($input: IssueCreateInput!) { issueCreate(input: $input) { success issue { id identifier url title } } }",{input:{teamId:e,title:n,...r?{description:r}:{},...o?{projectId:o}:{},...Number.isInteger(s)?{priority:s}:{}}}),i=a.issueCreate?.issue;if(!a.issueCreate?.success||!i)throw new Error("linear: issueCreate did not succeed");return{id:i.id,key:i.identifier,url:i.url,title:i.title}}var eh={linear:{api:rl,config:Wn},jira:{api:Qc,config:Ra}};async function ul(){let t=[];for(let[e,{api:n,config:r}]of Object.entries(eh)){let o=await r();o&&t.push({name:e,api:n,cfg:o})}return t}async function Gn(t){let e=eh[t?.provider];if(!e)return null;let n=await e.config();return n?{name:t.provider,api:e.api,cfg:n}:null}function dl(t,e){if(!t||t.name)return t;let n=e.find(r=>r.id===t.id);return n?{...t,name:n.name}:t}var nh=()=>process.env.BOSTRAT_STATE_DIR||Qm.join(process.env.HOME||sA.tmpdir(),".bostrat"),il=()=>Qm.join(nh(),"tickets.json"),rh=()=>(process.env.BOSTRAT_PUBLIC_URL||"http://bostrat.globopi").replace(/\/$/,""),Oa=t=>`${rh()}/track/${t}`,un=null,ol=null,sl=Promise.resolve();async function Fo(){return un||(ol??=jo.readFile(il(),"utf8").then(t=>({tickets:JSON.parse(t)?.tickets??{},existed:!0})).catch(()=>({tickets:{},existed:!1})).then(async({tickets:t,existed:e})=>{if(un=t,!e&&ht()){let{records:n,source:r}=await Rt("tickets").catch(()=>({records:[],source:"error"}));if(r==="plane"){for(let o of n){if(o.deletedAt||!o.data?.id)continue;let{id:s,updatedAt:a,...i}=o.data;un[s]=i}Object.keys(un).length&&await cl().catch(()=>{})}}return un}).finally(()=>ol=null),ol)}async function cl(){let t=JSON.stringify({tickets:un},null,2),e=`${il()}.${process.pid}.tmp`;await jo.mkdir(nh(),{recursive:!0}),await jo.writeFile(e,t),await jo.rename(e,il())}async function dn(t=null){if(sl=sl.then(cl,cl),await sl.catch(()=>{}),!t)return;let e=un?.[t];e?Nt("tickets",{id:t,updatedAt:Date.now(),...e},{trackId:t}).catch(()=>{}):re("tickets",t).catch(()=>{})}async function xe(t){return(await Fo())[t]??null}async function Ma(){return{...await Fo()}}async function oh(){return(await ul()).length>0}async function Rr(t,e,{source:n="api"}={}){let r=await ul();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 Fo())[t.id]=c,await dn(t.id),await R({id:t.id,cwd:t.cwd},{type:"ticket.linked",actor:"user",source:"orchestrator",payload:{ts:Date.now(),provider:c.provider,key:c.key,url:c.url,title:c.title,linkSource:n}}).catch(()=>{}),await Lo(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 Do(c,`**Bostrat** \u2014 Track **${c.track.name}** linked${l.length?` (${l.join(", ")})`:""}.`),await ih(t.id,c,"linked"),c}async function fl(t){let e=await Fo();return e[t]?(delete e[t],await dn(t),!0):!1}var ll=t=>`$${t>=100?Math.round(t):t.toFixed(2)}`;async function sh(t,e){if(!t.track?.cwd)return null;let n=me(await L({id:e,cwd:t.track.cwd}));return!n.totalUsd||n.totalUsd<.01?null:n}function aA(t){let e=Object.entries(t.byProvider).sort((r,o)=>o[1].usd-r[1].usd).map(([r,o])=>`${r} ${ll(o.usd)}`).join(" \xB7 "),n=Object.keys(t.byBox).length;return`${ll(t.totalUsd)}${e?` (${e})`:""}${n>1?` across ${n} boxes`:""}`}function iA(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(ll(e.totalUsd)),n.join(" \xB7 ")||"linked"}async function Lo(t,e){let n=await Gn(e);if(!n)return;let r=await sh(e,t).catch(()=>null);await n.api.upsertCard(n.cfg,e,{url:Oa(t),title:`Bostrat Track: ${e.track.name}`,subtitle:iA(e,r)})}function Ca(t){let e=new URL(rh());return`${e.protocol}//${t}.${e.host}/`}var cA={linked:"linked",opened:"prOpened",merged:"prMerged"};function lA(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 ah(t,e,{from:n,to:r,mode:o,phase:s=null,actor:a="user"}){e.state=r,await dn(t),await R({id:t,cwd:e.track?.cwd},{type:"ticket.state.changed",actor:a,source:"orchestrator",payload:{ts:Date.now(),key:e.key,url:e.url,from:n??null,to:r,mode:o,...s?{phase:s}:{}}}).catch(()=>{})}async function ih(t,e,n){let r=await Gn(e);if(!r)return;let o=r.cfg.ticketFlow?.[cA[n]]??"prompt";if(o==="off")return;let{current:s,states:a}=await r.api.issueStates(r.cfg,e),i=dl(s,a),c=lA(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 ah(t,e,{from:l,to:c.name,mode:"auto",phase:n,actor:"system"});return}await R({id:t,cwd:e.track?.cwd},{type:"ticket.move.offered",actor:"system",source:"orchestrator",payload:{ts:Date.now(),offerId:th(),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 ch(t){let e=await xe(t);if(!e)throw new Error("no ticket linked to this Track");let n=await Gn(e);if(!n)throw new Error("no ticketing integration configured");let{current:r,states:o}=await n.api.issueStates(n.cfg,e),s=dl(r,o);return s?.name&&e.state!==s.name&&(e.state=s.name,await dn(t)),{key:e.key,url:e.url,current:s?{id:s.id,name:s.name??null,type:s.type??null}:null,states:[...o].sort((a,i)=>a.position-i.position).map(({id:a,name:i,type:c,position:l})=>({id:a,name:i,type:c,position:l}))}}async function pl(t,e,{mode:n="manual"}={}){if(!e)throw new Error("stateId required");let r=await xe(t);if(!r)throw new Error("no ticket linked to this Track");let o=await Gn(r);if(!o)throw new Error("no ticketing integration configured");let{current:s,states:a}=await o.api.issueStates(o.cfg,r),i=dl(s,a),c=a.find(u=>u.id===e);if(!c)throw new Error("unknown state for this ticket's team");let l=i?.name??r.state??null;return i?.id===c.id?(r.state!==c.name&&(r.state=c.name,await dn(t)),{key:r.key,from:l,to:c.name,moved:!1}):(await o.api.moveIssue(o.cfg,r,c.id),await ah(t,r,{from:l,to:c.name,mode:n}),{key:r.key,from:l,to:c.name,moved:!0})}async function lh(t,e){let n=await xe(t);if(!n)throw new Error("no ticket linked to this Track");let r=await Gn(n);if(!r)throw new Error("no ticketing integration configured");let o=String(e||"").trim();if(!o)throw new Error("comment body required");let s=await r.api.createComment(r.cfg,n,o);return await R({id:t,cwd:n.track?.cwd},{type:"ticket.commented",actor:"user",source:"orchestrator",payload:{ts:Date.now(),key:n.key,url:s??n.url,excerpt:o.length>280?`${o.slice(0,279)}\u2026`:o}}).catch(()=>{}),{key:n.key,url:s??n.url}}async function uh(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 pl(t.id,o.payload.to?.id,{mode:"offer"})),await R({id:t.id,cwd:t.cwd},{type:"ticket.move.answered",actor:"user",source:"orchestrator",payload:{ts:Date.now(),offerId:e,action:n?"moved":"dismiss",...s?{from:s.from,to:s.to}:{}}}),{offerId:e,action:n?"moved":"dismiss",...s??{}}}var Jm=50;async function dh(){let t=await Wn();if(!t)throw new Error("no ticketing integration configured");return Ir(t)}function uA(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>Jm)throw new Error(`too many issues (${n.length}); cap is ${Jm} per proposal`);return{epic:r,issues:n,team:e.team?String(e.team).trim():null,linkCurrentTrack:e.linkCurrentTrack===!0}}async function dA(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 xe(n.id);if(a?.key){let i=a.key.split("-")[0],c=o.find(l=>l.key===i);if(c)return c}if(t.defaultTeam){let i=s(t.defaultTeam);if(i)return i}return o.length===1?o[0]:null}async function fh(t,e){let n=await Wn();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=uA(e),o=await Ir(n),s=await dA(n,r,t,o),a=s?{id:s.id,key:s.key,name:s.name}:null,i=th();return await R({id:t.id,cwd:t.cwd},{type:"ticket.create.offered",actor:"system",source:"orchestrator",payload:{ts:Date.now(),proposalId:i,provider:"linear",epic:r.epic,issues:r.issues,team:a,needsTeam:!s,linkCurrentTrack:r.linkCurrentTrack}}),{proposalId:i,epic:r.epic,issues:r.issues,team:a,needsTeam:!s}}async function ph(t,e,n,{teamId:r=null}={}){if(!e)throw new Error("proposalId required");let o=await L({id:t.id,cwd:t.cwd}),s=o.find(m=>m.type==="ticket.create.offered"&&m.payload?.proposalId===e);if(!s)throw new Error("unknown proposal");if(o.some(m=>m.type==="ticket.create.answered"&&m.payload?.proposalId===e))throw new Error("proposal already answered");if(!n)return await R({id:t.id,cwd:t.cwd},{type:"ticket.create.answered",actor:"user",source:"orchestrator",payload:{ts:Date.now(),proposalId:e,action:"dismiss"}}),{proposalId:e,action:"dismiss"};let a=await Wn();if(!a)throw new Error("no ticketing integration configured");let i=s.payload,c=i.team??null;if(!c&&r){let m=(await Ir(a)).find(w=>w.id===r);if(!m)throw new Error("unknown team");c={id:m.id,key:m.key,name:m.name}}if(!c)throw new Error("a team is required to create \u2014 pick one");let l=[],u=[],f=null;if(i.epic){let m=await el(a,{teamId:c.id,name:i.epic.name,description:i.epic.description});f=m.id,l.push({kind:"epic",title:m.name,url:m.url})}for(let m of i.issues||[])try{let w=await nl(a,{teamId:c.id,title:m.title,description:m.description,projectId:f,priority:m.priority});l.push({kind:"issue",key:w.key,title:w.title,url:w.url})}catch(w){u.push({title:m.title,message:w?.message||String(w)})}await R({id:t.id,cwd:t.cwd},{type:"ticket.created",actor:"user",source:"orchestrator",payload:{ts:Date.now(),proposalId:e,team:{key:c.key,name:c.name},items:l,failed:u}}),await R({id:t.id,cwd:t.cwd},{type:"ticket.create.answered",actor:"user",source:"orchestrator",payload:{ts:Date.now(),proposalId:e,action:"created",createdCount:l.length,failedCount:u.length}});let d=l.filter(m=>m.kind==="issue"),p=null;return i.linkCurrentTrack&&d.length===1&&!await xe(t.id)&&(p=await Rr(t,d[0].key,{source:"created"}).catch(()=>null)),{proposalId:e,action:"created",created:l,failed:u,linked:p?p.key:null}}async function Do(t,e){let n=await Gn(t);n&&await n.api.createComment(n.cfg,t,e)}async function fA(t,e,n){let r=await Gn(t);if(!r)return null;let o=await cn(e);if(!o)return null;let s=Ce(o,n);if(!s)return null;let a=await jo.readFile(Jt(o,s.v).png).catch(()=>null);return a?r.api.uploadImage(r.cfg,{issue:t.key,filename:`${o.name.replace(/[^\w.-]+/g,"-")}.v${s.v}.png`,contentType:"image/png",buffer:a}):null}var Xm=()=>Number(process.env.BOSTRAT_TICKET_COST_SYNC_MS??12e4),al=new Map;function pA(t,e){if(Xm()<=0)return Lo(t,e);if(al.has(t))return;let n=setTimeout(()=>{al.delete(t),xe(t).then(r=>r?Lo(t,r):null).catch(()=>{})},Xm());n.unref?.(),al.set(t,n)}var Ym=new Map;async function Zm(t,e,n){let r=Ym.get(t)??0;Date.now()-r<600*1e3||(Ym.set(t,Date.now()),await R({id:t,cwd:e},{type:"ticket.sync.failed",actor:"system",source:"orchestrator",payload:{ts:Date.now(),message:n?.message||String(n)}}).catch(()=>{}))}async function mA(t,{resolveTrack:e=null}={}){let n=await xe(t.trackId),r=t.payload||{};if(!n){if(t.type!=="turn.user"||t.actor!=="user")return;let o=null;for(let a of await ul())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 Rr(s,o,{source:"chat"})}catch(a){await Zm(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 dn(t.trackId))}try{switch(t.type){case"handoff.done":n.track.leadAgent=r.toAgent??n.track.leadAgent,await dn(t.trackId),await Lo(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 dn(t.trackId),await Lo(t.trackId,n),r.change==="opened"||r.change==="merged"){let o=r.change==="merged"?await sh(n,t.trackId).catch(()=>null):null;await Do(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: ${aA(o)}.`:"")),await ih(t.trackId,n,r.change)}break}case"mock.state.changed":{if(r.change==="ready"&&r.mockId){let o=await fA(n,r.mockId,r.version??null);if(!o)break;let s=(r.version??1)>1?` revised (v${r.version})`:"";await Do(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 Do(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 Do(n,`**Bostrat** \u2014 live preview **${r.name}**: ${Ca(r.slug)}
|
|
90
|
+
_(runs on the Track's branch; reachable on the operator's private network only)_`);break}case"cost.recorded":await pA(t.trackId,n);break;default:break}}catch(o){let s=e?await e(t.trackId).catch(()=>null):null;await Zm(t.trackId,s?.cwd??null,o)}}function mh({resolveTrack:t}){te.on("event",e=>{mA(e,{resolveTrack:t}).catch(()=>{})})}async function hh(){if(!ht())return{pushed:0,source:"unconfigured"};let{records:t,source:e}=await Rt("tickets");if(e!=="plane"||t.length)return{pushed:0,source:e};let n=await Fo(),r=0;for(let[o,s]of Object.entries(n))await Nt("tickets",{id:o,updatedAt:Date.now(),...s},{trackId:o}).catch(()=>{}),r++;return{pushed:r,source:"plane"}}import hA from"node:crypto";import Na from"node:fs/promises";import wh from"node:path";import wA from"node:os";var gh=process.env.BOSTRAT_STATE_DIR||wh.join(process.env.HOME||wA.tmpdir(),".bostrat"),wl=wh.join(gh,"pr-links.json"),V=(t,e)=>`${t}#${Number(e)}`,yh=t=>{let e=t.replace(/[^A-Za-z0-9._-]+/g,"-").replace(/^[^A-Za-z0-9]+/,"").slice(0,55),n=hA.createHash("sha256").update(t).digest("hex").slice(0,8);return`${e||"pr"}-${n}`},fn=null,ml=null,hl=Promise.resolve();async function kt(){return fn||(ml??=Na.readFile(wl,"utf8").then(t=>({links:JSON.parse(t)?.links??{},existed:!0})).catch(()=>({links:{},existed:!1})).then(async({links:t,existed:e})=>{if(fn=t,!e&&ht()){let{records:n,source:r}=await Rt("prlinks").catch(()=>({records:[],source:"error"}));if(r==="plane"){for(let o of n){if(o.deletedAt||!o.data?.repo||o.data?.number==null)continue;let{id:s,updatedAt:a,...i}=o.data;fn[V(i.repo,i.number)]=i}Object.keys(fn).length&&await gl().catch(()=>{})}}return fn}).finally(()=>ml=null),ml)}async function gl(){let t=JSON.stringify({links:fn},null,2),e=`${wl}.${process.pid}.tmp`;await Na.mkdir(gh,{recursive:!0}),await Na.writeFile(e,t),await Na.rename(e,wl)}async function Et(t){hl=hl.then(gl,gl),await hl.catch(()=>{});let e=fn?.[t];e&&Nt("prlinks",{id:yh(t),updatedAt:Date.now(),...e},{trackId:e.trackId??null}).catch(()=>{})}var gA=new Set(["api","observed","reconciled"]);async function Xt({repo:t,number:e,trackId:n,url:r=null,headRef:o=null,title:s=null,source:a="matched"}){if(!t||!Number.isInteger(Number(e))||!n)return{link:null,created:!1};let i=await kt(),c=V(t,e);return i[c]?{link:i[c],created:!1}:(i[c]={repo:t,number:Number(e),trackId:n,url:r,headRef:o,title:s,source:a,announced:gA.has(a),firstSeen:Date.now()},await Et(V(t,e)),{link:i[c],created:!0})}async function Uo(t,e){if(!t||e==null)return null;let n=(await kt())[V(t,e)];return!n||n.announced?null:(n.announced=!0,await Et(V(t,e)),n)}async function Pt(t,e){return!t||e==null?null:(await kt())[V(t,e)]??null}async function pn(t,e,n){if(!t||e==null)return null;let r=(await kt())[V(t,e)];return r?(r.state=n,r.resolvedAt=Date.now(),delete r.armedAt,delete r.autoMerge,delete r.archiveOnMerge,delete r.notifyOnMerge,delete r.notifyPending,await Et(V(t,e)),r):null}async function bh(t,e,{headSha:n=null,trackId:r=null}={}){if(!t||e==null)return null;let o=(await kt())[V(t,e)];return o?(o.ciFix={delegatedAt:Date.now(),headSha:n,trackId:r},await Et(V(t,e)),o):null}async function kh(t,e,{headSha:n=null,trackId:r=null}={}){if(!t||e==null)return null;let o=(await kt())[V(t,e)];return o?(o.reconcile={delegatedAt:Date.now(),headSha:n,trackId:r},await Et(V(t,e)),o):null}async function Pa(t,e,{headSha:n=null}={}){if(!t||e==null)return null;let r=(await kt())[V(t,e)];return!r||!r.ciFix?null:(r.ciFixAwait={since:Date.now(),trackId:r.ciFix.trackId??null,headSha:n},delete r.ciFix,await Et(V(t,e)),r)}async function Ba(t,e){if(!t||e==null)return null;let n=(await kt())[V(t,e)];return!n||!n.ciFixAwait?null:(delete n.ciFixAwait,await Et(V(t,e)),n)}async function Da(t,e,{headSha:n=null}={}){if(!t||e==null)return null;let r=(await kt())[V(t,e)];return!r||!r.reconcile?null:(r.reconcileAwait={since:Date.now(),trackId:r.reconcile.trackId??null,headSha:n},delete r.reconcile,await Et(V(t,e)),r)}async function ja(t,e){if(!t||e==null)return null;let n=(await kt())[V(t,e)];return!n||!n.reconcileAwait?null:(delete n.reconcileAwait,await Et(V(t,e)),n)}async function xh(t,e,{at:n=Date.now()}={}){if(!t||e==null)return null;let r=(await kt())[V(t,e)];return r?(r.baseFoldAt=n,await Et(V(t,e)),r):null}async function Sh(t,e){if(!t||e==null)return null;let n=(await kt())[V(t,e)];return!n||!n.baseFoldAt?null:(delete n.baseFoldAt,await Et(V(t,e)),n)}async function vh(t,e,n){if(!t||e==null||!n)return null;let r=(await kt())[V(t,e)];return r?(r.lastHeadSha=n,await Et(V(t,e)),r):null}async function Ho(t,e,{merge:n=null}={}){if(!t||e==null)return null;let r=(await kt())[V(t,e)];return r?(r.armedAt=Date.now(),n&&(r.autoMerge={method:n.method||"merge",armedAt:r.armedAt}),await Et(V(t,e)),r):null}async function Vn(t,e,n=!0){if(!t||e==null)return null;let r=(await kt())[V(t,e)];return r?(n?r.archiveOnMerge=!0:delete r.archiveOnMerge,await Et(V(t,e)),r):null}async function Wo(t,e,n=!0){if(!t||e==null)return null;let r=(await kt())[V(t,e)];return r?(n?r.notifyOnMerge=!0:delete r.notifyOnMerge,await Et(V(t,e)),r):null}async function Th(t,e,n){if(!t||e==null)return null;let r=(await kt())[V(t,e)];if(!r)return null;r.notifyOnMerge=!0;let o=new Map((r.notifyPending||[]).map(s=>[`${s.repo}#${s.number}`,s]));for(let s of n||[])s&&s.number!=null&&o.set(`${s.repo}#${s.number}`,s);return r.notifyPending=[...o.values()],await Et(V(t,e)),r}async function qo(t,e){if(!t||e==null)return null;let n=(await kt())[V(t,e)];return!n||!n.armedAt&&!n.autoMerge?null:(delete n.armedAt,delete n.autoMerge,delete n.archiveOnMerge,delete n.notifyOnMerge,delete n.notifyPending,await Et(V(t,e)),n)}var yA=new Set(["merged","closed","lost"]);async function Ah(){return Object.values(await kt()).filter(t=>!yA.has(t.state))}async function Se(){return Object.values(await kt()).sort((t,e)=>e.firstSeen-t.firstSeen)}async function Eh(){if(!ht())return{pushed:0,source:"unconfigured"};let{records:t,source:e}=await Rt("prlinks");if(e!=="plane"||t.length)return{pushed:0,source:e};let n=await kt(),r=0;for(let[o,s]of Object.entries(n))await Nt("prlinks",{id:yh(o),updatedAt:Date.now(),...s},{trackId:s.trackId??null}).catch(()=>{}),r++;return{pushed:r,source:"plane"}}var Ih=t=>Math.round(t*100)/100;function bA(t){return!t||t.kind==="setup"?[]:qt(t.workspace).filter(e=>e&&(e.repo||e.base)).map(e=>({key:e.repo||e.base,slug:e.repo??null,base:e.base??null,name:e.repo?e.repo.split("/").pop():_h.basename(e.base)}))}function Rh(t){let e=new Map;for(let n of t||[])for(let r of bA(n)){let o=e.get(r.key);o||e.set(r.key,o={...r,tracks:[]}),o.tracks.push(n)}return e}function La(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 yl=(t,e)=>`${t} ${e}${t===1?"":"s"}`;function kA(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 bl(t,e=[]){let n=[],r=t?.events||[],o=new Map,s=0;for(let u of e){if(u.type!=="pr.state.changed")continue;let f=u.payload||{};!f.repo||f.number==null||(f.change==="merged"?o.set(`${f.repo}#${f.number}`,f):f.change==="opened"&&s++)}if(o.size){let u=[...o.keys()].map(f=>{let[d,p]=f.split("#");return`${d.split("/").pop()}#${p}`});n.push(`Merged ${u.slice(0,4).join(", ")}${u.length>4?` +${u.length-4}`:""}`)}else s&&n.push(yl(s,"PR")+" opened");let a=Object.keys(t?.subtasks||{}).length||r.filter(u=>u.type==="subtask-delegated").length;a&&n.push(yl(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(`${yl(i.length,"handoff")} ${u}${i.length>3?"\u2026":""}`)}let c=me(e);c.totalUsd>=.01&&n.push(`$${c.totalUsd.toFixed(2)}`);let l=Number.isFinite(t?.archivedAt)&&Number.isFinite(t?.createdAt)?kA(t.archivedAt-t.createdAt):null;return n.length&&l&&n.push(l),n.length?n.join(" \xB7 "):null}function xA(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 $h(t,e,n){let r=new Set(t.tracks.map(d=>d.id)),o=0,s=0,a=0,i=null,c=null,l=0;for(let d of t.tracks){d.archivedAt?s++:o++,d.beat?.text&&a++;let p=La(d);p&&(!i||p>i)&&(i=p),d.createdAt&&(!c||d.createdAt<c)&&(c=d.createdAt),l+=e.get(d.id)||0}let u=0,f=0;for(let d of n)r.has(d.trackId)&&(t.slug&&d.repo!==t.slug||t.slug&&(d.state==="merged"?f++:d.state!=="closed"&&u++));return{key:t.key,slug:t.slug,base:t.base,name:t.name,tracks:{total:t.tracks.length,live:o,archived:s,withBeat:a},prs:{open:u,merged:f},usd:Ih(l),firstActivityAt:c,lastActivityAt:i}}async function Oh(t){let e=Rh(t),n=await Se().catch(()=>[]),r=new Map,o=new Set;for(let a of e.values())for(let i of a.tracks){if(o.has(i.id))continue;o.add(i.id);let c=await L(i).catch(()=>[]);r.set(i.id,me(c).totalUsd)}let s=[...e.values()].map(a=>$h(a,r,n)).sort((a,i)=>(i.lastActivityAt??0)-(a.lastActivityAt??0));return{generatedAt:Date.now(),arcs:s}}async function Mh(t,e){let r=Rh(t).get(e);if(!r)return null;let[o,s,a,i,c]=await Promise.all([Se().catch(()=>[]),Fn().catch(()=>[]),va().catch(()=>[]),Ea().catch(()=>[]),Ma().catch(()=>({}))]),l=new Map,u=[],f=(d,p)=>(La(p)||p.createdAt||0)-(La(d)||d.createdAt||0);for(let d of[...r.tracks].sort(f)){let p=await L(d).catch(()=>[]),m=me(p);l.set(d.id,m.totalUsd);let w=c[d.id]||null;u.push({trackId:d.id,name:d.name,createdAt:d.createdAt??null,archivedAt:d.archivedAt??null,live:!d.archivedAt,beat:d.beat??null,agents:xA(d),handoffs:(d.events||[]).filter(y=>y.type==="lead-handoff").length,subtasks:Object.keys(d.subtasks||{}).length,repos:qt(d.workspace).map(y=>y.repo||_h.basename(y.base||"")),prs:o.filter(y=>y.trackId===d.id&&(!r.slug||y.repo===r.slug)).map(y=>({repo:y.repo,number:y.number,state:y.state??null,title:y.title??null,url:y.url??null})),mocks:s.filter(y=>y.trackId===d.id).length,views:a.filter(y=>y.trackId===d.id).length,runbooks:i.filter(y=>y.trackId===d.id).map(y=>({name:y.name,status:y.status})),ticket:w?{provider:w.provider,key:w.key,url:w.url??null}:null,usd:Ih(m.totalUsd),lastActivityAt:La(d)})}return{...$h(r,l,o),generatedAt:Date.now(),segments:u}}import{execFile as SA}from"node:child_process";import{promisify as vA}from"node:util";import{randomUUID as TA}from"node:crypto";var $r=vA(SA),Fa=t=>new Promise(e=>setTimeout(e,t)),AA=Number(process.env.BOSTRAT_SWAP_EXIT_TIMEOUT_MS)||15e3,Ch=Number(process.env.BOSTRAT_SWAP_SHELL_TIMEOUT_MS)||8e3,EA=250,_A=400,Nh={claude:{escape:!0,wipe:!0,text:"/exit"},codex:{escape:!0,wipe:!0,text:"/quit"},cursor:{escape:!0,wipe:!0,text:"/quit"},default:{escape:!1,wipe:!1,text:"exit"}};async function kl(t,e){let n=Date.now()+e;for(;;){if(await wr(t))return!0;if(Date.now()>n)return!1;await Fa(EA)}}async function IA(t,e){let n=Nh[e]||Nh.default;n.escape&&(await xt(t,["Escape"],{kind:"swap-exit"}),await Fa(150)),n.wipe&&await Qs(t,400,{kind:"swap-exit"}),await ta(t,n.text,{perCharDelayMs:90,kind:"swap-exit"});let r=Me(e).submitDelayMs||0;await Fa(Math.max(r,250)),await xt(t,["Enter"],{kind:"swap-exit"})}async function RA(t,e){try{let{stdout:n}=await $r(M,P("show-environment","-t",t,e));return!String(n||"").trimStart().startsWith("-")}catch{return!1}}async function Ph(t){await $r(M,P("respawn-pane","-k","-t",t))}async function xl(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 nt(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||en(n),a=Number(e.exitTimeoutMs)||AA;await G({flow:"swap",phase:"start",sessionName:t,from:o,to:s,cwd:r.cwd});let i=!1;if(!await wr(t)&&(await IA(t,o),!await kl(t,a)&&(i=!0,await G({flow:"swap",phase:"respawn-forced",sessionName:t,from:o}),await Ph(t),!await kl(t,Ch))))throw new Error(`pane shell did not come back after respawn-pane in "${t}"`);let c=s==="claude"&&Pn(n),l=c?Ds():null,u=await RA(t,"ANTHROPIC_API_KEY"),f=!1;try{l?(await $r(M,P("set-environment","-t",t,"ANTHROPIC_API_KEY",l)),f=!0):u&&(await $r(M,P("set-environment","-u","-t",t,"ANTHROPIC_API_KEY")),f=!0)}catch(y){throw l?zs(y,l):y}if(f&&!i&&(await Ph(t),!await kl(t,Ch)))throw new Error(`pane shell did not come back after env respawn in "${t}"`);let d=!!e.resumeSessionId,p=e.resumeSessionId||TA(),m=d?n:mc(n,p),w=d?e.pinned===!0:/(^|\s)--session-id(\s|=)/.test(m);return c&&await Ye(r.cwd),s==="codex"&&await ir(r.cwd),s==="cursor"&&await cr(r.cwd),await Dn(t),await $r(M,P("set-option","-t",t,"@bostrat-sid",p)),await $r(M,P("set-option","-t",t,"@bostrat-agent",s)),await Fa(_A),await At(t,m,{kind:"swap-launch"}),await G({flow:"swap",phase:"done",sessionName:t,from:o,to:s,sid:p,forced:i,envRespawned:f}),{name:t,cwd:r.cwd,sessionId:p,agent:s,pinned:w,forced:i,envRespawned:f}}import cE from"node:fs/promises";function $A(t){let e=[],n="",r=null;for(let o=0;o<t.length;o++){let s=t[o];if(r){n+=s,s===r&&t[o-1]!=="\\"&&(r=null);continue}if(s==="'"||s==='"'||s==="`"){r=s,n+=s;continue}if(s==="&"&&t[o+1]==="&"){e.push(n),n="",o++;continue}if(s==="|"&&t[o+1]==="|"){e.push(n),n="",o++;continue}if(s==="|"||s===";"||s===`
|
|
91
|
+
`){e.push(n),n="";continue}n+=s}return e.push(n),e.map(o=>o.trim()).filter(Boolean)}function Ko(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 Go(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 Mr=(t,e)=>t.length>e?`${t.slice(0,e).trimEnd()}\u2026`:t,Ot=t=>t&&!/[$`]/.test(t)?t:null,wt=t=>t.replace(/\/+$/,"").split("/").pop()||t,Or=t=>{let e=t.replace(/\/+$/,"").split("/").filter(Boolean);return e.length>3?e.slice(-2).join("/"):t},jh=t=>t.startsWith("-"),Bh=new Set(["-g","--glob","-t","--type","-A","-B","-C","-m","--max-count","-e","--regexp","-f","--file"]);function ut(t,e=new Set){let n=[];for(let r=1;r<t.length;r++){let o=t[r];if(o==="<<")break;if(jh(o)){e.has(o)&&r++;continue}n.push(o)}return n}var OA={status:()=>"Check git status",diff:()=>"Review changes",log:()=>"Review commit history",show:()=>"Inspect a commit",add:()=>"Stage files",commit:()=>"Commit changes",push:()=>"Push to remote",pull:()=>"Pull from remote",fetch:()=>"Fetch from remote",merge:()=>"Merge branches",rebase:()=>"Rebase branch",stash:()=>"Stash changes",clone:t=>t[1]?`Clone ${wt(t[1])}`:"Clone repository",checkout:()=>"Switch branches",switch:()=>"Switch branches",restore:()=>"Restore files",reset:()=>"Reset git state",branch:(t,e)=>e.includes("--show-current")?"Check current branch":"List branches",remote:()=>"Check git remotes","rev-parse":()=>"Resolve a git ref",worktree:()=>"Manage git worktrees","merge-base":()=>"Find the merge base","ls-files":()=>"List tracked files"},MA={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 Lh(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 CA(t){let e=Lh(t),n=Ot(ut(t).find(r=>!/^\d+(,\d+)?p$/.test(r)));return e&&n?e.from===e.to?`Read line ${e.from} of ${wt(n)}`:`Read lines ${e.from}\u2013${e.to} of ${wt(n)}`:t.includes("-i")&&n?`Edit ${wt(n)}`:null}function Sl(t){if(t.includes("--files")){let s=Ot(ut(t,Bh)[0]);return s&&s!=="."?`List files in ${Or(s)}`:"List files"}let e=ut(t,Bh),n=e[0];if(!n)return null;let r=Ot(e.slice(1).find(s=>s!==".")),o=Mr(n,40);return r?`Search for \u201C${o}\u201D in ${Or(r)}`:`Search for \u201C${o}\u201D`}function vl(t){let e=ut(t,new Set(["--prefix"])),n=e[0];return n?n==="test"||n==="t"?"Run tests":n==="run"||n==="run-script"?e[1]?`Run ${e[1]}`:null:n==="ci"||n==="install"||n==="i"||n==="add"?"Install dependencies":null:null}function Tl(t,e){if(t.includes("-e")||t.includes("--eval")||t.includes("-c")||t[1]==="-"||t.includes("<<"))return`Run an inline ${e} script`;if(t.includes("--test"))return"Run tests";let n=ut(t)[0];return n?`Run ${wt(n)}`:null}function NA(t){let e=ut(t),n=e[0],r=e.find(a=>/\.(service|timer|socket|target)$/.test(a))||e[1],o=r?r.replace(/\.service$/,""):"a service";return{status:`Check ${o} status`,show:`Inspect ${o}`,restart:`Restart ${o}`,start:`Start ${o}`,stop:`Stop ${o}`,"daemon-reload":"Reload systemd"}[n]||null}function Dh(t){return t.find(e=>/^https?:\/\//.test(e))||null}var PA={git:t=>{let e=ut(t,new Set(["-C","-c"])),n=OA[e[0]];return n?n(e,t):null},gh:t=>{let e=ut(t),n=MA[e[0]];return n?n(e):null},sed:CA,rg:Sl,grep:Sl,egrep:Sl,nl:t=>{let e=Ot(ut(t)[0]);return e?`Read ${wt(e)}`:null},cat:t=>{let e=Ot(ut(t)[0]);return e?`Read ${wt(e)}`:null},head:t=>{let e=Ot(ut(t,new Set(["-n","-c"])).find(n=>!/^\d+$/.test(n)));return e?`Read the start of ${wt(e)}`:null},tail:t=>{let e=Ot(ut(t,new Set(["-n","-c"])).find(n=>!/^\d+$/.test(n)));return e?`Read the end of ${wt(e)}`:null},wc:t=>{let e=Ot(ut(t)[0]);return e?`Count lines in ${wt(e)}`:"Count input lines"},ls:t=>{let e=Ot(ut(t)[0]);return e&&e!=="."?`List files in ${Or(e)}`:"List files"},find:t=>Ot(t[1])&&!jh(t[1])&&t[1]!=="."?`Find files in ${Or(t[1])}`:"Find files",tree:()=>"List the directory tree",mkdir:t=>{let e=Ot(ut(t)[0]);return e?`Create directory ${Or(e)}`:null},touch:t=>{let e=Ot(ut(t)[0]);return e?`Create ${wt(e)}`:null},rm:t=>{let e=Ot(ut(t)[0]);return e?`Delete ${Or(e)}`:null},mv:t=>{let e=Ot(ut(t)[0]);return e?`Move ${wt(e)}`:null},cp:t=>{let e=Ot(ut(t)[0]);return e?`Copy ${wt(e)}`:null},chmod:t=>{let e=Ot(ut(t)[1]);return e?`Change permissions on ${wt(e)}`:null},diff:t=>{let e=ut(t);return e.length>=2?`Compare ${wt(e[0])} and ${wt(e[1])}`:null},curl:t=>{let e=Dh(t);return e?`Fetch ${Mr(e,56)}`:"Fetch a URL"},wget:t=>{let e=Dh(t);return e?`Fetch ${Mr(e,56)}`:"Fetch a URL"},npm:vl,pnpm:vl,yarn:vl,npx:t=>{let e=ut(t)[0];return e?`Run ${e}`:null},node:t=>Tl(t,"Node"),python:t=>Tl(t,"Python"),python3:t=>Tl(t,"Python"),make:t=>{let e=ut(t)[0];return e?`Run make ${e}`:"Run make"},systemctl:NA,journalctl:t=>{let e=t.findIndex(r=>r==="-u"||r==="--unit"||r==="--user-unit"),n=e!==-1?t[e+1]:null;return n?`Read ${n.replace(/\.service$/,"")} logs`:"Read system logs"},tmux:t=>{let e=ut(t,new Set(["-S","-L","-t","-f"]))[0];return{ls:"List tmux sessions","list-sessions":"List tmux sessions","kill-session":"Kill a tmux session","send-keys":"Send keys to a tmux session","capture-pane":"Capture a tmux pane","new-session":"Start a tmux session"}[e]||null},ps:()=>"List processes",which:t=>{let e=ut(t)[0];return e?`Locate ${e}`:null},command:t=>t[1]==="-v"&&t[2]?`Locate ${t[2]}`:null,fly:t=>{let e=ut(t)[0];return e==="deploy"?"Deploy to Fly":e?`Run fly ${e}`:null}},BA=new Set(["pwd","echo","cd","sleep","true","false","test","[","set","export","exit","read"]);function DA(t){let e=Go(Ko(t));if(!e.length)return null;if(/^(ba|z)?sh$/.test(wt(e[0]))&&(e[1]==="-lc"||e[1]==="-c")&&e[2])return Al(e[2]);let n=wt(e[0]);if(BA.has(n))return null;if(e.includes("--version"))return`Check ${n} version`;if(n.endsWith(".sh"))return`Run ${n}`;let r=PA[n];return r?r(e.map((o,s)=>s===0?n:o)):null}function Al(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=$A(e);for(let i=0;i<r.length-1;i++){let c=Go(Ko(r[i])),l=Go(Ko(r[i+1]));if(!(!c.length||!l.length)&&["nl","cat"].includes(wt(c[0]))&&wt(l[0])==="sed"){let u=Lh(l),f=ut(c)[0];if(u&&f)return Mr(u.from===u.to?`Read line ${u.from} of ${wt(f)}`:`Read lines ${u.from}\u2013${u.to} of ${wt(f)}`,88)}}let o=[];for(let i of r){let c=DA(i);c&&o.push({d:c,head:wt(Go(Ko(i))[0]||""),sub:ut(Go(Ko(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":Mr(s?s.d:o[0].d,88)}var _l=new Set(["Bash"]);function Cr(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 mn(t,e=""){let n=e.trim();return n||Al(t)||Cr(t)}function El(t,e){if(t){if(Array.isArray(t)){for(let n of t)El(n,e);return}if(typeof t=="object"){if(t.type==="tool_use"&&_l.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))El(n,e)}}}async function Fh(t,{maxBytes:e=1048576,limit:n=400}={}){let r;try{r=await Ut(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(El(c.content,l),!l.length)continue;let u=i.timestamp?Date.parse(i.timestamp):null;for(let f of l)o.push({...f,summary:mn(f.command,f.description),ts:u})}return o.slice(-n)}function Uh(t,e){let n=e.filter(l=>l.role==="assistant"&&(l.text||"").trim()).sort((l,u)=>(l.ts||0)-(u.ts||0)),r=[...t].sort((l,u)=>(l.ts||0)-(u.ts||0)),o=[],s=0,a=0,i=l=>{let u=l.n??a+1;return u>a&&(a=u),u};for(let l of n){let u=r.findIndex((m,w)=>w>=s&&(m.ts||0)>(l.ts||0)),f=u===-1?r.length:u,d=r.slice(s,f);if(s=f,!d.length)continue;let p=d.map(m=>({...m,n:i(m)}));o.push({id:`batch-${l.ts}-${l.box}`,anchorTs:l.ts,anchorBox:l.box,agent:l.agent,boxRole:l.boxRole,commands:p,ts:Math.max(...p.map(m=>m.ts||0)),pending:!1})}let c=r.slice(s);if(c.length){let l=c.map(u=>({...u,n:i(u)}));o.push({id:`batch-pending-${l[0]?.box||"lead"}`,anchorTs:null,anchorBox:l[l.length-1]?.box??null,agent:l[l.length-1]?.agent??"claude",boxRole:l[l.length-1]?.boxRole??"lead",commands:l,ts:Math.max(...l.map(u=>u.ts||0)),pending:!0})}return o}var jA=/^Async (?:agent|task) launched/i,LA=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 Hh(t,{maxBytes:e=2097152,limit:n=100}={}){let r;try{r=await Ut(t,e)}catch{return[]}let o=new Map,s=new Set;for(let i of r.split(`
|
|
95
|
+
`)){let c=i.trim();if(!c||c[0]!=="{")continue;let l;try{l=JSON.parse(c)}catch{continue}if(l.isSidechain)continue;let u=l.message?.content;if(!Array.isArray(u))continue;let f=l.timestamp?Date.parse(l.timestamp):null;for(let d of u)if(!(!d||typeof d!="object"))if(d.type==="tool_use"&&d.id){if(d.name==="Agent"){let p=d.input||{};o.set(d.id,{id:d.id,kind:"agent",name:String(p.description||"").trim()||null,detail:String(p.subagent_type||"").trim()||null,ts:f,proven:!1})}else if(d.name==="Bash"&&d.input?.run_in_background===!0){let p=String(d.input.command||"").trim();o.set(d.id,{id:d.id,kind:"command",name:String(d.input.description||"").trim()||(p?Cr(p):null),detail:p?Cr(p):null,ts:f,proven:!0})}}else d.type==="tool_result"&&d.tool_use_id&&jA.test(LA(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 Vo from"node:fs";import UA from"node:fs/promises";import zn from"node:path";import HA from"node:os";import WA from"node:crypto";import qA from"node:zlib";var Ua=new Set(["Shell","Read","Grep","Glob","StrReplace","Write","ReadLints","Delete","ApplyPatch","Await"]),ae=(t,e=88)=>t.length>e?`${t.slice(0,e).trimEnd()}\u2026`:t,Nr=t=>String(t).replace(/\/+$/,"").split("/").pop()||String(t);function hn(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 Il=t=>`"${String(t).replace(/"/g,'\\"')}"`;function FA(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 Wh(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:ae(`read ${hn(r,n)}${o?` (lines ${o}\u2013${s??"end"})`:""}`,140),description:o?`Read lines ${o}\u2013${s??"end"} of ${Nr(r)}`:`Read ${Nr(r)}`}}case"Grep":{let r=e?.pattern;if(!r)return null;let o=e?.path?hn(e.path,n):null,s=e?.glob?` --glob ${Il(e.glob)}`:"",a=ae(String(r),40);return{command:ae(`grep ${Il(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?hn(e.target_directory,n):null;return{command:ae(`glob ${Il(r)}${o&&o!=="."?` ${o}`:""}`,140),description:`Find files matching \u201C${ae(String(r),40)}\u201D`}}case"StrReplace":return e?.path?{command:ae(`edit ${hn(e.path,n)}`,140),description:`Edit ${Nr(e.path)}`}:null;case"Write":return e?.path?{command:ae(`write ${hn(e.path,n)}`,140),description:`Write ${Nr(e.path)}`}:null;case"Delete":return e?.path?{command:ae(`delete ${hn(e.path,n)}`,140),description:`Delete ${Nr(e.path)}`}:null;case"ReadLints":{let o=(Array.isArray(e?.paths)?e.paths:[]).map(s=>hn(s,n)).filter(Boolean);return{command:ae(o.length?`lints ${o.join(" ")}`:"lints",140),description:"Check lints"}}case"ApplyPatch":{let r=FA(e);if(!r.length)return null;let o=r.length>1?` (+${r.length-1} more)`:"";return{command:ae(`apply_patch ${hn(r[0],n)}${o}`,140),description:r.length>1?`Patch ${r.length} files`:`Patch ${Nr(r[0])}`}}case"Await":{let r=e?.shell_id??e?.task_id;return{command:ae(`await ${r??"background output"}`,140),description:"Wait for a background command"}}default:return null}}var KA=process.env.BOSTRAT_CURSOR_CHATS||zn.join(HA.homedir(),".cursor","chats");function Gh(t){let e=WA.createHash("md5").update(String(t)).digest("hex");return zn.join(KA,e)}function Vh(t){try{let e=JSON.parse(Vo.readFileSync(zn.join(t,"meta.json"),"utf8"));return{createdAtMs:Number(e.createdAtMs)||null,updatedAtMs:Number(e.updatedAtMs)||Number(e.createdAtMs)||null,hasConversation:e.hasConversation===!0}}catch{return null}}function GA(t){let e=Gh(t),n;try{n=Vo.readdirSync(e,{withFileTypes:!0}).filter(o=>o.isDirectory())}catch{return null}let r=null;for(let o of n){let s=zn.join(e,o.name),a=zn.join(s,"store.db");if(!Vo.existsSync(a))continue;let i=Vh(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 Ha(t,e){if(!t)return null;if(e){let n=zn.join(Gh(t),e),r=zn.join(n,"store.db");if(Vo.existsSync(r))return{id:e,dir:n,store:r,...Vh(n)||{createdAtMs:null,updatedAtMs:null}}}return GA(t)}function qh(t,e){let n=t.prepare("SELECT data FROM blobs WHERE id = ?").get(e);return n?Buffer.from(n.data):null}function VA(t){if(!t)return null;let e=t;if(e.length>1&&e[0]===31&&e[1]===139)try{e=qA.gunzipSync(e)}catch{}try{return JSON.parse(e.toString("utf8"))}catch{return null}}function zA(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 JA(t,e){if(!Array.isArray(t))return[];let n=[];for(let r of t){if(!r||r.type!=="tool-call"||!Ua.has(r.toolName))continue;let o=Wh(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 XA=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():"",YA=t=>Array.isArray(t)?t.filter(e=>e&&e.type==="tool-call"&&e.toolName).map(e=>e.toolName):[],Kh=process.getBuiltinModule?.("node:sqlite"),Pr={turns:[],trailingCommands:[],model:null,requestIds:[]};async function Rl(t,e){if(!Kh)return Pr;let{DatabaseSync:n}=Kh,r;try{r=new n(t,{readOnly:!0})}catch{return Pr}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 Pr;let s;try{s=JSON.parse(Buffer.from(String(o.value),"hex").toString("utf8"))}catch{return Pr}let a=s.lastUsedModel||null,i=s.latestRootBlobId?qh(r,s.latestRootBlobId):null;if(!i)return{...Pr,model:a};let c=[],l=[],u=[],f=null,d=[];for(let p of zA(i)){let m=VA(qh(r,p));if(!m)continue;let w=m.providerOptions?.cursor;if(w?.requestId&&d.push(w.requestId),w?.modelProviderMessageId&&d.push(w.modelProviderMessageId),m.role!=="assistant")continue;if(Array.isArray(m.content))for(let S of m.content){let _=S?.providerOptions?.cursor?.modelName;_&&(f=_)}l.push(...JA(m.content,e).map((S,_)=>({...S,id:S.id||`${p}:${_}`}))),u.push(...YA(m.content));let y=XA(m.content);if(!y)continue;let b=[...new Set(u)];u=[],c.push({id:p,text:y,tools:b.length?b:void 0,commands:l,model:f}),l=[]}return{turns:c,trailingCommands:l,model:a,requestIds:d}}catch{return Pr}finally{try{r.close()}catch{}}}function zh(t,e){let n=t.updatedAtMs;if(!n)try{n=Math.round(Vo.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 Jh(t,e,{limit:n=80}={}){let r=Ha(t,e);if(!r)return[];let{turns:o,model:s}=await Rl(r.store,t);if(!o.length)return[];let a=o.slice(-n),{tsAt:i}=zh(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 Xh(t,e){let n=Ha(t,e);if(!n)return null;let{turns:r,requestIds:o,model:s}=await Rl(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 Yh(t,e,{limit:n=80}={}){let r=Ha(t,e);if(!r)return[];let{turns:o,trailingCommands:s}=await Rl(r.store,t);if(!o.length&&!s.length)return[];let a=o.slice(-n),{end:i,tsAt:c}=zh(r,a.length||1),l=[];a.forEach((u,f)=>{for(let d of u.commands)l.push({...d,summary:mn(d.command,d.description),ts:c(f)})});for(let u of s)l.push({...u,summary:mn(u.command,u.description),ts:i+1});return l}async function Zh(t,e){let n=Ha(t,e);if(!n)return null;if(n.updatedAtMs)return n.updatedAtMs;try{return Math.round((await UA.stat(n.store)).mtimeMs)}catch{return null}}import Qh from"node:fs";import Ol from"node:fs/promises";import $l from"node:path";import ZA from"node:os";var QA=process.env.BOSTRAT_CODEX_SESSIONS||$l.join(process.env.CODEX_HOME||$l.join(ZA.homedir(),".codex"),"sessions");async function tw(t){try{return await Ol.realpath(t)}catch{return t}}function tE(t=QA){let e=[],n=r=>{let o;try{o=Qh.readdirSync(r,{withFileTypes:!0})}catch{return}for(let s of o){let a=$l.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:Qh.statSync(a).mtimeMs})}catch{}}}};return n(t),e.sort((r,o)=>o.mtimeMs-r.mtimeMs)}async function eE(t,e=1048576){let n;try{n=await Ol.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 nE(t){let e=await eE(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 Jn(t,e){if(!t&&!e)return null;let n=tE();if(e){let o=n.find(s=>s.id===e);return o||null}let r=await tw(t);for(let o of n){let s=await nE(o.file);if(s&&(s===t||await tw(s)===r))return o}return null}var rE=new Set(["output_text","text"]),oE=t=>typeof t=="string"?t.trim():Array.isArray(t)?t.filter(e=>e&&typeof e.text=="string"&&rE.has(e.type)).map(e=>e.text).join(`
|
|
98
|
+
`).trim():"";async function ew(t,e,{limit:n=80,maxBytes:r=524288}={}){let o=await Jn(t,e);if(!o)return[];let s;try{s=await Ut(o.file,r)}catch{return[]}let a=[],i=null,c=[],l=!1,u=!1,f=null;for(let d of s.split(`
|
|
99
|
+
`)){let p=d.trim();if(!p||p[0]!=="{")continue;let m;try{m=JSON.parse(p)}catch{continue}if((m.type==="turn_context"&&m.payload?.model||m.type==="session_meta"&&m.payload?.model)&&(i=m.payload.model),m.type==="event_msg"){let S=m.payload?.type;S==="task_started"?(l=!0,u=!1,f=m.payload?.turn_id||null):S==="task_complete"&&l&&(l=!1,!u&&m.payload?.last_agent_message==null&&a.push({role:"assistant",failed:!0,text:Cl,sourceId:m.payload?.turn_id||f||void 0,ts:m.timestamp?Date.parse(m.timestamp):null,model:i}));continue}if(m.type!=="response_item")continue;let w=m.payload;if(!w)continue;if((w.type==="function_call"||w.type==="custom_tool_call")&&w.name){u=!0,Ml.has(w.name)||c.push(w.name);continue}if(w.type!=="message"||w.role!=="assistant")continue;u=!0;let y=oE(w.content),b=c.length?c.slice():null;c=[],!(!y&&!b)&&a.push({role:"assistant",text:y,tools:b||void 0,ts:m.timestamp?Date.parse(m.timestamp):null,model:i})}if(i)for(let d of a)d.model||(d.model=i);return a.slice(-n)}var zo=new Set(["exec_command","shell","exec"]),Ml=new Set(["wait","write_stdin"]),Cl="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 sE(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 aE(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 iE(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=aE(i.slice(c.index+c[0].length).trimStart());l&&l.trim()&&o.push(l.trim())}),o}async function nw(t,e,{limit:n=400,maxBytes:r=524288}={}){let o=await Jn(t,e);if(!o)return[];let s;try{s=await Ut(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"&&zo.has(u.name)){let p;try{p=JSON.parse(u.arguments||"{}")}catch{continue}let m=sE(p);m&&(f=[m])}else u.type==="custom_tool_call"&&u.name==="exec"&&(f=iE(u.input));if(!f.length)continue;let d=u.call_id||u.id||null;f.forEach((p,m)=>{a.push({id:d?m?`${d}:${m}`:d:null,tool:u.name,command:p,description:"",summary:mn(p),ts:l.timestamp?Date.parse(l.timestamp):null})})}return a.slice(-n)}async function rw(t,e){let n=await Jn(t,e);if(!n)return null;try{return Math.round((await Ol.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}},Nl=class extends Br{transcript(e){return!e?.cwd||!e?.sessionId?null:$e(e.cwd,e.sessionId)}async readMessages(e,n){let r=this.transcript(e);return r?Sc(r,n):[]}async readCommands(e,n){let r=this.transcript(e);return r?Fh(r,n):[]}async readJobs(e,n){let r=this.transcript(e);return r?Hh(r,n):[]}async updatedAt(e){let n=this.transcript(e);if(!n)return null;try{return Math.round((await cE.stat(n)).mtimeMs)}catch{return null}}},Pl=class extends Br{async readMessages(e,n){return Jh(e.cwd,e.sessionId,n)}async readCommands(e,n){return Yh(e.cwd,e.sessionId,n)}async updatedAt(e){return Zh(e.cwd,e.sessionId)}},Bl=class extends Br{sessionId(e){return e?.pinned?e.sessionId:null}async readMessages(e,n){return ew(e.cwd,this.sessionId(e),n)}async readCommands(e,n){return nw(e.cwd,this.sessionId(e),n)}async updatedAt(e){return rw(e.cwd,this.sessionId(e))}},lE=new Br("unknown"),ow=new Map;function Dl(t){return ow.set(t.agent,t),t}function Jo(t){return ow.get(t)||lE}Dl(new Nl("claude",_l));Dl(new Pl("cursor",Ua));Dl(new Bl("codex",zo));var jl=t=>e=>({...e,box:t.name,agent:t.agent,boxRole:t.role});async function Ll(t,e={}){return(await Jo(t.agent).readMessages(t,e)).map(jl(t))}async function sw(t,e={}){return(await Jo(t.agent).readCommands(t,e)).map(jl(t))}async function aw(t,e={}){return(await Jo(t.agent).readJobs(t,e)).map(jl(t))}var lw=/to navigate\b.*\bEsc to cancel|Enter to select\b/,Hl=/Chat about this/i,Xn=/^\s*(❯)?\s*(\d+)\.\s+(?:\[([ xX✔])\]\s+)?(.+?)\s*$/,iw=/^(type something|chat about this|submit)\b/i,Yn=/^[─—-]{6,}\s*$/,Wl=/[\u2500-\u257F\u2550-\u256F┌┐└┘├┤│┬┴┼─✂]/;function Zn(t){let e=t.search(Wl);return(e>=0?t.slice(0,e):t).trim()}function uE(t){if(!t)return"";let e=t.replace(/[│┌└├─┘┤┬┴┼─✂].*$/,"").trim();return!e||/hidden|\d+\s+lines?/i.test(e)?"":e}function dE(t){let e=String(t).trim();return!!(!e||Yn.test(t)||Wl.test(e)||/hidden|\d+\s+lines?/i.test(e)||/notespress|press n to add/i.test(e)||/^\s*notes?\s*$/i.test(e))}function fE(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=pE(n,e);return n.forEach((a,i)=>{a.current=i===s}),{tabs:n,questionIndex:s,questionTotal:n.length,currentTabLabel:n[s].label}}function pE(t,e){let n=String(e).toLowerCase(),r=t.findIndex(c=>!c.answered),o=r>=0?r:t.length-1;if(!n)return o;let s=t.map(c=>{let l=c.label.toLowerCase(),u=l.split(/\s+/).filter(d=>d.length>3&&n.includes(d)).length;if(u)return u;let f=l.slice(0,8).trim();return f.length>=4&&n.includes(f)?1:0}),a=Math.max(...s);if(a===0||s[o]===a)return o;let i=s.flatMap((c,l)=>c===a?[l]:[]);return i.length===1?i[0]:o}function cw(t){return t.replace(/[←→✔☐☒●▶]/g," ").replace(/\bSubmit\b/g," ").replace(/\s+/g," ").trim()}var uw=/^\s*Switch model\?\s*$/m;function mE(t,e=""){let n=String(t);if(!uw.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(Xn);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&&!Yn.test(r[u])&&a.push(d)}}if(s.length<2)return null;let i=s.map(u=>({label:Zn(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 dw=/^\s*Enter to confirm\s*·\s*Esc to cancel\s*$/,hE=/^\s*[─—▔-]{6,}\s*$/;function wE(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||!dw.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(Xn);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&&!(hE.test(n[c])||Yn.test(n[c]));c--){let l=n[c].trim();l&&a.unshift(l)}let i=s.map(c=>({label:Zn(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 gE(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(Xn);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 Fl=/Resume from summary/i,yE=/^\s*This session is\b/;function bE(t,e=""){let n=String(t);if(!Fl.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
105
|
+
`),o=-1;for(let u=r.length-1;u>=0;u--)if(yE.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(Xn);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&&!Yn.test(r[u])&&a.push(d)}}if(s.length<2||!s.some(u=>Fl.test(u.label)))return null;let i=s.map(u=>({label:Zn(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 Qn=/shift\+tab to cycle|⏵⏵ bypass permissions on|⏸ manual mode on/;function ql(t){let e=[];for(let r=0;r<t.length;r++){let o=t[r].match(Xn);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 fw=/Choose the text style that looks best with your terminal/,pw=/Let's get started\./;function kE(t,e=""){let n=String(t);if(!fw.test(n)||!pw.test(n)||Qn.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
106
|
+
`),o=ql(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:Zn(s.label),description:""}))}]}:null}var mw=/Select login method:/;function xE(t,e=""){let n=String(t);if(!mw.test(n)||Qn.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
107
|
+
`),o=ql(r);if(!o)return null;let s=o.map(a=>{let[i,...c]=a.label.split("\xB7");return{label:Zn(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 Kl=/Paste code here if prompted/,SE=/^[A-Za-z0-9%&=+._~#?/:\-]+$/;function vE(t,e){let n=t[e].trim();for(let r=e+1;r<t.length;r++){let o=t[r].trim();if(!o||!SE.test(o)||Kl.test(t[r]))break;n+=o}return n}function TE(t,e=""){let n=String(t);if(!Kl.test(n)||Qn.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=vE(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 Ul=/OAuth error:/,AE=/Press Enter to retry\./;function EE(t,e=""){let n=String(t);if(!Ul.test(n)||!AE.test(n)||Qn.test(n))return null;let s=([...n.replace(/\r/g,"").split(`
|
|
109
|
+
`)].reverse().find(a=>Ul.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 hw=/Quick safety check: Is this a project you created or one you trust/;function _E(t,e=""){let n=String(t);if(!hw.test(n)||Qn.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
110
|
+
`),o=ql(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:Zn(s.label),description:""}))}]}:null}var ww=/^(.{0,60}?)Press\s+Enter\s+to\s+continue\s*[.…]{0,3}\s*$/;function IE(t,e=""){let n=String(t);if(Qn.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(ww);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(Yn.test(r[l])||Wl.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 RE(t){let e=-1;for(let r=t.length-1;r>=0;r--)if(Hl.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 $E(t,e=""){let n=String(t);if(!lw.test(n)||!Hl.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
112
|
+
`),o=RE(r);if(!o)return null;let{headerLine:s,chatLine:a,optStart:i,optEnd:c}=o,l=[],u=-1;for(let v=i;v<c;v++){let B=r[v].match(Xn);B&&(u<0&&(u=v),l.push({line:v,cursor:!!B[1],checked:B[3]!=null&&B[3]!==" ",hasCheckbox:B[3]!=null,label:B[4].trim(),desc:""}))}if(!l.length)return null;for(let v of l)for(let B=v.line+1;B<Math.min(r.length,v.line+4)&&!(Xn.test(r[B])||Yn.test(r[B]));B++){let D=r[B].trim();if(!(!D||dE(r[B])||iw.test(D))){v.desc=uE(D);break}}let f=l.filter(v=>!iw.test(v.label));if(!f.length)return null;let d=f.some(v=>v.hasCheckbox),p=Math.max(0,f.findIndex(v=>v.cursor)),m=s>=0?s+1:0,w=[];for(let v=m;v<u;v++){let B=r[v].trim();B&&!Yn.test(r[v])&&!/[☐☒]/.test(r[v])&&w.push(B)}let y=w.join(" ")||(s>=0?cw(r[s]):"")||"The agent is asking a question",b=s>=0?fE(r[s],y):null,S=b?.currentTabLabel||(s>=0?cw(r[s]):""),_=f.map(v=>({label:Zn(v.label),description:v.desc})),$=_.map(v=>v.label).join("|"),I=`claude:${b?`q${b.questionIndex+1}/${b.questionTotal}`:"q1/1"}:${S}:${y}:${$}`.slice(0,240),C=b?`claude:ask:${b.tabs.map(v=>v.label).join("|")}`.slice(0,240):I;return{provider:"claude",box:e,multiSelect:d,cursorIndex:p,promptId:I,groupId:C,...b?{questionIndex:b.questionIndex,questionTotal:b.questionTotal,currentTabLabel:b.currentTabLabel,tabs:b.tabs}:{},questions:[{question:y,header:S,multiSelect:d,options:_}]}}function Dr(t,e){let n=String(e||"");return n?t==="claude"?lw.test(n)&&Hl.test(n)||uw.test(n)||Fl.test(n)||dw.test(n.trimEnd().split(`
|
|
113
|
+
`).pop()||"")?!0:Qn.test(n)?!1:fw.test(n)&&pw.test(n)||mw.test(n)||Kl.test(n)||Ul.test(n)||hw.test(n)||ww.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 tr(t,e){return!t||!e?null:t.agent==="claude"?gE(e,t.name)||$E(e,t.name)||mE(e,t.name)||bE(e,t.name)||EE(e,t.name)||TE(e,t.name)||xE(e,t.name)||kE(e,t.name)||_E(e,t.name)||IE(e,t.name)||wE(e,t.name):t.agent==="cursor"||t.agent==="codex"?sp(t.agent,e,t.name):null}async function Gl(t){if(!t)return null;let e=await rn(t.name).catch(()=>"");return tr(t,e)}function gw(t,e){let n=Array.isArray(e?.[0])?e[0].filter(a=>Number.isInteger(a)&&a>=0):[],r=Number.isInteger(t?.cursorIndex)?t.cursorIndex:0,o=(a,i)=>{let c=i-a;return Array.from({length:Math.abs(c)},()=>c>0?"Down":"Up")},s=[];if(t?.multiSelect){let a=r;for(let i of[...n].sort((c,l)=>c-l))s.push(...o(a,i),"Enter"),a=i;s.push("Tab","Enter")}else{let a=n.length?n[0]:0;s.push(...o(r,a),"Enter")}return s}function yw(t,e){let n=Number.isInteger(t?.questionIndex)?t.questionIndex:0,o=(Number.isInteger(e)?e:n)-n;if(!o)return[];let s=o>0?"Tab":"BTab";return Array.from({length:Math.abs(o)},()=>s)}function jr(t){return typeof t=="string"?t.trim():""}var bw=/^\s*─{8,}\s*$/,OE=/^❯[ ]?(.*)$/;function Xo(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(bw.test(n[c])){r=c;break}if(r<=0)return null;let o=-1;for(let c=r-1;c>=0;c--)if(bw.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(OE);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 _t(t){return String(t||"").replace(/\s+/g," ").trim()}function kw(t,e,{slackMs:n=2e4}={}){let r=_t(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&&_t(o.text).includes(r))}function xw(t,e,n,{busy:r=!1}={}){let o=Xo(t,n);return o&&Vl(e,o)?"stuck":r||o?"submitted":"unknown"}function Vl(t,e){let n=_t(t),r=_t(e).replace(/^…\s*/,"").replace(/\s*…$/,"");return!n||!r||r.length<Math.min(n.length,12)?!1:n===r||n.includes(r)}import ME from"node:crypto";var zl=new Set(["cursor","codex"]),vw=4,CE=4,NE=2,PE=24,Sw=/^[ \t]{0,3}(`{3,})bostrat-question[ \t]*\r?\n([\s\S]*?)\r?\n[ \t]{0,3}\1`*[ \t]*$/gm,Yo=t=>typeof t=="string"?t.trim():"";function BE(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,vw)){let o=Yo(r?.question);if(!o)return null;let s=Array.isArray(r?.options)?r.options:[],a=[];for(let i of s.slice(0,CE)){let c=Yo(i?.label);if(!c)return null;let l=Yo(i?.description);a.push(l?{label:c,description:l}:{label:c})}if(a.length<NE)return null;n.push({question:o,header:Yo(r?.header).slice(0,PE),multiSelect:r?.multiSelect===!0,options:a})}return n.length?n:null}function Jl(t){if(typeof t!="string"||!t.includes("```bostrat-question"))return null;Sw.lastIndex=0;let e=null;for(let o;o=Sw.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=BE(n);return r?{text:t.slice(0,e.index).trimEnd(),questions:r}:null}function Xl(t,e,n){let r=ME.createHash("sha256").update(JSON.stringify(n)).digest("hex").slice(0,8);return`${t}:ask:${e||"turn"}:${r}`}function Yl(t,{leadBoxId:e,provider:n,isAnswered:r,maxQuestions:o=vw}={}){if(!Array.isArray(t)||!e||!zl.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||Jl(s.text)?.questions;if(!a?.length)return null;let i=a.slice(0,Math.max(1,o)),c=Xl(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 Tw(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=Yo(n),s;return r.length?r.length===1?s=`${r[0].name}: ${r[0].picks}`:s=`Answers:
|
|
116
116
|
${r.map(a=>`- ${a.name}: ${a.picks}`).join(`
|
|
117
117
|
`)}`:s=o,r.length&&o&&(s+=`
|
|
118
|
-
${o}`),s||""}var
|
|
118
|
+
${o}`),s||""}var Aw=`
|
|
119
119
|
When you are BLOCKED on a decision only the operator can make \u2014 an ambiguous
|
|
120
120
|
requirement, an irreversible choice, several valid approaches \u2014 do not bury the
|
|
121
121
|
question in prose. End your message with exactly ONE fenced code block tagged
|
|
@@ -135,44 +135,42 @@ your turn. The operator sees the questions as an interactive card in their chat
|
|
|
135
135
|
answer. Never use the block for rhetorical questions, progress updates, or
|
|
136
136
|
anything you can decide yourself; keep asking in prose for questions without
|
|
137
137
|
enumerable options.
|
|
138
|
-
`;import{execFile as
|
|
139
|
-
`).filter(Boolean);return e&&(r=r.filter(o=>{let s=o.slice(3);return!(s===".bostrat"||s===".bostrat/"||s.startsWith(".bostrat/")||o.startsWith("??")&&(s==="AGENTS.md"||s==="CLAUDE.md"))})),r.length>0}async function
|
|
140
|
-
`).map(s=>s.trim()).filter(Boolean)}async function
|
|
141
|
-
`).map(n=>n.trim()).filter(Boolean)}catch{return[]}}import
|
|
142
|
-
`),n=e.findIndex(o=>/Select Model and Effort/i.test(o));if(n<0)return null;let r=[];for(let o=n+1;o<e.length;o++){let s=e[o].match(/^\s*(›\s*)?(\d+)\.\s+(\S+?)(?:\s+\((current|default)\))?(?:\s{2,}|\s*$)/);if(s)r.push({n:Number(s[2]),id:s[3],current:s[4]==="current",active:!!s[1]});else if(r.length&&/Press enter|esc to go back/i.test(e[o]))break}return r.length?r:null}function
|
|
143
|
-
`),n=e.findIndex(o=>/Select Reasoning Level/i.test(o));if(n<0)return null;let r=[];for(let o=n+1;o<e.length;o++){let s=e[o].match(/^\s*(›\s*)?(\d+)\.\s+([A-Za-z ]+?)(?:\s+\((default|current)\))?(?:\s{2,}|\s*$)/);if(s)r.push({n:Number(s[2]),label:s[3].trim(),isDefault:!!s[4],active:!!s[1]});else if(r.length&&/Press enter|esc to go back/i.test(e[o]))break}return r.length?r:null}function
|
|
144
|
-
`);for(let n=e.length-1;n>=0;n--){let r=e[n].match(/Model changed to\s+(\S+)(?:\s+(\S+))?\s*$/i);if(r)return{model:r[1],effort:r[2]||null}}return null}function
|
|
145
|
-
`),n=e.findIndex(s=>/Available models|Models matching/i.test(s));if(n<0)return null;let r=/Models matching/i.test(e[n]),o=[];for(let s=n+1;s<e.length;s++){let a=e[s];if(/^\s*Filter:/i.test(a)||!a.trim())continue;if(/to filter|Enter to select|^\s*\d+-\d+ of \d+/i.test(a))break;let i=a.match(/^\s*(→\s+)?(.+?)\s*$/);if(!i)continue;let c=i[2].replace(/\s*\(Tab to modify\)\s*$/i,""),l=null,u=c.split(/\s{2,}/);u.length>1&&(c=u[0],l=u.slice(1).join(" ").trim()||null),c&&o.push({label:c.trim(),params:l,active:!!i[1]})}return o.length?{filtered:r,rows:o}:null}function _w(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&&!Tw.has(o))throw new Error(`unknown codex effort "${o}" (low | medium | high | xhigh)`);return{model:r,effort:o||null}}if(t==="cursor"){let r=Aw.find(o=>o.alias===n);return{model:n,filter:r?r.label:n}}if(!IE.has(n))throw new Error(`unknown model "${n}"`);return{model:n}}async function Vo(t,e,{timeoutMs:n=EE}={}){let r=Date.now()+n;for(;;){let o=await ft(t,{lines:80}).catch(()=>""),s=e(o);if(s)return s;if(Date.now()>r)return null;await Ba(_E)}}async function OE(t,e){return await St(t,`/model ${e.model}`,_e("claude")),{verified:!1,note:"confirmed when the next turn's transcript model flips"}}async function CE(t,e){await Ws(t),await St(t,"/model",{enter:!1}),await Ba(250),await bt(t,["Enter"],{kind:"model-switch"});let n=await Vo(t,kw);if(!n)throw new Error("codex model picker didn't open");let r=n.find(i=>i.id===e.model);if(!r)throw await bt(t,["Escape"],{kind:"model-switch"}).catch(()=>{}),new Error(`unknown model "${e.model}" \u2014 codex offers: ${n.map(i=>i.id).join(", ")}`);let o=i=>{let c=$E(i);return!c||c.model!==e.model||e.effort&&c.effort&&c.effort.toLowerCase()!==e.effort.toLowerCase()?null:c};await St(t,String(r.n),{enter:!1,kind:"model-switch"});let s=await Vo(t,i=>xw(i)||!kw(i)&&o(i)&&{done:o(i)});if(!s)throw await bt(t,["Escape"],{kind:"model-switch"}).catch(()=>{}),new Error("codex reasoning-level picker didn't open");if(!s.done){let i=e.effort?Tw.get(e.effort):null,c=i?s.find(l=>l.label.toLowerCase()===i.toLowerCase()):s.find(l=>l.isDefault)||s[0];if(!c)throw await bt(t,["Escape","Escape"],{kind:"model-switch"}).catch(()=>{}),new Error(`codex doesn't offer effort "${e.effort}" for ${e.model}`);await St(t,String(c.n),{enter:!1,kind:"model-switch"})}let a=s.done||await Vo(t,i=>!xw(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 ME(){let t=bw.join(process.env.CURSOR_CONFIG_DIR||bw.join(AE.homedir(),".cursor"),"cli-config.json");try{return JSON.parse(await TE.readFile(t,"utf8"))?.model||null}catch{return null}}async function NE(t,e){await Ws(t),await Ks(t,`/model ${e.filter}`,{kind:"model-switch"});let n=await Vo(t,Sw),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 Vo(t,Sw,{timeoutMs:1500});if(s=l?.rows.find(u=>u.active)||null,s&&o(s))break;if(!l||!l.rows.some(o)){s=null;break}await bt(t,["Down"],{kind:"model-switch"}),await Ba(250)}if(!n||!s||!o(s)){await bt(t,["Escape"],{kind:"model-switch"}).catch(()=>{});let c=Array(`/model ${e.filter}`.length).fill("BSpace");throw await bt(t,c,{settleMs:15,kind:"model-switch"}).catch(()=>{}),new Error(n?`cursor picker has no model matching "${e.filter}"`:"cursor model picker didn't open")}await bt(t,["Enter"],{kind:"model-switch"}),await Ba(800);let a=await ME();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 Iw(t,e,n){return t==="codex"?CE(e,n):t==="cursor"?NE(e,n):OE(e,n)}var Kt=t=>t||null;function ja(t){let e=t||{},n=PE(e);return n&&(n.payload={ts:e.at,...n.payload}),n}function PE(t){switch(t.type){case"said":return{type:"turn.user",actor:"user",source:"orchestrator",boxId:t.box,provider:Kt(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:Kt(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:Kt(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:Kt(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:Kt(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:Kt(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:Kt(t.agent),payload:{...Pr(t)}};case"subtask-failed":return{type:"error",actor:"system",source:"orchestrator",boxId:t.box||t.to,provider:Kt(t.agent||t.toAgent),payload:{kind:"subtask-failed",...Pr(t)}};case"subtask-resumed":return{type:"box.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Kt(t.agent),payload:{change:"subtask-resumed",...Pr(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",...Pr(t)}};case"archived":case"unarchived":case"track-deleted":return{type:"box.state.changed",actor:"user",source:"orchestrator",payload:{change:t.type,...Pr(t)}};case"rehydrated":return{type:"box.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Kt(t.agent),payload:{change:"rehydrated"}};case"retagged":return{type:"box.state.changed",actor:"user",source:"orchestrator",payload:{change:"retagged",...Pr(t)}};case"rehydrate-failed":return{type:"error",actor:"system",source:"orchestrator",boxId:t.box,provider:Kt(t.agent),payload:{kind:"rehydrate-failed",reason:t.reason}};default:return null}}var Da=Promise.resolve(),Yl=t=>(Da=Da.then(t).catch(()=>{}),Da),$w=()=>Da;function Ow(t,e){let n=ja(e);if(!n)return;let r=e?.type==="track-deleted"?{id:t.id}:t;Yl(()=>R(r,n))}var Rw=new Set;async function Cw(t){if(!(!t?.id||Rw.has(t.id))){Rw.add(t.id);try{let e=await D(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=ja(r);!o||n.has(`${o.type}|${o.payload?.ts??"?"}`)||await R(t,o)}}catch{}}}function Pr(t){let{type:e,at:n,...r}=t;return r}var Xl=new Map;function Mw(t,e,n){!n?.promptId||Xl.get(t.id)===n.promptId||(Xl.set(t.id,n.promptId),Yl(()=>R(t,{type:"prompt.raised",actor:"agent",source:"pane",boxId:e?.name,provider:Kt(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 Zl(t,e,n,r={}){Xl.delete(t.id),Yl(()=>R(t,{type:"prompt.answered",actor:"user",source:"orchestrator",boxId:e?.name,provider:Kt(n?.provider||e?.agent),payload:{promptId:n?.promptId,...r}}))}import{randomUUID as p_}from"node:crypto";var Pw=/(?<!\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,Bw=/(?<!\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,Dw=/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,jw=/(?<!\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,BE=new RegExp(`\\b(?:${Pw}|${Bw}|${Dw}|${jw})\\b`,"i"),DE=/\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,jE=/^(?:commit|push|open|create|raise|rebase|ready|ok(?:ay)?\s+to|good\s+to)\b/i,LE=90;function FE(t){return String(t).split(/(?<=[.?!])\s+|\n+/).map(e=>e.trim()).filter(Boolean)}function Lw(t){let e=String(t||"").trim();if(!e)return null;let n=e.slice(-600);for(let r of FE(n).reverse()){if(!BE.test(r))continue;let o=r.endsWith("?")&&r.length<=LE&&jE.test(r);if(DE.test(r)||o)return r.length>200?r.slice(0,197)+"\u2026":r}return null}var Fw=["commit","rebase","push","pr"],UE=new RegExp(`\\b(?:${Pw})\\b`,"i"),HE=new RegExp(`\\b(?:${jw})\\b`,"i"),WE=new RegExp(`\\b(?:${Bw})\\b`,"i"),qE=new RegExp(`\\b(?:${Dw})\\b`,"i"),KE=/\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 Uw(t){let e=String(t||""),n=[];UE.test(e)&&n.push("commit"),HE.test(e)&&n.push("rebase"),WE.test(e)&&n.push("push");let r=KE.test(e);!r&&qE.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 La(t,{branch:e=null,base:n=null,toPr:r=null,repos:o=null,offeredRebase:s=!1}={}){Array.isArray(t)||(t=t==="push"?["commit","push"]:["commit","push","pr"]);let a=new Set(t),i=Array.isArray(o)&&o.length>0,c=i?o.map(p=>`${p.name}/`).join(", "):null,l=e?` on this branch (${e})`:"",u=[];if(a.has("commit")&&u.push(i?`commit the work in each changed checkout (${c})${l} with a clear commit message per repo`:`commit the work${l} with a clear commit message`),a.has("rebase")){let p=a.has("commit")?"the branch":`this branch${e?` (${e})`:""}`;u.push(i?`rebase ${p} in each changed checkout (${c}) onto its own base branch (fetch origin first), resolving any conflicts so both sides' intent is preserved`:`rebase ${p} onto ${n?`the latest ${n}`:"its base branch"} (fetch origin first), resolving any conflicts so both sides' intent is preserved`)}if(a.has("push")||a.has("pr")){let p=r&&!a.has("pr")?` (the open PR${r.number?` #${r.number}`:""} picks it up)`:"",m=a.has("rebase")?" with `--force-with-lease` (the rebase rewrote history)":"";i?u.push(a.has("commit")||a.has("rebase")?`push each one to its remote${m}${p}`:`push this branch${e?` (${e})`:""} from each changed checkout (${c}) to its remote${p}`):u.push(a.has("commit")||a.has("rebase")?`push it to the remote${m}${p}`:`push this branch${l} to the remote${p}`)}a.has("pr")&&u.push(i?"open a pull request in each changed repo with a concise title and a description of what changed and why (run `gh pr create` inside each checkout)":`open a pull request${n?` against ${n}`:""} with a concise title and a description of what changed and why (use \`gh pr create\`)`);let f=[];a.has("commit")||f.push("don't create any new commits \u2014 ship what's already committed"),s&&!a.has("rebase")&&f.push("don't rebase \u2014 leave the branch's history as it is"),!a.has("push")&&!a.has("pr")&&f.push("don't push"),a.has("pr")||f.push("don't open a pull request");let d=`Yes \u2014 go ahead: ${u.join(", then ")}.`;return f.length&&(d+=` ${f.join(", and ").replace(/^./,p=>p.toUpperCase())}.`),a.has("pr")&&(d+=i?" If a repo's PR for this branch is already open, just push that repo's update instead.":" If a PR for this branch is already open, just push the update instead."),d}var GE=3e4;function Hw(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<=GE&&n.push({box:r.box,from:r.from,to:e+1e12}),n}function Ww(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 Nw={commit:"commit",rebase:"rebase",push:"push",pr:"open a PR"};function qw(t){let e=(Array.isArray(t)?t:[]).filter(r=>Nw[r]).map(r=>Nw[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 YE}from"node:child_process";import{promisify as ZE}from"node:util";import VE from"node:fs";import Ql from"node:fs/promises";import zw from"node:path";import zE from"node:os";var Jw=process.env.BOSTRAT_STATE_DIR||zw.join(process.env.HOME||zE.tmpdir(),".bostrat"),Fa=zw.join(Jw,"github-metrics.json"),Xw=60*6e4,Kw=5*6e4,JE=6e4,Br=Date.now(),je=new Map,dn=null,zn=0,Gw=0,Ua=t=>[...t.values()].reduce((e,n)=>e+n,0);try{let t=JSON.parse(VE.readFileSync(Fa,"utf8"));t?.windowStart&&(Date.now()-t.windowStart<Xw?(Br=t.windowStart,je=new Map(Object.entries(t.byCaller||{})),dn=t.lastHour||null):Ua(new Map(Object.entries(t.byCaller||{})))&&(dn={start:t.windowStart,total:Ua(new Map(Object.entries(t.byCaller||{}))),byCaller:t.byCaller||{}}))}catch{}var Vw=Promise.resolve();function Yw(t,e=!1){if(!e&&t-Gw<JE)return;Gw=t;let n=JSON.stringify({windowStart:Br,byCaller:Object.fromEntries(je),lastHour:dn});Vw=Vw.then(()=>Ql.mkdir(Jw,{recursive:!0})).then(()=>Ql.writeFile(`${Fa}.tmp`,n)).then(()=>Ql.rename(`${Fa}.tmp`,Fa)).catch(()=>{})}function Zw(t){t-Br<Xw||(dn={start:Br,total:Ua(je),byCaller:Object.fromEntries(je)},dn.total&&console.log(`[github] ${dn.total} calls last hour`,dn.byCaller),Br=t,je=new Map,Yw(t,!0))}function Le(t="other",e=Date.now()){Zw(e),je.set(t,(je.get(t)||0)+1),Yw(e)}function Qw(t=Date.now()){return Zw(t),{windowStart:Br,total:Ua(je),byCaller:Object.fromEntries(je),lastHour:dn,backoffUntil:zn>t?zn:null}}var XE=/rate limit|too quickly|abuse detection|HTTP 429/i;function fn(t){return XE.test(String(t?.message||t||""))}function pn(t,e=Date.now()){return zn<=e&&console.warn(`[github] rate limited \u2014 backing off ${Kw/6e4} min:`,String(t?.message||t||"").slice(0,200)),zn=Math.max(zn,e+Kw),zn}function Ha(t=Date.now()){return Math.max(0,zn-t)}async function tu(t,e){let n={working:!1,prompted:!1};if(!t)return n;let r=await ft(t,{lines:xt}).catch(()=>"");if(!Ee(e,r))return Yf(e,r)?{working:!0,prompted:!1}:n;if(Cr(e,r)){let o=await Ze(t).catch(()=>r);if(Vn({name:t,agent:e},o))return{working:!1,prompted:!0}}return{working:!0,prompted:!1}}async function Wa(t,e){return(await tu(t,e)).working}var tg=ZE(YE),eg=[4e3,15e3,45e3],QE=2500,t_=5*6e4;async function eu(t,e,{midTurn:n=Wa,pollMs:r=QE,capMs:o=t_}={}){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 e_=6e4,n_=9e4,r_=2e3,o_=/(?:^|[;&|]\s*|\)\s*)gh\s+(?:[^\s;&|"'`$]+\s+)*?pr\s+create\b/,s_=/(?:^|[;&|]\s*|\)\s*)gh\s+(?:[^\s;&|"'`$]+\s+)*?pr\s+merge\b/,a_=/\s--web\b/,ng=/(?:--repo|-R)[\s=]+([\w.-]+\/[\w.-]+)/;function nu(t,e){return t.slice(e).replace(/^[\s;&|)]+/,"").replace(/"[^"]*"|'[^']*'/g,'""').split(/[;&|]/)[0]}function rg(t){let e=String(t||""),n=e.match(o_);if(!n)return null;let r=nu(e,n.index);return a_.test(r)?null:{repo:r.match(ng)?.[1]??null}}var i_=new Set(["--subject","-t","--body","-b","--body-file","-F","--match-head-commit","--author-email","-A","--repo","-R"]);function og(t){let e=String(t||""),n=e.match(s_);if(!n)return null;let r=nu(e,n.index),o=nu(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("-")){i_.has(c)&&i++;continue}s=c.replace(/^["']+|["']+$/g,"")||null;break}return{repo:r.match(ng)?.[1]??null,selector:s,auto:/\s--auto\b/.test(r)}}var c_="number,url,title,headRefName,createdAt";async function l_(t,e){let n=async a=>{Le("observer");let{stdout:i}=await tg("gh",["pr","list","--repo",t,"--author","@me","--state","all","--limit","10","--json",c_],{timeout:3e4,maxBuffer:4*1024*1024,env:{...process.env,...a}}).catch(c=>{throw fn(c)&&pn(c),c});return JSON.parse(i||"[]")},r=a=>(a||[]).filter(i=>Date.parse(i.createdAt)>=e-e_),o=await ae({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 u_(t,e){if(e)return[e];let n=[];for(let r of sg(t)){let o=await tt(r).catch(()=>null);o&&!n.includes(o)&&n.push(o)}for(let r of await ve().catch(()=>[])){let o=await tt(r.path).catch(()=>null);o&&!n.includes(o)&&n.push(o)}return n}function sg(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 d_(t,e,n){let r=t.workspace?.branch,o=[];for(let s of await u_(t,e)){let a;try{a=await l_(s,n)}catch{continue}for(let i of a){let c=await $t(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 mn=new Set;function ag(t,e,{delays:n=eg,turnEnd:r}={}){let o=rg(e.command);if(!o)return null;let s=!!og(e.command),a=`${t.id}|${e.id??e.ts??e.command}`;if(mn.has(a))return null;mn.add(a);let i=e.ts??Date.now(),c=(async()=>{try{for(let l of n){await new Promise(g=>setTimeout(g,l));let u=await d_(t,o.repo,i).catch(()=>null);if(!u)continue;let{slug:f,pr:d}=u,{link:p,created:m}=await Wt({repo:f,number:d.number,trackId:t.id,url:d.url,headRef:d.headRefName,title:d.title,source:"observed"});return!m&&!(p?.trackId===t.id&&await Bo(f,d.number))?null:(await eu(e.box,e.agent,r),await R({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:Date.now(),change:"opened",number:d.number,url:d.url,title:d.title,branch:d.headRefName,repo:f,...s?{selfMerge:!0}:{}}}),d)}return null}finally{mn.delete(a)}})();return c.catch(()=>mn.delete(a)),c}var f_="number,url,title,headRefName,state";function ig(t,e,{delays:n=eg,turnEnd:r,openedWait:o}={}){let s=og(e.command);if(!s)return null;let a=`merge|${t.id}|${e.id??e.ts??e.command}`;if(mn.has(a))return null;mn.add(a);let i=(async()=>{try{let c=s.repo,l=s.selector,u=l?.match(/github\.com\/([\w.-]+\/[\w.-]+)\/pull\/(\d+)/);u&&([,c,l]=u);let f=[];if(c)f=[c];else{let m=sg(t);if(m.length>1&&/^\d+$/.test(String(s.selector??"")))return null;for(let g of m){let y=await tt(g).catch(()=>null);y&&!f.includes(y)&&f.push(y)}}if(l||=t.workspace?.branch,!f.length||!l)return null;c=f[0];let d=async(m,g)=>{let y=await $t(c,m.number);return y&&y.trackId!==t.id?null:(y||await Wt({repo:c,number:m.number,trackId:t.id,url:m.url,headRef:m.headRefName,title:m.title,source:"merge-observed"}),await Do(c,m.number),g&&((await D(t).catch(()=>[])).some(E=>E.type==="pr.state.changed"&&(E.payload?.change==="auto-armed"||E.payload?.change==="merged")&&E.payload?.number===m.number)||(await eu(e.box,e.agent,r),await R({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:Date.now(),change:"auto-armed",number:m.number,url:m.url,title:m.title,branch:m.headRefName,repo:c}}))),m)},p=null;for(let m of n){await new Promise($=>setTimeout($,m));let g=[];for(let $ of f)try{Le("observer");let{stdout:j}=await tg("gh",["pr","view",String(l),"--repo",$,"--json",f_],{timeout:3e4,maxBuffer:4*1024*1024,env:{...process.env,...await ae({slug:$})}}),v=JSON.parse(j||"null");v?.number&&g.push({slug:$,pr:v})}catch(j){fn(j)&&pn(j)}let y=g.find($=>$.pr.state==="MERGED")??(g.length===1?g[0]:null);if(!y)continue;c=y.slug;let b=y.pr;if(!b?.number)continue;if(b.state==="CLOSED")return null;if(b.state!=="MERGED"){if(p=b,s.auto)return d(b,!0);continue}let S=await $t(c,b.number);if(S&&S.trackId!==t.id)return null;S||await Wt({repo:c,number:b.number,trackId:t.id,url:b.url,headRef:b.headRefName,title:b.title,source:"merge-observed"}),await eu(e.box,e.agent,r);let E=await D(t).catch(()=>[]);if(E.some($=>$.type==="pr.state.changed"&&$.payload?.change==="merged"&&$.payload?.number===b.number))return null;let M=$=>$.some(j=>j.type==="pr.state.changed"&&j.payload?.change==="opened"&&j.payload?.number===b.number),J=0;if(!M(E)){if(rg(e.command)&&await Bo(c,b.number)){let $=Date.now();J=$+1,await R({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:$,change:"opened",number:b.number,url:b.url,title:b.title,branch:b.headRefName,repo:c,selfMerge:!0}})}else if((await $t(c,b.number))?.announced){let $=o?.capMs??n_,j=o?.pollMs??r_,v=Date.now()+$;for(;Date.now()<v&&(await new Promise(C=>setTimeout(C,j)),!M(await D(t).catch(()=>[]))););}}return await R({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:Math.max(Date.now(),J),change:"merged",number:b.number,url:b.url,title:b.title,branch:b.headRefName,repo:c}}),await wn(c,b.number).catch(()=>{}),await hn(c,b.number).catch(()=>{}),await cn(c,b.number,"merged"),b}return p?d(p,!1):null}finally{mn.delete(a)}})();return i.catch(()=>mn.delete(a)),i}function ou(t){return t.sourceId?`${t.role}|id:${t.sourceId}`:`${t.role}|${t.ts??"?"}|${String(t.text||"").slice(0,40)}`}function cg(t){return t.id?`cmd|${t.box}|id:${t.id}`:`cmd|${t.box}|${t.ts??"?"}|${String(t.command||"").slice(0,40)}`}function lg(t){return`job|${t.box}|id:${t.id}`}var ru=new Map,qa=new Map;async function su(t){return ru.has(t.id)?ru.get(t.id):(qa.has(t.id)||qa.set(t.id,D(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(ou({role:o,sourceId:r.payload.sourceId})),n.keys.add(ou({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(cg({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(lg({id:r.payload.id,box:r.boxId}));return ru.set(t.id,n),n}).finally(()=>qa.delete(t.id))),qa.get(t.id))}async function ug(t,e){try{let n=(await su(t)).keys;for(let r of e||[]){if(!r||r.ts==null||!String(r.text||"").trim()&&!r.images?.length)continue;let o=ou(r);if(!n.has(o)&&(n.add(o),await R(t,{type:r.role==="user"?"turn.user":"turn.agent",actor:r.role==="user"?"user":"agent",source:"transcript",boxId:r.box,provider:r.agent||null,payload:{text:r.text,ts:r.ts,model:r.model||null,tools:r.tools||void 0,boxRole:r.boxRole,sourceId:r.sourceId||void 0,note:r.note||void 0,images:r.images?.length?r.images:void 0}}),r.role==="assistant"&&r.box===t.leadBox&&t.workspace?.branch)){let s=Lw(r.text);if(s){let{offered:a,toPr:i}=Uw(s),c;if(t.workspace?.kind==="multi"){let l=await zo(t).catch(()=>[]);c=l.length?l.map(u=>({name:u.name,slug:u.repo??null})):void 0}await R(t,{type:"pr.offered",actor:"agent",source:"transcript",boxId:r.box,provider:r.agent||null,payload:{offerId:p_(),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 dg(t,e){try{let n=await su(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=cg(o);n.keys.has(s)||(n.keys.add(s),n.cmdN+=1,await R(t,{type:"tool.called",actor:"agent",source:"transcript",boxId:o.box,provider:o.agent||null,payload:{id:o.id??null,tool:o.tool||null,command:o.command,description:o.description||"",summary:o.summary||o.command,ts:o.ts??null,boxRole:o.boxRole,n:n.cmdN}}),ag(t,o),ig(t,o))}}catch{}}async function fg(t,e){try{let n=await su(t),r=[...e||[]].filter(o=>o&&o.id).sort((o,s)=>(o.ts||0)-(s.ts||0));for(let o of r){let s=lg(o);n.keys.has(s)||(n.keys.add(s),await R(t,{type:"job.started",actor:"agent",source:"transcript",boxId:o.box,provider:o.agent||null,payload:{id:o.id,kind:o.kind||"agent",name:o.name||null,detail:o.detail||null,ts:o.ts??null,boxRole:o.boxRole}}))}}catch{}}var m_=/^Base directory for this skill:\s*(\S+)/,h_=/^Run the "([^"]+)" workflow\./,w_=/<command-name>\s*\/?([a-z0-9:_-]+)\s*<\/command-name>/i,g_=/^<local-command-(?:stdout|caveat)>/i,y_=/^\[Image: original \d+x\d+, displayed at \d+x\d+\.\s*Multiply coordinates by [\d.]+ to map to original image\.\]$/,b_=/^Continue from where you left off\.$/,k_=/^<task-notification>/i,x_=/<summary>([\s\S]*?)<\/summary>/i,S_=/<task-id>([\s\S]*?)<\/task-id>/i,v_=/<tool-use-id>([\s\S]*?)<\/tool-use-id>/i,T_=/<status>([\s\S]*?)<\/status>/i,A_=/<result>([\s\S]*?)(?:<\/result>|$)/i;var au=new Set(["model","clear","compact","login","exit","quit"]);function pg(t){return au.has(String(t||"").replace(/^\//,"").toLowerCase())}var E_=/^\/([a-z0-9:_-]+)$/i;function Ka(t){let e=String(t||"").trim();if(!e)return null;let n=e.match(m_);if(n)return{kind:"skill",name:n[1].split("/").filter(Boolean).pop()||"skill"};let r=e.match(h_);if(r)return{kind:"skill",name:r[1]};let o=e.match(w_);if(o){let a=o[1].toLowerCase();return au.has(a)?{kind:"suppress"}:{kind:"command",command:`/${a}`}}let s=e.match(E_);if(s&&au.has(s[1].toLowerCase()))return{kind:"suppress"};if(g_.test(e))return{kind:"suppress"};if(b_.test(e))return{kind:"suppress"};if(y_.test(e))return{kind:"suppress"};if(k_.test(e)){let a=c=>__(e.match(c)?.[1].trim()||"")||null,i=a(A_);return{kind:"task",summary:a(x_)||"",taskId:a(S_),toolUseId:a(v_),status:a(T_)?.toLowerCase()||null,resultHead:i?i.length>600?`${i.slice(0,600).trimEnd()}\u2026`:i:null}}return null}function __(t){return t.replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/'/g,"'").replace(/&/g,"&")}function mg(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 hg(t){return t?.kind==="skill"?"skill-run":t?.kind==="task"?"task-done":"command-run"}var it=t=>t.payload?.ts??t.ts,I_=new Set(["opened","ci-fixed","reconciled","pushed"]),wg=t=>`${t.repo??""}#${t.number}`;function R_(t,e,n){let r=wg(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 Jo(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 w of t||[]){if(w.type!=="tool.called"||w.source!=="transcript")continue;let h=String(w.payload?.command||"");if(/\bbostrat-mock\s+(?:add|update)\b/.test(h))for(let T of h.matchAll(/[\w./~-]+\.(?:png|jpe?g|gif|webp|bmp|svg)\b/gi)){let _=T[0].split("/").pop().toLowerCase();f.has(_)||f.set(_,[]),f.get(_).push(w.payload?.ts??w.ts)}}let d=3*6e4,p=20*6e4,m=(w,h)=>!!(w?.name&&f.get(String(w.name).toLowerCase()))?.some(_=>_>=h-d&&_<=h+p);for(let w of t||[])if(w.type==="turn.retracted"){r.add(`${w.boxId}|${w.payload?.saidTs}`);let h=qt(w.payload?.text);h&&(o.has(w.boxId)||o.set(w.boxId,[]),o.get(w.boxId).push(h))}else if(w.type==="turn.user"&&w.source==="orchestrator"){let h=qt(w.payload?.text);h&&(s.has(w.boxId)||s.set(w.boxId,new Set),s.get(w.boxId).add(h))}else if(w.type==="job.started"&&w.payload?.id)a.add(w.payload.id),(w.payload.kind||"agent")==="agent"&&w.payload.name&&c.add(w.payload.name);else if(w.type==="turn.user"&&w.payload?.note?.kind==="task"){let h=w.payload.note,T={status:h.status||null,summary:h.summary||null,resultHead:h.resultHead??null,ts:it(w)};h.toolUseId&&i.set(h.toolUseId,T);let _=String(h.summary||"").match(u);_&&l.set(_[1],T)}let g=new Map,y=new Map,b=null,S=new Map,E=new Map,M=new Map,J=new Map,$=new Map,j=new Map,v=new Map,C=()=>{for(let w of v.values())w.questions.length&&e.push(w);v.clear()},q=6e4,rt=new Map,st=(w,h)=>{let T=o.get(w);if(!T?.length)return!1;let _=qt(h),O=s.get(w);return T.some(I=>{if(!_.startsWith(I))return!1;let A=qt(_.slice(I.length));return!!A&&!!O?.has(A)})};for(let w of t||[]){let h=w.payload||{};switch(w.type){case"turn.user":{if(w.source==="hook"||!String(h.text||"").trim()&&!h.images?.length)break;{let T=h.note||Ka(h.text);if(T){if(T.kind==="task"){if(T.toolUseId&&a.has(T.toolUseId))break;let _=String(T.summary||"").match(u);if(!T.toolUseId&&_&&c.has(_[1]))break}if(T.kind==="command"&&pg(T.command))break;T.kind!=="suppress"&&e.push({id:w.id,kind:"event",type:hg(T),name:T.name??null,command:T.command??null,summary:T.summary??null,ts:it(w),box:w.boxId,agent:w.provider});break}}if(r.has(`${w.boxId}|${it(w)}`)||w.source==="transcript"&&st(w.boxId,h.text))break;if(h.kind==="merge-notify"||h.kind==="ship-notify"){e.push({id:w.id,kind:"event",type:h.kind==="ship-notify"?"ship-notified":"merge-notified",text:h.text,ts:it(w),box:w.boxId,agent:w.provider});break}if(h.kind==="mock-picked"||h.kind==="ci-fix"||h.kind==="reconcile"||h.kind==="pr-go"||h.kind==="push-go"||h.kind==="spin-up-view"||h.kind==="prompt-answer")break;for(let T of S.values())T.status="stale";S.clear(),e.push({id:w.id,kind:"message",role:"user",text:h.text,ts:it(w),box:w.boxId,agent:w.provider,boxRole:h.boxRole,...w.actor==="orchestrator"?{orchestration:h.kind||!0}:{},...h.sourceId?{sourceId:h.sourceId}:{},...h.attachments?.length?{attachments:h.attachments}:{},...h.images?.length?{images:h.images}:{}});break}case"turn.agent":{if(h.model==="<synthetic>"&&/^No response requested\.$/.test(String(h.text||"").trim()))break;C();let T=(h.images||[]).filter(A=>!m(A,it(w)));if(!String(h.text||"").trim()&&!T.length)break;if(w.provider==="cursor"){let A=`${w.boxId}|${h.text}`;if(n.has(A))break;n.add(A)}let _=w.provider==="codex"?(h.tools||[]).filter(A=>!Sl.has(A)&&!Ho.has(A)):h.tools||[],O=h.text,I=null;if(jl.has(w.provider)){let A=Ll(String(h.text||""));A&&(O=A.text,I={questions:A.questions})}e.push({id:w.id,kind:"message",role:"assistant",text:O,ts:it(w),box:w.boxId,agent:w.provider,boxRole:h.boxRole,...I?{ask:I}:{},..._.length?{tools:_}:{},...h.model?{model:h.model}:{},...h.sourceId?{sourceId:h.sourceId}:{},...T.length?{images:T}:{}});break}case"job.started":{if(!h.id)break;let T=it(w),_=i.get(h.id);if(!_&&(h.kind||"agent")==="agent"&&h.name){let K=l.get(h.name);K&&K.ts>=T&&(_=K)}let O={jobId:h.id,jobKind:h.kind||"agent",name:h.name||null,detail:h.detail||null,status:_?_.status&&_.status!=="completed"?"failed":"done":"running",startedTs:T,finishedTs:_?.ts??null,summary:_?.summary??null,resultHead:_?.resultHead??null},I=rt.get(w.boxId),A=I?.jobs[I.jobs.length-1]?.startedTs;if(I&&A!=null&&T-A<=q)I.jobs.push(O);else{let K={id:w.id,kind:"job-dock",dockId:h.id,ts:T,box:w.boxId,agent:w.provider,jobs:[O]};rt.set(w.boxId,K),e.push(K)}break}case"handoff.done":e.push(gn(w,"lead-handoff"));break;case"subtask.delegated":e.push({...gn(w,"subtask-delegated"),to:w.boxId,toAgent:w.provider});break;case"subtask.merged":e.push(gn(w,"subtask-merged"));break;case"pr.state.changed":{if(!h.change)break;if(h.change==="opened"){for(let _ of S.values())_.status="stale";S.clear()}let T=gn(w,`pr-${h.change}`);if(I_.has(h.change)&&h.number!=null){let _=R_(g,y,h);if(g.size>=2){for(let I of y.values())for(let A of I)A.grouped=!0;T.groupPrs=[...g.values()];let O=[T];for(let I of g.keys())y.set(I,O)}else{let O=y.get(_)??[];O.push(T),y.set(_,O)}}else if(h.change==="merged"||h.change==="closed"||h.change==="lost"){let _=wg(h),O=b&&!b.complete||g.size>=2;if(g.has(_))g.delete(_),y.delete(_);else if(h.number!=null)for(let I of[...g.keys()])I.endsWith(`#${h.number}`)&&(g.delete(I),y.delete(I));if(O&&h.number!=null){(!b||b.complete)&&(b={id:w.id,kind:"event",type:"pr-group-settled",prs:[],remaining:[],complete:!1,at:it(w),ts:it(w),box:w.boxId,agent:w.provider},e.push(b)),b.prs.push({number:h.number,url:h.url??null,title:h.title??null,repo:h.repo??null,branch:h.branch??null,change:h.change,ts:it(w)}),b.remaining=[...g.values()],b.complete=g.size===0;break}}e.push(T);break}case"pr.offered":{if(!h.offerId)break;for(let _ of S.values())_.status="stale";S.clear();let T={id:w.id,kind:"pr-offer",offerId:h.offerId,excerpt:h.excerpt||null,branch:h.branch||null,base:h.base||null,offered:Array.isArray(h.offered)&&h.offered.length?h.offered:null,toPr:h.toPr&&typeof h.toPr=="object"?{number:h.toPr.number??null}:null,repos:Array.isArray(h.repos)&&h.repos.length?h.repos:null,status:"open",ts:it(w),box:w.boxId,agent:w.provider};S.set(h.offerId,T),E.set(h.offerId,T),e.push(T);break}case"pr.offer.answered":{let T=E.get(h.offerId);T&&(T.status=h.action||"dismiss",T.actions=Array.isArray(h.actions)?h.actions:null,T.notify=h.notify===!0,S.delete(h.offerId));break}case"box.state.changed":{let T=h.change==="model"?"model-change":h.change;if(!T)break;let _=gn(w,T);T==="model-change"&&(_.to=h.model),e.push(_);break}case"view.state.changed":{if(h.change!=="created"||h.restarted)break;e.push({id:w.id,kind:"view-card",viewId:h.viewId,name:h.name,slug:h.slug,port:h.port,origin:h.origin,ts:it(w),box:w.boxId,agent:w.provider});break}case"view.spinup.failed":{e.push({id:w.id,kind:"view-spinup-failed",name:h.name||null,reason:h.reason||"no-view",ts:it(w),box:w.boxId,agent:w.provider});break}case"mock.state.changed":{if(h.change==="picked"){e.push({id:w.id,kind:"mock-picked",mockId:h.mockId,name:h.name,group:h.group??null,version:h.version??null,siblings:h.siblings??[],ts:it(w),box:w.boxId,agent:w.provider});break}if(h.change!=="created")break;e.push({id:w.id,kind:"mock-card",mockId:h.mockId,name:h.name,group:h.group??null,ts:it(w),box:w.boxId,agent:w.provider});break}case"flow.state.changed":{if(h.change!=="created")break;e.push({id:w.id,kind:"flow-card",flowId:h.flowId,name:h.name,stepCount:h.stepCount??null,mockIds:h.mockIds??[],ts:it(w),box:w.boxId,agent:w.provider});break}case"runbook.state.changed":{if(h.change!=="created")break;e.push({id:w.id,kind:"runbook-card",runbookId:h.runbookId,name:h.name,ts:it(w),box:w.boxId,agent:w.provider});break}case"ticket.linked":{e.push({id:w.id,kind:"ticket-card",provider:h.provider||"linear",key:h.key,url:h.url,title:h.title??null,linkSource:h.linkSource??null,ts:it(w),box:w.boxId,agent:w.provider});break}case"ticket.move.offered":{if(!h.offerId)break;for(let _ of M.values())_.key===h.key&&(_.status="stale",M.delete(_.offerId));let T={id:w.id,kind:"ticket-offer",offerId:h.offerId,provider:h.provider||"linear",key:h.key,url:h.url??null,phase:h.phase??null,from:h.from??null,to:h.to?.name??null,prNumber:h.prNumber??null,status:"open",ts:it(w),box:w.boxId,agent:w.provider};M.set(h.offerId,T),J.set(h.offerId,T),e.push(T);break}case"ticket.move.answered":{let T=J.get(h.offerId);T&&(T.status=h.action==="moved"?"moved":"dismiss",M.delete(h.offerId));break}case"ticket.state.changed":{for(let T of M.values())T.key===h.key&&T.to===h.to&&(T.status="stale",M.delete(T.offerId));if(h.mode==="offer")break;e.push(gn(w,"ticket-state"));break}case"ticket.commented":{e.push({id:w.id,kind:"ticket-comment",key:h.key,url:h.url??null,excerpt:h.excerpt??null,ts:it(w),box:w.boxId,agent:w.provider});break}case"ticket.flow.changed":e.push(gn(w,"ticket-flow"));break;case"ticket.create.offered":{if(!h.proposalId)break;for(let _ of $.values())_.status="stale";$.clear();let T={id:w.id,kind:"ticket-create-offer",proposalId:h.proposalId,provider:h.provider||"linear",epic:h.epic??null,issues:Array.isArray(h.issues)?h.issues:[],team:h.team??null,needsTeam:!!h.needsTeam,linkCurrentTrack:!!h.linkCurrentTrack,status:"open",ts:it(w),box:w.boxId,agent:w.provider};$.set(h.proposalId,T),j.set(h.proposalId,T),e.push(T);break}case"ticket.create.answered":{let T=j.get(h.proposalId);T&&(T.status=h.action==="created"?"created":"dismiss",T.createdCount=h.createdCount??null,T.failedCount=h.failedCount??null,$.delete(h.proposalId));break}case"ticket.created":{let T={items:Array.isArray(h.items)?h.items:[],failed:Array.isArray(h.failed)?h.failed:[],team:h.team??null},_=h.proposalId?j.get(h.proposalId):null;if(_){_.created=T;break}e.push({id:w.id,kind:"ticket-created",...T,ts:it(w),box:w.boxId,agent:w.provider});break}case"error":{if(!h.kind)break;e.push(gn(w,h.kind));break}case"provider.connected":{e.push({id:w.id,kind:"event",type:"provider-connected",provider:h.provider||w.provider,ts:it(w),at:it(w),box:w.boxId,agent:w.provider});break}case"prompt.answered":{if(h.control||!h.question)break;let T=h.groupId||h.promptId;if(!T)break;let _=v.get(T);_||(_={id:w.id,kind:"prompt",groupId:T,provider:w.provider,box:w.boxId,agent:w.provider,questionTotal:h.questionTotal??1,questions:[],ts:it(w)},v.set(T,_));let O=h.questionIndex??0,I={index:O,header:h.header||"",question:h.question,multiSelect:!!h.multiSelect,chosen:Array.isArray(h.chosen)?h.chosen:[]},A=_.questions.findIndex(K=>K.index===O&&qt(K.question)===qt(I.question));A>=0?_.questions[A]=I:_.questions.push(I),_.questions.sort((K,dt)=>K.index-dt.index),_.questionTotal=Math.max(_.questionTotal,h.questionTotal??1),_.questions.length>=_.questionTotal&&(e.push(_),v.delete(T));break}default:break}}return O_(e.sort((w,h)=>(w.ts??0)-(h.ts??0)))}var $_=2*6e4;function O_(t){let e=new Map,n=[];for(let r of t){if(r.kind!=="message"||!r.box){n.push(r);continue}let o=r.role==="assistant"&&!String(r.text||"").trim()&&r.images?.length,s=e.get(r.box);if(o&&s&&(r.ts??0)-s.lastTs<=$_){s.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 iu(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:C_(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??Jo(t)).filter(o=>o.kind==="message");return Rh(n,r)}function C_(t){let e=String(t.summary||"").trim();return e&&e!==t.command&&e!==Ir(t.command)?e:ln(t.command,t.description||"")}function gn(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 M_(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
|
|
147
|
-
`)}catch{}}function Ag(){let t={};for(let[e,n]of fu)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:za.slice(-20)}}async function Y_(t){let e=/workspaces[/\\]([0-9a-f]{8})(?:[/\\]|$)/.exec(t||"")?.[1];if(e)try{let[{stdout:n},{stdout:r}]=await Promise.all([jr("git",["-C",t,"rev-parse","HEAD"]),jr("git",["-C",t,"rev-parse","--abbrev-ref","HEAD"])]);await R({id:e,cwd:t},{type:"code.pushed",actor:"orchestrator",payload:{ref:r.trim(),sha:n.trim(),wip:!1,ts:Date.now()}})}catch{}}var jr=X_(J_),Z_={timeout:3e4,maxBuffer:8*1024*1024};async function Yt(t,e,n={}){let{caller:r="prs",...o}=n,s=await ae({cwd:t});Le(r);try{let{stdout:a}=await jr("gh",e,{cwd:t,...Z_,...o,env:{...process.env,...s,...o.env||{}}});return String(a||"")}catch(a){throw fn(a)&&pn(a),a}}var Xa=null;async function hu(){if(Xa&&Date.now()-Xa.at<6e4)return Xa.data;let t;try{Le("auth");let e=await jr("gh",["auth","status"],{timeout:1e4});t={installed:!0,authenticated:!0,login:`${e.stdout||""}${e.stderr||""}`.match(/account\s+(\S+)/)?.[1]??null}}catch(e){t=e?.code==="ENOENT"||e?.code===127?{installed:!1,authenticated:!1,login:null}:{installed:!0,authenticated:!1,login:null}}if(t.installed&&!t.authenticated)try{let e=await co();e.configured&&(t={installed:!0,authenticated:!0,login:e.username,via:"broker"})}catch{}return Xa={at:Date.now(),data:t},t}async function Q_(t){try{let{stdout:e}=await jr("git",["-C",t,"config","--get","remote.origin.url"],{timeout:5e3});return/github\.com/.test(e)}catch{return!1}}function _g(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 pu(t){let e=(t||[]).map(_g),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 wu="number,title,url,headRefName,headRefOid,baseRefName,isDraft,author,reviewDecision,mergeable,mergeStateStatus,statusCheckRollup,autoMergeRequest,additions,deletions,createdAt,updatedAt",Ig=wu.replace("statusCheckRollup,","");function gu(t){return/Resource not accessible by personal access token/.test(String(t?.stderr||t?.message||""))}async function Rg(t,e){if(!e)return null;let n=await tt(t);if(!n)return null;try{let r=await Yt(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 $g(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 Og(t,e){let n=await Rg(t,e);return n===null?{...pu([]),unavailable:!0}:pu($g(n))}function Ya(t,e=pu(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 tI=9e4,Dr=new Map;function eI(t,e,n){let r=new Set;for(let o of e){let s=`${t}#${o.number}`;if(o.mergeable!=="UNKNOWN"&&o.mergeStateStatus!=="UNKNOWN"){Dr.delete(s);continue}r.add(s),Dr.has(s)||Dr.set(s,n),o.mergeabilityPending=n-Dr.get(s)<tI}for(let o of Dr.keys())o.startsWith(`${t}#`)&&!r.has(o)&&Dr.delete(o);return e}async function Lr(t,e){let n=await Fe(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");try{let o=await Yt(n.path,["pr","view",String(r),"--json",wu]);return Ya(JSON.parse(o))}catch(o){if(!gu(o))throw o;let s=await Yt(n.path,["pr","view",String(r),"--json",Ig]),a=JSON.parse(s);return Ya(a,await Og(n.path,a.headRefOid))}}var Eg=(t,e)=>t.number-e.number;async function Yo(t,{caller:e="prs"}={}){try{let n=await Yt(t,["pr","list","--json",wu,"--limit","50"],{caller:e});return JSON.parse(n||"[]").map(r=>Ya(r)).sort(Eg)}catch(n){if(!gu(n))throw n;let r=await Yt(t,["pr","list","--json",Ig,"--limit","50"],{caller:e});return(await Promise.all(JSON.parse(r||"[]").map(async o=>Ya(o,await Og(t,o.headRefOid))))).sort(Eg)}}var nI=3e4,rI=1500,Xo=new Map;function ke(){Xo.clear()}async function oI(t,e){if(!t)return e;for(let n of e){let r=await $t(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 Aa(t,n.number,{headSha:n.headSha}).catch(()=>{}):n.ciFix={delegatedAt:r.ciFix.delegatedAt,trackId:r.ciFix.trackId??null}),r.reconcile&&(o(r.reconcile)?await _a(t,n.number,{headSha:n.headSha}).catch(()=>{}):n.reconcile={delegatedAt:r.reconcile.delegatedAt,trackId:r.reconcile.trackId??null})}return e}async function Cg(){let t=await hu();if(!t.authenticated)return{auth:t,repos:[]};let e=[];for(let a of await ve())await Q_(a.path)&&e.push(a);let n=Date.now(),r=Ha(n)>0,o=await Promise.all(e.map(async a=>{let i=Xo.get(a.path),c=i?.pending?rI:nI;if(i&&(r||n-i.at<c))return i.entry;try{let l=await tt(a.path).catch(()=>null),u=eI(a.path,await Yo(a.path,{caller:"tab"}),n);if(await oI(l,u),i?.entry?.prs)for(let p of u)i.entry.prs.find(g=>g.number===p.number)?.checks?.pending>0&&p.checks.total>0&&p.checks.pending===0&&Ja({change:p.checks.failed>0?"checks-failed":"checks-green",repo:l||a.name,number:p.number,githubAt:p.checks.settledAt,source:"tab"});let f={name:a.name,path:a.path,prs:u},d=u.some(p=>p.mergeabilityPending||p.checks.pending>0||p.autoMergeArmed);return Xo.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:be(l)}}})),s=new Set(e.map(a=>a.path));for(let a of Xo.keys())s.has(a)||Xo.delete(a);return{auth:t,repos:o}}async function Fe(t){let e=(await ve()).find(n=>n.path===t);if(!e)throw new Error(`unknown repo: ${t}`);return e}async function Jn(t){return ao(t)}var Zo=new Set(["squash","merge","rebase"]);async function Fr(t,e,{method:n="squash"}={}){let r=await Fe(t);if(!Zo.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 ae({cwd:r.path})).GH_TOKEN){let a=await Ui().catch(()=>null);a&&s.push("--body",`Merged by bostrat on behalf of @${a}`)}try{await Yt(r.path,s)}catch(a){throw new Error(be(a))}finally{ke()}return{ok:!0,number:o}}async function Qo(t,e){let n=await Fe(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");try{await Yt(n.path,["api","-X","PUT",`repos/{owner}/{repo}/pulls/${r}/update-branch`])}catch(s){throw new Error(be(s))}finally{ke()}let o=await tt(n.path).catch(()=>null);return o&&await lh(o,r).catch(()=>{}),{ok:!0,number:r}}async function yu(t,e){let n=await Fe(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");try{await Yt(n.path,["pr","merge",String(r),"--disable-auto"])}catch(o){throw new Error(be(o))}finally{ke()}return{ok:!0,number:r}}var sI=2500,aI=9e4,iI=t=>new Promise(e=>setTimeout(e,t));function bu(t){return t.green&&!t.isDraft}function cI(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 Mg(t,{update:e=!0}={}){return t.isDraft?{action:"skip",reason:"draft"}:bu(t)?{action:"merge"}:e&&t.mergeStateStatus==="BEHIND"&&t.mergeable!=="CONFLICTING"&&t.checks.failed===0?{action:"update-arm"}:{action:"arm"}}function Ng(t){return[...t.filter(e=>e.plan.action!=="merge"),...t.filter(e=>e.plan.action==="merge")]}var mu=new Map;function Pg(){let t={};for(let[e,n]of mu)t[e]={...n};return t}async function Bg(t,{method:e="merge",onMerged:n=null}={}){let r=await Fe(t);if(!Zo.has(e))throw new Error(`invalid merge method: ${e}`);if(mu.get(r.path)?.running)throw new Error("a merge-all is already running for this repo");let o=(await Yo(r.path)).filter(bu).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 mu.set(r.path,s),uI(s,o,{listPrs:()=>Yo(r.path),merge:a=>Fr(r.path,a,{method:e}),sleep:iI,onMerged:n}).catch(()=>{}),{ok:!0,repo:r.path,total:s.total,queue:o.map(a=>a.number)}}async function lI(t,e){let n=Date.now()+(e.settleMaxMs??aI),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(sI)}}async function uI(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 lI(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: ${be(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(!bu(a)){t.skipped.push({number:a.number,title:a.title,reason:cI(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:be(i)})}}}finally{t.current=null,t.running=!1,t.finishedAt=Date.now(),ke()}}async function ku(t,{title:e,body:n="",base:r=null}={}){if(typeof e!="string"||!e.trim())throw new Error("title required");try{await jr("git",["-C",t,"push","-u","origin","HEAD"],{timeout:12e4,maxBuffer:4*1024*1024})}catch(l){throw new Error(`push failed: ${be(l)}`)}await Y_(t);let o=n||"";if((await ae({cwd:t})).GH_TOKEN){let l=await Ui().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
|
|
138
|
+
`;import{execFile as DE}from"node:child_process";import{promisify as jE}from"node:util";var Ql=jE(DE),Ew=64*1024*1024;async function gt(t,e,{input:n}={}){let r=Ql("git",["-C",t,...e],{maxBuffer:Ew});return n!==void 0&&r.child.stdin&&(r.child.stdin.on("error",()=>{}),r.child.stdin.end(n)),r}async function Wa(t){if(!t)return!1;try{let{stdout:e}=await gt(t,["rev-parse","--is-inside-work-tree"]);return e.trim()==="true"}catch{return!1}}async function _w(t){let{stdout:e}=await gt(t,["rev-parse","--show-toplevel"]);return e.trim()}async function LE(t){try{return await gt(t,["rev-parse","--verify","HEAD"]),!0}catch{return!1}}async function Zo(t,e="bostrat: subtask snapshot"){let{stdout:n}=await gt(t,["rev-parse","--absolute-git-dir"]),r=`${n.trim()}/bostrat-snapshot-index`,o={...process.env,GIT_INDEX_FILE:r},s=(a,i)=>Ql("git",["-C",t,...a],{maxBuffer:Ew,env:o,...i});try{let a=await LE(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 Ql("rm",["-f",r])}catch{}}}async function tu(t,e){let{stdout:n}=await gt(t,["status","--porcelain"]);return n.trim()?(await gt(t,["add","-A"]),await gt(t,["-c","user.email=agent@bostrat.local","-c","user.name=bostrat","commit","-m",e]),!0):!1}async function eu(t,{ignorePlumbing:e=!1}={}){let{stdout:n}=await gt(t,["status","--porcelain"]),r=n.split(`
|
|
139
|
+
`).filter(Boolean);return e&&(r=r.filter(o=>{let s=o.slice(3);return!(s===".bostrat"||s===".bostrat/"||s.startsWith(".bostrat/")||o.startsWith("??")&&(s==="AGENTS.md"||s==="CLAUDE.md"))})),r.length>0}async function nu(t,e){if(!e)return 0;try{let{stdout:n}=await gt(t,["rev-list","--count",`${e}..HEAD`]);return Number(n.trim())||0}catch{return 0}}async function Lr(t,e,n,r){return await gt(t,["worktree","add","--no-track","-b",n,e,r]),e}async function Iw(t,e){try{return await gt(t,["rev-parse","--verify","--quiet",`refs/heads/${e}`]),!0}catch{return!1}}async function Rw(t,e,n){return await gt(t,["worktree","add",e,n]),e}async function qa(t){if(process.env.BOSTRAT_TRACK_BASE_REF)return process.env.BOSTRAT_TRACK_BASE_REF;try{let{stdout:e}=await gt(t,["symbolic-ref","--short","refs/remotes/origin/HEAD"]);if(e.trim())return e.trim()}catch{}for(let[e,n]of[["refs/remotes/origin/main","origin/main"],["refs/remotes/origin/master","origin/master"],["refs/heads/main","main"],["refs/heads/master","master"]])try{return await gt(t,["rev-parse","--verify","--quiet",e]),n}catch{}return"HEAD"}async function $w(t){await gt(t,["init","-q"]),await gt(t,["-c","user.email=agent@bostrat.local","-c","user.name=bostrat","commit","--allow-empty","-qm","bostrat: track workspace"])}async function ee(t,e){try{await gt(t,["worktree","remove","--force",e])}catch{try{await gt(t,["worktree","prune"])}catch{}}}function FE(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 ru(t,e){return!e||!e.length?t:[...t,"--",".",...e.map(n=>`:(exclude)${n}`)]}async function ou(t,e,n,r){let{stdout:o}=await gt(t,ru(["diff","--shortstat",e,n],r));return FE(o)}async function su(t,e,n,r){let{stdout:o}=await gt(t,ru(["diff","--name-only",e,n],r));return o.split(`
|
|
140
|
+
`).map(s=>s.trim()).filter(Boolean)}async function au(t,e,n,r){let{stdout:o}=await gt(t,ru(["diff","--binary",e,n],r));if(!o.trim())return{applied:!0,empty:!0,conflicts:[]};if((await Zl(t,o,["--whitespace=nowarn"])).ok)return{applied:!0,empty:!1,conflicts:[]};let s=await Zl(t,o,["--3way","--whitespace=nowarn"]),a=await UE(t);return a.length>0?{applied:!1,empty:!1,conflicts:a}:s.ok?{applied:!0,empty:!1,conflicts:[]}:(await Zl(t,o,["--reject","--whitespace=nowarn"]),{applied:!1,empty:!1,conflicts:[`patch did not apply cleanly: ${s.err}`]})}async function Zl(t,e,n){try{return await gt(t,["apply",...n,"-"],{input:e}),{ok:!0}}catch(r){return{ok:!1,err:r?.stderr?.trim()||r?.message||String(r)}}}async function UE(t){try{let{stdout:e}=await gt(t,["diff","--name-only","--diff-filter=U"]);return e.split(`
|
|
141
|
+
`).map(n=>n.trim()).filter(Boolean)}catch{return[]}}import HE from"node:fs/promises";import WE from"node:os";import Ow from"node:path";var Ka=t=>new Promise(e=>setTimeout(e,t)),qE=Number(process.env.BOSTRAT_MODEL_PICKER_TIMEOUT_MS)||8e3,KE=300,Pw=[{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"}],GE=new Set([...Pw.map(t=>t.alias),"best","opus[1m]","sonnet[1m]","claude-opus-4-8","claude-sonnet-4-6","claude-haiku-4-5","claude-fable-5"]),VE=[{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"}],Bw=new Map([["low","Low"],["medium","Medium"],["high","High"],["xhigh","Extra high"]]),Dw=[{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 jw(t){return t==="codex"?VE:t==="cursor"?Dw:Pw}function Mw(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 Cw(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 zE(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 Nw(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 Lw(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&&!Bw.has(o))throw new Error(`unknown codex effort "${o}" (low | medium | high | xhigh)`);return{model:r,effort:o||null}}if(t==="cursor"){let r=Dw.find(o=>o.alias===n);return{model:n,filter:r?r.label:n}}if(!GE.has(n))throw new Error(`unknown model "${n}"`);return{model:n}}async function Qo(t,e,{timeoutMs:n=qE}={}){let r=Date.now()+n;for(;;){let o=await mt(t,{lines:80}).catch(()=>""),s=e(o);if(s)return s;if(Date.now()>r)return null;await Ka(KE)}}async function JE(t,e){return await At(t,`/model ${e.model}`,Me("claude")),{verified:!1,note:"confirmed when the next turn's transcript model flips"}}async function XE(t,e){await Zs(t),await At(t,"/model",{enter:!1}),await Ka(250),await xt(t,["Enter"],{kind:"model-switch"});let n=await Qo(t,Mw);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=zE(i);return!c||c.model!==e.model||e.effort&&c.effort&&c.effort.toLowerCase()!==e.effort.toLowerCase()?null:c};await At(t,String(r.n),{enter:!1,kind:"model-switch"});let s=await Qo(t,i=>Cw(i)||!Mw(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?Bw.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 At(t,String(c.n),{enter:!1,kind:"model-switch"})}let a=s.done||await Qo(t,i=>!Cw(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 YE(){let t=Ow.join(process.env.CURSOR_CONFIG_DIR||Ow.join(WE.homedir(),".cursor"),"cli-config.json");try{return JSON.parse(await HE.readFile(t,"utf8"))?.model||null}catch{return null}}async function ZE(t,e){await Zs(t),await ta(t,`/model ${e.filter}`,{kind:"model-switch"});let n=await Qo(t,Nw),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 Qo(t,Nw,{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 Ka(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 Ka(800);let a=await YE();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 Fw(t,e,n){return t==="codex"?XE(e,n):t==="cursor"?ZE(e,n):JE(e,n)}var Yt=t=>t||null;function Va(t){let e=t||{},n=QE(e);return n&&(n.payload={ts:e.at,...n.payload}),n}function QE(t){switch(t.type){case"said":return{type:"turn.user",actor:"user",source:"orchestrator",boxId:t.box,provider:Yt(t.agent),payload:{text:t.text,boxRole:t.boxRole,...t.kind?{kind:t.kind}:{},...t.attachments?.length?{attachments:t.attachments}:{}}};case"unsaid":return{type:"turn.retracted",actor:"user",source:"orchestrator",boxId:t.box,provider:Yt(t.agent),payload:{saidTs:t.saidTs,text:t.text,boxRole:t.boxRole}};case"orchestration-prompt":return{type:"turn.user",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Yt(t.agent),payload:{text:t.prompt,kind:t.kind,boxRole:t.boxRole}};case"lead-handoff":return{type:"handoff.done",actor:"orchestrator",source:"orchestrator",boxId:t.to,provider:Yt(t.toAgent),payload:{from:t.from,fromAgent:t.fromAgent,to:t.to,toAgent:t.toAgent,reason:t.reason,handoffStartedAt:t.handoffStartedAt,fromSessionId:t.fromSessionId,toSessionId:t.toSessionId,epoch:t.epoch,singleBox:t.singleBox,forced:t.forced,recovered:t.recovered}};case"lead-handoff-failed":return{type:"handoff.failed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Yt(t.toAgent),payload:{box:t.box,fromAgent:t.fromAgent,toAgent:t.toAgent,reason:t.reason,error:t.error,rolledBack:t.rolledBack,handoffStartedAt:t.handoffStartedAt}};case"subtask-delegated":return{type:"subtask.delegated",actor:"orchestrator",source:"orchestrator",boxId:t.to,provider:Yt(t.toAgent),payload:{task:t.task,subtaskId:t.subtaskId,reason:t.reason}};case"subtask-merged":return{type:"subtask.merged",actor:"orchestrator",source:"orchestrator",boxId:t.box||t.from,provider:Yt(t.agent),payload:{...Fr(t)}};case"subtask-failed":return{type:"error",actor:"system",source:"orchestrator",boxId:t.box||t.to,provider:Yt(t.agent||t.toAgent),payload:{kind:"subtask-failed",...Fr(t)}};case"subtask-resumed":return{type:"box.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Yt(t.agent),payload:{change:"subtask-resumed",...Fr(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",...Fr(t)}};case"archived":case"unarchived":case"track-deleted":return{type:"box.state.changed",actor:"user",source:"orchestrator",payload:{change:t.type,...Fr(t)}};case"rehydrated":return{type:"box.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Yt(t.agent),payload:{change:"rehydrated"}};case"retagged":return{type:"box.state.changed",actor:"user",source:"orchestrator",payload:{change:"retagged",...Fr(t)}};case"rehydrate-failed":return{type:"error",actor:"system",source:"orchestrator",boxId:t.box,provider:Yt(t.agent),payload:{kind:"rehydrate-failed",reason:t.reason}};default:return null}}var Ga=Promise.resolve(),cu=t=>(Ga=Ga.then(t).catch(()=>{}),Ga),Hw=()=>Ga;function Ww(t,e){let n=Va(e);if(!n)return;let r=e?.type==="track-deleted"?{id:t.id}:t;cu(()=>R(r,n))}var Uw=new Set;async function qw(t){if(!(!t?.id||Uw.has(t.id))){Uw.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=Va(r);!o||n.has(`${o.type}|${o.payload?.ts??"?"}`)||await R(t,o)}}catch{}}}function Fr(t){let{type:e,at:n,...r}=t;return r}var iu=new Map;function Kw(t,e,n){!n?.promptId||iu.get(t.id)===n.promptId||(iu.set(t.id,n.promptId),cu(()=>R(t,{type:"prompt.raised",actor:"agent",source:"pane",boxId:e?.name,provider:Yt(n.provider||e?.agent),payload:{promptId:n.promptId,question:n.question||n.title||null,options:(n.options||[]).map(r=>r?.label??r).slice(0,12)}})))}function lu(t,e,n,r={}){iu.delete(t.id),cu(()=>R(t,{type:"prompt.answered",actor:"user",source:"orchestrator",boxId:e?.name,provider:Yt(n?.provider||e?.agent),payload:{promptId:n?.promptId,...r}}))}import{randomUUID as O_}from"node:crypto";var Vw=/(?<!\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,zw=/(?<!\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,Jw=/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,Xw=/(?<!\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,t_=new RegExp(`\\b(?:${Vw}|${zw}|${Jw}|${Xw})\\b`,"i"),e_=/\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,n_=/^(?:commit|push|open|create|raise|rebase|ready|ok(?:ay)?\s+to|good\s+to)\b/i,r_=90;function o_(t){return String(t).split(/(?<=[.?!])\s+|\n+/).map(e=>e.trim()).filter(Boolean)}function Yw(t){let e=String(t||"").trim();if(!e)return null;let n=e.slice(-600);for(let r of o_(n).reverse()){if(!t_.test(r))continue;let o=r.endsWith("?")&&r.length<=r_&&n_.test(r);if(e_.test(r)||o)return r.length>200?r.slice(0,197)+"\u2026":r}return null}var Zw=["commit","rebase","push","pr"],s_=new RegExp(`\\b(?:${Vw})\\b`,"i"),a_=new RegExp(`\\b(?:${Xw})\\b`,"i"),i_=new RegExp(`\\b(?:${zw})\\b`,"i"),c_=new RegExp(`\\b(?:${Jw})\\b`,"i"),l_=/\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 Qw(t){let e=String(t||""),n=[];s_.test(e)&&n.push("commit"),a_.test(e)&&n.push("rebase"),i_.test(e)&&n.push("push");let r=l_.test(e);!r&&c_.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 za(t,{branch:e=null,base:n=null,toPr:r=null,repos:o=null,offeredRebase:s=!1}={}){Array.isArray(t)||(t=t==="push"?["commit","push"]:["commit","push","pr"]);let a=new Set(t),i=Array.isArray(o)&&o.length>0,c=i?o.map(p=>`${p.name}/`).join(", "):null,l=e?` on this branch (${e})`:"",u=[];if(a.has("commit")&&u.push(i?`commit the work in each changed checkout (${c})${l} with a clear commit message per repo`:`commit the work${l} with a clear commit message`),a.has("rebase")){let p=a.has("commit")?"the branch":`this branch${e?` (${e})`:""}`;u.push(i?`rebase ${p} in each changed checkout (${c}) onto its own base branch (fetch origin first), resolving any conflicts so both sides' intent is preserved`:`rebase ${p} onto ${n?`the latest ${n}`:"its base branch"} (fetch origin first), resolving any conflicts so both sides' intent is preserved`)}if(a.has("push")||a.has("pr")){let p=r&&!a.has("pr")?` (the open PR${r.number?` #${r.number}`:""} picks it up)`:"",m=a.has("rebase")?" with `--force-with-lease` (the rebase rewrote history)":"";i?u.push(a.has("commit")||a.has("rebase")?`push each one to its remote${m}${p}`:`push this branch${e?` (${e})`:""} from each changed checkout (${c}) to its remote${p}`):u.push(a.has("commit")||a.has("rebase")?`push it to the remote${m}${p}`:`push this branch${l} to the remote${p}`)}a.has("pr")&&u.push(i?"open a pull request in each changed repo with a concise title and a description of what changed and why (run `gh pr create` inside each checkout)":`open a pull request${n?` against ${n}`:""} with a concise title and a description of what changed and why (use \`gh pr create\`)`);let f=[];a.has("commit")||f.push("don't create any new commits \u2014 ship what's already committed"),s&&!a.has("rebase")&&f.push("don't rebase \u2014 leave the branch's history as it is"),!a.has("push")&&!a.has("pr")&&f.push("don't push"),a.has("pr")||f.push("don't open a pull request");let d=`Yes \u2014 go ahead: ${u.join(", then ")}.`;return f.length&&(d+=` ${f.join(", and ").replace(/^./,p=>p.toUpperCase())}.`),a.has("pr")&&(d+=i?" If a repo's PR for this branch is already open, just push that repo's update instead.":" If a PR for this branch is already open, just push the update instead."),d}var u_=3e4;function tg(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<=u_&&n.push({box:r.box,from:r.from,to:e+1e12}),n}function eg(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 Gw={commit:"commit",rebase:"rebase",push:"push",pr:"open a PR"};function ng(t){let e=(Array.isArray(t)?t:[]).filter(r=>Gw[r]).map(r=>Gw[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 h_}from"node:child_process";import{promisify as w_}from"node:util";import d_ from"node:fs";import uu from"node:fs/promises";import ag from"node:path";import f_ from"node:os";var ig=process.env.BOSTRAT_STATE_DIR||ag.join(process.env.HOME||f_.tmpdir(),".bostrat"),Ja=ag.join(ig,"github-metrics.json"),cg=60*6e4,rg=5*6e4,p_=6e4,Ur=Date.now(),We=new Map,wn=null,er=0,og=0,Xa=t=>[...t.values()].reduce((e,n)=>e+n,0);try{let t=JSON.parse(d_.readFileSync(Ja,"utf8"));t?.windowStart&&(Date.now()-t.windowStart<cg?(Ur=t.windowStart,We=new Map(Object.entries(t.byCaller||{})),wn=t.lastHour||null):Xa(new Map(Object.entries(t.byCaller||{})))&&(wn={start:t.windowStart,total:Xa(new Map(Object.entries(t.byCaller||{}))),byCaller:t.byCaller||{}}))}catch{}var sg=Promise.resolve();function lg(t,e=!1){if(!e&&t-og<p_)return;og=t;let n=JSON.stringify({windowStart:Ur,byCaller:Object.fromEntries(We),lastHour:wn});sg=sg.then(()=>uu.mkdir(ig,{recursive:!0})).then(()=>uu.writeFile(`${Ja}.tmp`,n)).then(()=>uu.rename(`${Ja}.tmp`,Ja)).catch(()=>{})}function ug(t){t-Ur<cg||(wn={start:Ur,total:Xa(We),byCaller:Object.fromEntries(We)},wn.total&&console.log(`[github] ${wn.total} calls last hour`,wn.byCaller),Ur=t,We=new Map,lg(t,!0))}function qe(t="other",e=Date.now()){ug(e),We.set(t,(We.get(t)||0)+1),lg(e)}function dg(t=Date.now()){return ug(t),{windowStart:Ur,total:Xa(We),byCaller:Object.fromEntries(We),lastHour:wn,backoffUntil:er>t?er:null}}var m_=/rate limit|too quickly|abuse detection|HTTP 429/i;function gn(t){return m_.test(String(t?.message||t||""))}function yn(t,e=Date.now()){return er<=e&&console.warn(`[github] rate limited \u2014 backing off ${rg/6e4} min:`,String(t?.message||t||"").slice(0,200)),er=Math.max(er,e+rg),er}function Ya(t=Date.now()){return Math.max(0,er-t)}async function du(t,e){let n={working:!1,prompted:!1};if(!t)return n;let r=await mt(t,{lines:Tt}).catch(()=>"");if(!Oe(e,r))return lp(e,r)?{working:!0,prompted:!1}:n;if(Dr(e,r)){let o=await rn(t).catch(()=>r);if(tr({name:t,agent:e},o))return{working:!1,prompted:!0}}return{working:!0,prompted:!1}}async function Za(t,e){return(await du(t,e)).working}var fg=w_(h_),pg=[4e3,15e3,45e3],g_=2500,y_=5*6e4;async function fu(t,e,{midTurn:n=Za,pollMs:r=g_,capMs:o=y_}={}){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 b_=6e4,k_=9e4,x_=2e3,S_=/(?:^|[;&|]\s*|\)\s*)gh\s+(?:[^\s;&|"'`$]+\s+)*?pr\s+create\b/,v_=/(?:^|[;&|]\s*|\)\s*)gh\s+(?:[^\s;&|"'`$]+\s+)*?pr\s+merge\b/,T_=/\s--web\b/,mg=/(?:--repo|-R)[\s=]+([\w.-]+\/[\w.-]+)/;function pu(t,e){return t.slice(e).replace(/^[\s;&|)]+/,"").replace(/"[^"]*"|'[^']*'/g,'""').split(/[;&|]/)[0]}function hg(t){let e=String(t||""),n=e.match(S_);if(!n)return null;let r=pu(e,n.index);return T_.test(r)?null:{repo:r.match(mg)?.[1]??null}}var A_=new Set(["--subject","-t","--body","-b","--body-file","-F","--match-head-commit","--author-email","-A","--repo","-R"]);function wg(t){let e=String(t||""),n=e.match(v_);if(!n)return null;let r=pu(e,n.index),o=pu(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("-")){A_.has(c)&&i++;continue}s=c.replace(/^["']+|["']+$/g,"")||null;break}return{repo:r.match(mg)?.[1]??null,selector:s,auto:/\s--auto\b/.test(r)}}var E_="number,url,title,headRefName,createdAt";async function __(t,e){let n=async a=>{qe("observer");let{stdout:i}=await fg("gh",["pr","list","--repo",t,"--author","@me","--state","all","--limit","10","--json",E_],{timeout:3e4,maxBuffer:4*1024*1024,env:{...process.env,...a}}).catch(c=>{throw gn(c)&&yn(c),c});return JSON.parse(i||"[]")},r=a=>(a||[]).filter(i=>Date.parse(i.createdAt)>=e-b_),o=await ue({slug:t}),s=r(await n(o));return!s.length&&o.GH_TOKEN&&(s=r(await n({}).catch(()=>[]))),s.sort((a,i)=>Date.parse(i.createdAt)-Date.parse(a.createdAt))}async function I_(t,e){if(e)return[e];let n=[];for(let r of gg(t)){let o=await ot(r).catch(()=>null);o&&!n.includes(o)&&n.push(o)}for(let r of await _e().catch(()=>[])){let o=await ot(r.path).catch(()=>null);o&&!n.includes(o)&&n.push(o)}return n}function gg(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 R_(t,e,n){let r=t.workspace?.branch,o=[];for(let s of await I_(t,e)){let a;try{a=await __(s,n)}catch{continue}for(let i of a){let c=await Pt(s,i.number);if(!(c&&(c.trackId!==t.id||c.announced))){if(r&&i.headRefName===r)return{slug:s,pr:i};o.push({slug:s,pr:i})}}}return o.length?(o.sort((s,a)=>Date.parse(a.pr.createdAt)-Date.parse(s.pr.createdAt)),o[0]):null}var bn=new Set;function yg(t,e,{delays:n=pg,turnEnd:r}={}){let o=hg(e.command);if(!o)return null;let s=!!wg(e.command),a=`${t.id}|${e.id??e.ts??e.command}`;if(bn.has(a))return null;bn.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 R_(t,o.repo,i).catch(()=>null);if(!u)continue;let{slug:f,pr:d}=u,{link:p,created:m}=await Xt({repo:f,number:d.number,trackId:t.id,url:d.url,headRef:d.headRefName,title:d.title,source:"observed"});return!m&&!(p?.trackId===t.id&&await Uo(f,d.number))?null:(await fu(e.box,e.agent,r),await R({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:Date.now(),change:"opened",number:d.number,url:d.url,title:d.title,branch:d.headRefName,repo:f,...s?{selfMerge:!0}:{}}}),d)}return null}finally{bn.delete(a)}})();return c.catch(()=>bn.delete(a)),c}var $_="number,url,title,headRefName,state";function bg(t,e,{delays:n=pg,turnEnd:r,openedWait:o}={}){let s=wg(e.command);if(!s)return null;let a=`merge|${t.id}|${e.id??e.ts??e.command}`;if(bn.has(a))return null;bn.add(a);let i=(async()=>{try{let c=s.repo,l=s.selector,u=l?.match(/github\.com\/([\w.-]+\/[\w.-]+)\/pull\/(\d+)/);u&&([,c,l]=u);let f=[];if(c)f=[c];else{let m=gg(t);if(m.length>1&&/^\d+$/.test(String(s.selector??"")))return null;for(let w of m){let y=await ot(w).catch(()=>null);y&&!f.includes(y)&&f.push(y)}}if(l||=t.workspace?.branch,!f.length||!l)return null;c=f[0];let d=async(m,w)=>{let y=await Pt(c,m.number);return y&&y.trackId!==t.id?null:(y||await Xt({repo:c,number:m.number,trackId:t.id,url:m.url,headRef:m.headRefName,title:m.title,source:"merge-observed"}),await Ho(c,m.number),w&&((await L(t).catch(()=>[])).some(_=>_.type==="pr.state.changed"&&(_.payload?.change==="auto-armed"||_.payload?.change==="merged")&&_.payload?.number===m.number)||(await fu(e.box,e.agent,r),await R({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:Date.now(),change:"auto-armed",number:m.number,url:m.url,title:m.title,branch:m.headRefName,repo:c}}))),m)},p=null;for(let m of n){await new Promise(I=>setTimeout(I,m));let w=[];for(let I of f)try{qe("observer");let{stdout:C}=await fg("gh",["pr","view",String(l),"--repo",I,"--json",$_],{timeout:3e4,maxBuffer:4*1024*1024,env:{...process.env,...await ue({slug:I})}}),v=JSON.parse(C||"null");v?.number&&w.push({slug:I,pr:v})}catch(C){gn(C)&&yn(C)}let y=w.find(I=>I.pr.state==="MERGED")??(w.length===1?w[0]:null);if(!y)continue;c=y.slug;let b=y.pr;if(!b?.number)continue;if(b.state==="CLOSED")return null;if(b.state!=="MERGED"){if(p=b,s.auto)return d(b,!0);continue}let S=await Pt(c,b.number);if(S&&S.trackId!==t.id)return null;S||await Xt({repo:c,number:b.number,trackId:t.id,url:b.url,headRef:b.headRefName,title:b.title,source:"merge-observed"}),await fu(e.box,e.agent,r);let _=await L(t).catch(()=>[]);if(_.some(I=>I.type==="pr.state.changed"&&I.payload?.change==="merged"&&I.payload?.number===b.number))return null;let $=I=>I.some(C=>C.type==="pr.state.changed"&&C.payload?.change==="opened"&&C.payload?.number===b.number),et=0;if(!$(_)){if(hg(e.command)&&await Uo(c,b.number)){let I=Date.now();et=I+1,await R({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:I,change:"opened",number:b.number,url:b.url,title:b.title,branch:b.headRefName,repo:c,selfMerge:!0}})}else if((await Pt(c,b.number))?.announced){let I=o?.capMs??k_,C=o?.pollMs??x_,v=Date.now()+I;for(;Date.now()<v&&(await new Promise(B=>setTimeout(B,C)),!$(await L(t).catch(()=>[]))););}}return await R({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:Math.max(Date.now(),et),change:"merged",number:b.number,url:b.url,title:b.title,branch:b.headRefName,repo:c}}),await xn(c,b.number).catch(()=>{}),await kn(c,b.number).catch(()=>{}),await pn(c,b.number,"merged"),b}return p?d(p,!1):null}finally{bn.delete(a)}})();return i.catch(()=>bn.delete(a)),i}function hu(t){return t.sourceId?`${t.role}|id:${t.sourceId}`:`${t.role}|${t.ts??"?"}|${String(t.text||"").slice(0,40)}`}function kg(t){return t.id?`cmd|${t.box}|id:${t.id}`:`cmd|${t.box}|${t.ts??"?"}|${String(t.command||"").slice(0,40)}`}function xg(t){return`job|${t.box}|id:${t.id}`}var mu=new Map,Qa=new Map;async function wu(t){return mu.has(t.id)?mu.get(t.id):(Qa.has(t.id)||Qa.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(hu({role:o,sourceId:r.payload.sourceId})),n.keys.add(hu({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(kg({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(xg({id:r.payload.id,box:r.boxId}));return mu.set(t.id,n),n}).finally(()=>Qa.delete(t.id))),Qa.get(t.id))}async function Sg(t,e){try{let n=(await wu(t)).keys;for(let r of e||[]){if(!r||r.ts==null||!String(r.text||"").trim()&&!r.images?.length)continue;let o=hu(r);if(!n.has(o)&&(n.add(o),await R(t,{type:r.role==="user"?"turn.user":"turn.agent",actor:r.role==="user"?"user":"agent",source:"transcript",boxId:r.box,provider:r.agent||null,payload:{text:r.text,ts:r.ts,model:r.model||null,tools:r.tools||void 0,boxRole:r.boxRole,sourceId:r.sourceId||void 0,note:r.note||void 0,images:r.images?.length?r.images:void 0}}),r.role==="assistant"&&r.box===t.leadBox&&t.workspace?.branch)){let s=Yw(r.text);if(s){let{offered:a,toPr:i}=Qw(s),c;if(t.workspace?.kind==="multi"){let l=await ts(t).catch(()=>[]);c=l.length?l.map(u=>({name:u.name,slug:u.repo??null})):void 0}await R(t,{type:"pr.offered",actor:"agent",source:"transcript",boxId:r.box,provider:r.agent||null,payload:{offerId:O_(),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 vg(t,e){try{let n=await wu(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=kg(o);n.keys.has(s)||(n.keys.add(s),n.cmdN+=1,await R(t,{type:"tool.called",actor:"agent",source:"transcript",boxId:o.box,provider:o.agent||null,payload:{id:o.id??null,tool:o.tool||null,command:o.command,description:o.description||"",summary:o.summary||o.command,ts:o.ts??null,boxRole:o.boxRole,n:n.cmdN}}),yg(t,o),bg(t,o))}}catch{}}async function Tg(t,e){try{let n=await wu(t),r=[...e||[]].filter(o=>o&&o.id).sort((o,s)=>(o.ts||0)-(s.ts||0));for(let o of r){let s=xg(o);n.keys.has(s)||(n.keys.add(s),await R(t,{type:"job.started",actor:"agent",source:"transcript",boxId:o.box,provider:o.agent||null,payload:{id:o.id,kind:o.kind||"agent",name:o.name||null,detail:o.detail||null,ts:o.ts??null,boxRole:o.boxRole}}))}}catch{}}var M_=/^Base directory for this skill:\s*(\S+)/,C_=/^Run the "([^"]+)" workflow\./,N_=/<command-name>\s*\/?([a-z0-9:_-]+)\s*<\/command-name>/i,P_=/^<local-command-(?:stdout|caveat)>/i,B_=/^\[Image: original \d+x\d+, displayed at \d+x\d+\.\s*Multiply coordinates by [\d.]+ to map to original image\.\]$/,D_=/^Continue from where you left off\.$/,j_=/^<task-notification>/i,L_=/<summary>([\s\S]*?)<\/summary>/i,F_=/<task-id>([\s\S]*?)<\/task-id>/i,U_=/<tool-use-id>([\s\S]*?)<\/tool-use-id>/i,H_=/<status>([\s\S]*?)<\/status>/i,W_=/<result>([\s\S]*?)(?:<\/result>|$)/i;var gu=new Set(["model","clear","compact","login","exit","quit"]);function Ag(t){return gu.has(String(t||"").replace(/^\//,"").toLowerCase())}var q_=/^\/([a-z0-9:_-]+)$/i;function ti(t){let e=String(t||"").trim();if(!e)return null;let n=e.match(M_);if(n)return{kind:"skill",name:n[1].split("/").filter(Boolean).pop()||"skill"};let r=e.match(C_);if(r)return{kind:"skill",name:r[1]};let o=e.match(N_);if(o){let a=o[1].toLowerCase();return gu.has(a)?{kind:"suppress"}:{kind:"command",command:`/${a}`}}let s=e.match(q_);if(s&&gu.has(s[1].toLowerCase()))return{kind:"suppress"};if(P_.test(e))return{kind:"suppress"};if(D_.test(e))return{kind:"suppress"};if(B_.test(e))return{kind:"suppress"};if(j_.test(e)){let a=c=>K_(e.match(c)?.[1].trim()||"")||null,i=a(W_);return{kind:"task",summary:a(L_)||"",taskId:a(F_),toolUseId:a(U_),status:a(H_)?.toLowerCase()||null,resultHead:i?i.length>600?`${i.slice(0,600).trimEnd()}\u2026`:i:null}}return null}function K_(t){return t.replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/'/g,"'").replace(/&/g,"&")}function Eg(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 _g(t){return t?.kind==="skill"?"skill-run":t?.kind==="task"?"task-done":"command-run"}var it=t=>t.payload?.ts??t.ts;function G_(t){return{id:t.id,kind:"prompt",groupId:t.groupId,provider:t.provider,box:t.box,agent:t.provider,questionTotal:t.questions.length,answeredVia:"chat",questions:t.questions.map((e,n)=>({index:n,header:e.header||"",question:e.question,multiSelect:!!e.multiSelect,chosen:[]})),ts:t.ts}}var V_=new Set(["opened","ci-fixed","reconciled","pushed"]),Ig=t=>`${t.repo??""}#${t.number}`;function z_(t,e,n){let r=Ig(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 es(t){let e=[],n=new Set,r=new Set,o=new Map,s=new Map,a=new Set,i=new Map,c=new Set,l=new Map,u=/^Agent "([\s\S]+)" finished$/,f=new Map;for(let g of t||[]){if(g.type!=="tool.called"||g.source!=="transcript")continue;let h=String(g.payload?.command||"");if(/\bbostrat-mock\s+(?:add|update)\b/.test(h))for(let A of h.matchAll(/[\w./~-]+\.(?:png|jpe?g|gif|webp|bmp|svg)\b/gi)){let E=A[0].split("/").pop().toLowerCase();f.has(E)||f.set(E,[]),f.get(E).push(g.payload?.ts??g.ts)}}let d=3*6e4,p=20*6e4,m=(g,h)=>!!(g?.name&&f.get(String(g.name).toLowerCase()))?.some(E=>E>=h-d&&E<=h+p);for(let g of t||[])if(g.type==="turn.retracted"){r.add(`${g.boxId}|${g.payload?.saidTs}`);let h=_t(g.payload?.text);h&&(o.has(g.boxId)||o.set(g.boxId,[]),o.get(g.boxId).push(h))}else if(g.type==="turn.user"&&g.source==="orchestrator"){let h=_t(g.payload?.text);h&&(s.has(g.boxId)||s.set(g.boxId,new Set),s.get(g.boxId).add(h))}else if(g.type==="job.started"&&g.payload?.id)a.add(g.payload.id),(g.payload.kind||"agent")==="agent"&&g.payload.name&&c.add(g.payload.name);else if(g.type==="turn.user"&&g.payload?.note?.kind==="task"){let h=g.payload.note,A={status:h.status||null,summary:h.summary||null,resultHead:h.resultHead??null,ts:it(g)};h.toolUseId&&i.set(h.toolUseId,A);let E=String(h.summary||"").match(u);E&&l.set(E[1],A)}let w=new Map,y=new Map,b=null,S=new Map,_=new Map,$=new Map,et=new Map,I=new Map,C=new Map,v=new Map,B=()=>{for(let g of v.values())g.questions.length&&e.push(g);v.clear()},D=null,lt=6e4,q=new Map,T=(g,h)=>{let A=o.get(g);if(!A?.length)return!1;let E=_t(h),z=s.get(g);return A.some(W=>{if(!E.startsWith(W))return!1;let at=_t(E.slice(W.length));return!!at&&!!z?.has(at)})};for(let g of t||[]){let h=g.payload||{};switch(g.type){case"turn.user":{if(g.source==="hook"||!String(h.text||"").trim()&&!h.images?.length)break;{let A=h.note||ti(h.text);if(A){if(A.kind==="task"){if(A.toolUseId&&a.has(A.toolUseId))break;let E=String(A.summary||"").match(u);if(!A.toolUseId&&E&&c.has(E[1]))break}if(A.kind==="command"&&Ag(A.command))break;A.kind!=="suppress"&&e.push({id:g.id,kind:"event",type:_g(A),name:A.name??null,command:A.command??null,summary:A.summary??null,ts:it(g),box:g.boxId,agent:g.provider});break}}if(r.has(`${g.boxId}|${it(g)}`)||g.source==="transcript"&&T(g.boxId,h.text))break;if(h.kind==="merge-notify"||h.kind==="ship-notify"){e.push({id:g.id,kind:"event",type:h.kind==="ship-notify"?"ship-notified":"merge-notified",text:h.text,ts:it(g),box:g.boxId,agent:g.provider});break}if(h.kind==="mock-picked"||h.kind==="ci-fix"||h.kind==="reconcile"||h.kind==="pr-go"||h.kind==="push-go"||h.kind==="spin-up-view"||h.kind==="prompt-answer"){h.kind==="prompt-answer"&&(D=null);break}for(let A of S.values())A.status="stale";S.clear(),D&&D.box===g.boxId&&(e.push(G_(D)),D=null),e.push({id:g.id,kind:"message",role:"user",text:h.text,ts:it(g),box:g.boxId,agent:g.provider,boxRole:h.boxRole,...g.actor==="orchestrator"?{orchestration:h.kind||!0}:{},...h.sourceId?{sourceId:h.sourceId}:{},...h.attachments?.length?{attachments:h.attachments}:{},...h.images?.length?{images:h.images}:{}});break}case"turn.agent":{if(h.model==="<synthetic>"&&/^No response requested\.$/.test(String(h.text||"").trim()))break;B();let A=(h.images||[]).filter(at=>!m(at,it(g)));if(!String(h.text||"").trim()&&!A.length)break;if(g.provider==="cursor"){let at=`${g.boxId}|${h.text}`;if(n.has(at))break;n.add(at)}(!D||D.box===g.boxId)&&(D=null);let E=g.provider==="codex"?(h.tools||[]).filter(at=>!Ml.has(at)&&!zo.has(at)):h.tools||[],z=h.text,W=null;if(zl.has(g.provider)){let at=Jl(String(h.text||""));at&&(z=at.text,W={questions:at.questions})}e.push({id:g.id,kind:"message",role:"assistant",text:z,ts:it(g),box:g.boxId,agent:g.provider,boxRole:h.boxRole,...W?{ask:W}:{},...E.length?{tools:E}:{},...h.model?{model:h.model}:{},...h.sourceId?{sourceId:h.sourceId}:{},...A.length?{images:A}:{}}),W&&(D={id:g.id,groupId:Xl(g.provider,h.sourceId||g.id,W.questions),provider:g.provider,box:g.boxId,questions:W.questions,ts:it(g)});break}case"job.started":{if(!h.id)break;let A=it(g),E=i.get(h.id);if(!E&&(h.kind||"agent")==="agent"&&h.name){let K=l.get(h.name);K&&K.ts>=A&&(E=K)}let z={jobId:h.id,jobKind:h.kind||"agent",name:h.name||null,detail:h.detail||null,status:E?E.status&&E.status!=="completed"?"failed":"done":"running",startedTs:A,finishedTs:E?.ts??null,summary:E?.summary??null,resultHead:E?.resultHead??null},W=q.get(g.boxId),at=W?.jobs[W.jobs.length-1]?.startedTs;if(W&&at!=null&&A-at<=lt)W.jobs.push(z);else{let K={id:g.id,kind:"job-dock",dockId:h.id,ts:A,box:g.boxId,agent:g.provider,jobs:[z]};q.set(g.boxId,K),e.push(K)}break}case"handoff.done":e.push(Sn(g,"lead-handoff"));break;case"subtask.delegated":e.push({...Sn(g,"subtask-delegated"),to:g.boxId,toAgent:g.provider});break;case"subtask.merged":e.push(Sn(g,"subtask-merged"));break;case"pr.state.changed":{if(!h.change)break;if(h.change==="opened"){for(let E of S.values())E.status="stale";S.clear()}let A=Sn(g,`pr-${h.change}`);if(V_.has(h.change)&&h.number!=null){let E=z_(w,y,h);if(w.size>=2){for(let W of y.values())for(let at of W)at.grouped=!0;A.groupPrs=[...w.values()];let z=[A];for(let W of w.keys())y.set(W,z)}else{let z=y.get(E)??[];z.push(A),y.set(E,z)}}else if(h.change==="merged"||h.change==="closed"||h.change==="lost"){let E=Ig(h),z=b&&!b.complete||w.size>=2;if(w.has(E))w.delete(E),y.delete(E);else if(h.number!=null)for(let W of[...w.keys()])W.endsWith(`#${h.number}`)&&(w.delete(W),y.delete(W));if(z&&h.number!=null){(!b||b.complete)&&(b={id:g.id,kind:"event",type:"pr-group-settled",prs:[],remaining:[],complete:!1,at:it(g),ts:it(g),box:g.boxId,agent:g.provider},e.push(b)),b.prs.push({number:h.number,url:h.url??null,title:h.title??null,repo:h.repo??null,branch:h.branch??null,change:h.change,ts:it(g)}),b.remaining=[...w.values()],b.complete=w.size===0;break}}e.push(A);break}case"pr.offered":{if(!h.offerId)break;for(let E of S.values())E.status="stale";S.clear();let A={id:g.id,kind:"pr-offer",offerId:h.offerId,excerpt:h.excerpt||null,branch:h.branch||null,base:h.base||null,offered:Array.isArray(h.offered)&&h.offered.length?h.offered:null,toPr:h.toPr&&typeof h.toPr=="object"?{number:h.toPr.number??null}:null,repos:Array.isArray(h.repos)&&h.repos.length?h.repos:null,status:"open",ts:it(g),box:g.boxId,agent:g.provider};S.set(h.offerId,A),_.set(h.offerId,A),e.push(A);break}case"pr.offer.answered":{let A=_.get(h.offerId);A&&(A.status=h.action||"dismiss",A.actions=Array.isArray(h.actions)?h.actions:null,A.notify=h.notify===!0,S.delete(h.offerId));break}case"box.state.changed":{let A=h.change==="model"?"model-change":h.change;if(!A)break;let E=Sn(g,A);A==="model-change"&&(E.to=h.model),e.push(E);break}case"view.state.changed":{if(h.change!=="created"||h.restarted)break;e.push({id:g.id,kind:"view-card",viewId:h.viewId,name:h.name,slug:h.slug,port:h.port,origin:h.origin,ts:it(g),box:g.boxId,agent:g.provider});break}case"view.spinup.failed":{e.push({id:g.id,kind:"view-spinup-failed",name:h.name||null,reason:h.reason||"no-view",ts:it(g),box:g.boxId,agent:g.provider});break}case"mock.state.changed":{if(h.change==="picked"){e.push({id:g.id,kind:"mock-picked",mockId:h.mockId,name:h.name,group:h.group??null,version:h.version??null,siblings:h.siblings??[],ts:it(g),box:g.boxId,agent:g.provider});break}if(h.change!=="created")break;e.push({id:g.id,kind:"mock-card",mockId:h.mockId,name:h.name,group:h.group??null,ts:it(g),box:g.boxId,agent:g.provider});break}case"flow.state.changed":{if(h.change!=="created")break;e.push({id:g.id,kind:"flow-card",flowId:h.flowId,name:h.name,stepCount:h.stepCount??null,mockIds:h.mockIds??[],ts:it(g),box:g.boxId,agent:g.provider});break}case"runbook.state.changed":{if(h.change!=="created")break;e.push({id:g.id,kind:"runbook-card",runbookId:h.runbookId,name:h.name,ts:it(g),box:g.boxId,agent:g.provider});break}case"ticket.linked":{e.push({id:g.id,kind:"ticket-card",provider:h.provider||"linear",key:h.key,url:h.url,title:h.title??null,linkSource:h.linkSource??null,ts:it(g),box:g.boxId,agent:g.provider});break}case"ticket.move.offered":{if(!h.offerId)break;for(let E of $.values())E.key===h.key&&(E.status="stale",$.delete(E.offerId));let A={id:g.id,kind:"ticket-offer",offerId:h.offerId,provider:h.provider||"linear",key:h.key,url:h.url??null,phase:h.phase??null,from:h.from??null,to:h.to?.name??null,prNumber:h.prNumber??null,status:"open",ts:it(g),box:g.boxId,agent:g.provider};$.set(h.offerId,A),et.set(h.offerId,A),e.push(A);break}case"ticket.move.answered":{let A=et.get(h.offerId);A&&(A.status=h.action==="moved"?"moved":"dismiss",$.delete(h.offerId));break}case"ticket.state.changed":{for(let A of $.values())A.key===h.key&&A.to===h.to&&(A.status="stale",$.delete(A.offerId));if(h.mode==="offer")break;e.push(Sn(g,"ticket-state"));break}case"ticket.commented":{e.push({id:g.id,kind:"ticket-comment",key:h.key,url:h.url??null,excerpt:h.excerpt??null,ts:it(g),box:g.boxId,agent:g.provider});break}case"ticket.flow.changed":e.push(Sn(g,"ticket-flow"));break;case"ticket.create.offered":{if(!h.proposalId)break;for(let E of I.values())E.status="stale";I.clear();let A={id:g.id,kind:"ticket-create-offer",proposalId:h.proposalId,provider:h.provider||"linear",epic:h.epic??null,issues:Array.isArray(h.issues)?h.issues:[],team:h.team??null,needsTeam:!!h.needsTeam,linkCurrentTrack:!!h.linkCurrentTrack,status:"open",ts:it(g),box:g.boxId,agent:g.provider};I.set(h.proposalId,A),C.set(h.proposalId,A),e.push(A);break}case"ticket.create.answered":{let A=C.get(h.proposalId);A&&(A.status=h.action==="created"?"created":"dismiss",A.createdCount=h.createdCount??null,A.failedCount=h.failedCount??null,I.delete(h.proposalId));break}case"ticket.created":{let A={items:Array.isArray(h.items)?h.items:[],failed:Array.isArray(h.failed)?h.failed:[],team:h.team??null},E=h.proposalId?C.get(h.proposalId):null;if(E){E.created=A;break}e.push({id:g.id,kind:"ticket-created",...A,ts:it(g),box:g.boxId,agent:g.provider});break}case"error":{if(!h.kind)break;e.push(Sn(g,h.kind));break}case"provider.connected":{e.push({id:g.id,kind:"event",type:"provider-connected",provider:h.provider||g.provider,ts:it(g),at:it(g),box:g.boxId,agent:g.provider});break}case"prompt.answered":{if(D=null,h.control||!h.question)break;let A=h.groupId||h.promptId;if(!A)break;let E=v.get(A);E||(E={id:g.id,kind:"prompt",groupId:A,provider:g.provider,box:g.boxId,agent:g.provider,questionTotal:h.questionTotal??1,questions:[],ts:it(g)},v.set(A,E));let z=h.questionIndex??0,W={index:z,header:h.header||"",question:h.question,multiSelect:!!h.multiSelect,chosen:Array.isArray(h.chosen)?h.chosen:[]},at=E.questions.findIndex(K=>K.index===z&&_t(K.question)===_t(W.question));at>=0?E.questions[at]=W:E.questions.push(W),E.questions.sort((K,J)=>K.index-J.index),E.questionTotal=Math.max(E.questionTotal,h.questionTotal??1),E.questions.length>=E.questionTotal&&(e.push(E),v.delete(A));break}default:break}}return X_(e.sort((g,h)=>(g.ts??0)-(h.ts??0)))}var J_=2*6e4;function X_(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<=J_){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 yu(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:Y_(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??es(t)).filter(o=>o.kind==="message");return Uh(n,r)}function Y_(t){let e=String(t.summary||"").trim();return e&&e!==t.command&&e!==Cr(t.command)?e:mn(t.command,t.description||"")}function Sn(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 Z_(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 bu=new Map;async function Q_(t,e){let n=`${t.id}:${e.name}`;if(!bu.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);bu.set(n,o)}return bu.get(n)}async function Rg(t,e){try{if(e?.agent!=="codex"||!e.cwd)return;let n=await Jn(e.cwd,e.pinned?e.sessionId:null);if(!n)return;let r=await Q_(t,e),o=Z_(await Ut(n.file,262144)),s=o[o.length-1];for(let a of o){if(a.ts==null||r.has(a.ts))continue;r.add(a.ts);let i={input:Math.max(0,a.last.input-a.last.cachedInput),output:a.last.output,cacheRead:a.last.cachedInput,cacheWrite:0},c=a.model||e.model||"gpt-5.5";await R(t,{type:"cost.recorded",actor:"agent",source:"transcript",boxId:e.name,provider:"codex",payload:{usd:pe(c,i),model:c,tokens:i,reasoningOutput:a.last.reasoningOutput||void 0,codexTs:a.ts,ts:a.ts}}),a===s&&a.contextWindow&&await R(t,{type:"context.usage",actor:"agent",source:"transcript",boxId:e.name,provider:"codex",payload:{occupancy:a.occupancy,window:a.contextWindow,pct:a.occupancy!=null?a.occupancy/a.contextWindow:null,model:a.model||null,ts:a.ts}})}}catch{}}import ei from"node:fs";import xu from"node:path";import tI from"node:os";var eI=process.env.BOSTRAT_STATE_DIR||xu.join(process.env.HOME||tI.tmpdir(),".bostrat"),Og=(t=process.env)=>!!t.BOSTRAT_CURSOR_USAGE_COOKIE,nI="https://cursor.com/api/dashboard/get-filtered-usage-events",$g=100;async function rI({sinceMs:t,cookie:e,fetchImpl:n=fetch}){let r=[];for(let o=1;o<=20;o++){let s=await n(nI,{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:$g})});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<$g)break}return r}function oI(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 sI=5*6e4,vn={at:0,rows:null,error:null},ni=()=>xu.join(eI,"cursor-usage.json");function Mg(){try{return JSON.parse(ei.readFileSync(ni(),"utf8"))}catch{return{sinceMs:0,seen:[]}}}function aI(t){try{ei.mkdirSync(xu.dirname(ni()),{recursive:!0});let e=ni()+".tmp";ei.writeFileSync(e,JSON.stringify(t)),ei.renameSync(e,ni())}catch{}}var iI=5e3;async function cI({fetchImpl:t,now:e=Date.now()}={}){if(vn.rows&&e-vn.at<sI)return vn.rows;let n=process.env.BOSTRAT_CURSOR_USAGE_COOKIE;if(!n)return null;let r=Mg(),o=Math.max(r.sinceMs-6e4,e-24*36e5);try{let s=await rI({sinceMs:o,cookie:n,fetchImpl:t});vn={at:e,rows:s.map(oI).filter(Boolean),error:null}}catch(s){vn={at:e,rows:null,error:String(s?.message||s)}}return vn.rows}var Su=()=>({enabled:Og(),lastPollAt:vn.at||null,error:vn.error}),ku=new Map;async function lI(t,e){let n=`${t.id}:${e.name}`;if(!ku.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);ku.set(n,o)}return ku.get(n)}async function Cg(t,e,{fetchImpl:n}={}){try{if(e?.agent!=="cursor"||!e.cwd||!Og())return;let r=await cI({fetchImpl:n});if(!r||!r.length)return;let o=await Xh(e.cwd,e.pinned?e.sessionId:null);if(!o?.chatId)return;let s=await lI(t,e),a=Mg(),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=pe(u,f)),!d&&l.chargedCents!=null&&(d=l.chargedCents/100),await R(t,{type:"cost.recorded",actor:"agent",source:"vendor",boxId:e.name,provider:"cursor",payload:{usd:d,model:u,...f?{tokens:f}:{},...l.vendorCents!=null?{vendorCents:l.vendorCents}:{},...l.chargedCents!=null?{chargedCents:l.chargedCents}:{},...l.kind?{kind:l.kind}:{},conversationId:l.conversationId,cursorEventId:l.id,ts:l.ts??void 0}})}aI({sinceMs:c,seen:[...i].slice(-iI)})}catch{}}import{execFile as mI}from"node:child_process";import{promisify as hI}from"node:util";import Ng from"node:fs/promises";import Pg from"node:path";import uI from"node:os";var Bg=process.env.BOSTRAT_STATE_DIR||Pg.join(process.env.HOME||uI.tmpdir(),".bostrat"),dI=Pg.join(Bg,"github-latency.ndjson"),fI=100,ri=[],vu=new Map;function oi({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};ri.push(l),ri.length>fI&&ri.shift();let u=vu.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)),vu.set(t,u),pI(l),l}async function pI(t){try{await Ng.mkdir(Bg,{recursive:!0}),await Ng.appendFile(dI,JSON.stringify(t)+`
|
|
147
|
+
`)}catch{}}function Dg(){let t={};for(let[e,n]of vu)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:ri.slice(-20)}}async function wI(t){let e=/workspaces[/\\]([0-9a-f]{8})(?:[/\\]|$)/.exec(t||"")?.[1];if(e)try{let[{stdout:n},{stdout:r}]=await Promise.all([Wr("git",["-C",t,"rev-parse","HEAD"]),Wr("git",["-C",t,"rev-parse","--abbrev-ref","HEAD"])]);await R({id:e,cwd:t},{type:"code.pushed",actor:"orchestrator",payload:{ref:r.trim(),sha:n.trim(),wip:!1,ts:Date.now()}})}catch{}}var Wr=hI(mI),gI={timeout:3e4,maxBuffer:8*1024*1024};async function ne(t,e,n={}){let{caller:r="prs",...o}=n,s=await ue({cwd:t});qe(r);try{let{stdout:a}=await Wr("gh",e,{cwd:t,...gI,...o,env:{...process.env,...s,...o.env||{}}});return String(a||"")}catch(a){throw gn(a)&&yn(a),a}}var si=null;async function Eu(){if(si&&Date.now()-si.at<6e4)return si.data;let t;try{qe("auth");let e=await Wr("gh",["auth","status"],{timeout:1e4});t={installed:!0,authenticated:!0,login:`${e.stdout||""}${e.stderr||""}`.match(/account\s+(\S+)/)?.[1]??null}}catch(e){t=e?.code==="ENOENT"||e?.code===127?{installed:!1,authenticated:!1,login:null}:{installed:!0,authenticated:!1,login:null}}if(t.installed&&!t.authenticated)try{let e=await ho();e.configured&&(t={installed:!0,authenticated:!0,login:e.username,via:"broker"})}catch{}return si={at:Date.now(),data:t},t}async function yI(t){try{let{stdout:e}=await Wr("git",["-C",t,"config","--get","remote.origin.url"],{timeout:5e3});return/github\.com/.test(e)}catch{return!1}}function Lg(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 Tu(t){let e=(t||[]).map(Lg),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 _u="number,title,url,headRefName,headRefOid,baseRefName,isDraft,author,reviewDecision,mergeable,mergeStateStatus,statusCheckRollup,autoMergeRequest,additions,deletions,createdAt,updatedAt",Fg=_u.replace("statusCheckRollup,","");function Iu(t){return/Resource not accessible by personal access token/.test(String(t?.stderr||t?.message||""))}async function Ug(t,e){if(!e)return null;let n=await ot(t);if(!n)return null;try{let r=await ne(t,["api",`repos/${n}/actions/runs?head_sha=${encodeURIComponent(e)}&per_page=100`,"--jq",".workflow_runs"]),o=new Map;for(let s of JSON.parse(r||"[]"))o.has(s.name)||o.set(s.name,s);return[...o.values()]}catch{return null}}function Hg(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 Wg(t,e){let n=await Ug(t,e);return n===null?{...Tu([]),unavailable:!0}:Tu(Hg(n))}function ai(t,e=Tu(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 bI=9e4,Hr=new Map;function kI(t,e,n){let r=new Set;for(let o of e){let s=`${t}#${o.number}`;if(o.mergeable!=="UNKNOWN"&&o.mergeStateStatus!=="UNKNOWN"){Hr.delete(s);continue}r.add(s),Hr.has(s)||Hr.set(s,n),o.mergeabilityPending=n-Hr.get(s)<bI}for(let o of Hr.keys())o.startsWith(`${t}#`)&&!r.has(o)&&Hr.delete(o);return e}async function qr(t,e){let n=await Ke(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");try{let o=await ne(n.path,["pr","view",String(r),"--json",_u]);return ai(JSON.parse(o))}catch(o){if(!Iu(o))throw o;let s=await ne(n.path,["pr","view",String(r),"--json",Fg]),a=JSON.parse(s);return ai(a,await Wg(n.path,a.headRefOid))}}var jg=(t,e)=>t.number-e.number;async function rs(t,{caller:e="prs"}={}){try{let n=await ne(t,["pr","list","--json",_u,"--limit","50"],{caller:e});return JSON.parse(n||"[]").map(r=>ai(r)).sort(jg)}catch(n){if(!Iu(n))throw n;let r=await ne(t,["pr","list","--json",Fg,"--limit","50"],{caller:e});return(await Promise.all(JSON.parse(r||"[]").map(async o=>ai(o,await Wg(t,o.headRefOid))))).sort(jg)}}var xI=3e4,SI=1500,ns=new Map;function Te(){ns.clear()}async function vI(t,e){if(!t)return e;for(let n of e){let r=await Pt(t,n.number).catch(()=>null);if(!r)continue;r.trackId&&(n.trackId=r.trackId),r.autoMerge&&(n.autoMergeArmed=!0),r.archiveOnMerge&&(n.archiveOnMerge=!0),r.notifyOnMerge&&(n.notifyOnMerge=!0);let o=s=>n.headSha&&s.headSha&&n.headSha!==s.headSha;r.ciFix&&(o(r.ciFix)?await Pa(t,n.number,{headSha:n.headSha}).catch(()=>{}):n.ciFix={delegatedAt:r.ciFix.delegatedAt,trackId:r.ciFix.trackId??null}),r.reconcile&&(o(r.reconcile)?await Da(t,n.number,{headSha:n.headSha}).catch(()=>{}):n.reconcile={delegatedAt:r.reconcile.delegatedAt,trackId:r.reconcile.trackId??null})}return e}async function qg(){let t=await Eu();if(!t.authenticated)return{auth:t,repos:[]};let e=[];for(let a of await _e())await yI(a.path)&&e.push(a);let n=Date.now(),r=Ya(n)>0,o=await Promise.all(e.map(async a=>{let i=ns.get(a.path),c=i?.pending?SI:xI;if(i&&(r||n-i.at<c))return i.entry;try{let l=await ot(a.path).catch(()=>null),u=kI(a.path,await rs(a.path,{caller:"tab"}),n);if(await vI(l,u),i?.entry?.prs)for(let p of u)i.entry.prs.find(w=>w.number===p.number)?.checks?.pending>0&&p.checks.total>0&&p.checks.pending===0&&oi({change:p.checks.failed>0?"checks-failed":"checks-green",repo:l||a.name,number:p.number,githubAt:p.checks.settledAt,source:"tab"});let f={name:a.name,path:a.path,prs:u},d=u.some(p=>p.mergeabilityPending||p.checks.pending>0||p.autoMergeArmed);return ns.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:ve(l)}}})),s=new Set(e.map(a=>a.path));for(let a of ns.keys())s.has(a)||ns.delete(a);return{auth:t,repos:o}}async function Ke(t){let e=(await _e()).find(n=>n.path===t);if(!e)throw new Error(`unknown repo: ${t}`);return e}async function nr(t){return po(t)}var os=new Set(["squash","merge","rebase"]);async function Kr(t,e,{method:n="squash"}={}){let r=await Ke(t);if(!os.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 ue({cwd:r.path})).GH_TOKEN){let a=await Xi().catch(()=>null);a&&s.push("--body",`Merged by bostrat on behalf of @${a}`)}try{await ne(r.path,s)}catch(a){throw new Error(ve(a))}finally{Te()}return{ok:!0,number:o}}async function ss(t,e){let n=await Ke(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");try{await ne(n.path,["api","-X","PUT",`repos/{owner}/{repo}/pulls/${r}/update-branch`])}catch(s){throw new Error(ve(s))}finally{Te()}let o=await ot(n.path).catch(()=>null);return o&&await xh(o,r).catch(()=>{}),{ok:!0,number:r}}async function Ru(t,e){let n=await Ke(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");try{await ne(n.path,["pr","merge",String(r),"--disable-auto"])}catch(o){throw new Error(ve(o))}finally{Te()}return{ok:!0,number:r}}var TI=2500,AI=9e4,EI=t=>new Promise(e=>setTimeout(e,t));function $u(t){return t.green&&!t.isDraft}function _I(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 Kg(t,{update:e=!0}={}){return t.isDraft?{action:"skip",reason:"draft"}:$u(t)?{action:"merge"}:e&&t.mergeStateStatus==="BEHIND"&&t.mergeable!=="CONFLICTING"&&t.checks.failed===0?{action:"update-arm"}:{action:"arm"}}function Gg(t){return[...t.filter(e=>e.plan.action!=="merge"),...t.filter(e=>e.plan.action==="merge")]}var Au=new Map;function Vg(){let t={};for(let[e,n]of Au)t[e]={...n};return t}async function zg(t,{method:e="merge",onMerged:n=null}={}){let r=await Ke(t);if(!os.has(e))throw new Error(`invalid merge method: ${e}`);if(Au.get(r.path)?.running)throw new Error("a merge-all is already running for this repo");let o=(await rs(r.path)).filter($u).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 Au.set(r.path,s),RI(s,o,{listPrs:()=>rs(r.path),merge:a=>Kr(r.path,a,{method:e}),sleep:EI,onMerged:n}).catch(()=>{}),{ok:!0,repo:r.path,total:s.total,queue:o.map(a=>a.number)}}async function II(t,e){let n=Date.now()+(e.settleMaxMs??AI),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(TI)}}async function RI(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 II(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: ${ve(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(!$u(a)){t.skipped.push({number:a.number,title:a.title,reason:_I(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:ve(i)})}}}finally{t.current=null,t.running=!1,t.finishedAt=Date.now(),Te()}}async function Ou(t,{title:e,body:n="",base:r=null}={}){if(typeof e!="string"||!e.trim())throw new Error("title required");try{await Wr("git",["-C",t,"push","-u","origin","HEAD"],{timeout:12e4,maxBuffer:4*1024*1024})}catch(l){throw new Error(`push failed: ${ve(l)}`)}await wI(t);let o=n||"";if((await ue({cwd:t})).GH_TOKEN){let l=await Xi().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 ne(t,s)}catch(l){throw new Error(ve(l))}finally{Te()}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 Jg(t,e){let n=await Ke(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");let o;try{let i;try{i=await ne(n.path,["pr","checks",String(r),"--json","name,state,link,bucket"])}catch(c){if(i=String(c?.stdout||""),!i.trim())throw new Error(ve(c))}o=JSON.parse(i||"[]").filter(c=>c.bucket==="fail")}catch(i){if(!Iu(i))throw i;let c=await ne(n.path,["pr","view",String(r),"--json","headRefOid"]),l=await Ug(n.path,JSON.parse(c).headRefOid);if(l===null)throw new Error(ve(i));o=Hg(l).map(Lg).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=$I(i.link),l=null;c&&!s.has(c)&&(s.add(c),l=await ne(n.path,["run","view",c,"--log-failed"]).then(u=>OI(u)).catch(()=>null)),a.push({name:i.name,link:i.link||null,log:l})}return a}function $I(t){return String(t||"").match(/\/actions\/runs\/(\d+)/)?.[1]??null}function OI(t,{maxLines:e=400,maxBytes:n=64*1024}={}){let r=String(t||""),o=r.split(`
|
|
150
150
|
`);return o.length>e&&(r=`\u2026 (${o.length-e} earlier lines truncated)
|
|
151
151
|
`+o.slice(-e).join(`
|
|
152
152
|
`)),Buffer.byteLength(r)>n&&(r=`\u2026 (truncated)
|
|
153
|
-
`+r.slice(-n)),r}function
|
|
154
|
-
`).find(n=>n.trim())||"gh failed"}var pI=6e3,Za=new Map;function xu(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 jg(t){let e=t?.workspace?.branch;if(!e)return;let n;try{n=await D(t)}catch{return}if(xu(n)==null)return;let r=(t.boxes||[]).find(c=>c.name===t.leadBox);if(await Wa(t.leadBox,r?.agent).catch(()=>!1))return;let o=Date.now();if(o-(Za.get(t.id)||0)<pI)return;Za.set(t.id,o);let s;try{s=await Yo(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 Wt({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 Bo(i,a.number))){Za.delete(t.id);return}}await R(t,{type:"pr.state.changed",actor:"agent",source:"orchestrator",boxId:t.leadBox,payload:{ts:Date.now(),change:"opened",number:a.number,url:a.url,title:a.title,branch:e,...i?{repo:i}:{}}}).catch(()=>{}),Za.delete(t.id)}import mI from"node:fs/promises";import Wg from"node:path";import hI from"node:os";import{randomUUID as qg}from"node:crypto";var wI=process.env.BOSTRAT_STATE_DIR||Wg.join(process.env.HOME||hI.tmpdir(),".bostrat"),Kg=Wg.join(wI,"flows.json"),Lg=20,Fg=80,Ug=500,Hg=80,Ue=null,Su=null;async function He(){return Ue||(Su||=(async()=>{let t=!0;try{Ue=JSON.parse(await mI.readFile(Kg,"utf8"))}catch{Ue={flows:[]},t=!1}finally{Su=null}if(Ue.flows||=[],!t&&pt()){let{records:e,source:n}=await Tt("flows").catch(()=>({records:[],source:"error"}));if(n==="plane"){for(let r of e)!r.deletedAt&&r.data?.id&&Ue.flows.push(r.data);Ue.flows.length&&await Ur().catch(()=>{})}}return Ue})(),Su)}async function Ur(){await Mt(Kg,Ue)}var Qa=t=>Rt("flows",{...t},{trackId:t.trackId}).catch(()=>{});function vu(t,e,n={}){return R({id:t.trackId,cwd:t.cwd},{type:"flow.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box||null,provider:t.agent||null,payload:{ts:Date.now(),change:e,flowId:t.id,name:t.name,stepCount:t.steps.length,mockIds:[...new Set(t.steps.map(r=>r.mockId))],...n}}).catch(()=>{})}async function Gg({trackId:t,steps:e,prevSteps:n=[]}){if(!Array.isArray(e)||e.length<1||e.length>Lg)throw new Error(`steps must be an array of 1-${Lg}`);let r=await Nn({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>Ug)throw new Error(`${l}: caption over ${Ug} chars`);let f=c.title==null?null:String(c.title).trim()||null;if(f&&f.length>Hg)throw new Error(`${l}: title over ${Hg} chars`);let d=String(c.mock??"").trim().replace(/^mock:/,"");if(!d)throw new Error(`${l}: mock (name or id) required`);let p=r.filter(S=>S.id===d);if(p.length||(p=r.filter(S=>S.name.toLowerCase()===d.toLowerCase())),!p.length)throw new Error(`${l}: no mock matching "${d}" in this Track \u2014 see \`bostrat-mock ls\``);if(p.length>1)throw new Error(`${l}: "${d}" is ambiguous (${p.map(S=>`mock:${S.id}`).join(", ")}) \u2014 use the id`);let m=p[0],g=null;if(c.v!=null&&(g=Number(c.v),!Number.isInteger(g)||!(m.versions||[]).some(S=>S.v===g)))throw new Error(`${l}: "${m.name}" has no captured v${c.v} to pin`);let y=o.findIndex(S=>S.mockId===m.id),b=y>=0?o.splice(y,1)[0].id:qg().slice(0,8);s.push({id:b,mockId:m.id,caption:u,title:f,v:g})}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 gI(t){if(typeof t!="string"||!t.trim())throw new Error("name (string) required");if(t.trim().length>Fg)throw new Error(`name over ${Fg} chars`);return t.trim()}async function Vg({trackId:t}={}){let e=await He();return(t?e.flows.filter(r=>r.trackId===t):e.flows).map(r=>({...r}))}async function yI(t){let n=(await He()).flows.find(r=>r.id===t);return n?{...n}:null}async function zg(t){let e=await yI(t);if(!e)return null;let n=new Map((await Nn({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=Ie(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 Jg({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=gI(e),i=await He(),c=i.flows.find(p=>p.trackId===t&&p.name.toLowerCase()===a.toLowerCase());if(c)throw new Error(`flow "${c.name}" already exists in this Track (flow:${c.id}) \u2014 revise it with \`bostrat-flow update\``);let{steps:l,resolved:u}=await Gg({trackId:t,steps:n}),f=Date.now(),d={id:qg().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 Ur(),await vu(d,"created"),Qa(d),{...d}}async function Xg(t,{steps:e,note:n}={}){let o=(await He()).flows.find(i=>i.id===t);if(!o)throw new Error("flow not found");let{steps:s,resolved:a}=await Gg({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 Ur(),await vu(o,"updated",{version:o.version,...n?{note:String(n).slice(0,200)}:{}}),Qa(o),{...o}}async function Yg(t){let e=await He(),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 Ur(),await vu(r,"deleted"),Qt("flows",r.id).catch(()=>{}),{ok:!0,id:t,name:r.name}}async function Zg(t){return(await He()).flows.filter(n=>n.steps.some(r=>r.mockId===t)).map(n=>({...n}))}async function Qg(t){let n=(await He()).flows.filter(r=>r.trackId===t.id);for(let r of n)r.trackDeletedAt=Date.now(),r.trackName=t.name||t.id,r.updatedAt=Date.now(),Qa(r);return n.length&&await Ur(),n.length}async function ty(t){let e=await He(),n=e.flows.filter(r=>r.trackId===t);if(n.length){e.flows=e.flows.filter(r=>r.trackId!==t),await Ur();for(let r of n)Qt("flows",r.id).catch(()=>{})}return n.length}async function ey(){if(!pt())return{pushed:0,source:"unconfigured"};let{records:t,source:e}=await Tt("flows");if(e!=="plane"||t.length)return{pushed:0,source:e};let n=await He();for(let r of n.flows)await Qa(r);return{pushed:n.flows.length,source:"plane"}}import Tu from"node:fs/promises";import ts from"node:path";import{randomUUID as bI}from"node:crypto";var ry=".bostrat/uploads",Au=t=>ts.join(t,ry),ny=Number(process.env.BOSTRAT_MAX_UPLOAD_BYTES)||24*1024*1024,kI=new Set(["png","jpg","jpeg","gif","webp","svg","bmp","tif","tiff","heic","heif","avif","ico"]),xI=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 Eu(t){let e=String(t||"").toLowerCase().replace(/^\./,"");return kI.has(e)?"image":xI.has(e)?"doc":null}function SI(t){let r=ts.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 oy(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>ny)throw new Error(`file too large \u2014 max ${Math.floor(ny/(1024*1024))} MB`);let r=SI(e),o=ts.extname(r).slice(1),s=Eu(o);if(!s)throw new Error(`unsupported file type "${o||"unknown"}" \u2014 attach an image or a document`);let a=bI().slice(0,8),i=Au(t);await Tu.mkdir(i,{recursive:!0});let c=`${a}-${r}`;return await Tu.writeFile(ts.join(i,c),n,{mode:384}),await Tu.chmod(ts.join(i,c),384).catch(()=>{}),{id:a,name:r,rel:`${ry}/${c}`,kind:s,size:n.length}}function sy(t){let e=t.map(r=>`\`${r}\``).join(", ");return`[Attached \u2014 please read ${t.length===1?"this file":`these ${t.length} files`}: ${e}]`}var vI=/\[Attached — please read (?:this file|these \d+ files): [^\]]*\]$/;function _u(t){return t.role==="user"&&vI.test((t.text||"").trim())}function Hr(t,e,n=Date.now()){return e?t.idleSince==null?(t.idleSince=n,!1):n-t.idleSince>=6500:(t.idleSince=null,!1)}var EI=AI(TI),We=process.env.BOSTRAT_STATE_DIR||F.join(process.env.HOME||"/tmp",".bostrat"),my=F.join(We,"tracks.json"),_I=F.join(We,"events.jsonl"),Nu=F.join(We,"workspaces"),II=process.env.BOSTRAT_DEFAULT_REPO||null,ri={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"}},oi=Object.keys(ri);function RI(t,{skipPermissions:e=!0}={}){let n=ri[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 hy(t,e,{skipPermissions:n=!0}={}){let r=ri[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 ls(t){let e=ri[t];if(e)return process.env[e.envVar]||e.base}var Xn={at:0,value:null};async function Bu({maxAgeMs:t=6e4}={}){if(Xn.value&&Date.now()-Xn.at<t)return Xn.value;let e=await Promise.all(oi.map(async n=>[n,await Hs(ls(n))]));return Xn.value=Object.fromEntries(e),Xn.at=Date.now(),Xn.value}async function si(t,e=!0){let n=RI(t,{skipPermissions:e});if(!await Hs(n)){Xn.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.":ac[r]?`Install it on this node: ${ac[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 ai=".bostrat/handoff/latest.md";function $I(t,e=ai){return`${t.trim()}
|
|
153
|
+
`+r.slice(-n)),r}function ve(t){return String(t?.stderr||t?.message||t||"").trim().split(`
|
|
154
|
+
`).find(n=>n.trim())||"gh failed"}var MI=6e3,ii=new Map;function Mu(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 Xg(t){let e=t?.workspace?.branch;if(!e)return;let n;try{n=await L(t)}catch{return}if(Mu(n)==null)return;let r=(t.boxes||[]).find(c=>c.name===t.leadBox);if(await Za(t.leadBox,r?.agent).catch(()=>!1))return;let o=Date.now();if(o-(ii.get(t.id)||0)<MI)return;ii.set(t.id,o);let s;try{s=await rs(t.cwd,{caller:"reconcile"})}catch{return}let a=(s||[]).find(c=>c.headRefName===e);if(!a)return;let i=await ot(t.cwd).catch(()=>null);if(i){let{link:c,created:l}=await Xt({repo:i,number:a.number,trackId:t.id,url:a.url,headRef:e,title:a.title,source:"reconciled"});if(!l&&!(c?.trackId===t.id&&await Uo(i,a.number))){ii.delete(t.id);return}}await R(t,{type:"pr.state.changed",actor:"agent",source:"orchestrator",boxId:t.leadBox,payload:{ts:Date.now(),change:"opened",number:a.number,url:a.url,title:a.title,branch:e,...i?{repo:i}:{}}}).catch(()=>{}),ii.delete(t.id)}import CI from"node:fs/promises";import ey from"node:path";import NI from"node:os";import{randomUUID as ny}from"node:crypto";var PI=process.env.BOSTRAT_STATE_DIR||ey.join(process.env.HOME||NI.tmpdir(),".bostrat"),ry=ey.join(PI,"flows.json"),Yg=20,Zg=80,Qg=500,ty=80,Ge=null,Cu=null;async function Ve(){return Ge||(Cu||=(async()=>{let t=!0;try{Ge=JSON.parse(await CI.readFile(ry,"utf8"))}catch{Ge={flows:[]},t=!1}finally{Cu=null}if(Ge.flows||=[],!t&&ht()){let{records:e,source:n}=await Rt("flows").catch(()=>({records:[],source:"error"}));if(n==="plane"){for(let r of e)!r.deletedAt&&r.data?.id&&Ge.flows.push(r.data);Ge.flows.length&&await Gr().catch(()=>{})}}return Ge})(),Cu)}async function Gr(){await Ft(ry,Ge)}var ci=t=>Nt("flows",{...t},{trackId:t.trackId}).catch(()=>{});function Nu(t,e,n={}){return R({id:t.trackId,cwd:t.cwd},{type:"flow.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box||null,provider:t.agent||null,payload:{ts:Date.now(),change:e,flowId:t.id,name:t.name,stepCount:t.steps.length,mockIds:[...new Set(t.steps.map(r=>r.mockId))],...n}}).catch(()=>{})}async function oy({trackId:t,steps:e,prevSteps:n=[]}){if(!Array.isArray(e)||e.length<1||e.length>Yg)throw new Error(`steps must be an array of 1-${Yg}`);let r=await Fn({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>Qg)throw new Error(`${l}: caption over ${Qg} chars`);let f=c.title==null?null:String(c.title).trim()||null;if(f&&f.length>ty)throw new Error(`${l}: title over ${ty} chars`);let d=String(c.mock??"").trim().replace(/^mock:/,"");if(!d)throw new Error(`${l}: mock (name or id) required`);let p=r.filter(S=>S.id===d);if(p.length||(p=r.filter(S=>S.name.toLowerCase()===d.toLowerCase())),!p.length)throw new Error(`${l}: no mock matching "${d}" in this Track \u2014 see \`bostrat-mock ls\``);if(p.length>1)throw new Error(`${l}: "${d}" is ambiguous (${p.map(S=>`mock:${S.id}`).join(", ")}) \u2014 use the id`);let m=p[0],w=null;if(c.v!=null&&(w=Number(c.v),!Number.isInteger(w)||!(m.versions||[]).some(S=>S.v===w)))throw new Error(`${l}: "${m.name}" has no captured v${c.v} to pin`);let y=o.findIndex(S=>S.mockId===m.id),b=y>=0?o.splice(y,1)[0].id:ny().slice(0,8);s.push({id:b,mockId:m.id,caption:u,title:f,v:w})}let a={};for(let i of s)i.mockId in a||(a[i.mockId]=r.find(c=>c.id===i.mockId)?.readyVersion??null);return{steps:s,resolved:a}}function BI(t){if(typeof t!="string"||!t.trim())throw new Error("name (string) required");if(t.trim().length>Zg)throw new Error(`name over ${Zg} chars`);return t.trim()}async function sy({trackId:t}={}){let e=await Ve();return(t?e.flows.filter(r=>r.trackId===t):e.flows).map(r=>({...r}))}async function DI(t){let n=(await Ve()).flows.find(r=>r.id===t);return n?{...n}:null}async function ay(t){let e=await DI(t);if(!e)return null;let n=new Map((await Fn({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=Ce(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 iy({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=BI(e),i=await Ve(),c=i.flows.find(p=>p.trackId===t&&p.name.toLowerCase()===a.toLowerCase());if(c)throw new Error(`flow "${c.name}" already exists in this Track (flow:${c.id}) \u2014 revise it with \`bostrat-flow update\``);let{steps:l,resolved:u}=await oy({trackId:t,steps:n}),f=Date.now(),d={id:ny().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 Gr(),await Nu(d,"created"),ci(d),{...d}}async function cy(t,{steps:e,note:n}={}){let o=(await Ve()).flows.find(i=>i.id===t);if(!o)throw new Error("flow not found");let{steps:s,resolved:a}=await oy({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 Gr(),await Nu(o,"updated",{version:o.version,...n?{note:String(n).slice(0,200)}:{}}),ci(o),{...o}}async function ly(t){let e=await Ve(),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 Gr(),await Nu(r,"deleted"),re("flows",r.id).catch(()=>{}),{ok:!0,id:t,name:r.name}}async function uy(t){return(await Ve()).flows.filter(n=>n.steps.some(r=>r.mockId===t)).map(n=>({...n}))}async function dy(t){let n=(await Ve()).flows.filter(r=>r.trackId===t.id);for(let r of n)r.trackDeletedAt=Date.now(),r.trackName=t.name||t.id,r.updatedAt=Date.now(),ci(r);return n.length&&await Gr(),n.length}async function fy(t){let e=await Ve(),n=e.flows.filter(r=>r.trackId===t);if(n.length){e.flows=e.flows.filter(r=>r.trackId!==t),await Gr();for(let r of n)re("flows",r.id).catch(()=>{})}return n.length}async function py(){if(!ht())return{pushed:0,source:"unconfigured"};let{records:t,source:e}=await Rt("flows");if(e!=="plane"||t.length)return{pushed:0,source:e};let n=await Ve();for(let r of n.flows)await ci(r);return{pushed:n.flows.length,source:"plane"}}import Pu from"node:fs/promises";import as from"node:path";import{randomUUID as jI}from"node:crypto";var hy=".bostrat/uploads",Bu=t=>as.join(t,hy),my=Number(process.env.BOSTRAT_MAX_UPLOAD_BYTES)||24*1024*1024,LI=new Set(["png","jpg","jpeg","gif","webp","svg","bmp","tif","tiff","heic","heif","avif","ico"]),FI=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 Du(t){let e=String(t||"").toLowerCase().replace(/^\./,"");return LI.has(e)?"image":FI.has(e)?"doc":null}function UI(t){let r=as.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 wy(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>my)throw new Error(`file too large \u2014 max ${Math.floor(my/(1024*1024))} MB`);let r=UI(e),o=as.extname(r).slice(1),s=Du(o);if(!s)throw new Error(`unsupported file type "${o||"unknown"}" \u2014 attach an image or a document`);let a=jI().slice(0,8),i=Bu(t);await Pu.mkdir(i,{recursive:!0});let c=`${a}-${r}`;return await Pu.writeFile(as.join(i,c),n,{mode:384}),await Pu.chmod(as.join(i,c),384).catch(()=>{}),{id:a,name:r,rel:`${hy}/${c}`,kind:s,size:n.length}}function gy(t){let e=t.map(r=>`\`${r}\``).join(", ");return`[Attached \u2014 please read ${t.length===1?"this file":`these ${t.length} files`}: ${e}]`}var HI=/\[Attached — please read (?:this file|these \d+ files): [^\]]*\]$/;function ju(t){return t.role==="user"&&HI.test((t.text||"").trim())}function Vr(t,e,n=Date.now()){return e?t.idleSince==null?(t.idleSince=n,!1):n-t.idleSince>=6500:(t.idleSince=null,!1)}var KI=qI(WI),ze=process.env.BOSTRAT_STATE_DIR||U.join(process.env.HOME||"/tmp",".bostrat"),Ey=U.join(ze,"tracks.json"),GI=U.join(ze,"events.jsonl"),Ku=U.join(ze,"workspaces"),VI=process.env.BOSTRAT_DEFAULT_REPO||null,di={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"}},fi=Object.keys(di);function zI(t,{skipPermissions:e=!0}={}){let n=di[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 _y(t,e,{skipPermissions:n=!0}={}){let r=di[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 ws(t){let e=di[t];if(e)return process.env[e.envVar]||e.base}var Tn={at:0,value:null};async function Vu({maxAgeMs:t=6e4}={}){if(Tn.value&&Date.now()-Tn.at<t)return Tn.value;let e=Tn.value,n=await Promise.all(fi.map(async r=>{let o=await Ys(ws(r),{retries:[]});return o===mr?[r,e?.[r]??!0]:[r,o===Xs]}));return Tn.value=Object.fromEntries(n),Tn.at=Date.now(),Tn.value}async function pi(t,e=!0){let n=zI(t,{skipPermissions:e});if(await Ys(n)===pr){Tn.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.":hc[r]?`Install it on this node: ${hc[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 mi=".bostrat/handoff/latest.md";function JI(t,e=mi){return`${t.trim()}
|
|
155
155
|
|
|
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
|
|
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 XI=/\(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 yy(t){return t.role==="user"&&XI.test((t.text||"").trim())}var li=".bostrat/handoff/subtask-result.md",cs=".bostrat/handoff/lead-context.md",YI=(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 ${cs} \u2014 read that first for context. Your scoped task:
|
|
157
157
|
|
|
158
158
|
${t}
|
|
159
159
|
|
|
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 ${
|
|
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 ${li} (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 ${cs} \u2014 read that first for context. Your scoped task:
|
|
161
161
|
|
|
162
162
|
${t}
|
|
163
163
|
|
|
164
|
-
Work ONLY inside this directory. When you are finished: (1) make your code changes, (2) commit them with git and a clear message, (3) LAST, write a short markdown digest to ${
|
|
165
|
-
`;return Ru=Ru.then(()=>L.mkdir(We,{recursive:!0})).then(()=>L.appendFile(_I,n)).catch(()=>{}),Ru}var ct=(t,e,n={})=>{let r={at:Date.now(),type:e,...n};return t.events.push(r),NI(t.id,r),Ow(t,r),r};Uf(async({sessionName:t,from:e,to:n,version:r,status:o,error:s})=>{let i=(await W()).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 $u=(t,e,n)=>e!=null&&n.some(r=>t===r.box&&e>=r.from&&e<=r.to),PI=new Set(["stuck","wrong-approach","too-slow","want-different-model","context-refresh","cost","parallelize","other"]);function wy(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=>PI.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 gy(t,e,n,{kind:r}={}){let o=await Z(e);if(!o)throw new Error(`box "${e}" is not running`);let s=ls(o.agent);await tn({cwd:o.cwd,sessionId:o.sessionId,sessionName:e,agent:o.agent,agentCmd:s}),await St(e,n,{..._e(o.agent),kind:r||"orchestration"}),await U({flow:"orchestration",phase:"sent",trackId:t,sessionName:e,agent:o.agent,kind:r||"orchestration",promptHead:n.slice(0,160)});let a=await W(),i=et(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 Yn=new Set(["merged","conflict","canceled","failed","handed-off"]),iy=new Map;async function BI(t){let e=F.join(ea(t),`${t.id}.ndjson`),n;try{n=await L.stat(e)}catch{return null}let r=iy.get(t.id);if(r&&r.mtimeMs===n.mtimeMs)return r.at;let o=null;for(let s of await D(t)){if(s.type!=="turn.agent")continue;let a=s.payload?.ts??s.ts;a&&(!o||a>o)&&(o=a)}return iy.set(t.id,{mtimeMs:n.mtimeMs,at:o}),o}async function DI(t,e){let n=e.find(r=>r.name===t.leadBox);return n?.state!=="live"?{working:!1,prompted:!1}:tu(n.name,n.agent)}async function bn(t,e=null){let n=e??new Map((await Rn()).map(i=>[i.name,i])),r=t.boxes.map(i=>{let c=n.get(i.name),l=Yn.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([BI(t),DI(t,r)]);return{...t,boxes:r,lastResponseAt:o,working:s,prompted:a}}var jI=5e3;function LI(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-jI,d=r[u+1]?.createdAt??1/0,p=o.find(m=>!s.has(m.id)&&m.birthMs>=f&&m.birthMs<d);p&&(s.add(p.id),c.push({box:l.name,sessionId:p.id}))}return c}async function yy(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=Pf(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=LI(u,a,d=>i.has(d));for(let d of f){let p=u.find(m=>m.name===d.box);p&&(p.sessionId=d.sessionId),await Ef(d.box,d.sessionId),n=!0}}return n}async function ot(){let t=await W();await yy(t)&&await z();let e=new Map((await Rn()).map(n=>[n.name,n]));return Promise.all(t.tracks.map(n=>bn(n,e)))}async function by(t,e){let n=await W();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 ky(t){let e=oo(t);if(!e)return null;let n=await W();for(let r of n.tracks||[])for(let o of r.boxes||[])if(o.hookTokenHash&&Md(o.hookTokenHash,e))return{track:r,box:o};return null}async function Q(t){let e=await W(),n=et(e,t);return n?(await yy(e)&&await z(),bn(n)):null}var FI={claude:[/^\[Request interrupted by user(?: for tool use)?\]$/i],codex:[/^\[?Request interrupted/i,/^Conversation interrupted/i],cursor:[/^\[?Request interrupted/i]};function cy(t){if(t.role!=="user")return!1;let e=(t.text||"").trim();return(FI[t.agent]||[]).some(n=>n.test(e))}var UI=[/^\/model(?:\s|$)/i,/<command-(?:name|message|args)>[^<]*\bmodel\b/i,/^Model changed to\b/i];function ly(t){let e=(t.text||"").trim();return e?UI.some(n=>n.test(e)):!1}var HI=10*6e4;async function Du(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=us(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=Mc(n.id),c=!!(i&&!i.finishedAt),l=c&&i.startedAt&&i.box?[{box:i.box,from:i.startedAt,to:Date.now()+1e12}]:[],u=[...cm(n),...l],d={windows:[...vp(n),...Tp(n,oa),...a,...u]},m=(await Promise.all(n.boxes.map(k=>Rl(k,e)))).flat(),g=m.map(k=>{let V={kind:"message",...k};if(V.role==="assistant"&&(V.text||"").trim()&&V.tools?.length){let er=Wo(V.agent).cliTools,As=V.tools.filter(nr=>!er.has(nr));return{...V,tools:As.length?As:void 0}}return V}).map(k=>{if(k.role!=="user")return k;let V=Ka(k.text);return V?V.kind==="suppress"?null:{...k,note:V,text:mg(V)}:k}).filter(Boolean).filter(k=>!(k.role==="user"&&r.has((k.text||"").trim()))).filter(k=>!cy(k)).filter(k=>!ly(k)).filter(k=>!ay(k)).filter(k=>!_u(k)).filter(k=>k.role!=="assistant"||!!(k.text||"").trim()||!!k.images?.length).filter(k=>!Ap(k,d)),y=g.filter(k=>k.failed&&k.text===vl);if(y.length){let k=new Map;for(let V of y){if(!k.has(V.box)){let nr=[...(await ft(V.box,{lines:xt}).catch(()=>"")).matchAll(/^\s*■\s+(.+?)\s*$/gm)].map(Ni=>Ni[1]);k.set(V.box,nr.length?nr[nr.length-1]:null)}let er=k.get(V.box);er&&(V.text=`\u25A0 ${er}`)}}let b=await Promise.all(n.boxes.map(k=>zh(k,e)));await fg(n,b.flat()),await ug(n,g),await Cw(n),await hm(n,g).catch(()=>{});for(let k of n.boxes)k.agent==="codex"&&k.state==="live"&&await gg(n,k),k.agent==="cursor"&&k.state==="live"&&await xg(n,k);let S=await Promise.all(n.boxes.map(k=>Vh(k,e)));await dg(n,S.flat().filter(k=>!$u(k.box,k.ts,u)));let E=await D(n),M=c&&i.viewId?i.viewId:null,J=Hw(E),$=Jo(E).filter(k=>!(k.kind==="message"&&(cy(k)||ly(k)||ay(k)||_u(k)))&&!(k.kind==="view-card"&&M&&k.viewId===M)),j=Ww($,J),v=$.filter(k=>!(k.kind==="message"&&k.role==="assistant"&&!j.has(k)&&$u(k.box,k.ts,J))),C=iu(E,v).map(k=>({kind:"command-batch",id:k.id,ts:k.pending?k.ts:(k.anchorTs??k.ts)-1,anchorTs:k.anchorTs,anchorBox:k.anchorBox,agent:k.agent,boxRole:k.boxRole,commands:k.commands,pending:k.pending})).filter(k=>!$u(k.anchorBox,k.anchorTs??k.ts,u)),q=[...v,...C].filter(k=>k.ts!=null).sort((k,V)=>k.ts-V.ts),rt=WI(n),st=rt.size?q.filter(k=>k.kind==="event"||!rt.has(k.box??k.anchorBox)):q,w=n.boxes.find(k=>k.name===n.leadBox)||null,h=w?await bo(w.cwd,w.sessionId,{fallbackNewest:!1}):null;if(!h&&w){let k=[...E].reverse().find(V=>V.type==="context.usage"&&V.boxId===w.name);k?.payload?.window&&(h={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 T=[...m].reverse().find(k=>k.role==="assistant"&&k.model),_=T?.model||null,O=[...n.events||[]].reverse().find(k=>k.type==="model-change"),I=h?.model||_;O&&(!T?.ts||O.at>=T.ts)&&(I=O.to);let A=w?await ft(w.name,{lines:xt}).catch(()=>""):"",G=w&&Cr(w.agent,A)?await Ze(w.name).catch(()=>A):A,K=w?Vn(w,G):null;!K&&w&&!Ee(w.agent,A)&&!uy(n.id).length&&(K=Fl(st,{leadBoxId:w.name,provider:w.agent,isAnswered:k=>E.some(V=>V.type==="prompt.answered"&&V.payload?.promptId===k)})),K&&Mw(n,w,K);let dt=st.filter(k=>!(k.kind==="message"&&k.role==="assistant"&&k.ask&&!String(k.text||"").trim()&&!k.images?.length)),yt=!!w&&!K&&Ee(w.agent,A);w&&!w.readyAt&&(yt||K||A&&Xs(w.agent,A))&&(w.readyAt=Date.now(),await z());let Ot=!!w&&!w.readyAt&&Date.now()-(n.createdAt||0)<HI;if(n.kind==="setup"&&n.setup?.provider&&!n.setup.connectedAt&&w&&Xs(w.agent,A)&&!K){let k=await W(),V=et(k,n.id);V?.setup&&!V.setup.connectedAt&&(V.setup.connectedAt=Date.now(),n.setup.connectedAt=V.setup.connectedAt,await R(V,{type:"provider.connected",actor:"system",source:"pane",boxId:w.name,provider:w.agent,payload:{provider:w.agent,ts:V.setup.connectedAt}}).catch(()=>{}),await z())}let Vt=s?{running:!0,step:o.step,toAgent:o.toAgent||null}:null,Zt=null;if(c){let k=Date.now()-i.startedAt;if(i.viewId&&!yt){let V=da(n.id);V&&(ct(n,"view-spinup",{box:i.box,startedAt:i.startedAt,endedAt:V.finishedAt}),await z())}else if(K||k>am&&!yt&&!i.viewId||k>Cc){let V=K?"needs-input":k>Cc?"timeout":"no-view";da(n.id,{error:V})&&await R(n,{type:"view.spinup.failed",actor:"orchestrator",source:"orchestrator",boxId:i.box,provider:w?.agent||null,payload:{ts:i.startedAt,name:i.name,reason:V}}).catch(()=>{})}else Zt={running:!0,step:i.step,name:i.name}}return{trackId:n.id,leadBox:n.leadBox,items:dt,context:h?{pct:h.pct,occupancy:h.occupancy,window:h.window,windowKnown:h.windowKnown,model:h.model}:null,models:{current:I,options:Ew(w?.agent)},pendingPrompt:K,working:yt,stopping:JI(n.id),leadStarting:Ot,handoff:Vt,view:Zt,queued:uy(n.id).filter(k=>k.kind!=="ship-notify"&&k.kind!=="prompt-answer")}}function WI(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 xy(t,e){let n=await Q(t);if(!n)return null;let r=zr(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 D(n),a=Jo(s).filter(f=>f.box===o&&!(f.kind==="message"&&f.role==="assistant"&&f.ask&&!String(f.text||"").trim()&&!f.images?.length)),i=iu(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 ft(o,{lines:xt}).catch(()=>"");u=Ee(l.agent||r.agent,f)}return{items:c,working:u,box:o,agent:r.agent,task:r.task}}async function ju(t,{promptId:e,answers:n,text:r}={}){let o=et(await W(),t);if(!o)throw new Error(`track "${t}" not found`);Uu(t,"prompt-answer");let s=o.boxes.find(d=>d.name===o.leadBox);if(!s)throw new Error(`track "${t}" has no lead box`);if(!await Z(o.leadBox))throw new Error(`lead box "${o.leadBox}" is not running`);let i=await Bl(s);if(i||(i=await qI(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 St(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 bt(o.leadBox,["Escape"],{kind:"answer-prompt"}),c={mode:"keys",value:"Escape",count:1};else if(i.answerMode==="say"){let d=fw(i,n||[],typeof r=="string"?r:"");if(!d)throw new Error("no answer selected \u2014 pick an option or reply in chat");await oe(o.id,d,{kind:"prompt-answer"}),c={mode:"say",value:d,count:1}}else if(i.provider==="claude"||i.answerMode==="keys"){let d=sw(i,n||[]);await bt(o.leadBox,d,{kind:"answer-prompt"}),c={mode:"keys",value:d.join(" "),count:d.length}}else{let d=zf(i,n||[]);await St(o.leadBox,d,{enter:!0,kind:"answer-prompt"}),c={mode:"text",value:d,count:1}}if(i.answerMode==="say"){let d=i.questions||[];for(let p=0;p<d.length;p++){let m=d[p],g=(Array.isArray(n?.[p])?n[p]:[]).map(y=>m.options?.[y]).filter(Boolean).map(y=>({label:y.label,description:y.description||""}));Zl(o,s,i,{...c,groupId:i.groupId||i.promptId,questionIndex:p,questionTotal:i.questionTotal??d.length,header:m.header||"",question:m.question||"",multiSelect:!!m.multiSelect,chosen:g})}return await U({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 U({flow:"prompt",phase:"answered",sessionName:o.leadBox,agent:s.agent,provider:i.provider,promptId:i.promptId,...c}).catch(()=>{}),Zl(o,s,i,c),{ok:!0,promptId:i.promptId,sent:c.count}}async function qI(t,e){if(!e)return null;let n=await D(t).catch(()=>[]);return Fl(Jo(n),{leadBoxId:e.name,provider:e.agent,isAnswered:r=>n.some(o=>o.type==="prompt.answered"&&o.payload?.promptId===r)})}async function Sy(t,{promptId:e,questionIndex:n}={}){let r=et(await W(),t);if(!r)throw new Error(`track "${t}" not found`);let o=r.boxes.find(l=>l.name===r.leadBox);if(!o)throw new Error(`track "${t}" has no lead box`);if(!await Z(r.leadBox))throw new Error(`lead box "${r.leadBox}" is not running`);let a=await Bl(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=aw(a,n);return c.length?(await bt(r.leadBox,c,{kind:"navigate-prompt"}),await U({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 KI=1500,rs=new Map;function Lu(t){let e=rs.get(t);return e||(e={items:[],timer:null,flushing:!1,idleSince:null},rs.set(t,e)),e}function uy(t){return(rs.get(t)?.items??[]).map(e=>({...e}))}function ii(t){let e=rs.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 vy(t){let e=Lu(t);e.timer||(e.timer=setInterval(()=>GI(t).catch(()=>{}),KI))}async function GI(t){let e=rs.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=et(await W(),t);if(!n||n.archivedAt){ii(t);return}let r=us(t);if(r&&!r.finishedAt&&r.step!=="done"&&!r.error){Hr(e,!1);return}let o=await Z(n.leadBox);if(!o){Hr(e,!1);return}let s=n.boxes.find(c=>c.name===n.leadBox),a=await ft(n.leadBox,{lines:xt}).catch(()=>"");if(Js(s?.agent,a)){Hr(e,!1);return}if(Cr(s?.agent,a)){let c=await Ze(n.leadBox).catch(()=>a);if(Vn(s,c)){Hr(e,!1);return}}if(!Hr(e,!0))return;let i=e.items.shift();e.idleSince=null,await Ty(n,s,o,i.text,i.kind??null,i.attachments??null),await z()}finally{e.flushing=!1}}}async function Ty(t,e,n,r,o=null,s=null){let a=ei.get(t.id)||0,i=r,c=!1;t.pendingHandoffHydrate&&(i=$I(r,t.pendingHandoffHydrate.artifact||ai),delete t.pendingHandoffHydrate,c=!0);let l=Array.isArray(s)?s.filter(f=>f&&f.rel):[];if(l.length){let f=sy(l.map(d=>d.rel));i=i.trim()?`${i.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 ${li} 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.`,ZI=(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."),Ae=null,Lu=null;async function H(){return Ae||(Lu||=(async()=>{try{Ae=JSON.parse(await F.readFile(Ey,"utf8"))}catch{Ae={tracks:[]}}finally{Lu=null}return Ae})(),Lu)}async function X(){await Ft(Ey,Ae)}var tt=(t,e)=>t.tracks.find(n=>n.id===e)||null,Fu=Promise.resolve();function QI(t,e){let n=JSON.stringify({trackId:t,...e})+`
|
|
165
|
+
`;return Fu=Fu.then(()=>F.mkdir(ze,{recursive:!0})).then(()=>F.appendFile(GI,n)).catch(()=>{}),Fu}var dt=(t,e,n={})=>{let r={at:Date.now(),type:e,...n};return t.events.push(r),QI(t.id,r),Ww(t,r),r};Qf(async({sessionName:t,from:e,to:n,version:r,status:o,error:s})=>{let i=(await H()).tracks.find(c=>c.leadBox===t||(c.boxes||[]).some(l=>l.name===t));i&&(dt(i,"codex-update",{box:t,from:e,to:r||n,status:o,...s?{error:s}:{}}),await X().catch(()=>{}))});var Uu=(t,e,n)=>e!=null&&n.some(r=>t===r.box&&e>=r.from&&e<=r.to),tR=new Set(["stuck","wrong-approach","too-slow","want-different-model","context-refresh","cost","parallelize","other"]);function Iy(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=>tR.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 Ry(t,e,n,{kind:r}={}){let o=await nt(e);if(!o)throw new Error(`box "${e}" is not running`);let s=ws(o.agent);await sn({cwd:o.cwd,sessionId:o.sessionId,sessionName:e,agent:o.agent,agentCmd:s}),await At(e,n,{...Me(o.agent),kind:r||"orchestration"}),await G({flow:"orchestration",phase:"sent",trackId:t,sessionName:e,agent:o.agent,kind:r||"orchestration",promptHead:n.slice(0,160)});let a=await H(),i=tt(a,t);if(!i)return;let c=i.boxes.find(l=>l.name===e);dt(i,"orchestration-prompt",{box:e,agent:c?.agent||o.agent,boxRole:c?.role,prompt:n,kind:r||"orchestration"}),await X()}var or=new Set(["merged","conflict","canceled","failed","handed-off"]),by=new Map;async function eR(t){let e=U.join(ua(t),`${t.id}.ndjson`),n;try{n=await F.stat(e)}catch{return null}let r=by.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 by.set(t.id,{mtimeMs:n.mtimeMs,at:o}),o}async function nR(t,e){let n=e.find(r=>r.name===t.leadBox);return n?.state!=="live"?{working:!1,prompted:!1}:du(n.name,n.agent)}async function An(t,e=null){let n=e??new Map((await Re()).map(i=>[i.name,i])),r=t.boxes.map(i=>{let c=n.get(i.name),l=or.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([eR(t),nR(t,r)]);return{...t,boxes:r,lastResponseAt:o,working:s,prompted:a}}var rR=5e3;function oR(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-rR,d=r[u+1]?.createdAt??1/0,p=o.find(m=>!s.has(m.id)&&m.birthMs>=f&&m.birthMs<d);p&&(s.add(p.id),c.push({box:l.name,sessionId:p.id}))}return c}async function $y(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=Vf(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=oR(u,a,d=>i.has(d));for(let d of f){let p=u.find(m=>m.name===d.box);p&&(p.sessionId=d.sessionId),await jf(d.box,d.sessionId),n=!0}}return n}async function ct(){let t=await H();await $y(t)&&await X();let e=new Map((await Re()).map(n=>[n.name,n]));return Promise.all(t.tracks.map(n=>An(n,e)))}async function Oy(t,e){let n=await H();if(t)for(let r of n.tracks||[]){let o=r.boxes.find(s=>s.sessionId&&s.sessionId===t);if(o)return{track:r,box:o}}if(e)for(let r of n.tracks||[]){let o=r.boxes.find(s=>s.cwd===e);if(o)return{track:r,box:o}}return null}async function My(t){let e=uo(t);if(!e)return null;let n=await H();for(let r of n.tracks||[])for(let o of r.boxes||[])if(o.hookTokenHash&&Wd(o.hookTokenHash,e))return{track:r,box:o};return null}async function rt(t){let e=await H(),n=tt(e,t);return n?(await $y(e)&&await X(),An(n)):null}var sR={claude:[/^\[Request interrupted by user(?: for tool use)?\]$/i],codex:[/^\[?Request interrupted/i,/^Conversation interrupted/i],cursor:[/^\[?Request interrupted/i]};function ky(t){if(t.role!=="user")return!1;let e=(t.text||"").trim();return(sR[t.agent]||[]).some(n=>n.test(e))}var aR=[/^\/model(?:\s|$)/i,/<command-(?:name|message|args)>[^<]*\bmodel\b/i,/^Model changed to\b/i];function xy(t){let e=(t.text||"").trim();return e?aR.some(n=>n.test(e)):!1}var iR=10*6e4;async function zu(t,e={}){let n=await rt(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=gs(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=Wc(n.id),c=!!(i&&!i.finishedAt),l=c&&i.startedAt&&i.box?[{box:i.box,from:i.startedAt,to:Date.now()+1e12}]:[],u=[...km(n),...l],d={windows:[...Pp(n),...Bp(n,pa),...a,...u]},m=(await Promise.all(n.boxes.map(k=>Ll(k,e)))).flat(),w=m.map(k=>{let N={kind:"message",...k};if(N.role==="assistant"&&(N.text||"").trim()&&N.tools?.length){let St=Jo(N.agent).cliTools,Bt=N.tools.filter(Dt=>!St.has(Dt));return{...N,tools:Bt.length?Bt:void 0}}return N}).map(k=>{if(k.role!=="user")return k;let N=ti(k.text);return N?N.kind==="suppress"?null:{...k,note:N,text:Eg(N)}:k}).filter(Boolean).filter(k=>!(k.role==="user"&&r.has((k.text||"").trim()))).filter(k=>!ky(k)).filter(k=>!xy(k)).filter(k=>!yy(k)).filter(k=>!ju(k)).filter(k=>k.role!=="assistant"||!!(k.text||"").trim()||!!k.images?.length).filter(k=>!Dp(k,d)),y=w.filter(k=>k.failed&&k.text===Cl);if(y.length){let k=new Map;for(let N of y){if(!k.has(N.box)){let Dt=[...(await mt(N.box,{lines:Tt}).catch(()=>"")).matchAll(/^\s*■\s+(.+?)\s*$/gm)].map(jt=>jt[1]);k.set(N.box,Dt.length?Dt[Dt.length-1]:null)}let St=k.get(N.box);St&&(N.text=`\u25A0 ${St}`)}}let b=await Promise.all(n.boxes.map(k=>aw(k,e)));await Tg(n,b.flat()),await Sg(n,w),await qw(n),await _m(n,w).catch(()=>{});for(let k of n.boxes)k.agent==="codex"&&k.state==="live"&&await Rg(n,k),k.agent==="cursor"&&k.state==="live"&&await Cg(n,k);let S=await Promise.all(n.boxes.map(k=>sw(k,e)));await vg(n,S.flat().filter(k=>!Uu(k.box,k.ts,u)));let _=await L(n),$=c&&i.viewId?i.viewId:null,et=tg(_),I=es(_).filter(k=>!(k.kind==="message"&&(ky(k)||xy(k)||yy(k)||ju(k)))&&!(k.kind==="view-card"&&$&&k.viewId===$)),C=eg(I,et),v=I.filter(k=>!(k.kind==="message"&&k.role==="assistant"&&!C.has(k)&&Uu(k.box,k.ts,et))),B=yu(_,v).map(k=>({kind:"command-batch",id:k.id,ts:k.pending?k.ts:(k.anchorTs??k.ts)-1,anchorTs:k.anchorTs,anchorBox:k.anchorBox,agent:k.agent,boxRole:k.boxRole,commands:k.commands,pending:k.pending})).filter(k=>!Uu(k.anchorBox,k.anchorTs??k.ts,u)),D=[...v,...B].filter(k=>k.ts!=null).sort((k,N)=>k.ts-N.ts),lt=cR(n),q=lt.size?D.filter(k=>k.kind==="event"||!lt.has(k.box??k.anchorBox)):D,T=n.boxes.find(k=>k.name===n.leadBox)||null,g=T?await Ao(T.cwd,T.sessionId,{fallbackNewest:!1}):null;if(!g&&T){let k=[..._].reverse().find(N=>N.type==="context.usage"&&N.boxId===T.name);k?.payload?.window&&(g={pct:k.payload.pct??(k.payload.occupancy!=null?k.payload.occupancy/k.payload.window:null),occupancy:k.payload.occupancy??null,window:k.payload.window,windowKnown:!0,model:k.payload.model||null})}let h=[...m].reverse().find(k=>k.role==="assistant"&&k.model),A=h?.model||null,E=[...n.events||[]].reverse().find(k=>k.type==="model-change"),z=g?.model||A;E&&(!h?.ts||E.at>=h.ts)&&(z=E.to);let W=T?await mt(T.name,{lines:Tt}).catch(()=>""):"",at=T&&Dr(T.agent,W)?await rn(T.name).catch(()=>W):W,Ct=T?tr(T,at):null;!Ct&&T&&!Oe(T.agent,W)&&!Sy(n.id).length&&(Ct=Yl(q,{leadBoxId:T.name,provider:T.agent,isAnswered:k=>_.some(N=>N.type==="prompt.answered"&&N.payload?.promptId===k)})),Ct&&Kw(n,T,Ct);let K=q.filter(k=>!(k.kind==="message"&&k.role==="assistant"&&k.ask&&!String(k.text||"").trim()&&!k.images?.length)),J=!!T&&!Ct&&Oe(T.agent,W);T&&!T.readyAt&&(J||Ct||W&&sa(T.agent,W))&&(T.readyAt=Date.now(),await X());let O=!!T&&!T.readyAt&&Date.now()-(n.createdAt||0)<iR;if(n.kind==="setup"&&n.setup?.provider&&!n.setup.connectedAt&&T&&sa(T.agent,W)&&!Ct){let k=await H(),N=tt(k,n.id);N?.setup&&!N.setup.connectedAt&&(N.setup.connectedAt=Date.now(),n.setup.connectedAt=N.setup.connectedAt,await R(N,{type:"provider.connected",actor:"system",source:"pane",boxId:T.name,provider:T.agent,payload:{provider:T.agent,ts:N.setup.connectedAt}}).catch(()=>{}),await X())}let Q=s?{running:!0,step:o.step,toAgent:o.toAgent||null}:null,bt=null;if(c){let k=Date.now()-i.startedAt;if(i.viewId&&!J){let N=xa(n.id);N&&(dt(n,"view-spinup",{box:i.box,startedAt:i.startedAt,endedAt:N.finishedAt}),await X())}else if(Ct||k>ym&&!J&&!i.viewId||k>Hc){let N=Ct?"needs-input":k>Hc?"timeout":"no-view";xa(n.id,{error:N})&&await R(n,{type:"view.spinup.failed",actor:"orchestrator",source:"orchestrator",boxId:i.box,provider:T?.agent||null,payload:{ts:i.startedAt,name:i.name,reason:N}}).catch(()=>{})}else bt={running:!0,step:i.step,name:i.name}}return{trackId:n.id,leadBox:n.leadBox,items:K,context:g?{pct:g.pct,occupancy:g.occupancy,window:g.window,windowKnown:g.windowKnown,model:g.model}:null,models:{current:z,options:jw(T?.agent)},pendingPrompt:Ct,working:J,stopping:gR(n.id),leadStarting:O,handoff:Q,view:bt,queued:Sy(n.id).filter(k=>k.kind!=="ship-notify"&&k.kind!=="prompt-answer"),draft:n.draft||null,draftRev:n.draftRev||0}}function cR(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 Cy(t,e){let n=await rt(t);if(!n)return null;let r=to(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=es(s).filter(f=>f.box===o&&!(f.kind==="message"&&f.role==="assistant"&&f.ask&&!String(f.text||"").trim()&&!f.images?.length)),i=yu(s.filter(f=>f.boxId===o),a.filter(f=>f.kind==="message")).map(f=>({kind:"command-batch",id:f.id,ts:f.pending?f.ts:(f.anchorTs??f.ts)-1,anchorTs:f.anchorTs,anchorBox:f.anchorBox,agent:f.agent,boxRole:f.boxRole,commands:f.commands,pending:f.pending})),c=[...a,...i].filter(f=>f.ts!=null).sort((f,d)=>f.ts-d.ts),l=n.boxes.find(f=>f.name===o)||null,u=!1;if(l&&l.state==="live"){let f=await mt(o,{lines:Tt}).catch(()=>"");u=Oe(l.agent||r.agent,f)}return{items:c,working:u,box:o,agent:r.agent,task:r.task}}async function Ju(t,{promptId:e,answers:n,text:r}={}){let o=tt(await H(),t);if(!o)throw new Error(`track "${t}" not found`);wi(t,"prompt-answer");let s=o.boxes.find(d=>d.name===o.leadBox);if(!s)throw new Error(`track "${t}" has no lead box`);if(!await nt(o.leadBox))throw new Error(`lead box "${o.leadBox}" is not running`);let i=await Gl(s);if(i||(i=await lR(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 At(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=Tw(i,n||[],typeof r=="string"?r:"");if(!d)throw new Error("no answer selected \u2014 pick an option or reply in chat");await ce(o.id,d,{kind:"prompt-answer"}),c={mode:"say",value:d,count:1}}else if(i.provider==="claude"||i.answerMode==="keys"){let d=gw(i,n||[]);await xt(o.leadBox,d,{kind:"answer-prompt"}),c={mode:"keys",value:d.join(" "),count:d.length}}else{let d=ap(i,n||[]);await At(o.leadBox,d,{enter:!0,kind:"answer-prompt"}),c={mode:"text",value:d,count:1}}if(i.answerMode==="say"){let d=i.questions||[];for(let p=0;p<d.length;p++){let m=d[p],w=(Array.isArray(n?.[p])?n[p]:[]).map(y=>m.options?.[y]).filter(Boolean).map(y=>({label:y.label,description:y.description||""}));lu(o,s,i,{...c,groupId:i.groupId||i.promptId,questionIndex:p,questionTotal:i.questionTotal??d.length,header:m.header||"",question:m.question||"",multiSelect:!!m.multiSelect,chosen:w})}return await G({flow:"prompt",phase:"answered",sessionName:o.leadBox,agent:s.agent,provider:i.provider,promptId:i.promptId,...c}).catch(()=>{}),{ok:!0,promptId:i.promptId,sent:c.count}}let l=i.questions?.[0]||{},u=(Array.isArray(n?.[0])?n[0]:[]).map(d=>l.options?.[d]).filter(Boolean).map(d=>({label:d.label,description:d.description||""})),f=i.review?"review":/^claude:model-confirm:/.test(i.promptId||"")?"model-confirm":/^claude:resume-confirm:/.test(i.promptId||"")?"resume-confirm":void 0;return c={...c,groupId:i.groupId||i.promptId,questionIndex:i.questionIndex??0,questionTotal:i.questionTotal??1,header:l.header||"",question:l.question||"",multiSelect:!!l.multiSelect,chosen:u,...f?{control:f}:{}},await G({flow:"prompt",phase:"answered",sessionName:o.leadBox,agent:s.agent,provider:i.provider,promptId:i.promptId,...c}).catch(()=>{}),lu(o,s,i,c),{ok:!0,promptId:i.promptId,sent:c.count}}async function lR(t,e){if(!e)return null;let n=await L(t).catch(()=>[]);return Yl(es(n),{leadBoxId:e.name,provider:e.agent,isAnswered:r=>n.some(o=>o.type==="prompt.answered"&&o.payload?.promptId===r)})}async function Ny(t,{promptId:e,questionIndex:n}={}){let r=tt(await H(),t);if(!r)throw new Error(`track "${t}" not found`);let o=r.boxes.find(l=>l.name===r.leadBox);if(!o)throw new Error(`track "${t}" has no lead box`);if(!await nt(r.leadBox))throw new Error(`lead box "${r.leadBox}" is not running`);let a=await Gl(o);if(!a)throw new Error("no pending prompt to navigate");if(a.provider!=="claude")throw new Error("prompt navigation not supported for this provider");if(a.review)throw new Error("cannot navigate on the review screen \u2014 answer or cancel");let i=a.questionTotal??1;if(i<2)throw new Error("not a multi-question prompt");if(!Number.isInteger(n)||n<0||n>=i)throw new Error("questionIndex out of range");if(e&&e!==a.promptId)throw new Error("prompt changed since it was shown \u2014 reload and try again");let c=yw(a,n);return c.length?(await 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 uR=1500,ls=new Map;function Xu(t){let e=ls.get(t);return e||(e={items:[],timer:null,flushing:!1,idleSince:null},ls.set(t,e)),e}function Sy(t){return(ls.get(t)?.items??[]).map(e=>({...e}))}function hi(t){let e=ls.get(t);if(!e)return[];let n=e.items.splice(0);return e.idleSince=null,e.timer&&(clearInterval(e.timer),e.timer=null),n}async function Py(t){let e=hi(t),n=e.filter(o=>o.text&&!o.kind).map(o=>o.text),r=null;if(n.length){let o=tt(await H(),t);if(o)return r=ds(o,n),await X(),{removed:e,restored:r,draftRev:o.draftRev||0}}return{removed:e,restored:r,draftRev:null}}function By(t){let e=Xu(t);e.timer||(e.timer=setInterval(()=>dR(t).catch(()=>{}),uR))}async function dR(t){let e=ls.get(t);if(!(!e||e.flushing)){if(!e.items.length){e.timer&&(clearInterval(e.timer),e.timer=null);return}e.flushing=!0;try{let n=tt(await H(),t);if(!n||n.archivedAt){hi(t);return}let r=gs(t);if(r&&!r.finishedAt&&r.step!=="done"&&!r.error){Vr(e,!1);return}let o=await nt(n.leadBox);if(!o){Vr(e,!1);return}let s=n.boxes.find(c=>c.name===n.leadBox),a=await mt(n.leadBox,{lines:Tt}).catch(()=>"");if(oa(s?.agent,a)){Vr(e,!1);return}if(Dr(s?.agent,a)){let c=await rn(n.leadBox).catch(()=>a);if(tr(s,c)){Vr(e,!1);return}}if(!Vr(e,!0))return;let i=e.items.shift();e.idleSince=null,await Dy(n,s,o,i.text,i.kind??null,i.attachments??null),await X()}finally{e.flushing=!1}}}async function Dy(t,e,n,r,o=null,s=null){let a=us.get(t.id)||0,i=r,c=!1;t.pendingHandoffHydrate&&(i=JI(r,t.pendingHandoffHydrate.artifact||mi),delete t.pendingHandoffHydrate,c=!0);let l=Array.isArray(s)?s.filter(f=>f&&f.rel):[];if(l.length){let f=gy(l.map(d=>d.rel));i=i.trim()?`${i.trim()}
|
|
166
166
|
|
|
167
|
-
${f}`:f}let u=
|
|
167
|
+
${f}`:f}let u=dt(t,"said",{box:t.leadBox,agent:e?.agent,boxRole:e?.role,text:r,...o?{kind:o}:{},...l.length?{attachments:l}:{}});rr(t,null);try{if(await sn({cwd:n.cwd,sessionId:n.sessionId,sessionName:t.leadBox,agent:n.agent,agentCmd:ws(n.agent)}),(us.get(t.id)||0)!==a){!o&&r.trim()&&(ds(t,[r]),await X());let f=new Error("delivery stopped \u2014 the message was handed back as the draft");throw f.interrupted=!0,f}await At(t.leadBox,i,{...Me(e?.agent),kind:c?"lead-handoff-first-prompt":"say"})}catch(f){throw dt(t,"unsaid",{box:t.leadBox,agent:e?.agent,boxRole:e?.role,saidTs:u.at,text:r,...f?.interrupted?{reason:"interrupted"}:{}}),f}await _R(t,e,i)||(await Uy(t.leadBox,e?.agent,i.length+8).catch(()=>{}),dt(t,"unsaid",{box:t.leadBox,agent:e?.agent,boxRole:e?.role,saidTs:u.at,text:r,reason:"unsubmitted"}),o||ds(t,[r])),c&&await G({flow:"lead-handoff",phase:"first-prompt",trackId:t.id,sessionName:t.leadBox,agent:e?.agent,userTextLen:r.length})}function jy(t,{actions:e}={}){Xu(t).items.push({id:hs(),text:ng(e),ts:Date.now(),kind:"ship-notify"}),By(t)}async function ce(t,e,{kind:n=null,attachments:r=null}={}){let o=Array.isArray(r)?r.filter(d=>d&&typeof d.rel=="string"):[];if((typeof e!="string"||!jr(e))&&!o.length)throw new Error("text (string) or an attachment required");let s=tt(await H(),t);if(!s)throw new Error(`track "${t}" not found`);if(s.archivedAt)throw new Error(`track "${t}" is archived \u2014 restore it before driving it`);wi(t,"say");let a=await nt(s.leadBox);if(!a&&await td(s)&&(await X(),a=await nt(s.leadBox)),!a)throw new Error(`lead box "${s.leadBox}" is not running`);let i=s.boxes.find(d=>d.name===s.leadBox),c=jr(e),l=Xu(t),u=await mt(s.leadBox,{lines:Tt}).catch(()=>""),f=l.items.length>0||oa(i?.agent,u);if(!f&&Dr(i?.agent,u)){let d=await rn(s.leadBox).catch(()=>u);f=!!tr(i,d)}if(f){let d={id:hs(),text:c,ts:Date.now(),...n?{kind:n}:{},...o.length?{attachments:o}:{}};return l.items.push(d),By(t),rr(s,null),await X(),{ok:!0,leadBox:s.leadBox,queued:!0,id:d.id,position:l.items.length}}return await Dy(s,i,a,c,n,o),await X(),{ok:!0,leadBox:s.leadBox}}var vy="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 Ly(t,{hint:e}={}){let n=tt(await H(),t);if(!n)throw new Error(`track "${t}" not found`);if(n.archivedAt)throw new Error(`track "${t}" is archived \u2014 restore it first`);let r=Wc(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";bm(n.id,{name:s,box:n.leadBox});let a=String(e||"").trim()?`${vy}
|
|
168
168
|
|
|
169
|
-
|
|
169
|
+
What to preview: ${String(e).trim()}`:vy;try{await ce(t,a,{kind:"spin-up-view"})}catch(i){throw xa(n.id,{error:"drive-failed"}),i}return{ok:!0,running:!0}}async function Yu(t){let e=tt(await H(),t);if(!e)throw new Error(`track "${t}" not found`);if(e.archivedAt)throw new Error(`track "${t}" is archived`);us.set(t,(us.get(t)||0)+1);let n=hi(t),r=n.filter(i=>i.text&&!i.kind).map(i=>i.text),o=Xr.get(t);if(o&&!o.canceled){o.clicks++;let i=null;return r.length&&(i=ds(e,r),await X()),{ok:!0,interrupted:!0,stopped:!1,stopping:!0,dropped:n.length,restored:i,draftRev:e.draftRev||0}}let s=yR(t,{droppedCount:n.length,droppedTexts:r});await Promise.race([s.settled,En(fR)]);let a=tt(await H(),t)||e;return{ok:!0,interrupted:s.result!=="no-session",stopped:s.result==="stopped"||s.result==="stopped-late",stopping:!s.result,dropped:n.length,restored:s.restored,draftRev:a.draftRev||0}}var fR=4e3,pR=3,mR=[300,350,400],hR=1e3,wR=12,Xr=new Map,us=new Map;function gR(t){let e=Xr.get(t);return!!e&&!e.canceled}function wi(t,e){let n=Xr.get(t);n&&!n.canceled&&(n.canceled=!0,n.cancelReason=e||null)}function Fy(t){wi(t,"track-ended"),us.delete(t),hi(t)}function yR(t,{droppedCount:e,droppedTexts:n}){let r={startedAt:Date.now(),clicks:1,escSends:0,bgSends:0,queuedTexts:[...n],canceled:!1,cancelReason:null,result:null,restored:null},o;return r.settled=new Promise(s=>{o=s}),Xr.set(t,r),(async()=>{let s=tt(await H(),t),a=s?.boxes.find(y=>y.name===s.leadBox),i=s?await nt(s.leadBox):null,c=!1,l=!1;if(i){t:for(let y=0;y<pR&&!r.canceled;y++){await xt(s.leadBox,["Escape"],{kind:"interrupt"}).catch(()=>{}),r.escSends++;for(let b of mR){await En(b);let S=await mt(s.leadBox,{lines:Tt}).catch(()=>"");if(!Oe(a?.agent,S)){c=!0;break t}}}if(!c){l=!0;for(let y=0;y<wR&&!r.canceled&&(await En(hR),!r.canceled);y++){let b=await mt(s.leadBox,{lines:Tt}).catch(()=>"");if(!Oe(a?.agent,b)){c=!0;break}await xt(s.leadBox,["Escape"],{kind:"interrupt"}).catch(()=>{}),r.bgSends++}}}let u=tt(await H(),t)||s,f=u?.boxes.find(y=>y.name===u.leadBox)||a,d=null,p=null;u&&f&&i&&c&&!r.canceled&&(d=await SR(u,f).catch(()=>null),d||(p=await AR(u,f).catch(()=>null)));let m=u&&i&&!r.canceled?await kR(u).catch(()=>null):null,w=[d||p,...r.queuedTexts].filter(Boolean);u&&w.length&&(r.restored=ds(u,w),await X()),r.result=i?r.canceled?"canceled":c?l?"stopped-late":"stopped":"gave-up":"no-session",await bR(u||s,f,{ts:r.startedAt,result:r.result,dropped:e,escSends:r.escSends,bgSends:r.bgSends,clicks:r.clicks,unsent:!!d,lostRestored:!!p,queuedRestored:r.queuedTexts.length,shipDismissed:!!m,durationMs:Date.now()-r.startedAt,...r.cancelReason?{cancelReason:r.cancelReason}:{}}),await G({flow:"interrupt",phase:r.result==="gave-up"?"failed":"done",trackId:t,sessionName:u?.leadBox,agent:f?.agent,result:r.result,escSends:r.escSends,bgSends:r.bgSends,clicks:r.clicks,unsent:!!d,lostRestored:!!p,queuedRestored:r.queuedTexts.length,shipDismissed:!!m}).catch(()=>{})})().catch(()=>{}).finally(()=>{r.result||(r.result=r.canceled?"canceled":"gave-up"),Xr.get(t)===r&&Xr.delete(t),o()}),r}async function bR(t,e,n){await R(t,{type:"turn.interrupted",actor:"user",source:"orchestrator",boxId:t.leadBox,provider:e?.agent||null,payload:n}).catch(()=>{})}async function kR(t){let e=Mu(await L(t));return e?.offerId?(await R(t,{type:"pr.offer.answered",actor:"user",source:"orchestrator",boxId:e.box,payload:{ts:Date.now(),offerId:e.offerId,action:"dismiss",actions:null,reason:"interrupt"}}),e.offerId):null}var xR=[250,400,500,600];async function SR(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 xR){await En(r);let o=await mt(t.leadBox,{lines:Tt}).catch(()=>""),s=Xo(e.agent,o);if(s&&Vl(n.text,s))return await Uy(t.leadBox,e.agent,n.text.length+8),dt(t,"unsaid",{box:t.leadBox,agent:e.agent,boxRole:e.role,saidTs:n.at,text:n.text}),n.text}return null}var vR=3e4,TR=[350,650,900];async function AR(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>vR||n.some(s=>s.type==="unsaid"&&s.saidTs===r.at)||!_t(r.text))return null;for(let s of TR){await En(s);let a;try{a=await Ll(e,{})}catch{return null}if(kw(a,r))return null;let i=await mt(t.leadBox,{lines:Tt}).catch(()=>"");if(Xo(e.agent,i))return null}return dt(t,"unsaid",{box:t.leadBox,agent:e.agent,boxRole:e.role,saidTs:r.at,text:r.text,reason:"lost"}),r.text}var ER=[250,450,700],Hu=2;async function _R(t,e,n){if(e?.agent!=="claude")return!0;for(let r=0;r<=Hu;r++){let o=!1;for(let s of ER){await En(s);let a=await mt(t.leadBox,{lines:Tt}).catch(()=>""),i=xw(e.agent,n,a,{busy:Oe(e.agent,a)});if(i==="stuck"){o=!0;break}if(i==="submitted")return!0}if(!o)return!0;r<Hu&&(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:Hu,textHead:n.slice(0,120)}).catch(()=>{}),!1}async function Uy(t,e,n){for(let r=0;r<3;r++){await Qs(t,n,{kind:"unsend-wipe"}).catch(()=>{}),await En(200);let o=await mt(t,{lines:Tt}).catch(()=>""),s=Xo(e,o);if(!s)return!0;n=s.length+8}return!1}function rr(t,e,{source:n}={}){t.draftRev=(t.draftRev||0)+1;let r=typeof e=="string"?e:"";return r.trim()?t.draft={text:r,updatedAt:Date.now(),...n?{source:n}:{}}:delete t.draft,t.draft||null}function ds(t,e){let n=[],r=o=>n.some(s=>_t(s).includes(_t(o)));for(let o of[t.draft?.text,...e])o&&o.trim()&&!r(o)&&n.push(o);return n.length?rr(t,n.join(`
|
|
170
170
|
|
|
171
|
-
|
|
171
|
+
`),{source:"restore"})?.text??null:null}async function Hy(t,e,{rev:n=null}={}){if(typeof e!="string")throw new Error("text (string) required");let r=tt(await H(),t);if(!r)throw new Error(`track "${t}" not found`);let o=jr(e);if(typeof n=="number"&&n<(r.draftRev||0)&&r.draft?.source==="restore"){let a=r.draft.text;o?_t(o).includes(_t(a))?rr(r,o):rr(r,`${a}
|
|
172
172
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
`),e.draft={text:d,updatedAt:Date.now()},await z());let p={dropped:r.length,escSends:i.sends,unsent:!!c,lostRestored:!!l,queuedRestored:f.length,shipDismissed:!!u},m=!!a&&!i.stopped;return m?XI(e,n,{startedAt:s,syncSends:i.sends,outcome:p}):await _y(e,n,{ts:s,result:a?"stopped":"no-session",...p,clicks:1,bgSends:0,durationMs:Date.now()-s}),await U({flow:"interrupt",phase:m?"failed":"done",trackId:t,sessionName:e.leadBox,agent:n?.agent,stopped:i.stopped,stopping:m,...p}).catch(()=>{}),{ok:!0,interrupted:!!a,stopped:i.stopped,stopping:m,dropped:r.length,restored:d||null}}var VI=1e3,zI=12,os=new Map,ei=new Map;function JI(t){let e=os.get(t);return!!e&&!e.canceled}function Uu(t,e){let n=os.get(t);n&&!n.canceled&&(n.canceled=!0,n.cancelReason=e||null)}function XI(t,e,{startedAt:n,syncSends:r,outcome:o}){let s={startedAt:n,sends:0,clicks:1,canceled:!1,cancelReason:null};os.set(t.id,s),(async()=>{let a=!1;for(let l=0;l<zI&&!s.canceled&&(await Zn(VI),!s.canceled);l++){let u=await ft(t.leadBox,{lines:xt}).catch(()=>"");if(!Ee(e?.agent,u)){a=!0;break}await bt(t.leadBox,["Escape"],{kind:"interrupt"}).catch(()=>{}),s.sends++}let i=!1;if(a&&!s.canceled)try{let l=et(await W(),t.id),u=l?.boxes.find(f=>f.name===l.leadBox);l&&u&&(i=!!await Iy(l,u))}catch{}let c=s.canceled?"canceled":a?"stopped-late":"gave-up";await _y(t,e,{ts:n,result:c,...o,lostRestored:o.lostRestored||i,escSends:r,bgSends:s.sends,clicks:s.clicks,durationMs:Date.now()-n,...s.cancelReason?{cancelReason:s.cancelReason}:{}}),await U({flow:"interrupt",phase:c==="gave-up"?"failed":"done",trackId:t.id,sessionName:t.leadBox,agent:e?.agent,result:c,bgSends:s.sends,clicks:s.clicks}).catch(()=>{})})().catch(()=>{}).finally(()=>{os.delete(t.id)})}async function _y(t,e,n){await R(t,{type:"turn.interrupted",actor:"user",source:"orchestrator",boxId:t.leadBox,provider:e?.agent||null,payload:n}).catch(()=>{})}var YI=[300,350,400],ZI=3;async function QI(t,e){let n=0;for(let r=0;r<ZI;r++){await bt(t,["Escape"],{kind:"interrupt"}),n++;for(let o of YI){await Zn(o);let s=await ft(t,{lines:xt}).catch(()=>"");if(!Ee(e,s))return{stopped:!0,sends:n}}}return{stopped:!1,sends:n}}async function tR(t){let e=xu(await D(t));return e?.offerId?(await R(t,{type:"pr.offer.answered",actor:"user",source:"orchestrator",boxId:e.box,payload:{ts:Date.now(),offerId:e.offerId,action:"dismiss",actions:null,reason:"interrupt"}}),e.offerId):null}var eR=[250,400,500,600];async function nR(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 eR){await Zn(r);let o=await ft(t.leadBox,{lines:xt}).catch(()=>""),s=qo(e.agent,o);if(s&&Dl(n.text,s))return await Ry(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 rR=3e4,oR=[350,650,900];async function Iy(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>rR||n.some(s=>s.type==="unsaid"&&s.saidTs===r.at)||!qt(r.text))return null;for(let s of oR){await Zn(s);let a;try{a=await Rl(e,{})}catch{return null}if(cw(a,r))return null;let i=await ft(t.leadBox,{lines:xt}).catch(()=>"");if(qo(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 sR=[250,450,700],Ou=2;async function aR(t,e,n){if(e?.agent!=="claude")return!0;for(let r=0;r<=Ou;r++){let o=!1;for(let s of sR){await Zn(s);let a=await ft(t.leadBox,{lines:xt}).catch(()=>""),i=lw(e.agent,n,a,{busy:Ee(e.agent,a)});if(i==="stuck"){o=!0;break}if(i==="submitted")return!0}if(!o)return!0;r<Ou&&(await bt(t.leadBox,["Enter"],{kind:"submit-retry"}),U({flow:"say",phase:"submit-retry",trackId:t.id,sessionName:t.leadBox,agent:e.agent,attempt:r+1,textHead:n.slice(0,120)}).catch(()=>{}))}return U({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 Ry(t,e,n){for(let r=0;r<3;r++){await qs(t,n,{kind:"unsend-wipe"}).catch(()=>{}),await Zn(200);let o=await ft(t,{lines:xt}).catch(()=>""),s=qo(e,o);if(!s)return!0;n=s.length+8}return!1}async function $y(t,e){if(typeof e!="string")throw new Error("text (string) required");let n=et(await W(),t);if(!n)throw new Error(`track "${t}" not found`);let r=Mr(e);return r?n.draft={text:r,updatedAt:Date.now()}:delete n.draft,await z(),{ok:!0,draft:n.draft||null}}async function Oy(t,e){if(typeof e!="string"||!e.trim())throw new Error("model (string) required");let n=e.trim(),r=et(await W(),t);if(!r)throw new Error(`track "${t}" not found`);let o=await Z(r.leadBox);if(!o)throw new Error(`lead box "${r.leadBox}" is not running`);let a=r.boxes.find(f=>f.name===r.leadBox)?.agent||o.agent||"claude",i=_w(a,n),l=[...r.events||[]].reverse().find(f=>f.type==="model-change")?.to??null;l||(l=(await bo(o.cwd,o.sessionId,{fallbackNewest:!1}).catch(()=>null))?.model??null),await tn({cwd:o.cwd,sessionId:o.sessionId,sessionName:r.leadBox,agent:o.agent,agentCmd:ls(o.agent)});let u=await Iw(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 Cy(t){for(let e of t){if(!e||!await Na(e))continue;let n=await hw(e).catch(()=>null);if(!n)continue;if((await L.stat(F.join(n,".git")).catch(()=>null))?.isDirectory())return n}return null}function Dt(t){let e=t;return e?e.kind==="multi"?Array.isArray(e.repos)?e.repos.filter(Boolean):[]:e.base?[{name:F.basename(e.base),dir:e.dir??null,base:e.base,baseRef:e.baseRef??null,repo:e.repo??null}]:[]:[]}async function zo(t){let e=[];for(let n of Dt(t?.workspace)){if(!n?.dir)continue;let r=await ql(n.dir,{ignorePlumbing:!0}).catch(()=>!1),o=await Kl(n.dir,n.baseRef).catch(()=>0);(r||o>0)&&e.push({...n,dirty:r,ahead:o})}return e}async function My(t){let e=t;typeof e=="string"&&!F.isAbsolute(e)&&/^[^/\s]+\/[^/\s]+$/.test(e)&&(e=await ao(e)??await Wi(e));let n=await Cy([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 iR(t,e,n,r){let o=`track/${Vu(e)}-${t}`,s=[];try{await L.mkdir(n,{recursive:!0});let a=new Set([".bostrat"]);for(let i of r){let c=await My(i);if(s.some(p=>p.base===c))throw new Error(`repo tagged twice: ${i}`);let l=await tt(c).catch(()=>null),u=F.basename(c);a.has(u)&&l&&(u=l.replace("/","-"));for(let p=2;a.has(u);p++)u=`${F.basename(c)}-${p}`;a.add(u);let f=F.join(n,u),d=await Pa(c);await Nr(c,f,o,d),s.push({name:u,dir:f,base:c,baseRef:d,repo:l})}return await ci(n,s,o),{dir:n,kind:"multi",branch:o,repos:s}}catch(a){for(let i of s)await Xt(i.base,i.dir).catch(()=>{});throw await L.rm(n,{recursive:!0,force:!0}).catch(()=>{}),a}}var Ny=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 Py(t,e){let n=await W(),r=et(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 My(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=F.basename(s);i.has(c)&&a&&(c=a.replace("/","-"));for(let d=2;i.has(c);d++)c=`${F.basename(s)}-${d}`;let l=F.join(o.dir,c),u=await Pa(s);await ww(s,o.branch)?await gw(s,l,o.branch):await Nr(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 ci(o.dir,o.repos,o.branch),ct(r,"retagged",{action:"add",repo:a??s,name:c,workspace:Ny(o)}),await z(),f}async function By(t,e){let n=await W(),r=et(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 re.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 ql(s.dir,{ignorePlumbing:!0}).catch(()=>!1),i=await Kl(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 Xt(s.base,s.dir).catch(()=>{}),await L.rm(s.dir,{recursive:!0,force:!0}).catch(()=>{}),o.repos=(o.repos??[]).filter(c=>c&&c!==s),await ci(o.dir,o.repos,o.branch),ct(r,"retagged",{action:"remove",repo:s.repo??s.base,name:s.name,workspace:Ny(o)}),await z(),{removed:s.name}}async function cR(t,e,{repo:n,repos:r,cwd:o}={}){await L.mkdir(Nu,{recursive:!0});let s=F.join(Nu,t),a=Array.isArray(r)?r.map(c=>typeof c=="string"?c.trim():"").filter(Boolean):[];if(a.length>=2)return iR(t,e,s,a);a.length===1&&!n&&(n=a[0]),typeof n=="string"&&!F.isAbsolute(n)&&/^[^/\s]+\/[^/\s]+$/.test(n)&&(n=await ao(n)??await Wi(n));let i=await Cy([n,o,II]);if(i){let c=`track/${Vu(e)}-${t}`;try{let l=await Pa(i);await Nr(i,s,c,l),await Pu(s);let u=await tt(i).catch(()=>null);return{dir:s,base:i,branch:c,kind:"worktree",baseRef:l,repo:u}}catch{await L.rm(s,{recursive:!0,force:!0}).catch(()=>{})}}return await L.mkdir(s,{recursive:!0}),await yw(s),await Pu(s),{dir:s,base:null,branch:null,kind:"repo"}}var ss="# bostrat workspace",Dy=`${ss}
|
|
173
|
+
${o}`,{source:"restore"}):rr(r,a,{source:"restore"})}else rr(r,o);return await X(),{ok:!0,draft:r.draft||null,rev:r.draftRev||0}}async function Wy(t,e){if(typeof e!="string"||!e.trim())throw new Error("model (string) required");let n=e.trim(),r=tt(await H(),t);if(!r)throw new Error(`track "${t}" not found`);let o=await nt(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=Lw(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 sn({cwd:o.cwd,sessionId:o.sessionId,sessionName:r.leadBox,agent:o.agent,agentCmd:ws(o.agent)});let u=await Fw(a,r.leadBox,i);return dt(r,"model-change",{to:n,from:l,box:r.leadBox,agent:a,verified:!!u.verified}),await X(),{ok:!0,requested:n,leadBox:r.leadBox,...u}}async function qy(t){for(let e of t){if(!e||!await Wa(e))continue;let n=await _w(e).catch(()=>null);if(!n)continue;if((await F.stat(U.join(n,".git")).catch(()=>null))?.isDirectory())return n}return null}function qt(t){let e=t;return e?e.kind==="multi"?Array.isArray(e.repos)?e.repos.filter(Boolean):[]:e.base?[{name:U.basename(e.base),dir:e.dir??null,base:e.base,baseRef:e.baseRef??null,repo:e.repo??null}]:[]:[]}async function ts(t){let e=[];for(let n of qt(t?.workspace)){if(!n?.dir)continue;let r=await eu(n.dir,{ignorePlumbing:!0}).catch(()=>!1),o=await nu(n.dir,n.baseRef).catch(()=>0);(r||o>0)&&e.push({...n,dirty:r,ahead:o})}return e}async function Ky(t){let e=t;typeof e=="string"&&!U.isAbsolute(e)&&/^[^/\s]+\/[^/\s]+$/.test(e)&&(e=await po(e)??await Zi(e));let n=await qy([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 IR(t,e,n,r){let o=`track/${rd(e)}-${t}`,s=[];try{await F.mkdir(n,{recursive:!0});let a=new Set([".bostrat"]);for(let i of r){let c=await Ky(i);if(s.some(p=>p.base===c))throw new Error(`repo tagged twice: ${i}`);let l=await ot(c).catch(()=>null),u=U.basename(c);a.has(u)&&l&&(u=l.replace("/","-"));for(let p=2;a.has(u);p++)u=`${U.basename(c)}-${p}`;a.add(u);let f=U.join(n,u),d=await qa(c);await Lr(c,f,o,d),s.push({name:u,dir:f,base:c,baseRef:d,repo:l})}return await gi(n,s,o),{dir:n,kind:"multi",branch:o,repos:s}}catch(a){for(let i of s)await ee(i.base,i.dir).catch(()=>{});throw await F.rm(n,{recursive:!0,force:!0}).catch(()=>{}),a}}var Gy=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 Vy(t,e){let n=await H(),r=tt(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 Ky(typeof e=="string"?e.trim():e);if((o.repos??[]).some(d=>d?.base===s))throw new Error(`repo already tagged: ${e}`);let a=await ot(s).catch(()=>null),i=new Set([".bostrat",...(o.repos??[]).map(d=>d?.name).filter(Boolean)]),c=U.basename(s);i.has(c)&&a&&(c=a.replace("/","-"));for(let d=2;i.has(c);d++)c=`${U.basename(s)}-${d}`;let l=U.join(o.dir,c),u=await qa(s);await Iw(s,o.branch)?await Rw(s,l,o.branch):await Lr(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 gi(o.dir,o.repos,o.branch),dt(r,"retagged",{action:"add",repo:a??s,name:c,workspace:Gy(o)}),await X(),f}async function zy(t,e){let n=await H(),r=tt(n,t);if(!r)throw new Error(`track "${t}" not found`);let o=r.workspace;if(o?.kind!=="multi")throw new Error("live re-tagging needs a multi-repo Track");let s=(o.repos??[]).find(c=>c&&(c.name===e||c.repo===e||c.base===e));if(!s)throw new Error(`repo "${e}" is not tagged on this Track`);if((o.repos??[]).filter(Boolean).length<=1)throw new Error("can't untag the last repo \u2014 archive or delete the Track instead");for(let c of ie.values())if(c.trackId===t&&!c.finishedAt)throw new Error("a subtask is still running on this Track \u2014 wait for it to merge back before untagging");let a=await eu(s.dir,{ignorePlumbing:!0}).catch(()=>!1),i=await nu(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 ee(s.base,s.dir).catch(()=>{}),await F.rm(s.dir,{recursive:!0,force:!0}).catch(()=>{}),o.repos=(o.repos??[]).filter(c=>c&&c!==s),await gi(o.dir,o.repos,o.branch),dt(r,"retagged",{action:"remove",repo:s.repo??s.base,name:s.name,workspace:Gy(o)}),await X(),{removed:s.name}}async function RR(t,e,{repo:n,repos:r,cwd:o}={}){await F.mkdir(Ku,{recursive:!0});let s=U.join(Ku,t),a=Array.isArray(r)?r.map(c=>typeof c=="string"?c.trim():"").filter(Boolean):[];if(a.length>=2)return IR(t,e,s,a);a.length===1&&!n&&(n=a[0]),typeof n=="string"&&!U.isAbsolute(n)&&/^[^/\s]+\/[^/\s]+$/.test(n)&&(n=await po(n)??await Zi(n));let i=await qy([n,o,VI]);if(i){let c=`track/${rd(e)}-${t}`;try{let l=await qa(i);await Lr(i,s,c,l),await Gu(s);let u=await ot(i).catch(()=>null);return{dir:s,base:i,branch:c,kind:"worktree",baseRef:l,repo:u}}catch{await F.rm(s,{recursive:!0,force:!0}).catch(()=>{})}}return await F.mkdir(s,{recursive:!0}),await $w(s),await Gu(s),{dir:s,base:null,branch:null,kind:"repo"}}var fs="# bostrat workspace",Jy=`${fs}
|
|
176
174
|
|
|
177
175
|
TRACK MEMORY: if \`.bostrat/memory/MEMORY.md\` exists in your workspace, read it
|
|
178
176
|
before substantive work \u2014 it is the shared cross-provider memory for this repo
|
|
@@ -339,9 +337,9 @@ which steps to run (and which to skip). An offer buried mid-message, phrased as
|
|
|
339
337
|
statement, or omitted entirely renders no controls \u2014 so close EVERY completed
|
|
340
338
|
implementation turn this way, every time. When the operator has already asked you
|
|
341
339
|
to commit / push / open the PR, just do it; do not re-offer.
|
|
342
|
-
|
|
343
|
-
`)),
|
|
344
|
-
`);return`${
|
|
340
|
+
`,$R=Jy,is=Jy+Aw;async function Gu(t){try{await F.writeFile(U.join(t,"AGENTS.md"),is,{flag:"wx"})}catch{return}await CR(t,["/AGENTS.md"])}var Wu=t=>typeof t=="string"&&(t===fs||t.startsWith(`${fs}
|
|
341
|
+
`)),OR="# bostrat multi-repo workspace";function MR(t,e){let n=t.map(r=>`- \`${r.name}/\`${r.repo?` \u2014 github.com/${r.repo}`:""}${r.baseRef?` (forked from ${r.baseRef})`:""}`).join(`
|
|
342
|
+
`);return`${OR}
|
|
345
343
|
|
|
346
344
|
This directory is NOT a git repo. It is a bostrat Track workspace holding one
|
|
347
345
|
git worktree per tagged repo, ALL on the same branch \`${e}\`:
|
|
@@ -353,25 +351,25 @@ push / open PRs) in each repo separately, always on that shared branch. Work
|
|
|
353
351
|
only needs to land in the repos it actually touches \u2014 an untouched repo's
|
|
354
352
|
branch simply stays at its fork point and never gets pushed. When asked to run
|
|
355
353
|
or preview an app, run it from that app's own repo directory.
|
|
356
|
-
`}async function
|
|
357
|
-
`,`${
|
|
354
|
+
`}async function gi(t,e,n){let r=MR(e,n);for(let o of["CLAUDE.md","AGENTS.md"])await F.writeFile(U.join(t,o),r).catch(()=>{})}var qu=is.replace(`${fs}
|
|
355
|
+
`,`${fs}
|
|
358
356
|
|
|
359
357
|
(Global copy managed by bostrat \u2014 rewritten on service start; edits will be lost.)
|
|
360
358
|
SCOPE: these instructions apply ONLY when your working directory is under
|
|
361
359
|
~/.bostrat/workspaces/ or ~/.bostrat/worktrees/ \u2014 a bostrat-orchestrated box.
|
|
362
360
|
When working anywhere else, ignore everything below.
|
|
363
|
-
`);async function
|
|
364
|
-
`).includes(s));o.length&&await
|
|
361
|
+
`);async function Xy(t){let{stdout:e}=await KI("git",["-C",t,"rev-parse","--git-path","info/exclude"]);return U.resolve(t,e.trim())}async function CR(t,e){try{let n=await Xy(t);await F.mkdir(U.dirname(n),{recursive:!0});let r=await F.readFile(n,"utf8").catch(()=>""),o=e.filter(s=>!r.split(`
|
|
362
|
+
`).includes(s));o.length&&await F.appendFile(n,(r.endsWith(`
|
|
365
363
|
`)||!r?"":`
|
|
366
364
|
`)+o.join(`
|
|
367
365
|
`)+`
|
|
368
|
-
`)}catch{}}async function
|
|
366
|
+
`)}catch{}}async function NR(t,e){try{let n=await Xy(t),r=await F.readFile(n,"utf8"),o=r.split(`
|
|
369
367
|
`).filter(s=>s!==e);o.length!==r.split(`
|
|
370
|
-
`).length&&await
|
|
371
|
-
`))}catch{}}async function
|
|
368
|
+
`).length&&await F.writeFile(n,o.join(`
|
|
369
|
+
`))}catch{}}async function Yy(){let t={refreshed:0,removed:0,codexGlobal:null};try{await F.mkdir(ze,{recursive:!0}),await F.writeFile(U.join(ze,"CLAUDE.md"),$R).catch(()=>{}),await F.writeFile(U.join(ze,"AGENTS.md"),is).catch(()=>{});let e=process.env.CODEX_HOME||U.join(process.env.HOME||"/tmp",".codex");if((await F.stat(e).catch(()=>null))?.isDirectory()){let r=U.join(e,"AGENTS.md"),o=await F.readFile(r,"utf8").catch(()=>null);o===null?(await F.writeFile(r,qu,{flag:"wx"}).catch(()=>{}),t.codexGlobal="created"):Wu(o)&&o!==qu&&(await F.writeFile(r,qu).catch(()=>{}),t.codexGlobal="refreshed")}for(let r of[Ku,Yr]){let o=await F.readdir(r,{withFileTypes:!0}).catch(()=>[]);for(let s of o){if(!s.isDirectory())continue;let a=U.join(r,s.name),i=U.join(a,"AGENTS.md"),c=await F.readFile(i,"utf8").catch(()=>null);c!==null&&Wu(c)&&c!==is&&(await F.writeFile(i,is).catch(()=>{}),t.refreshed++);let l=U.join(a,"CLAUDE.md"),u=await F.readFile(l,"utf8").catch(()=>null);u!==null&&Wu(u)&&(await F.rm(l,{force:!0}).catch(()=>{}),await NR(a,"/CLAUDE.md"),t.removed++)}}}catch{}return t}async function PR(t){let e=t.workspace;if(!(!e||!e.dir))try{if(e.kind==="multi"){for(let n of qt(e))n.base&&n.dir&&await ee(n.base,n.dir).catch(()=>{});await F.rm(e.dir,{recursive:!0,force:!0})}else e.kind==="worktree"&&e.base?await ee(e.base,e.dir):await F.rm(e.dir,{recursive:!0,force:!0})}catch{}}async function Zu({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 pi(o,s),i=await H(),c=hs().slice(0,8),l=await RR(c,t,{repo:n,repos:r,cwd:e}),u=new Set(i.tracks.flatMap(m=>(m.boxes||[]).map(w=>w.name))),f=`${t.trim()} lead`;u.has(wc(f))&&(f=`${t.trim().slice(0,50).trim()} ${c} lead`);let d=await Bn(f,l.dir,{agentCmd:a,agent:o});aa({cwd:d.cwd,sessionName:d.name,agent:o,agentCmd:a});let p={id:c,name:t.trim(),cwd:d.cwd,workspace:l,leadBox:d.name,boxes:[ib(d,"lead","live")],events:[],createdAt:Date.now()};return dt(p,"opened",{agent:o,box:d.name,skipPermissions:s,name:p.name,cwd:p.cwd,workspace:{base:l.base??null,branch:l.branch,kind:l.kind,...l.baseRef?{baseRef:l.baseRef}:{},...l.repo?{repo:l.repo}:{},...l.repos?{repos:l.repos.map(({name:m,base:w,baseRef:y,repo:b})=>({name:m,base:w,baseRef:y,repo:b}))}:{}}}),i.tracks.push(p),await X(),An(p)}var BR=new Set(["claude","codex","cursor"]);async function Zy({provider:t="claude",fresh:e=!1}={}){if(!BR.has(t))throw new Error(`unknown setup provider "${t}" (claude | codex | cursor)`);let r=(await H()).tracks.find(u=>u.kind==="setup"&&u.setup?.provider===t&&!u.archivedAt);if(r){if(!e){let u=await An(r);if(u.boxes.find(d=>d.name===u.leadBox)?.state==="live")return u}await Qr(r.id).catch(()=>{})}let o=(await pi(t,!1)).replace(/\s+--(?:dangerously-skip-permissions|dangerously-bypass-approvals-and-sandbox)\b/g,""),s=hs().slice(0,8),a=U.join(ze,"setup",s);await F.mkdir(a,{recursive:!0});let i=await Bn(`Connect ${t} ${s}`,a,{agentCmd:o,agent:t,firstRun:!0});t==="codex"&&aa({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:[ib(i,"lead","live")],events:[],createdAt:Date.now()};return dt(c,"opened",{agent:t,box:i.name,skipPermissions:!1,trackKind:"setup",name:c.name,cwd:c.cwd,workspace:{base:null,branch:null,kind:"scratch"}}),(await H()).tracks.push(c),await X(),An(c)}async function Qy(t,e,n){await Em(t).catch(()=>{});let r=ps.get(t);r&&!r.finishedAt&&(r.error=n,r.finishedAt=Date.now()),ps.delete(t),e.handoff&&!e.handoff.finishedAt&&(e.handoff.step="error",e.handoff.error=n,e.handoff.finishedAt=Date.now());for(let a of to(t))a.finishedAt||(a.canceled=!0,a.finishedAt=Date.now()),await sd(a).catch(()=>{}),ie.delete(a.id);for(let a of Object.values(e.subtasks||{}))a.finishedAt||(a.step="error",a.error=n,a.canceled=!0,a.finishedAt=Date.now());let o=await H(),s=new Set((o.tracks||[]).filter(a=>a.id!==t&&!a.archivedAt).flatMap(a=>(a.boxes||[]).map(i=>i.name)));for(let a of e.boxes)s.has(a.name)||await fe(a.name).catch(()=>{})}async function tb(t,{cascadeMocks:e=!0,cascadeRunbooks:n=!1}={}){let r=await H(),o=r.tracks.findIndex(a=>a.id===t);if(o===-1)throw new Error(`track "${t}" not found`);let s=r.tracks[o];return e||await im(s),Fy(t),await Qy(t,s,"track deleted"),n?await jm(t).catch(()=>{}):await Dm(t).catch(()=>{}),await Hw(),await PR(s),e&&await cm(t).catch(()=>{}),e?await fy(t).catch(()=>{}):await dy(s).catch(()=>{}),dt(s,"track-deleted",{name:s.name,boxes:s.boxes.length,ageMs:s.createdAt?Date.now()-s.createdAt:null}),r.tracks.splice(o,1),await X(),{ok:!0,id:t,name:s.name,boxes:s.boxes.length}}async function Qr(t){let e=await H(),n=tt(e,t);if(!n)throw new Error(`track "${t}" not found`);if(n.archivedAt)throw new Error(`track "${t}" is already archived`);return n.archivedAt=Date.now(),Fy(t),dt(n,"archived",{boxes:n.boxes.length}),await X(),n.kind==="setup"?await eb(t):jR(t).catch(()=>{}),An(n)}var DR=9e4;async function jR(t){try{await LR(t)}catch{}await eb(t).catch(()=>{})}async function eb(t){let e=await H(),n=tt(e,t);if(!(!n||!n.archivedAt)){await Qy(t,n,"track archived");for(let r of n.boxes)or.has(r.state)||(r.state="stopped");await X()}}async function LR(t){let e=await H(),n=tt(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 nt(n.leadBox).catch(()=>null):null;if(i?.cwd&&a)try{({text:r,items:o}=await Cp(n.leadBox,i.cwd,{timeoutMs:DR})),s="lead-cli"}catch{}if(r||(r=bl(n,await L(n).catch(()=>[]))),!r)return;let c={text:r,source:s,at:Date.now()};o.length&&(c.items=o),n.beat=c,await R(n,{type:"track.summary.recorded",actor:"orchestrator",source:"orchestrator",boxId:n.leadBox||null,provider:i?.agent||null,payload:o.length?{text:r,items:o,source:s}:{text:r,source:s}}).catch(()=>{}),await X()}async function nb(){let t=await H(),e=0,n=0,r=0;for(let o of t.tracks){if(!o.archivedAt||o.kind==="setup"||o.beat?.text){n++;continue}let s=bl(o,await L(o).catch(()=>[]));if(!s){r++;continue}o.beat={text:s,source:"derived",at:Date.now()},await R(o,{type:"track.summary.recorded",actor:"orchestrator",source:"orchestrator",payload:{text:s,source:"derived",backfill:!0}}).catch(()=>{}),e++}return await X(),{recorded:e,skipped:n,empty:r}}async function kn(t,e){let n=await Pt(t,e).catch(()=>null);if(!n?.archiveOnMerge||!n.trackId)return!1;let r=await rb(n);if(r.length){for(let o of r)await Vn(o.repo,o.number,!0).catch(()=>{});return!1}try{return await Qr(n.trackId),!0}catch(o){if(/already archived/i.test(String(o?.message||o)))return!1;throw o}}async function rb(t){return(await Se().catch(()=>[])).filter(n=>n.trackId===t.trackId&&n.state!=="merged"&&n.state!=="closed"&&n.state!=="lost"&&!(n.repo===t.repo&&n.number===t.number))}async function xn(t,e){return Qu(t,e,"merged")}async function Qu(t,e,n="merged"){let r=await Pt(t,e).catch(()=>null);if(!r?.notifyOnMerge||r.archiveOnMerge||!r.trackId)return!1;let o={repo:r.repo??null,number:r.number,title:r.title??null,url:r.url??null,headRef:r.headRef??null,outcome:n},s=await rb(r);if(s.length){let i=[...r.notifyPending||[],o];for(let c of s)await Th(c.repo,c.number,i).catch(()=>{});return!1}let a=[...r.notifyPending||[],o];try{return await ce(r.trackId,FR(a),{kind:"merge-notify"}),!0}catch{return!1}}var Ty={merged:"merged",closed:"closed on GitHub WITHOUT merging",lost:"no longer found on GitHub (deleted, or its repo left the grant)"};function FR(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 ${Ty[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 ${Ty[i.outcome]??i.outcome}`).join(`
|
|
372
370
|
`),a=n.length?r.length?`This is an automated merge notification: pick up whatever follow-up work the merged PR${n.length===1?"":"s"} unblock${n.length===1?"s":""} \u2014 and note the unmerged one${r.length===1?"":"s"}: that work did NOT land, so check whether it still needs to ship. If nothing needs doing, say so briefly.`:"This is an automated merge notification: pick up whatever follow-up work these merges unblock (dependent tasks, cleanup, next steps that were waiting on them). If nothing needs doing, say so briefly.":"This is an automated notification: NONE of the PRs landed \u2014 the work did not ship. Check whether it still needs to go out another way. If not, say so briefly.";return`${o}
|
|
373
371
|
${s}
|
|
374
|
-
${a}`}async function qu(t){if(t.archivedAt)return!1;let e=t.boxes.find(r=>r.name===t.leadBox);if(!e||Yn.has(e.state)||await Z(e.name))return!1;let n=hy(e.agent,e.sessionId);if(!await Hs(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 $n(e.name,e.cwd,{agentCmd:n,agent:e.agent,resumeSessionId:e.sessionId,pinned:e.pinned===!0});return e.hookTokenHash=oo(r.hookToken),Ys({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 Gy(t){let e=await W(),n=et(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 qu(n),await z(),bn(n)}async function Vy(){let t=await W(),e=new Set((await Rn()).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 ce(a.name).catch(()=>{}),ct(s,"orphan-reaped",{box:a.name,agent:a.agent}),r++,o=!0),!Yn.has(a.state)&&a.state!=="stopped"&&(a.state="stopped",o=!0);return o&&await z(),r}async function zy(t){let e=await W(),n=et(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(Yn.has(r.state))throw new Error("lead box is terminal \u2014 nothing to restart");if(await Z(n.leadBox)){let a=await ft(n.leadBox,{lines:xt}).catch(()=>"");if(Js(r.agent,a))throw new Error("lead is mid-turn \u2014 retry when it goes idle");await ce(n.leadBox),r.state="stopped"}let s=await qu(n);if(await z(),!s)throw new Error("rehydrate failed \u2014 see the track's rehydrate-failed event");return bn(n)}var Jy=(t,e,n)=>({name:t.name,agent:t.agent,role:e,state:n,cwd:t.cwd,sessionId:t.sessionId,pinned:t.pinned===!0,hookTokenHash:oo(t.hookToken),createdAt:Date.now()});function Xy(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 as=new Map,us=t=>as.get(t)||(xe?et(xe,t)?.handoff:null)||null;async function ni(t,e){let n=await W(),r=et(n,t);r&&(r.handoff={...r.handoff||{},...e},await z())}function kR(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 Yy(){let t=await W(),e=!1;for(let n of t.tracks||[]){let r=n.handoff,o=kR(n);if(!o)continue;e=!0;let s=!!r.toBox&&r.toBox===r.fromBox;if(s&&o.step==="error"){let c=await Z(r.fromBox).catch(()=>null),l=n.boxes.find(u=>u.name===r.fromBox);if(c&&l&&c.agent===r.toAgent&&r.toAgent!==l.agent){let u=Date.now(),f=Xy(l),d=f[f.length-1],p=d.agent,m=d.sessionId??null;d.endedAt=u,r.reason&&(d.endReason=r.reason);let g=c.agent==="claude"&&!!c.sessionId;f.push({agent:r.toAgent,sessionId:c.sessionId||null,pinned:g,startedAt:u,endedAt:null}),l.agent=r.toAgent,l.sessionId=c.sessionId||null,l.pinned=g,l.state="live",n.pendingHandoffHydrate={artifact:ai,at:u},ct(n,"lead-handoff",{from:r.fromBox,to:r.fromBox,fromAgent:p,toAgent:r.toAgent,fromSessionId:m,toSessionId:c.sessionId||null,epoch:f.length-1,singleBox:!0,skipPermissions:r.skipPermissions,reason:r.reason??null,handoffStartedAt:r.startedAt,recovered:!0}),n.handoff={...r,step:"done",finishedAt:u,recovered:"swap adopted from live tmux tags after restart"},await U({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 U({flow:"lead-handoff",phase:"reconciled",trackId:n.id,outcome:o.step,recovered:o.recovered,fromBox:o.fromBox,toBox:o.toBox}).catch(()=>{}),i&&await ce(i).catch(()=>{}),a&&await ce(a).catch(()=>{})}return e&&await z(),e}async function Ku(t,e,n=!0,r=null){let o=as.get(t);if(o&&!o.finishedAt)throw new Error("a lead handoff is already running for this Track");let s=et(await W(),t);if(!s)throw new Error(`track "${t}" not found`);Uu(t,"handoff");let a=await si(e,n),i=await Z(s.leadBox);if(!i)throw new Error(`lead box "${s.leadBox}" is not running`);let c={trackId:t,step:"checkpoint",fromBox:s.leadBox,toAgent:e,skipPermissions:n,reason:wy(r),toBox:s.leadBox,startedAt:Date.now(),finishedAt:null,error:null};return as.set(t,c),await ni(t,{step:c.step,fromBox:c.fromBox,toAgent:e,toBox:c.toBox,skipPermissions:n,startedAt:c.startedAt,finishedAt:null,error:null}),xR(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 ni(t,{step:c.step,error:c.error,finishedAt:c.finishedAt}).catch(()=>{}),U({flow:"lead-handoff",phase:"failed",trackId:t,step:c.step,error:c.error,fromBox:c.fromBox,toAgent:c.toAgent,toBox:c.toBox}).catch(()=>{})}),U({flow:"lead-handoff",phase:"started",trackId:t,fromBox:c.fromBox,toAgent:e,cwd:i.cwd}).catch(()=>{}),c}async function xR(t,e,n,r,o){let s=async(E,M={})=>{t.step=E,console.log(`[lead-handoff ${t.trackId}] step \u2192 ${E}`),await ni(t.trackId,{step:E,toBox:t.toBox}),await U({flow:"lead-handoff",phase:E,trackId:t.trackId,fromBox:t.fromBox,toBox:t.toBox,toAgent:o,sessionName:M.sessionName,...M})};console.log(`[lead-handoff ${t.trackId}] ${t.fromBox}: ${n.agent} \u2192 ${o} IN PLACE`);let a=await W(),i=et(a,t.trackId)?.boxes.find(E=>E.name===t.fromBox)||null,c=i?.agent||n.agent,l=i?.sessionId??n.sessionId??null,u=i?.pinned===!0;await s("checkpoint",{sessionName:t.fromBox}),await Ip(t.fromBox),await s("swap",{cwd:n.cwd,agentCmd:r});let f=await dl(t.fromBox,{agentCmd:r,agent:o});await s("hydrate",{sessionName:t.fromBox}),await tn({cwd:n.cwd,sessionId:f.sessionId,sessionName:t.fromBox,agent:o,agentCmd:ls(o)});let d=await ft(t.fromBox,{lines:xt}).catch(()=>"");if(!Xs(o,d)){await s("rollback",{sessionName:t.fromBox,fromAgent:c});let E=hy(c,l,{skipPermissions:t.skipPermissions});await dl(t.fromBox,{agentCmd:E,agent:c,resumeSessionId:l,pinned:u});let M=await W(),J=et(M,t.trackId);if(J){let $=J.boxes.find(j=>j.name===t.fromBox);$&&($.state="live"),ct(J,"lead-handoff-failed",{box:t.fromBox,fromAgent:c,toAgent:o,reason:t.reason,error:`incoming ${o} CLI never drew its composer`,rolledBack:!0,handoffStartedAt:t.startedAt}),await z()}throw new Error(`incoming ${o} CLI never drew its composer \u2014 rolled back to ${c} (--resume, context intact)`)}await s("commit");let p=await W(),m=et(p,t.trackId);if(!m)throw new Error(`track "${t.trackId}" not found during handoff commit \u2014 was it deleted or archived mid-flight?`);let g=m.boxes.find(E=>E.name===t.fromBox);if(!g)throw new Error(`lead box row "${t.fromBox}" missing during handoff commit`);let y=Date.now(),b=Xy(g),S=b[b.length-1];S.endedAt=y,t.reason&&(S.endReason=t.reason),b.push({agent:o,sessionId:f.sessionId,pinned:f.pinned,startedAt:y,endedAt:null}),g.agent=o,g.sessionId=f.sessionId,g.pinned=f.pinned,g.state="live",g.role="lead",m.pendingHandoffHydrate={artifact:ai,at:y},ct(m,"lead-handoff",{from:t.fromBox,to:t.fromBox,fromAgent:c,toAgent:o,fromSessionId:l,toSessionId:f.sessionId,epoch:b.length-1,singleBox:!0,forced:f.forced||void 0,skipPermissions:t.skipPermissions,reason:t.reason,handoffStartedAt:t.startedAt}),await z(),await s("done",{sessionName:t.fromBox}),await ni(t.trackId,{finishedAt:Date.now(),error:null}),t.finishedAt=Date.now()}var SR=Number(process.env.BOSTRAT_SUBTASK_TIMEOUT_MS)||18e5,vR=Number(process.env.BOSTRAT_SUBTASK_POLL_MS)||3e3,Kr=F.join(We,"worktrees"),Wr=[ns,ti],re=new Map;var zr=t=>{let e=new Map([...re.values()].filter(r=>r.trackId===t).map(r=>[r.id,r])),n=xe?et(xe,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)},Gu=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 TR(t,e,n){let r=await W(),o=et(r,t);o&&(o.subtasks=o.subtasks||{},o.subtasks[e]={...o.subtasks[e]||{},...n},await z())}var Gr=t=>TR(t.trackId,t.id,Gu(t));async function qr(t,e){t.step=e,await Gr(t)}function AR(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 ER(t){return!!t&&!t.finishedAt&&!t.canceled&&t.step==="working"&&!!t.box&&!!t.dir&&!!t.base&&!!t.branch}async function _R(t,e,n){if(!ER(n)||re.has(e)||!await Z(n.box).catch(()=>null))return!1;try{await L.access(n.dir)}catch{return!1}let o=await Z(t.leadBox).catch(()=>null);if(!o?.cwd)return!1;let s={...n,id:e,leadBox:t.leadBox,step:"working",canceled:!1,forceMerge:!1};return re.set(e,s),nb(s,o).catch(a=>tb(s,a)),!0}async function Zy(){let t=await W(),e=!1;for(let n of t.tracks||[]){for(let[r,o]of Object.entries(n.subtasks||{})){if(re.has(r))continue;if(await _R(n,r,o)){e=!0,ct(n,"subtask-resumed",{box:o.box,agent:o.agent,task:o.task,subtaskId:r,resumedStep:o.step}),await U({flow:"subtask",phase:"resumed",trackId:n.id,subtaskId:r,box:o.box,branch:o.branch}).catch(()=>{});continue}let s=o.step,a=AR(o);if(!a)continue;e=!0,n.subtasks[r]=a;let i=n.boxes.find(c=>c.subtaskId===r);i&&!Yn.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 U({flow:"subtask",phase:"reconciled",trackId:n.id,subtaskId:r,outcome:a.step,recovered:a.recovered,box:o.box,branch:o.branch}).catch(()=>{}),await py(n,o.box,o.dir)}for(let r of n.boxes){if(r.role!=="subtask"||Yn.has(r.state)||re.has(r.subtaskId)||n.subtasks?.[r.subtaskId])continue;e=!0,r.state="canceled";let o=r.subtaskId?F.join(Kr,`${n.id}-${r.subtaskId}`):null;await U({flow:"subtask",phase:"reaped-legacy-orphan",trackId:n.id,subtaskId:r.subtaskId,box:r.name}).catch(()=>{}),await py(n,r.name,o)}}return e=await IR(t)||e,e&&await z(),e}async function py(t,e,n){if(e&&await ce(e).catch(()=>{}),!n)return;if(t.workspace?.kind==="multi"){for(let o of Dt(t.workspace)){let s=F.join(n,o.name);await Xt(o.base,s).then(()=>!0,()=>!1)||await Xt(o.dir,s).catch(()=>{})}await L.rm(n,{recursive:!0,force:!0}).catch(()=>{});return}let r=await Z(t.leadBox).catch(()=>null);r?.cwd&&await Xt(r.cwd,n).catch(()=>{})}async function IR(t){let e;try{e=await L.readdir(Kr)}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 re.values())o.dir&&n.add(F.basename(o.dir));let r=!1;for(let o of e){if(n.has(o))continue;let s=F.join(Kr,o),a=(t.tracks||[]).find(i=>o.startsWith(`${i.id}-`));if(a?.workspace?.kind==="multi"){for(let i of Dt(a.workspace))await Xt(i.base,F.join(s,i.name)).catch(()=>{});await L.rm(s,{recursive:!0,force:!0}).catch(()=>{})}else{let i=a?await Z(a.leadBox).catch(()=>null):null;i?.cwd?await Xt(i.cwd,s).catch(()=>{}):await L.rm(s,{recursive:!0,force:!0}).catch(()=>{})}await U({flow:"subtask",phase:"swept-orphan-worktree",dir:s}).catch(()=>{}),r=!0}return r}var Vu=t=>t.trim().replace(/\s+/g,"-").replace(/[^A-Za-z0-9._-]/g,"").toLowerCase()||"track",is=class extends Error{};async function Qy(t,{agent:e,task:n,skipPermissions:r=!0,reason:o=null}={}){if(typeof n!="string"||!Mr(n))throw new Error("task (string) required");let s=await si(e,r),a=et(await W(),t);if(!a)throw new Error(`track "${t}" not found`);let i=await Z(a.leadBox);if(!i)throw new Error(`lead box "${a.leadBox}" is not running`);if(!i.cwd)throw new Error("can't resolve the lead's working directory");let c=a.workspace?.kind==="multi",l=null;if(c){let d=Dt(a.workspace);if(!d.length)throw new Error("this multi-repo Track has no tagged repos to delegate into");for(let p of d)if(!p?.dir||!await Na(p.dir))throw new Error(`tagged checkout "${p?.name??"?"}" is missing from the lead workspace (${p?.dir??"no dir"})`);l=d.map(p=>({name:p.name,leadDir:p.dir,base:null}))}else if(!await Na(i.cwd))throw new Error(`subtask delegation needs the lead's workspace to be a git repo (got ${i.cwd})`);let u=cs().slice(0,8),f={id:u,trackId:t,trackName:a.name,agent:e,task:Mr(n),skipPermissions:r,reason:wy(o),step:"snapshot",leadBox:a.leadBox,box:null,branch:`bostrat/${Vu(a.name)}-${u}`,base:null,repos:l,dir:F.join(Kr,`${t}-${u}`),stat:null,conflicts:[],applied:null,startedAt:Date.now(),finishedAt:null,error:null,canceled:!1,forceMerge:!1};return re.set(u,f),await Gr(f),RR(f,i,s).catch(d=>tb(f,d)),f}async function tb(t,e){t.failedStep=t.step,t.error=e instanceof is?"canceled":e?.message||String(e),t.step=e instanceof is?"canceled":"error",t.finishedAt=Date.now(),await Ju(t).catch(()=>{}),await Gr(t).catch(()=>{}),await MR(t).catch(()=>{})}function zu(t){let e=re.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 eb(t){let e=re.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 RR(t,e,n){if(await qr(t,"snapshot"),t.repos){for(let s of t.repos)s.base=await Go(s.leadDir,`bostrat: snapshot for subtask ${t.id}`);await Gr(t)}else t.base=await Go(e.cwd,`bostrat: snapshot for subtask ${t.id}`);if(await qr(t,"spawn"),await L.mkdir(Kr,{recursive:!0}),t.repos){await L.mkdir(t.dir,{recursive:!0});for(let a of t.repos)await Nr(a.leadDir,F.join(t.dir,a.name),t.branch,a.base);let s=Dt((et(await W(),t.trackId)||{}).workspace);await ci(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 Nr(e.cwd,t.dir,t.branch,t.base),await Pu(t.dir);await $R(t,e);let r=t.trackName.slice(0,38).trim(),o=await $n(`${r} ${t.agent} sub ${t.id}`,t.dir,{agentCmd:n,agent:t.agent});t.box=o.name,await OR(t,o),await gy(t.trackId,o.name,CI(t.task,!!t.repos),{kind:"subtask"}),await nb(t,e)}async function nb(t,e){await qr(t,"working");let n=F.join(t.dir,ti);if(await PR(n,t),t.canceled)throw new is;if(await qr(t,"merge"),t.repos){let o={files:0,insertions:0,deletions:0},s=[],a=[],i=!0;for(let c of t.repos){let l=F.join(t.dir,c.name);await Wl(l,`bostrat: subtask ${t.id} result (auto-committed)`),c.stat=await Vl(c.leadDir,c.base,t.branch,Wr),c.files=await zl(c.leadDir,c.base,t.branch,Wr);let u=await Jl(c.leadDir,c.base,t.branch,Wr);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=>F.join(c.name,f))),a.push(...c.conflicts.map(f=>f.startsWith("patch did not apply cleanly")?`${c.name}: ${f}`:F.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 Vl(e.cwd,t.base,t.branch,Wr),t.files=await zl(e.cwd,t.base,t.branch,Wr);let o=await Jl(e.cwd,t.base,t.branch,Wr);t.applied=o.applied,t.conflicts=o.conflicts}await Gr(t);let r=await NR(t,e,n);await qr(t,"resume"),await gy(t.trackId,t.leadBox,MI(t.task,t.stat,t.conflicts,r),{kind:"subtask-resume"}),await qr(t,"reap"),await Ju(t),t.step="done",t.finishedAt=Date.now(),await CR(t),await Gr(t)}async function $R(t,e){let n=F.join(t.dir,F.dirname(ns));await L.mkdir(n,{recursive:!0});let r="";try{r=await Df(e.cwd,e.sessionId)}catch{}if(!r)try{r=await L.readFile(F.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}
|
|
372
|
+
${a}`}async function td(t){if(t.archivedAt)return!1;let e=t.boxes.find(o=>o.name===t.leadBox);if(!e||or.has(e.state)||await nt(e.name))return!1;let n=_y(e.agent,e.sessionId),r=await Ys(n);if(r===pr)return dt(t,"rehydrate-failed",{box:e.name,agent:e.agent,reason:`agent "${e.agent}" is not installed on this box`}),!1;r===mr&&console.warn(`[track] ${e.name}: could not confirm "${e.agent}" on PATH \u2014 relaunching anyway (launch verifier will catch a real miss)`);try{let o=await Bn(e.name,e.cwd,{agentCmd:n,agent:e.agent,resumeSessionId:e.sessionId,pinned:e.pinned===!0});return e.hookTokenHash=uo(o.hookToken),aa({cwd:e.cwd,sessionName:e.name,agent:e.agent,agentCmd:n}),e.state="live",dt(t,"rehydrated",{box:e.name,agent:e.agent}),!0}catch(o){return dt(t,"rehydrate-failed",{box:e.name,agent:e.agent,reason:String(o?.message||o)}),!1}}async function ob(t){let e=await H(),n=tt(e,t);if(!n)throw new Error(`track "${t}" not found`);if(!n.archivedAt)throw new Error(`track "${t}" is not archived`);return delete n.archivedAt,dt(n,"unarchived",{}),await td(n),await X(),An(n)}async function sb(){let t=await H(),e=new Set((await Re()).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 fe(a.name).catch(()=>{}),dt(s,"orphan-reaped",{box:a.name,agent:a.agent}),r++,o=!0),!or.has(a.state)&&a.state!=="stopped"&&(a.state="stopped",o=!0);return o&&await X(),r}async function ab(t){let e=await H(),n=tt(e,t);if(!n)throw new Error(`track "${t}" not found`);if(n.archivedAt)throw new Error(`track "${t}" is archived \u2014 restore it before restarting the lead`);let r=n.boxes.find(a=>a.name===n.leadBox);if(!r)throw new Error(`track "${t}" has no lead box`);if(or.has(r.state))throw new Error("lead box is terminal \u2014 nothing to restart");if(await nt(n.leadBox)){let a=await mt(n.leadBox,{lines:Tt}).catch(()=>"");if(oa(r.agent,a))throw new Error("lead is mid-turn \u2014 retry when it goes idle");await fe(n.leadBox),r.state="stopped"}let s=await td(n);if(await X(),!s)throw new Error("rehydrate failed \u2014 see the track's rehydrate-failed event");return An(n)}var ib=(t,e,n)=>({name:t.name,agent:t.agent,role:e,state:n,cwd:t.cwd,sessionId:t.sessionId,pinned:t.pinned===!0,hookTokenHash:uo(t.hookToken),createdAt:Date.now()});function cb(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 ps=new Map,gs=t=>ps.get(t)||(Ae?tt(Ae,t)?.handoff:null)||null;async function ui(t,e){let n=await H(),r=tt(n,t);r&&(r.handoff={...r.handoff||{},...e},await X())}function UR(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 lb(){let t=await H(),e=!1;for(let n of t.tracks||[]){let r=n.handoff,o=UR(n);if(!o)continue;e=!0;let s=!!r.toBox&&r.toBox===r.fromBox;if(s&&o.step==="error"){let c=await nt(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=cb(l),d=f[f.length-1],p=d.agent,m=d.sessionId??null;d.endedAt=u,r.reason&&(d.endReason=r.reason);let w=c.agent==="claude"&&!!c.sessionId;f.push({agent:r.toAgent,sessionId:c.sessionId||null,pinned:w,startedAt:u,endedAt:null}),l.agent=r.toAgent,l.sessionId=c.sessionId||null,l.pinned=w,l.state="live",n.pendingHandoffHydrate={artifact:mi,at:u},dt(n,"lead-handoff",{from:r.fromBox,to:r.fromBox,fromAgent:p,toAgent:r.toAgent,fromSessionId:m,toSessionId:c.sessionId||null,epoch:f.length-1,singleBox:!0,skipPermissions:r.skipPermissions,reason:r.reason??null,handoffStartedAt:r.startedAt,recovered:!0}),n.handoff={...r,step:"done",finishedAt:u,recovered:"swap adopted from live tmux tags after restart"},await G({flow:"lead-handoff",phase:"reconciled",trackId:n.id,outcome:"done",recovered:n.handoff.recovered,fromBox:r.fromBox,toBox:r.toBox}).catch(()=>{});continue}}let a=!s&&o.recovered==="rolled-back"&&r.toBox&&r.toBox!==n.leadBox?r.toBox:null,i=!s&&o.step==="done"?r.fromBox:null;n.handoff=o,await G({flow:"lead-handoff",phase:"reconciled",trackId:n.id,outcome:o.step,recovered:o.recovered,fromBox:o.fromBox,toBox:o.toBox}).catch(()=>{}),i&&await fe(i).catch(()=>{}),a&&await fe(a).catch(()=>{})}return e&&await X(),e}async function ed(t,e,n=!0,r=null){let o=ps.get(t);if(o&&!o.finishedAt)throw new Error("a lead handoff is already running for this Track");let s=tt(await H(),t);if(!s)throw new Error(`track "${t}" not found`);wi(t,"handoff");let a=await pi(e,n),i=await nt(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:Iy(r),toBox:s.leadBox,startedAt:Date.now(),finishedAt:null,error:null};return ps.set(t,c),await ui(t,{step:c.step,fromBox:c.fromBox,toAgent:e,toBox:c.toBox,skipPermissions:n,startedAt:c.startedAt,finishedAt:null,error:null}),HR(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 ui(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 HR(t,e,n,r,o){let s=async(_,$={})=>{t.step=_,console.log(`[lead-handoff ${t.trackId}] step \u2192 ${_}`),await ui(t.trackId,{step:_,toBox:t.toBox}),await G({flow:"lead-handoff",phase:_,trackId:t.trackId,fromBox:t.fromBox,toBox:t.toBox,toAgent:o,sessionName:$.sessionName,...$})};console.log(`[lead-handoff ${t.trackId}] ${t.fromBox}: ${n.agent} \u2192 ${o} IN PLACE`);let a=await H(),i=tt(a,t.trackId)?.boxes.find(_=>_.name===t.fromBox)||null,c=i?.agent||n.agent,l=i?.sessionId??n.sessionId??null,u=i?.pinned===!0;await s("checkpoint",{sessionName:t.fromBox}),await Fp(t.fromBox),await s("swap",{cwd:n.cwd,agentCmd:r});let f=await xl(t.fromBox,{agentCmd:r,agent:o});await s("hydrate",{sessionName:t.fromBox}),await sn({cwd:n.cwd,sessionId:f.sessionId,sessionName:t.fromBox,agent:o,agentCmd:ws(o)});let d=await mt(t.fromBox,{lines:Tt}).catch(()=>"");if(!sa(o,d)){await s("rollback",{sessionName:t.fromBox,fromAgent:c});let _=_y(c,l,{skipPermissions:t.skipPermissions});await xl(t.fromBox,{agentCmd:_,agent:c,resumeSessionId:l,pinned:u});let $=await H(),et=tt($,t.trackId);if(et){let I=et.boxes.find(C=>C.name===t.fromBox);I&&(I.state="live"),dt(et,"lead-handoff-failed",{box:t.fromBox,fromAgent:c,toAgent:o,reason:t.reason,error:`incoming ${o} CLI never drew its composer`,rolledBack:!0,handoffStartedAt:t.startedAt}),await X()}throw new Error(`incoming ${o} CLI never drew its composer \u2014 rolled back to ${c} (--resume, context intact)`)}await s("commit");let p=await H(),m=tt(p,t.trackId);if(!m)throw new Error(`track "${t.trackId}" not found during handoff commit \u2014 was it deleted or archived mid-flight?`);let w=m.boxes.find(_=>_.name===t.fromBox);if(!w)throw new Error(`lead box row "${t.fromBox}" missing during handoff commit`);let y=Date.now(),b=cb(w),S=b[b.length-1];S.endedAt=y,t.reason&&(S.endReason=t.reason),b.push({agent:o,sessionId:f.sessionId,pinned:f.pinned,startedAt:y,endedAt:null}),w.agent=o,w.sessionId=f.sessionId,w.pinned=f.pinned,w.state="live",w.role="lead",m.pendingHandoffHydrate={artifact:mi,at:y},dt(m,"lead-handoff",{from:t.fromBox,to:t.fromBox,fromAgent:c,toAgent:o,fromSessionId:l,toSessionId:f.sessionId,epoch:b.length-1,singleBox:!0,forced:f.forced||void 0,skipPermissions:t.skipPermissions,reason:t.reason,handoffStartedAt:t.startedAt}),await X(),await s("done",{sessionName:t.fromBox}),await ui(t.trackId,{finishedAt:Date.now(),error:null}),t.finishedAt=Date.now()}var WR=Number(process.env.BOSTRAT_SUBTASK_TIMEOUT_MS)||18e5,qR=Number(process.env.BOSTRAT_SUBTASK_POLL_MS)||3e3,Yr=U.join(ze,"worktrees"),zr=[cs,li],ie=new Map;var to=t=>{let e=new Map([...ie.values()].filter(r=>r.trackId===t).map(r=>[r.id,r])),n=Ae?tt(Ae,t):null;if(n?.subtasks)for(let[r,o]of Object.entries(n.subtasks))e.has(r)||e.set(r,o);return[...e.values()].sort((r,o)=>r.startedAt-o.startedAt)},nd=t=>({id:t.id,trackId:t.trackId,trackName:t.trackName,agent:t.agent,task:t.task,skipPermissions:t.skipPermissions,reason:t.reason||null,step:t.step,failedStep:t.failedStep||null,box:t.box,branch:t.branch,base:t.base,repos:t.repos||null,dir:t.dir,stat:t.stat,files:t.files||null,conflicts:t.conflicts||[],applied:t.applied,error:t.error,canceled:t.canceled,startedAt:t.startedAt,finishedAt:t.finishedAt});async function KR(t,e,n){let r=await H(),o=tt(r,t);o&&(o.subtasks=o.subtasks||{},o.subtasks[e]={...o.subtasks[e]||{},...n},await X())}var Zr=t=>KR(t.trackId,t.id,nd(t));async function Jr(t,e){t.step=e,await Zr(t)}function GR(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 VR(t){return!!t&&!t.finishedAt&&!t.canceled&&t.step==="working"&&!!t.box&&!!t.dir&&!!t.base&&!!t.branch}async function zR(t,e,n){if(!VR(n)||ie.has(e)||!await nt(n.box).catch(()=>null))return!1;try{await F.access(n.dir)}catch{return!1}let o=await nt(t.leadBox).catch(()=>null);if(!o?.cwd)return!1;let s={...n,id:e,leadBox:t.leadBox,step:"working",canceled:!1,forceMerge:!1};return ie.set(e,s),mb(s,o).catch(a=>fb(s,a)),!0}async function ub(){let t=await H(),e=!1;for(let n of t.tracks||[]){for(let[r,o]of Object.entries(n.subtasks||{})){if(ie.has(r))continue;if(await zR(n,r,o)){e=!0,dt(n,"subtask-resumed",{box:o.box,agent:o.agent,task:o.task,subtaskId:r,resumedStep:o.step}),await G({flow:"subtask",phase:"resumed",trackId:n.id,subtaskId:r,box:o.box,branch:o.branch}).catch(()=>{});continue}let s=o.step,a=GR(o);if(!a)continue;e=!0,n.subtasks[r]=a;let i=n.boxes.find(c=>c.subtaskId===r);i&&!or.has(i.state)&&(i.state="canceled"),dt(n,"subtask-failed",{from:o.box,fromAgent:o.agent,task:o.task,subtaskId:r,failedStep:s,error:a.error,recovered:a.recovered,durationMs:o.startedAt?a.finishedAt-o.startedAt:null}),await G({flow:"subtask",phase:"reconciled",trackId:n.id,subtaskId:r,outcome:a.step,recovered:a.recovered,box:o.box,branch:o.branch}).catch(()=>{}),await Ay(n,o.box,o.dir)}for(let r of n.boxes){if(r.role!=="subtask"||or.has(r.state)||ie.has(r.subtaskId)||n.subtasks?.[r.subtaskId])continue;e=!0,r.state="canceled";let o=r.subtaskId?U.join(Yr,`${n.id}-${r.subtaskId}`):null;await G({flow:"subtask",phase:"reaped-legacy-orphan",trackId:n.id,subtaskId:r.subtaskId,box:r.name}).catch(()=>{}),await Ay(n,r.name,o)}}return e=await JR(t)||e,e&&await X(),e}async function Ay(t,e,n){if(e&&await fe(e).catch(()=>{}),!n)return;if(t.workspace?.kind==="multi"){for(let o of qt(t.workspace)){let s=U.join(n,o.name);await ee(o.base,s).then(()=>!0,()=>!1)||await ee(o.dir,s).catch(()=>{})}await F.rm(n,{recursive:!0,force:!0}).catch(()=>{});return}let r=await nt(t.leadBox).catch(()=>null);r?.cwd&&await ee(r.cwd,n).catch(()=>{})}async function JR(t){let e;try{e=await F.readdir(Yr)}catch{return!1}let n=new Set;for(let o of t.tracks||[]){for(let s of Object.keys(o.subtasks||{}))n.add(`${o.id}-${s}`);for(let s of o.boxes||[])s.subtaskId&&n.add(`${o.id}-${s.subtaskId}`)}for(let o of ie.values())o.dir&&n.add(U.basename(o.dir));let r=!1;for(let o of e){if(n.has(o))continue;let s=U.join(Yr,o),a=(t.tracks||[]).find(i=>o.startsWith(`${i.id}-`));if(a?.workspace?.kind==="multi"){for(let i of qt(a.workspace))await ee(i.base,U.join(s,i.name)).catch(()=>{});await F.rm(s,{recursive:!0,force:!0}).catch(()=>{})}else{let i=a?await nt(a.leadBox).catch(()=>null):null;i?.cwd?await ee(i.cwd,s).catch(()=>{}):await F.rm(s,{recursive:!0,force:!0}).catch(()=>{})}await G({flow:"subtask",phase:"swept-orphan-worktree",dir:s}).catch(()=>{}),r=!0}return r}var rd=t=>t.trim().replace(/\s+/g,"-").replace(/[^A-Za-z0-9._-]/g,"").toLowerCase()||"track",ms=class extends Error{};async function db(t,{agent:e,task:n,skipPermissions:r=!0,reason:o=null}={}){if(typeof n!="string"||!jr(n))throw new Error("task (string) required");let s=await pi(e,r),a=tt(await H(),t);if(!a)throw new Error(`track "${t}" not found`);let i=await nt(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=qt(a.workspace);if(!d.length)throw new Error("this multi-repo Track has no tagged repos to delegate into");for(let p of d)if(!p?.dir||!await Wa(p.dir))throw new Error(`tagged checkout "${p?.name??"?"}" is missing from the lead workspace (${p?.dir??"no dir"})`);l=d.map(p=>({name:p.name,leadDir:p.dir,base:null}))}else if(!await Wa(i.cwd))throw new Error(`subtask delegation needs the lead's workspace to be a git repo (got ${i.cwd})`);let u=hs().slice(0,8),f={id:u,trackId:t,trackName:a.name,agent:e,task:jr(n),skipPermissions:r,reason:Iy(o),step:"snapshot",leadBox:a.leadBox,box:null,branch:`bostrat/${rd(a.name)}-${u}`,base:null,repos:l,dir:U.join(Yr,`${t}-${u}`),stat:null,conflicts:[],applied:null,startedAt:Date.now(),finishedAt:null,error:null,canceled:!1,forceMerge:!1};return ie.set(u,f),await Zr(f),XR(f,i,s).catch(d=>fb(f,d)),f}async function fb(t,e){t.failedStep=t.step,t.error=e instanceof ms?"canceled":e?.message||String(e),t.step=e instanceof ms?"canceled":"error",t.finishedAt=Date.now(),await sd(t).catch(()=>{}),await Zr(t).catch(()=>{}),await t$(t).catch(()=>{})}function od(t){let e=ie.get(t);if(!e)throw new Error(`subtask "${t}" not found`);if(e.finishedAt)throw new Error("subtask already finished");return e.canceled=!0,e}function pb(t){let e=ie.get(t);if(!e)throw new Error(`subtask "${t}" not found`);if(e.finishedAt)throw new Error("subtask already finished");if(e.step!=="working")throw new Error(`subtask is not waiting to merge (step: ${e.step})`);return e.forceMerge=!0,e}async function XR(t,e,n){if(await Jr(t,"snapshot"),t.repos){for(let s of t.repos)s.base=await Zo(s.leadDir,`bostrat: snapshot for subtask ${t.id}`);await Zr(t)}else t.base=await Zo(e.cwd,`bostrat: snapshot for subtask ${t.id}`);if(await Jr(t,"spawn"),await F.mkdir(Yr,{recursive:!0}),t.repos){await F.mkdir(t.dir,{recursive:!0});for(let a of t.repos)await Lr(a.leadDir,U.join(t.dir,a.name),t.branch,a.base);let s=qt((tt(await H(),t.trackId)||{}).workspace);await gi(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 Lr(e.cwd,t.dir,t.branch,t.base),await Gu(t.dir);await YR(t,e);let r=t.trackName.slice(0,38).trim(),o=await Bn(`${r} ${t.agent} sub ${t.id}`,t.dir,{agentCmd:n,agent:t.agent});t.box=o.name,await ZR(t,o),await Ry(t.trackId,o.name,YI(t.task,!!t.repos),{kind:"subtask"}),await mb(t,e)}async function mb(t,e){await Jr(t,"working");let n=U.join(t.dir,li);if(await n$(n,t),t.canceled)throw new ms;if(await Jr(t,"merge"),t.repos){let o={files:0,insertions:0,deletions:0},s=[],a=[],i=!0;for(let c of t.repos){let l=U.join(t.dir,c.name);await tu(l,`bostrat: subtask ${t.id} result (auto-committed)`),c.stat=await ou(c.leadDir,c.base,t.branch,zr),c.files=await su(c.leadDir,c.base,t.branch,zr);let u=await au(c.leadDir,c.base,t.branch,zr);c.applied=u.applied,c.conflicts=u.conflicts??[],o.files+=c.stat.files,o.insertions+=c.stat.insertions,o.deletions+=c.stat.deletions,s.push(...c.files.map(f=>U.join(c.name,f))),a.push(...c.conflicts.map(f=>f.startsWith("patch did not apply cleanly")?`${c.name}: ${f}`:U.join(c.name,f))),u.applied||(i=!1)}t.stat=o,t.files=s,t.conflicts=a,t.applied=i}else{await tu(t.dir,`bostrat: subtask ${t.id} result (auto-committed)`),t.stat=await ou(e.cwd,t.base,t.branch,zr),t.files=await su(e.cwd,t.base,t.branch,zr);let o=await au(e.cwd,t.base,t.branch,zr);t.applied=o.applied,t.conflicts=o.conflicts}await Zr(t);let r=await e$(t,e,n);await Jr(t,"resume"),await Ry(t.trackId,t.leadBox,ZI(t.task,t.stat,t.conflicts,r),{kind:"subtask-resume"}),await Jr(t,"reap"),await sd(t),t.step="done",t.finishedAt=Date.now(),await QR(t),await Zr(t)}async function YR(t,e){let n=U.join(t.dir,U.dirname(cs));await F.mkdir(n,{recursive:!0});let r="";try{r=await Jf(e.cwd,e.sessionId)}catch{}if(!r)try{r=await F.readFile(U.join(e.cwd,".bostrat/handoff/latest.md"),"utf8")}catch{r="_(no prior conversation digest available \u2014 rely on the working tree.)_"}let o=`# Lead context for subtask ${t.id}
|
|
375
373
|
|
|
376
374
|
**Delegated task:** ${t.task}
|
|
377
375
|
|
|
@@ -382,50 +380,50 @@ ${a}`}async function qu(t){if(t.archivedAt)return!1;let e=t.boxes.find(r=>r.name
|
|
|
382
380
|
## Conversation so far (lead \u2192 you)
|
|
383
381
|
|
|
384
382
|
${r}
|
|
385
|
-
`;await
|
|
386
|
-
${n}`}},{type:"section",text:{type:"mrkdwn",text:r}},{type:"context",elements:[{type:"mrkdwn",text:`<${
|
|
383
|
+
`;await F.writeFile(U.join(t.dir,cs),o)}async function ZR(t,e){let n=await H(),r=tt(n,t.trackId);r&&(r.boxes.push({name:e.name,agent:e.agent,role:"subtask",state:"live",cwd:e.cwd,sessionId:e.sessionId,hookTokenHash:uo(e.hookToken),createdAt:Date.now(),subtaskId:t.id,branch:t.branch,task:t.task}),dt(r,"subtask-delegated",{to:e.name,toAgent:t.agent,task:t.task,subtaskId:t.id,skipPermissions:t.skipPermissions,reason:t.reason}),r.subtasks=r.subtasks||{},r.subtasks[t.id]={...r.subtasks[t.id]||{},...nd(t)},await X())}async function QR(t){let e=await H(),n=tt(e,t.trackId);if(!n)return;let r=n.boxes.find(o=>o.subtaskId===t.id);r&&(r.state=t.conflicts.length?"conflict":"merged",r.stat=t.stat),dt(n,"subtask-merged",{from:t.box,fromAgent:t.agent,task:t.task,subtaskId:t.id,stat:t.stat,conflicts:t.conflicts.length,files:t.files,durationMs:t.finishedAt?t.finishedAt-t.startedAt:null}),await X()}async function t$(t){let e=await H(),n=tt(e,t.trackId);if(!n)return;let r=t.canceled||t.error==="canceled",o=n.boxes.find(s=>s.subtaskId===t.id);o&&(o.state=r?"canceled":"failed"),dt(n,r?"subtask-canceled":"subtask-failed",{from:t.box,fromAgent:t.agent,task:t.task,subtaskId:t.id,failedStep:t.failedStep||t.step,error:t.error,durationMs:t.finishedAt?t.finishedAt-t.startedAt:null}),n.subtasks=n.subtasks||{},n.subtasks[t.id]={...n.subtasks[t.id]||{},...nd(t)},await X()}async function e$(t,e,n){let r=`.bostrat/handoff/subtask-${t.id}.md`;try{let o=U.join(e.cwd,r);await F.mkdir(U.dirname(o),{recursive:!0}),await F.copyFile(n,o)}catch{}return r}async function sd(t){if(t.box&&await fe(t.box).catch(()=>{}),t.repos&&t.dir){for(let e of t.repos)await ee(e.leadDir,U.join(t.dir,e.name)).catch(()=>{});await F.rm(t.dir,{recursive:!0,force:!0}).catch(()=>{})}else if(t.dir){let n=(await nt(t.leadBox).catch(()=>null))?.cwd;n&&await ee(n,t.dir).catch(()=>{})}}async function n$(t,e){let n=(e.startedAt||Date.now())+WR;for(;;){if(e.canceled||e.forceMerge)return;try{await F.access(t);return}catch{}if(Date.now()>n)throw new Error("subtask timed out without signaling completion (no result digest written)");await En(qR)}}function En(t){return new Promise(e=>setTimeout(e,t))}import ys from"node:fs/promises";import ki from"node:path";import i$ from"node:os";import r$ from"ws";var hb=()=>process.env.BOSTRAT_SLACK_API_BASE||"https://slack.com/api";async function yi(t,e,n={}){let r=await fetch(`${hb()}/${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 ad=(t,e)=>yi("chat.postMessage",t,e),eo=(t,e)=>yi("chat.update",t,e);async function Kt(t,e){await fetch(t,{method:"POST",headers:{"content-type":"application/json; charset=utf-8"},body:JSON.stringify(e)}).catch(()=>{})}async function o$(t,e,n={}){let r=await fetch(`${hb()}/${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 wb(t,{filename:e,buffer:n,title:r,channelId:o,threadTs:s,initialComment:a}){let{upload_url:i,file_id:c}=await o$("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 yi("files.completeUploadExternal",t,{files:[{id:c,title:r}],channel_id:o,thread_ts:s,...a?{initial_comment:a}:{}})}var s$=2200;function gb({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 yi("apps.connections.open",u))}catch(u){return n(`connections.open failed: ${u?.message||u}`),c()}o=new r$(l),o.on("message",u=>{let f;try{f=JSON.parse(u)}catch{return}if(f.type==="hello"){a=1e3,n("socket connected");return}if(f.type==="disconnect"){try{o.close()}catch{}return}if(!f.envelope_id)return;let d=w=>{try{o.send(JSON.stringify(w?{envelope_id:f.envelope_id,payload:w}:{envelope_id:f.envelope_id}))}catch{}},p=!1,m=setTimeout(()=>{p=!0,d()},s$);Promise.resolve().then(()=>e(f)).then(w=>{clearTimeout(m),!p&&(p=!0,d(f.accepts_response_payload?w:void 0))},w=>{clearTimeout(m),p||(p=!0,d()),n(`envelope handler failed: ${w?.message||w}`)})}),o.on("close",c),o.on("error",u=>{n(`socket error: ${u?.message||u}`);try{o.close()}catch{}})}function c(){if(r||s)return;let l=a;a=Math.min(a*2,6e4),s=setTimeout(()=>{s=null,i()},l)}return i(),()=>{r=!0,s&&clearTimeout(s);try{o?.close()}catch{}}}var kb=()=>process.env.BOSTRAT_STATE_DIR||ki.join(process.env.HOME||i$.tmpdir(),".bostrat"),dd=()=>ki.join(kb(),"slack.json"),bi=null,id=null,cd=Promise.resolve();async function pd(){return bi||(id??=ys.readFile(dd(),"utf8").then(t=>JSON.parse(t)?.threads??{}).catch(()=>({})).then(t=>bi=t).finally(()=>id=null),id)}async function yb(){let t=JSON.stringify({threads:bi},null,2),e=`${dd()}.${process.pid}.tmp`;await ys.mkdir(kb(),{recursive:!0}),await ys.writeFile(e,t),await ys.rename(e,dd())}async function Je(){cd=cd.then(yb,yb),await cd.catch(()=>{})}async function md(t){return(await pd())[t]??null}async function c$(t,e){let n=await pd();for(let[r,o]of Object.entries(n))if(o.channel===t&&o.ts===e)return{trackId:r,rec:o};return null}var Z=t=>String(t??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");function bs(t,e){let n=String(t??"").trim();return n.length<=e?n:`${n.slice(0,e-1).trimEnd()}\u2026`}function l$(t,e=500){let n=String(t??"").trim().split(/\n\s*\n/)[0]??"";return bs(n.replace(/\n+/g," "),e)}function xb(t){let e=`*${Z(t.name)}*${t.archivedAt?" \xB7 _archived_":""}`,n=`${Z(t.repoLabel)} \xB7 \`${Z(t.branch||"?")}\` \xB7 lead *${Z(t.lead)}*`,r=t.kickoff?`> ${Z(bs(t.kickoff,300))}`:"_no kickoff yet \u2014 reply in this thread to steer the lead_";return[{type:"section",text:{type:"mrkdwn",text:`${e}
|
|
384
|
+
${n}`}},{type:"section",text:{type:"mrkdwn",text:r}},{type:"context",elements:[{type:"mrkdwn",text:`<${Oa(t.trackId)}|open the Track> \xB7 reachable on the operator's private network only`}]}]}async function fd(t,e){await eo(t.botToken,{channel:e.channel,ts:e.ts,text:`Track: ${e.name}`,blocks:xb(e)})}async function ar(t,e,n,r=void 0){return ad(t.botToken,{channel:e.channel,thread_ts:e.ts,text:n,...r?{blocks:r}:{},unfurl_links:!1})}var bb=new Set;function sr(t){bb.add(t),t.catch(()=>{}).finally(()=>bb.delete(t))}var pt=t=>({response_type:"ephemeral",text:t}),u$="*/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._",d$=["claude","cursor","codex"];function ld(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 ud=(t,e)=>pt(`No live Track matches \u201C${Z(e)}\u201D. Live: ${t.map(n=>`\`${Z(n.name)}\``).join(" \xB7 ")||"none"}`);async function Sb(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 Kt(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 Kt(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?ki.basename(c.base):"fresh workspace"),lead:"claude"},u=await ad(e.botToken,{channel:e.channel,text:`Track started: ${l.name}`,blocks:xb(l),unfurl_links:!1});l.ts=u.ts,(await pd())[i.id]=l,await Je(),await Kt(o,{...pt(`\u2713 *${Z(l.name)}* started \u2014 root message posted.`),replace_original:!0})}catch(a){await Kt(o,{...pt(`Start failed: ${a?.message||a}`),replace_original:!0})}}async function f$(t,e,{track:n,responseUrl:r}){let o=t.deps;try{await o.archiveTrack(n.id);let s=await md(n.id);s&&(s.archivedAt=Date.now(),await Je(),await fd(e,s).catch(()=>{})),await Kt(r,{...pt(`\u2713 *${Z(n.name)}* archived.`),replace_original:!0})}catch(s){await Kt(r,{...pt(`Archive failed: ${s?.message||s}`),replace_original:!0})}}async function p$(t,e,{track:n,agent:r,responseUrl:o}){try{await t.deps.startLeadHandoff(n.id,r),await Kt(o,{...pt(`\u2713 Handing *${Z(n.name)}* to ${Z(r)} \u2014 the root message updates when the baton lands.`),replace_original:!0})}catch(s){await Kt(o,{...pt(`Handoff failed: ${s?.message||s}`),replace_original:!0})}}async function m$(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 Kt(r,{...pt(`\u2713 Stopped *${Z(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 Kt(r,{...pt(`Stop failed: ${s?.message||s}`),replace_original:!0})}}function h$(t,e){let n=[...e.slice(0,99).map(r=>({text:{type:"plain_text",text:bs(`${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 *${Z(t)}*:`},accessory:{type:"static_select",action_id:"bostrat-start-repo",placeholder:{type:"plain_text",text:"Choose a repo"},options:n}}]}async function w$(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?ki.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 *${Z(s.name)}* \u2014 ${Z(i)}${a.branch?` \`${Z(a.branch)}\``:""} \xB7 lead ${Z(c)} \xB7 ${l}`}),o=e.length-n.length;return r.join(`
|
|
387
385
|
`)+(o?`
|
|
388
|
-
_${o} archived_`:"")}async function
|
|
389
|
-
\u2713 *${
|
|
390
|
-
\u2713 _answered_`}}]})}async function
|
|
391
|
-
_runs on the Track's branch; reachable on the operator's private network only_`}}]);return}case"mock.state.changed":return
|
|
392
|
-
`)){let r=n.trim();if(!r||r[0]!=="{")continue;let o;try{o=JSON.parse(r)}catch{continue}let s=o?.message?.usage;if(!s)continue;let a={input:s.input_tokens||0,output:s.output_tokens||0,cacheWrite:s.cache_creation_input_tokens||0,cacheRead:s.cache_read_input_tokens||0};if(a.input+a.output+a.cacheWrite+a.cacheRead===0)continue;let i=o.message?.id||o.uuid||null;i&&e.set(i,{messageId:i,tokens:a,model:o.message?.model||o.model||null})}return[...e.values()]}function p$(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 m$(t,e){return t?.transcript_path?t.transcript_path:e?.cwd&&e?.sessionId?Ae(e.cwd,e.sessionId):e?.cwd?dc(e.cwd):null}async function fb(t,e){let n=await e(t?.session_id,t?.cwd);if(!n?.track)return[];let{track:r,box:o}=n,s={boxId:o?.name||null,provider:"claude",source:"hook"},a=[],i=async c=>{a.push(await R(r,{...s,...c}))};switch(t?.hook_event_name){case"SessionStart":await i({type:"session.ready",actor:"system",payload:{sessionId:t.session_id}});break;case"UserPromptSubmit":await i({type:"turn.user",actor:"user",payload:{promptHead:String(t.prompt??t.user_prompt??"").slice(0,160)||null}});break;case"PostToolUse":await i({type:"tool.called",actor:"agent",payload:{tool:t.tool_name??null}});break;case"Stop":await h$(r,o,t,i);break;default:break}return a}async function h$(t,e,n,r){let o=[],s=m$(n,e);if(s)try{o=f$(await d$.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=p$(await D(t)),c=o.filter(u=>!i.has(u.messageId));if(c.length){let u=new Map;for(let f of c){let d=f.model||"unknown",p=u.get(d)||{tokens:{input:0,output:0,cacheWrite:0,cacheRead:0},messageIds:[]};for(let m of Object.keys(p.tokens))p.tokens[m]+=f.tokens[m];p.messageIds.push(f.messageId),u.set(d,p)}for(let[f,d]of u)await r({type:"cost.recorded",actor:"system",source:"transcript",payload:{model:f,usd:le(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 y$ from"node:fs";import sd from"node:fs/promises";import b$ from"node:http";import Xr from"node:path";import pi from"node:os";import{createRequire as k$}from"node:module";import ad from"ws";import w$ from"node:path";import g$ from"node:os";var pb=process.env.BOSTRAT_STATE_DIR||w$.join(process.env.HOME||g$.tmpdir(),".bostrat");function mb(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 hb(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 fi=(()=>{try{return k$(import.meta.url)("../package.json").version||null}catch{return null}})(),x$=process.env.BOSTRAT_STATE_DIR||Xr.join(process.env.HOME||pi.tmpdir(),".bostrat");function S$(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 v$({home:t=process.env.HOME||pi.homedir(),env:e=process.env}={}){let n=r=>{try{return y$.statSync(r),!0}catch{return!1}};return{claude:n(Xr.join(t,".claude",".credentials.json")),codex:n(Xr.join(e.CODEX_HOME||Xr.join(t,".codex"),"auth.json"))}}var _t=1,wb=1e3,T$=3e4,A$=6e4,E$=1500,_$=500,gb=100,I$=2e3,R$=25e3;function yb({url:t,token:e,nodeId:n=pi.hostname(),name:r=pi.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=hb(t);let c=a.replace(/^http/,"ws"),l=new Map,u=new Map,f=Xr.join(x$,"plane-link","cursor.json"),d={},p=new Map,m=null,g=!1,y=!1,b=wb,S=null,E=null,M=null,J=null,$=!1;async function j(){try{d=JSON.parse(await sd.readFile(f,"utf8"))}catch{d={}}}function v(){$=!0,!M&&(M=setTimeout(async()=>{if(M=null,!!$){$=!1;try{await sd.mkdir(Xr.dirname(f),{recursive:!0}),await sd.writeFile(f,JSON.stringify(d))}catch(O){i.error("[plane-link] cursor flush failed:",O?.message||O)}}},I$),M.unref?.())}function C(O){return!m||m.readyState!==ad.OPEN?!1:(m.send(JSON.stringify(O)),!0)}function q(O){for(let{trackId:I,event:A}of O)p.set(A.id,{trackId:I,seq:A.seq});return C({v:_t,ch:"events",type:"append",events:O})}async function rt(){let O;try{O=await o()}catch(I){i.error("[plane-link] backfill listTracks failed:",I?.message||I);return}C({v:_t,ch:"ctl",type:"tracks",ids:O.map(I=>I.id).filter(I=>typeof I=="string")});for(let I of O){if(!g)return;let A;try{A=await D(I)}catch{continue}let G=d[I.id]??0,K=A.filter(dt=>dt.seq>G&&typeof dt.id=="string");for(let dt=0;dt<K.length;dt+=gb){let yt=K.slice(dt,dt+gb).map(Ot=>({trackId:I.id,event:Ot}));if(!q(yt))return}}}function st(O){!g||!O?.trackId||typeof O.id!="string"||q([{trackId:O.trackId,event:O}])}let w=new Map;async function h(O){let{reqId:I,method:A,path:G,headers:K={},body:dt}=O,yt=Object.fromEntries(Object.entries(K).filter(([,Vt])=>Vt!=null)),Ot=new AbortController;w.set(I,Ot);try{if(O.host){await T(O,yt,Ot);return}let Vt=await fetch(a+G,{method:A,headers:yt,body:dt?Buffer.from(dt,"base64"):void 0,signal:Ot.signal}),Zt=Object.fromEntries(Vt.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:_t,ch:"control",type:"http_head",reqId:I,status:Vt.status,headers:Zt}),Vt.body){for await(let k of Vt.body)if(!C({v:_t,ch:"control",type:"http_data",reqId:I,data:Buffer.from(k).toString("base64")}))break}C({v:_t,ch:"control",type:"http_end",reqId:I,error:null})}finally{w.delete(I)}}function T(O,I,A){let{reqId:G,method:K,path:dt,body:yt}=O,Ot=new URL(a);return new Promise(Vt=>{let Zt=b$.request({host:Ot.hostname,port:Ot.port,method:K,path:dt,headers:{...I,host:O.host},signal:A.signal},k=>{let{connection:V,"keep-alive":er,"transfer-encoding":As,upgrade:nr,...Ni}=k.headers;C({v:_t,ch:"control",type:"http_head",reqId:G,status:k.statusCode||502,headers:Ni}),k.on("data",nx=>{C({v:_t,ch:"control",type:"http_data",reqId:G,data:Buffer.from(nx).toString("base64")})||k.destroy()}),k.on("end",()=>{C({v:_t,ch:"control",type:"http_end",reqId:G,error:null}),Vt()}),k.on("error",()=>{C({v:_t,ch:"control",type:"http_end",reqId:G,error:"view upstream error"}),Vt()})});Zt.on("error",k=>{C({v:_t,ch:"control",type:"http_end",reqId:G,error:k?.message||"view request failed"}),Vt()}),yt&&Zt.write(Buffer.from(yt,"base64")),Zt.end()})}function _(){if(y)return;m=new ad(t,{headers:{authorization:`Bearer ${e}`}}),m.on("open",async()=>{let I=null;try{I=await s?.()||null}catch{I=null}C({v:_t,ch:"ctl",type:"hello",nodeId:n,name:r,platform:process.platform,version:fi,providers:v$(),...I?{agents:I}:{}})}),m.on("message",I=>{let A;try{A=JSON.parse(I)}catch{return}if(A.ch==="ctl"&&A.type==="hello_ok"){g=!0,b=wb,S=null,i.log(`[plane-link] connected to plane as ${n}`),A.minNodeVersion&&fi&&S$(fi,A.minNodeVersion)&&i.error(`[plane-link] this node is bostrat-node ${fi}; the plane asks for \u2265${A.minNodeVersion} \u2014 update with: npm install -g bostrat-node`),J&&clearInterval(J),J=setInterval(()=>C({v:_t,ch:"ctl",type:"ping"}),R$),J.unref?.(),rt().catch(G=>i.error("[plane-link] backfill failed:",G?.message||G));return}if(A.ch==="events"&&A.type==="ack"&&Array.isArray(A.ids)){for(let G of A.ids){let K=p.get(G);K&&(p.delete(G),(d[K.trackId]??0)<K.seq&&(d[K.trackId]=K.seq))}v();return}if(A.ch==="control"&&A.type==="http_open"){h(A).catch(G=>C({v:_t,ch:"control",type:"http_end",reqId:A.reqId,error:G?.message||"tunnel error"}));return}if(A.ch==="control"&&A.type==="http_cancel"){w.get(A.reqId)?.abort();return}if(A.ch==="attach"&&A.type==="open"){let G=new ad(c+A.path,Array.isArray(A.protocols)?A.protocols:[],A.host?{headers:{host:A.host}}:void 0);l.set(A.streamId,G),u.set(A.streamId,[]),G.on("open",()=>{let dt=u.get(A.streamId)||[];u.delete(A.streamId);for(let{buf:yt,bin:Ot}of dt)G.send(yt,{binary:Ot})}),G.on("message",(dt,yt)=>{let Ot=yt?dt:Buffer.from(String(dt));C({v:_t,ch:"attach",type:"data",streamId:A.streamId,data:Ot.toString("base64"),...yt?{bin:!0}:{}})});let K=()=>{u.delete(A.streamId),l.delete(A.streamId)&&C({v:_t,ch:"attach",type:"close",streamId:A.streamId})};G.on("close",K),G.on("error",K);return}if(A.ch==="attach"&&A.type==="data"){let G=l.get(A.streamId);if(!G)return;let K=Buffer.from(A.data,"base64"),dt=A.bin===!0,yt=u.get(A.streamId);yt?yt.push({buf:K,bin:dt}):G.send(K,{binary:dt});return}if(A.ch==="attach"&&A.type==="close"){let G=l.get(A.streamId);l.delete(A.streamId),u.delete(A.streamId),G?.close();return}});let O=()=>{g&&i.log("[plane-link] disconnected from plane"),g=!1,J&&(clearInterval(J),J=null),p.clear();for(let G of l.values())G.close();l.clear(),u.clear();for(let G of w.values())G.abort();if(w.clear(),y||E)return;S==null&&(S=Date.now());let I=Date.now()-S<A$,A=I?E$+Math.random()*_$:b;E=setTimeout(()=>{E=null,_()},A),E.unref?.(),I||(b=Math.min(b*2,T$))};m.on("close",O),m.on("error",O)}return Jt.on("event",st),j().then(_),{stop:()=>{y=!0,Jt.off("event",st),E&&clearTimeout(E),M&&clearTimeout(M),J&&clearInterval(J),C({v:_t,ch:"ctl",type:"bye",reason:"shutdown"}),m?.close()},status:()=>({connected:g,nodeId:n,cursor:{...d},inflight:p.size})}}function cd(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 bb=()=>({input:0,output:0,cacheRead:0,cacheCreation:0}),kb=(t,e)=>{for(let n of Object.keys(t))t[n]+=e?.[n]||0},id=t=>Math.round(t*1e4)/1e4;async function xb(t){let e=[],n={},r={},o=0,s=0,a=0,i=0,c=new Set,l=bb(),u=0,f=[];for(let d of t||[]){let p=await D(d),m=ue(p,{boxLabels:cd(d)});if(m.turns===0){d.archivedAt?u++:f.push(d.name||d.id);continue}o+=m.totalUsd,s+=m.turns,a+=m.repricedTurns||0,i+=m.unpricedTurns||0;for(let g of m.unpricedModels||[])c.add(g);kb(l,m.totalTokens);for(let[g,y]of Object.entries(m.byModel))n[g]||={usd:0,events:0,tokens:bb()},n[g].usd=id(n[g].usd+y.usd),n[g].events+=y.events,kb(n[g].tokens,y.tokens);for(let[g,y]of Object.entries(m.byProvider))r[g]||={usd:0,events:0},r[g].usd=id(r[g].usd+y.usd),r[g].events+=y.events;e.push({id:d.id,name:d.name,usd:m.totalUsd,rollup:m})}return e.sort((d,p)=>p.usd-d.usd),{generatedAt:new Date().toISOString(),pricingBasis:"api-equivalent (subscription usage priced at public API rates)",fleet:{totalUsd:id(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 ps=t=>`$${t.toFixed(4)}`,Yr=(t,e)=>String(t).padEnd(e);function Sb(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 ${ps(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(` ${Yr(a,26)} ${Yr(ps(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(` ${Yr(a.name||a.id,30)} ${ps(a.usd)}`);let i=Object.values(a.rollup.byBox).sort((c,l)=>l.usd-c.usd);for(let c of i)if(r.push(` ${Yr(c.label,40)} ${Yr(ps(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))}${Yr(ps(u.usd),10)} ${u.models.join(",")||"?"}`)}return r.join(`
|
|
393
|
-
`)}import N$ from"node:fs/promises";import P$ from"node:path";var mi=t=>t.payload?.ts??t.ts,$$=12e4,kn=t=>Math.round(t*1e4)/1e4,O$=()=>({usd:0,activeMs:0,turns:0,toolCalls:0,models:{},providers:{},byTrack:new Map});function C$(t,{since:e}){let{track:n,events:r,brief:o}=t,s=S=>mi(S)>=e,a=pr([xo(n?.name),xo(n?.workspace?.branch),o].filter(Boolean).join(" ")),i=new Map;for(let S of r)if(S.type==="subtask.delegated"&&S.boxId&&S.payload?.task){let E=pr(S.payload.task);E&&i.set(S.boxId,E)}let c=new Map,l=S=>{let E=S||"unattributed";return c.has(E)||c.set(E,{segments:[u(null)],lastTs:null,provider:null}),c.get(E)},u=S=>({textCat:S,tools:{},usd:0,tokens:{input:0,output:0,cacheRead:0,cacheWrite:0},models:{},provider:null,activeMs:0,turns:0,toolCalls:0}),f=[...r].sort((S,E)=>mi(S)-mi(E)),d=0,p=0,m=0,g={turns:0};for(let S of f){let E=l(S.boxId),M=E.segments[E.segments.length-1],J=mi(S);switch(E.lastTs!=null&&J>E.lastTs&&s(S)&&(M.activeMs+=Math.min(J-E.lastTs,$$)),E.lastTs=J,S.provider&&(E.provider=S.provider),S.type){case"turn.user":{let $=S.payload?.text;if(S.source==="hook"||!String($||"").trim())break;let j=u(pr($));E.segments.push(j),s(S)&&(j.turns+=1);break}case"tool.called":{if(S.source==="transcript")break;let $=S.payload?.tool;$&&(M.tools[$]=(M.tools[$]||0)+1),s(S)&&(M.toolCalls+=1);break}case"cost.recorded":{if(!s(S))break;let $=S.payload||{},j={input:$.tokens?.input||0,output:$.tokens?.output||0,cacheRead:$.tokens?.cacheRead||0,cacheWrite:$.tokens?.cacheWrite??$.tokens?.cacheCreation??0},v=Number($.usd)||0;!v&&$.tokens&&(v=le($.model,j)),!v&&$.tokens&&!sa($.model)&&(g.turns+=1),M.usd+=v,M.tokens.input+=j.input,M.tokens.output+=j.output,M.tokens.cacheRead+=j.cacheRead,M.tokens.cacheWrite+=j.cacheWrite;let C=$.model||"unknown";M.models[C]=(M.models[C]||0)+v,S.provider&&(M.provider=S.provider);break}case"handoff.done":s(S)&&(d+=1);break;case"subtask.merged":s(S)&&(p+=1);break;case"error":s(S)&&S.payload?.kind==="subtask-failed"&&(m+=1);break;default:break}}let y=new Map,b=!1;for(let[S,E]of c)for(let M of E.segments){if(!(M.usd>0||M.activeMs>0||M.turns>0||M.toolCalls>0))continue;let $=M.textCat?.category??i.get(S)?.category??Qf(M.tools)?.category??a?.category??"uncategorized";y.has($)||y.set($,{usd:0,activeMs:0,turns:0,toolCalls:0,models:{},providers:{}});let j=y.get($);j.usd+=M.usd,j.activeMs+=M.activeMs,j.turns+=M.turns,j.toolCalls+=M.toolCalls;for(let[C,q]of Object.entries(M.models))j.models[C]=(j.models[C]||0)+q;let v=M.provider||E.provider;v&&(j.providers[v]=(j.providers[v]||0)+M.usd),v==="cursor"&&M.usd===0&&(b=!0)}return{perCategory:y,handoffs:d,merged:p,failed:m,sawCursorActivity:b,unpricedTurns:g.turns}}function Tb(t,{windowMs:e=null,now:n=Date.now()}={}){let r=e==null?0:n-e,o=new Map,s=[],a=0,i=!1,c=0;for(let g of t||[]){let{track:y,events:b}=g;if(!b?.length){a+=1;continue}let{perCategory:S,handoffs:E,merged:M,failed:J,sawCursorActivity:$,unpricedTurns:j}=C$(g,{since:r});$&&(i=!0),c+=j;let v={trackId:y?.id,name:y?.name||y?.id,archived:!!y?.archivedAt,usd:0,activeMs:0,turns:0,categories:{},models:{},providers:{},handoffs:E,subtasksMerged:M,subtasksFailed:J};for(let[C,q]of S){v.usd+=q.usd,v.activeMs+=q.activeMs,v.turns+=q.turns,v.categories[C]={usd:kn(q.usd),activeMs:q.activeMs};for(let[w,h]of Object.entries(q.models))v.models[w]=kn((v.models[w]||0)+h);for(let[w,h]of Object.entries(q.providers))v.providers[w]=kn((v.providers[w]||0)+h);o.has(C)||o.set(C,O$());let rt=o.get(C);rt.usd+=q.usd,rt.activeMs+=q.activeMs,rt.turns+=q.turns,rt.toolCalls+=q.toolCalls;for(let[w,h]of Object.entries(q.models))rt.models[w]=(rt.models[w]||0)+h;for(let[w,h]of Object.entries(q.providers))rt.providers[w]=(rt.providers[w]||0)+h;let st=rt.byTrack.get(y?.id)||{usd:0,activeMs:0};st.usd+=q.usd,st.activeMs+=q.activeMs,rt.byTrack.set(y?.id,st)}(v.usd>0||v.activeMs>0||v.turns>0)&&(v.usd=kn(v.usd),v.dominantCategory=M$(v.categories),s.push(v))}let l=[...o.values()].reduce((g,y)=>g+y.usd,0),u=[...o.values()].reduce((g,y)=>g+y.activeMs,0),f=l>0?"usd":"time",d=f==="usd"?l:u,p=new Map((t||[]).map(g=>[g.track?.id,g.track?.name||g.track?.id])),m=[...o.entries()].map(([g,y])=>({category:g,sharePct:d>0?Math.round((f==="usd"?y.usd:y.activeMs)/d*100):0,shareBasis:f,usd:kn(y.usd),activeMs:y.activeMs,turns:y.turns,toolCalls:y.toolCalls,models:vb(y.models),providers:vb(y.providers),topWorkstreams:[...y.byTrack.entries()].sort((b,S)=>S[1].usd-b[1].usd||S[1].activeMs-b[1].activeMs).slice(0,2).map(([b,S])=>({trackId:b,name:p.get(b),usd:kn(S.usd),activeMs:S.activeMs}))})).sort((g,y)=>y.usd-g.usd||y.activeMs-g.activeMs);return s.sort((g,y)=>y.usd-g.usd||y.activeMs-g.activeMs),{since:r,capturedAt:n,totalUsd:kn(l),activeMs:u,turns:[...o.values()].reduce((g,y)=>g+y.turns,0),bullets:m,workstreams:s,gaps:{tracksWithoutEvents:a,cursorCostUnknown:i,unpricedTurns:c}}}var M$=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},vb=t=>Object.fromEntries(Object.entries(t).map(([e,n])=>[e,kn(n)]));var Zr={"24h":24*36e5,"7d":168*36e5,"30d":720*36e5,all:null};function B$(t){let e=String(t||"7d");return e in Zr?{key:e,ms:Zr[e]}:{key:"7d",ms:Zr["7d"]}}function D$(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=ja(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 j$(t){if(!t?.cwd)return null;try{let n=(await N$.readFile(P$.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 Ab(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}:B$(e),f=[];for(let d of t||[]){let p=await D(d),m=D$(d,p);i&&c<o&&(m=m.filter(g=>(g.ts??0)<=c)),f.push({track:d,events:m,brief:await j$(d)})}return{window:l,...Tb(f,{windowMs:u,now:c})}}var xn=36e5,Rb=24*xn,L$=400,F$=12,Qr=t=>Math.round(t*1e4)/1e4;function U$({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 Zr?String(t||"7d"):"7d",i=Zr[a];return{key:a,since:i==null?null:r-i,until:r}}var H$=(t,e)=>t!=null&&e-t<=48*xn?xn:Rb;function Eb(t,e){if(e===xn)return Math.floor(t/xn)*xn;let n=new Date(t);return n.setHours(0,0,0,0),n.getTime()}function W$(t,e){if(e===xn)return t+xn;let n=new Date(t);return n.setDate(n.getDate()+1),n.setHours(0,0,0,0),n.getTime()}var _b=()=>({input:0,output:0,cacheRead:0,cacheCreation:0}),Ib=(t,e)=>{for(let n of Object.keys(t))t[n]+=e?.[n]||0},q$=t=>Object.fromEntries(Object.entries(t).map(([e,n])=>[e,Qr(n)]));async function $b(t,e={}){let n=e.now??Date.now(),r=U$({...e,now:n}),o=r.since==null?Rb:H$(r.since,r.until),s=v=>(r.since==null||v>=r.since)&&v<r.until,a=r.since!=null?r.until-r.since:null,i=v=>a!=null&&v>=r.since-a&&v<r.since,c={},l={},u=_b(),f=0,d=0,p=0,m=0,g=0,y=new Set,b=new Map,S=[],E=[],M=[],J=null;for(let v of t||[]){let q=(await D(v)).filter(w=>w.type==="cost.recorded");if(!q.length){v.archivedAt||M.push(v.name||v.id);continue}let rt=[];for(let w of q){let h=w.ts??0;s(h)?rt.push(w):i(h)&&(g+=ia(w).usd)}if(!rt.length)continue;let st=ue(rt,{boxLabels:cd(v)});f+=st.totalUsd,d+=st.turns,p+=st.repricedTurns||0,m+=st.unpricedTurns||0;for(let w of st.unpricedModels||[])y.add(w);Ib(u,st.totalTokens);for(let[w,h]of Object.entries(st.byModel))c[w]||={usd:0,events:0,tokens:_b()},c[w].usd=Qr(c[w].usd+h.usd),c[w].events+=h.events,Ib(c[w].tokens,h.tokens);for(let[w,h]of Object.entries(st.byProvider))l[w]||={usd:0,events:0},l[w].usd=Qr(l[w].usd+h.usd),l[w].events+=h.events;for(let w of rt){let{usd:h}=ia(w);if(!h)continue;let T=Eb(w.ts??0,o),_=b.get(T)??{t:T,total:0,byProvider:{}};_.total+=h;let O=w.provider||"unknown";_.byProvider[O]=(_.byProvider[O]||0)+h,b.set(T,_),(J==null||T<J)&&(J=T)}S.push({id:v.id,name:v.name||v.id,archived:!!v.archivedAt,usd:st.totalUsd,turns:st.turns,providers:Object.keys(st.byProvider)});for(let[w,h]of Object.entries(st.byBox))E.push({trackId:v.id,trackName:v.name||v.id,boxId:w,...h})}let $=r.since!=null?Eb(r.since,o):J,j=[];for(let v=$,C=0;v!=null&&v<r.until&&C<L$;v=W$(v,o),C++){let q=b.get(v);j.push(q?{t:v,total:Qr(q.total),byProvider:q$(q.byProvider)}:{t:v,total:0,byProvider:{}})}return S.sort((v,C)=>C.usd-v.usd),E.sort((v,C)=>C.usd-v.usd),{generatedAt:new Date().toISOString(),pricingBasis:"api-equivalent (subscription usage priced at public API rates)",window:r.key,since:r.since,until:r.until,bucketMs:o,series:j,fleet:{totalUsd:Qr(f),turns:d,repricedTurns:p,unpricedTurns:m,unpricedModels:[...y],totalTokens:u,byModel:c,byProvider:l,tracksWithCost:S.length,tracksSilentLive:M},prior:a!=null?{totalUsd:Qr(g),since:r.since-a,until:r.since}:null,tracks:S.slice(0,50),boxes:E.slice(0,F$)}}import to from"node:fs/promises";import K$ from"node:fs";import It from"node:path";import G$ from"node:os";import{execFile as V$}from"node:child_process";import{promisify as z$}from"node:util";var J$=z$(V$),Ob=process.env.BOSTRAT_STATE_DIR||It.join(process.env.HOME||G$.tmpdir(),".bostrat"),hi=!1,ld=null;async function ud(t,e){let n=await to.stat(t).catch(()=>null);if(!n?.isFile())return!1;let r=await to.stat(e).catch(()=>null);return r&&r.size===n.size&&r.mtimeMs>=n.mtimeMs?!1:(await to.mkdir(It.dirname(e),{recursive:!0}),await to.copyFile(t,e),!0)}var X$=new Set([".claude",".codex",".credentials.json","auth.json",".claude.json","integrations.json"]);async function wi(t,e,{skip:n=null}={}){let r=0,o=await to.readdir(t,{withFileTypes:!0}).catch(()=>[]);for(let s of o){if(X$.has(s.name)||n?.has(s.name))continue;let a=It.join(t,s.name);s.isDirectory()?r+=await wi(a,It.join(e,s.name)):s.isFile()&&(r+=await ud(a,It.join(e,s.name))?1:0)}return r}async function Cb({stateDir:t=Ob,dest:e=process.env.BOSTRAT_SYNC_DIR||`${Ob}-sync`,remote:n=process.env.BOSTRAT_STATE_REMOTE||null,tracks:r=null}={}){try{await to.mkdir(e,{recursive:!0});let o=0;o+=await ud(It.join(t,"tracks.json"),It.join(e,"tracks.json"))?1:0,o+=await ud(It.join(t,"events.jsonl"),It.join(e,"events.jsonl"))?1:0,o+=await wi(It.join(t,"events"),It.join(e,"events")),o+=await wi(It.join(t,"memory"),It.join(e,"memory"));let s=new Set(["mocks"]);for(let u of r||[])!u?.cwd||!u?.id||(o+=await wi(It.join(u.cwd,".bostrat"),It.join(e,"workspaces",u.id,".bostrat"),{skip:s}));let a=(...u)=>J$("git",["-C",e,...u]);K$.existsSync(It.join(e,".git"))||(await a("init","-q"),await a("config","user.email","agent@bostrat.local"),await a("config","user.name","bostrat state sync")),n&&/^https:\/\/github\.com\//i.test(n)&&(await a("config","--get","credential.useHttpPath").then(()=>!0).catch(()=>!1)||await Hi(e).catch(()=>{}));let i=!1,{stdout:c}=await a("status","--porcelain");c.trim()&&(await a("add","-A"),await a("commit","-q","-m",`state sync ${new Date().toISOString()}`),i=!0);let l=!1;if(n&&(i||hi))try{await a("push","-q",n,"HEAD:refs/heads/main"),l=!0,hi&&console.log("[state-sync] push recovered \u2014 off-box copy is current again"),hi=!1,ld=null}catch(u){hi=!0;let f=(u?.stderr||u?.message||String(u)).trim().split(`
|
|
394
|
-
`).slice(-3).join(" | ").slice(0,300);f!==
|
|
395
|
-
`)[0]}`),{pushed:!1,reason:"push-failed"}}}function
|
|
396
|
-
`).map(s=>s.replace(/\s+$/,"").trim()),n=s=>{let a=null,i=null;for(let c=s+1;c<Math.min(s+5,e.length)&&!/^Current\s/i.test(e[c]);c++){let l=e[c].match(/(\d+)%\s*used/i);l&&a===null&&(a=Number(l[1]));let u=e[c].match(/^Resets\s+(.+)$/i);u&&i===null&&(i=u[1].trim())}return a===null?null:{pct:a,resets:i||null}},r=s=>{let a=e.findIndex(i=>s.test(i));return a<0?null:n(a)},o=[];return e.forEach((s,a)=>{let i=s.match(/^Current week \(([^)]+)\)$/i);if(!i||/all models/i.test(i[1]))return;let c=n(a);c&&o.push({model:i[1].trim(),...c})}),{session:r(/^Current session\b/i),week:r(/^Current week \(all models\)/i)||r(/^Current week$/i),weekModels:o.length?o:null}}function
|
|
397
|
-
`).map(s=>s.replace(/\s+$/,"")),n=e.findIndex(s=>/What's contributing/i.test(s));if(n<0)return null;let r=/(Usage credits|Esc to cancel|d to day · w to week|^\s*d to day)/i,o=[];for(let s=n+1;s<e.length&&!r.test(e[s]);s++)e[s].trim()&&o.push(e[s].trim());return o.length?o:null}async function
|
|
398
|
-
`),n=!1;for(let r=e.length-1;r>=0;r--){let o=e[r].trim();if(!o||o[0]!=="{"||!o.includes("token_count"))continue;let s;try{s=JSON.parse(o)}catch{continue}let a=s?.payload;if(a?.type!=="token_count")continue;n=!0;let i=a.rate_limits;if(!i)return{session:null,week:null,sawTokenCount:n};if(i.primary||i.secondary)return{session:
|
|
399
|
-
`,{mode:384}),
|
|
386
|
+
_${o} archived_`:"")}async function g$(t,e){let n=await qn();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?(sr(Sb(t,n,{name:u,repo:l.path,responseUrl:e.response_url})),pt(`Starting *${Z(u)}* on ${Z(l.name)}\u2026`)):{response_type:"ephemeral",text:`Pick a repo for \u201C${u}\u201D`,blocks:h$(u,i)}}case"archive":{if(!a.length)return pt("Usage: `/bostrat archive <track>`");let{match:i,live:c}=ld(await r.listTracks(),a.join(" "));return i?(sr(f$(t,n,{track:i,responseUrl:e.response_url})),pt(`Archiving *${Z(i.name)}*\u2026`)):ud(c,a.join(" "))}case"handoff":{let i=r.knownAgents??d$,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}=ld(await r.listTracks(),l);return u?(sr(p$(t,n,{track:u,agent:c,responseUrl:e.response_url})),pt(`Handing *${Z(u.name)}* to ${Z(c)}\u2026`)):ud(f,l)}case"stop":{if(!a.length)return pt("Usage: `/bostrat stop <track>`");let{match:i,live:c}=ld(await r.listTracks(),a.join(" "));return i?(sr(m$(t,n,{track:i,responseUrl:e.response_url})),pt(`Stopping *${Z(i.name)}*\u2026`)):ud(c,a.join(" "))}case"list":return pt(await w$(r));default:return pt(u$)}}async function y$(t,e,n,r){let{trackId:o,promptId:s,label:a}=r,i=await md(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 Je(),await eo(e.botToken,{channel:i.channel,ts:c,text:`\u2713 ${a}`,blocks:[{type:"section",text:{type:"mrkdwn",text:`~${Z(l||"question")}~
|
|
387
|
+
\u2713 *${Z(a)}*`}}]}).catch(()=>{})}}catch(c){await Kt(n.response_url,pt(`Couldn't answer: ${c?.message||c}`))}}async function b$(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,za(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 Kt(n.response_url,pt(`Couldn't act on the offer: ${c?.message||c}`))}}async function k$(t,e){let n=e.actions?.[0];if(!n?.action_id)return;let r=await qn();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 Kt(e.response_url,{...pt(`Starting *${Z(s)}*\u2026`),replace_original:!0}),sr(Sb(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"?sr(y$(t,r,e,o)):n.action_id==="bostrat-ship"&&sr(b$(t,r,e,o))}async function x$(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 c$(e.channel,e.thread_ts);if(!n)return;let r=await qn();if(r)try{await t.deps.sayToLead(n.trackId,e.text.trim())}catch(o){await ar(r,n.rec,`Couldn't deliver that to the lead: ${o?.message||o}`).catch(()=>{})}}function S$(t){return async e=>{if(e.type==="slash_commands")return g$(t,e.payload);if(e.type==="interactive"){await k$(t,e.payload);return}if(e.type==="events_api"){await x$(t,e.payload?.event);return}}}var v$=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 T$(t,e){let n=[{type:"section",text:{type:"mrkdwn",text:`\u2753 ${Z(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:bs(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 A$(t,e,n){let r=n.payload||{};if(!r.promptId||e.prompt?.promptId===r.promptId)return;e.prompt&&await eo(t.botToken,{channel:e.channel,ts:e.prompt.ts,text:"superseded",blocks:[{type:"section",text:{type:"mrkdwn",text:`~${Z(e.prompt.question||"question")}~ _superseded_`}}]}).catch(()=>{});let o=await ar(t,e,r.question||"The lead needs an answer",T$(n.trackId,r));e.prompt={promptId:r.promptId,ts:o.ts,question:r.question||null},await Je()}async function E$(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 Je(),await eo(t.botToken,{channel:e.channel,ts:o,text:"answered",blocks:[{type:"section",text:{type:"mrkdwn",text:`~${Z(s||"question")}~
|
|
388
|
+
\u2713 _answered_`}}]})}async function _$(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 ar(t,e,r.excerpt||"Ship it?",[{type:"section",text:{type:"mrkdwn",text:`\u{1F6A2} ${Z(bs(r.excerpt||"The lead offered to ship.",300))}`}},{type:"actions",elements:s}]);e.offers=e.offers||{},e.offers[r.offerId]=a.ts,await Je()}async function I$(t,e,n){let r=n.payload||{},o=e.offers?.[r.offerId];if(!o)return;delete e.offers[r.offerId],await Je();let s=r.action==="dismiss"?"dismissed":r.action==="pr"?"\u2713 opening a PR":"\u2713 pushing";await eo(t.botToken,{channel:e.channel,ts:o,text:s,blocks:[{type:"section",text:{type:"mrkdwn",text:`\u{1F6A2} ${s}`}}]})}async function R$(t,e,n,r){let o=n.payload||{};if(o.change==="ready"&&o.mockId){let s=await(r.readMockPng??$$)(o.mockId,o.version??null);if(!s)return;let a=(o.version??1)>1?` (v${o.version})`:"";await wb(t.botToken,{filename:s.filename,buffer:s.buffer,title:`${o.name}${a}`,channelId:e.channel,threadTs:e.ts,initialComment:`\u{1F3A8} mock *${Z(o.name)}*${a}${o.group?` (group \`${Z(o.group)}\`)`:""}`})}else if(o.change==="picked"){let s=(o.siblings??[]).map(a=>`*${Z(a.name)}*`).join(", ");await ar(t,e,`Design decision: ${o.name} picked`,[{type:"section",text:{type:"mrkdwn",text:`\u{1F3A8} design decision: *${Z(o.name)}* picked${s?` over ${s}`:""}`}}])}}async function $$(t,e){let n=await cn(t);if(!n)return null;let r=Ce(n,e);if(!r)return null;let o=await ys.readFile(Jt(n,r.v).png).catch(()=>null);return o?{filename:`${n.name.replace(/[^\w.-]+/g,"-")}.v${r.v}.png`,buffer:o}:null}async function O$(t,e){if(!v$.has(e.type))return;let n=await md(e.trackId);if(!n)return;let r=await qn();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 Je(),await fd(r,n);return;case"turn.agent":{if(!o.text)return;let s=l$(o.text);if(!s)return;await ar(r,n,s,[{type:"section",text:{type:"mrkdwn",text:Z(s)}},{type:"context",elements:[{type:"mrkdwn",text:`<${Oa(e.trackId)}|full turn \u2192>`}]}]);return}case"prompt.raised":return A$(r,n,e);case"prompt.answered":return E$(r,n,e);case"pr.offered":return _$(r,n,e);case"pr.offer.answered":return I$(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 ar(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 Je(),await fd(r,n);return;case"view.state.changed":{if(o.change!=="created"||o.restarted||!o.slug)return;await ar(r,n,`Live preview ${o.name}: ${Ca(o.slug)}`,[{type:"section",text:{type:"mrkdwn",text:`\u{1F50D} live preview *${Z(o.name)}*: ${Ca(o.slug)}
|
|
389
|
+
_runs on the Track's branch; reachable on the operator's private network only_`}}]);return}case"mock.state.changed":return R$(r,n,e,t.deps)}}function vb(t){let e={deps:t};te.on("event",s=>{O$(e,s).catch(()=>{})});let n=null,r=null,o=async()=>{n||!await qn()||(r&&clearInterval(r),r=null,n=gb({getToken:async()=>(await qn())?.appToken,onEnvelope:S$(e),log:s=>console.log(`[slack] ${s}`)}))};return r=setInterval(()=>o().catch(()=>{}),3e4),r.unref?.(),o().catch(()=>{}),()=>{r&&clearInterval(r),n?.()}}import M$ from"node:fs/promises";function C$(t){let e=new Map;for(let n of String(t||"").split(`
|
|
390
|
+
`)){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 N$(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 P$(t,e){return t?.transcript_path?t.transcript_path:e?.cwd&&e?.sessionId?$e(e.cwd,e.sessionId):e?.cwd?kc(e.cwd):null}async function Tb(t,e){let n=await e(t?.session_id,t?.cwd);if(!n?.track)return[];let{track:r,box:o}=n,s={boxId:o?.name||null,provider:"claude",source:"hook"},a=[],i=async c=>{a.push(await R(r,{...s,...c}))};switch(t?.hook_event_name){case"SessionStart":await i({type:"session.ready",actor:"system",payload:{sessionId:t.session_id}});break;case"UserPromptSubmit":await i({type:"turn.user",actor:"user",payload:{promptHead:String(t.prompt??t.user_prompt??"").slice(0,160)||null}});break;case"PostToolUse":await i({type:"tool.called",actor:"agent",payload:{tool:t.tool_name??null}});break;case"Stop":await B$(r,o,t,i);break;default:break}return a}async function B$(t,e,n,r){let o=[],s=P$(n,e);if(s)try{o=C$(await M$.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=N$(await L(t)),c=o.filter(u=>!i.has(u.messageId));if(c.length){let u=new Map;for(let f of c){let d=f.model||"unknown",p=u.get(d)||{tokens:{input:0,output:0,cacheWrite:0,cacheRead:0},messageIds:[]};for(let m of Object.keys(p.tokens))p.tokens[m]+=f.tokens[m];p.messageIds.push(f.messageId),u.set(d,p)}for(let[f,d]of u)await r({type:"cost.recorded",actor:"system",source:"transcript",payload:{model:f,usd:pe(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 L$ from"node:fs";import hd from"node:fs/promises";import F$ from"node:http";import no from"node:path";import Si from"node:os";import{createRequire as U$}from"node:module";import wd from"ws";import D$ from"node:path";import j$ from"node:os";var ks=process.env.BOSTRAT_STATE_DIR||D$.join(process.env.HOME||j$.tmpdir(),".bostrat");function Ab(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 Eb(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 xi=(()=>{try{return U$(import.meta.url)("../package.json").version||null}catch{return null}})(),H$=process.env.BOSTRAT_STATE_DIR||no.join(process.env.HOME||Si.tmpdir(),".bostrat");function W$(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 q$({home:t=process.env.HOME||Si.homedir(),env:e=process.env}={}){let n=r=>{try{return L$.statSync(r),!0}catch{return!1}};return{claude:n(no.join(t,".claude",".credentials.json")),codex:n(no.join(e.CODEX_HOME||no.join(t,".codex"),"auth.json"))}}var It=1,_b=1e3,K$=3e4,G$=6e4,V$=1500,z$=500,Ib=100,J$=2e3,X$=25e3,Y$=6e4;function Rb({url:t,token:e,nodeId:n=Si.hostname(),name:r=Si.hostname(),listTracks:o,detectAgents:s=null,sampleMetrics:a=null,metricsMs:i=Y$,selfUrl:c=`http://127.0.0.1:${process.env.PORT||3362}`,log:l=console}){if(!t||!e)throw new Error("plane-link: url and token are required");if(typeof o!="function")throw new Error("plane-link: listTracks is required");t=Eb(t);let u=c.replace(/^http/,"ws"),f=new Map,d=new Map,p=no.join(H$,"plane-link","cursor.json"),m={},w=new Map,y=null,b=!1,S=!1,_=_b,$=null,et=null,I=null,C=null,v=null,B=!1;async function D(){try{m=JSON.parse(await hd.readFile(p,"utf8"))}catch{m={}}}function lt(){B=!0,!I&&(I=setTimeout(async()=>{if(I=null,!!B){B=!1;try{await hd.mkdir(no.dirname(p),{recursive:!0}),await hd.writeFile(p,JSON.stringify(m))}catch(K){l.error("[plane-link] cursor flush failed:",K?.message||K)}}},J$),I.unref?.())}function q(K){return!y||y.readyState!==wd.OPEN?!1:(y.send(JSON.stringify(K)),!0)}function T(K){for(let{trackId:J,event:O}of K)w.set(O.id,{trackId:J,seq:O.seq});return q({v:It,ch:"events",type:"append",events:K})}async function g(){if(!a)return;let K;try{K=await a()}catch(J){l.error("[plane-link] metrics sample failed:",J?.message||J);return}K&&q({v:It,ch:"ctl",type:"metrics",metrics:K})}function h(){a&&(v&&clearInterval(v),g(),v=setInterval(g,i),v.unref?.())}async function A(){let K;try{K=await o()}catch(J){l.error("[plane-link] backfill listTracks failed:",J?.message||J);return}q({v:It,ch:"ctl",type:"tracks",ids:K.map(J=>J.id).filter(J=>typeof J=="string")});for(let J of K){if(!b)return;let O;try{O=await L(J)}catch{continue}let Q=m[J.id]??0,bt=O.filter(k=>k.seq>Q&&typeof k.id=="string");for(let k=0;k<bt.length;k+=Ib){let N=bt.slice(k,k+Ib).map(St=>({trackId:J.id,event:St}));if(!T(N))return}}}function E(K){!b||!K?.trackId||typeof K.id!="string"||T([{trackId:K.trackId,event:K}])}let z=new Map;async function W(K){let{reqId:J,method:O,path:Q,headers:bt={},body:k}=K,N=Object.fromEntries(Object.entries(bt).filter(([,Bt])=>Bt!=null)),St=new AbortController;z.set(J,St);try{if(K.host){await at(K,N,St);return}let Bt=await fetch(c+Q,{method:O,headers:N,body:k?Buffer.from(k,"base64"):void 0,signal:St.signal}),Dt=Object.fromEntries(Bt.headers);for(let jt of["connection","keep-alive","transfer-encoding","upgrade","content-encoding","content-length"])delete Dt[jt];if(Dt["content-type"]||(Dt["content-type"]="application/octet-stream"),q({v:It,ch:"control",type:"http_head",reqId:J,status:Bt.status,headers:Dt}),Bt.body){for await(let jt of Bt.body)if(!q({v:It,ch:"control",type:"http_data",reqId:J,data:Buffer.from(jt).toString("base64")}))break}q({v:It,ch:"control",type:"http_end",reqId:J,error:null})}finally{z.delete(J)}}function at(K,J,O){let{reqId:Q,method:bt,path:k,body:N}=K,St=new URL(c);return new Promise(Bt=>{let Dt=F$.request({host:St.hostname,port:St.port,method:bt,path:k,headers:{...J,host:K.host},signal:O.signal},jt=>{let{connection:WC,"keep-alive":qC,"transfer-encoding":KC,upgrade:GC,...hx}=jt.headers;q({v:It,ch:"control",type:"http_head",reqId:Q,status:jt.statusCode||502,headers:hx}),jt.on("data",wx=>{q({v:It,ch:"control",type:"http_data",reqId:Q,data:Buffer.from(wx).toString("base64")})||jt.destroy()}),jt.on("end",()=>{q({v:It,ch:"control",type:"http_end",reqId:Q,error:null}),Bt()}),jt.on("error",()=>{q({v:It,ch:"control",type:"http_end",reqId:Q,error:"view upstream error"}),Bt()})});Dt.on("error",jt=>{q({v:It,ch:"control",type:"http_end",reqId:Q,error:jt?.message||"view request failed"}),Bt()}),N&&Dt.write(Buffer.from(N,"base64")),Dt.end()})}function Ct(){if(S)return;y=new wd(t,{headers:{authorization:`Bearer ${e}`}}),y.on("open",async()=>{let J=null;try{J=await s?.()||null}catch{J=null}q({v:It,ch:"ctl",type:"hello",nodeId:n,name:r,platform:process.platform,version:xi,providers:q$(),...J?{agents:J}:{}})}),y.on("message",J=>{let O;try{O=JSON.parse(J)}catch{return}if(O.ch==="ctl"&&O.type==="hello_ok"){b=!0,_=_b,$=null,l.log(`[plane-link] connected to plane as ${n}`),O.minNodeVersion&&xi&&W$(xi,O.minNodeVersion)&&l.error(`[plane-link] this node is bostrat-node ${xi}; the plane asks for \u2265${O.minNodeVersion} \u2014 update with: npm install -g bostrat-node`),C&&clearInterval(C),C=setInterval(()=>q({v:It,ch:"ctl",type:"ping"}),X$),C.unref?.(),h(),A().catch(Q=>l.error("[plane-link] backfill failed:",Q?.message||Q));return}if(O.ch==="events"&&O.type==="ack"&&Array.isArray(O.ids)){for(let Q of O.ids){let bt=w.get(Q);bt&&(w.delete(Q),(m[bt.trackId]??0)<bt.seq&&(m[bt.trackId]=bt.seq))}lt();return}if(O.ch==="control"&&O.type==="http_open"){W(O).catch(Q=>q({v:It,ch:"control",type:"http_end",reqId:O.reqId,error:Q?.message||"tunnel error"}));return}if(O.ch==="control"&&O.type==="http_cancel"){z.get(O.reqId)?.abort();return}if(O.ch==="attach"&&O.type==="open"){let Q=new wd(u+O.path,Array.isArray(O.protocols)?O.protocols:[],O.host?{headers:{host:O.host}}:void 0);f.set(O.streamId,Q),d.set(O.streamId,[]),Q.on("open",()=>{let k=d.get(O.streamId)||[];d.delete(O.streamId);for(let{buf:N,bin:St}of k)Q.send(N,{binary:St})}),Q.on("message",(k,N)=>{let St=N?k:Buffer.from(String(k));q({v:It,ch:"attach",type:"data",streamId:O.streamId,data:St.toString("base64"),...N?{bin:!0}:{}})});let bt=()=>{d.delete(O.streamId),f.delete(O.streamId)&&q({v:It,ch:"attach",type:"close",streamId:O.streamId})};Q.on("close",bt),Q.on("error",bt);return}if(O.ch==="attach"&&O.type==="data"){let Q=f.get(O.streamId);if(!Q)return;let bt=Buffer.from(O.data,"base64"),k=O.bin===!0,N=d.get(O.streamId);N?N.push({buf:bt,bin:k}):Q.send(bt,{binary:k});return}if(O.ch==="attach"&&O.type==="close"){let Q=f.get(O.streamId);f.delete(O.streamId),d.delete(O.streamId),Q?.close();return}});let K=()=>{b&&l.log("[plane-link] disconnected from plane"),b=!1,C&&(clearInterval(C),C=null),v&&(clearInterval(v),v=null),w.clear();for(let Q of f.values())Q.close();f.clear(),d.clear();for(let Q of z.values())Q.abort();if(z.clear(),S||et)return;$==null&&($=Date.now());let J=Date.now()-$<G$,O=J?V$+Math.random()*z$:_;et=setTimeout(()=>{et=null,Ct()},O),et.unref?.(),J||(_=Math.min(_*2,K$))};y.on("close",K),y.on("error",K)}return te.on("event",E),D().then(Ct),{stop:()=>{S=!0,te.off("event",E),et&&clearTimeout(et),I&&clearTimeout(I),C&&clearInterval(C),v&&clearInterval(v),q({v:It,ch:"ctl",type:"bye",reason:"shutdown"}),y?.close()},status:()=>({connected:b,nodeId:n,cursor:{...m},inflight:w.size})}}import Z$ from"node:fs";import $b from"node:fs/promises";import xs from"node:os";import Q$ from"node:path";var tO=1024*1024,vi=t=>Math.round(t/tO);async function eO(t){try{let e=await $b.statfs(t),n=e.blocks*e.bsize,r=n-e.bavail*e.bsize;return{diskUsedMb:vi(r),diskTotalMb:vi(n)}}catch{return{diskUsedMb:null,diskTotalMb:null}}}async function nO(){try{return(await Re()).filter(e=>e.isBostrat).length}catch{return null}}async function rO(){let t=0,e=!1;for(let n of["workspaces","worktrees"])try{let r=await $b.readdir(Q$.join(ks,n),{withFileTypes:!0});t+=r.filter(o=>o.isDirectory()).length,e=!0}catch{}return e?t:null}async function Ob({stateDir:t=ks,now:e=Date.now}={}){let n=xs.totalmem(),[r,o,s,a]=await Promise.all([ga().catch(()=>null),eO(Z$.existsSync(t)?t:xs.tmpdir()),nO(),rO()]),i=xs.cpus()?.length||null,c=xs.loadavg()?.[0]??null;return{ts:e(),loadavg1:c!=null?Math.round(c*100)/100:null,cpus:i,cpuPct:c!=null&&i?Math.min(100,Math.round(c/i*100)):null,memUsedMb:r!=null?vi(n-r):null,memTotalMb:vi(n),...o,boxes:s,worktrees:a,procUptimeS:Math.round(process.uptime()),osUptimeS:Math.round(xs.uptime())}}function yd(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 Mb=()=>({input:0,output:0,cacheRead:0,cacheCreation:0}),Cb=(t,e)=>{for(let n of Object.keys(t))t[n]+=e?.[n]||0},gd=t=>Math.round(t*1e4)/1e4;async function Nb(t){let e=[],n={},r={},o=0,s=0,a=0,i=0,c=new Set,l=Mb(),u=0,f=[];for(let d of t||[]){let p=await L(d),m=me(p,{boxLabels:yd(d)});if(m.turns===0){d.archivedAt?u++:f.push(d.name||d.id);continue}o+=m.totalUsd,s+=m.turns,a+=m.repricedTurns||0,i+=m.unpricedTurns||0;for(let w of m.unpricedModels||[])c.add(w);Cb(l,m.totalTokens);for(let[w,y]of Object.entries(m.byModel))n[w]||={usd:0,events:0,tokens:Mb()},n[w].usd=gd(n[w].usd+y.usd),n[w].events+=y.events,Cb(n[w].tokens,y.tokens);for(let[w,y]of Object.entries(m.byProvider))r[w]||={usd:0,events:0},r[w].usd=gd(r[w].usd+y.usd),r[w].events+=y.events;e.push({id:d.id,name:d.name,usd:m.totalUsd,rollup:m})}return e.sort((d,p)=>p.usd-d.usd),{generatedAt:new Date().toISOString(),pricingBasis:"api-equivalent (subscription usage priced at public API rates)",fleet:{totalUsd:gd(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 Ss=t=>`$${t.toFixed(4)}`,ro=(t,e)=>String(t).padEnd(e);function Pb(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 ${Ss(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(` ${ro(a,26)} ${ro(Ss(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(` ${ro(a.name||a.id,30)} ${Ss(a.usd)}`);let i=Object.values(a.rollup.byBox).sort((c,l)=>l.usd-c.usd);for(let c of i)if(r.push(` ${ro(c.label,40)} ${ro(Ss(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))}${ro(Ss(u.usd),10)} ${u.models.join(",")||"?"}`)}return r.join(`
|
|
391
|
+
`)}import cO from"node:fs/promises";import lO from"node:path";var Ti=t=>t.payload?.ts??t.ts,oO=12e4,_n=t=>Math.round(t*1e4)/1e4,sO=()=>({usd:0,activeMs:0,turns:0,toolCalls:0,models:{},providers:{},byTrack:new Map});function aO(t,{since:e}){let{track:n,events:r,brief:o}=t,s=S=>Ti(S)>=e,a=br([_o(n?.name),_o(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 _=br(S.payload.task);_&&i.set(S.boxId,_)}let c=new Map,l=S=>{let _=S||"unattributed";return c.has(_)||c.set(_,{segments:[u(null)],lastTs:null,provider:null}),c.get(_)},u=S=>({textCat:S,tools:{},usd:0,tokens:{input:0,output:0,cacheRead:0,cacheWrite:0},models:{},provider:null,activeMs:0,turns:0,toolCalls:0}),f=[...r].sort((S,_)=>Ti(S)-Ti(_)),d=0,p=0,m=0,w={turns:0};for(let S of f){let _=l(S.boxId),$=_.segments[_.segments.length-1],et=Ti(S);switch(_.lastTs!=null&&et>_.lastTs&&s(S)&&($.activeMs+=Math.min(et-_.lastTs,oO)),_.lastTs=et,S.provider&&(_.provider=S.provider),S.type){case"turn.user":{let I=S.payload?.text;if(S.source==="hook"||!String(I||"").trim())break;let C=u(br(I));_.segments.push(C),s(S)&&(C.turns+=1);break}case"tool.called":{if(S.source==="transcript")break;let I=S.payload?.tool;I&&($.tools[I]=($.tools[I]||0)+1),s(S)&&($.toolCalls+=1);break}case"cost.recorded":{if(!s(S))break;let I=S.payload||{},C={input:I.tokens?.input||0,output:I.tokens?.output||0,cacheRead:I.tokens?.cacheRead||0,cacheWrite:I.tokens?.cacheWrite??I.tokens?.cacheCreation??0},v=Number(I.usd)||0;!v&&I.tokens&&(v=pe(I.model,C)),!v&&I.tokens&&!ma(I.model)&&(w.turns+=1),$.usd+=v,$.tokens.input+=C.input,$.tokens.output+=C.output,$.tokens.cacheRead+=C.cacheRead,$.tokens.cacheWrite+=C.cacheWrite;let B=I.model||"unknown";$.models[B]=($.models[B]||0)+v,S.provider&&($.provider=S.provider);break}case"handoff.done":s(S)&&(d+=1);break;case"subtask.merged":s(S)&&(p+=1);break;case"error":s(S)&&S.payload?.kind==="subtask-failed"&&(m+=1);break;default:break}}let y=new Map,b=!1;for(let[S,_]of c)for(let $ of _.segments){if(!($.usd>0||$.activeMs>0||$.turns>0||$.toolCalls>0))continue;let I=$.textCat?.category??i.get(S)?.category??dp($.tools)?.category??a?.category??"uncategorized";y.has(I)||y.set(I,{usd:0,activeMs:0,turns:0,toolCalls:0,models:{},providers:{}});let C=y.get(I);C.usd+=$.usd,C.activeMs+=$.activeMs,C.turns+=$.turns,C.toolCalls+=$.toolCalls;for(let[B,D]of Object.entries($.models))C.models[B]=(C.models[B]||0)+D;let v=$.provider||_.provider;v&&(C.providers[v]=(C.providers[v]||0)+$.usd),v==="cursor"&&$.usd===0&&(b=!0)}return{perCategory:y,handoffs:d,merged:p,failed:m,sawCursorActivity:b,unpricedTurns:w.turns}}function Db(t,{windowMs:e=null,now:n=Date.now()}={}){let r=e==null?0:n-e,o=new Map,s=[],a=0,i=!1,c=0;for(let w of t||[]){let{track:y,events:b}=w;if(!b?.length){a+=1;continue}let{perCategory:S,handoffs:_,merged:$,failed:et,sawCursorActivity:I,unpricedTurns:C}=aO(w,{since:r});I&&(i=!0),c+=C;let v={trackId:y?.id,name:y?.name||y?.id,archived:!!y?.archivedAt,usd:0,activeMs:0,turns:0,categories:{},models:{},providers:{},handoffs:_,subtasksMerged:$,subtasksFailed:et};for(let[B,D]of S){v.usd+=D.usd,v.activeMs+=D.activeMs,v.turns+=D.turns,v.categories[B]={usd:_n(D.usd),activeMs:D.activeMs};for(let[T,g]of Object.entries(D.models))v.models[T]=_n((v.models[T]||0)+g);for(let[T,g]of Object.entries(D.providers))v.providers[T]=_n((v.providers[T]||0)+g);o.has(B)||o.set(B,sO());let lt=o.get(B);lt.usd+=D.usd,lt.activeMs+=D.activeMs,lt.turns+=D.turns,lt.toolCalls+=D.toolCalls;for(let[T,g]of Object.entries(D.models))lt.models[T]=(lt.models[T]||0)+g;for(let[T,g]of Object.entries(D.providers))lt.providers[T]=(lt.providers[T]||0)+g;let q=lt.byTrack.get(y?.id)||{usd:0,activeMs:0};q.usd+=D.usd,q.activeMs+=D.activeMs,lt.byTrack.set(y?.id,q)}(v.usd>0||v.activeMs>0||v.turns>0)&&(v.usd=_n(v.usd),v.dominantCategory=iO(v.categories),s.push(v))}let l=[...o.values()].reduce((w,y)=>w+y.usd,0),u=[...o.values()].reduce((w,y)=>w+y.activeMs,0),f=l>0?"usd":"time",d=f==="usd"?l:u,p=new Map((t||[]).map(w=>[w.track?.id,w.track?.name||w.track?.id])),m=[...o.entries()].map(([w,y])=>({category:w,sharePct:d>0?Math.round((f==="usd"?y.usd:y.activeMs)/d*100):0,shareBasis:f,usd:_n(y.usd),activeMs:y.activeMs,turns:y.turns,toolCalls:y.toolCalls,models:Bb(y.models),providers:Bb(y.providers),topWorkstreams:[...y.byTrack.entries()].sort((b,S)=>S[1].usd-b[1].usd||S[1].activeMs-b[1].activeMs).slice(0,2).map(([b,S])=>({trackId:b,name:p.get(b),usd:_n(S.usd),activeMs:S.activeMs}))})).sort((w,y)=>y.usd-w.usd||y.activeMs-w.activeMs);return s.sort((w,y)=>y.usd-w.usd||y.activeMs-w.activeMs),{since:r,capturedAt:n,totalUsd:_n(l),activeMs:u,turns:[...o.values()].reduce((w,y)=>w+y.turns,0),bullets:m,workstreams:s,gaps:{tracksWithoutEvents:a,cursorCostUnknown:i,unpricedTurns:c}}}var iO=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},Bb=t=>Object.fromEntries(Object.entries(t).map(([e,n])=>[e,_n(n)]));var oo={"24h":24*36e5,"7d":168*36e5,"30d":720*36e5,all:null};function uO(t){let e=String(t||"7d");return e in oo?{key:e,ms:oo[e]}:{key:"7d",ms:oo["7d"]}}function dO(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=Va(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 fO(t){if(!t?.cwd)return null;try{let n=(await cO.readFile(lO.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 jb(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}:uO(e),f=[];for(let d of t||[]){let p=await L(d),m=dO(d,p);i&&c<o&&(m=m.filter(w=>(w.ts??0)<=c)),f.push({track:d,events:m,brief:await fO(d)})}return{window:l,...Db(f,{windowMs:u,now:c})}}var In=36e5,Hb=24*In,pO=400,mO=12,so=t=>Math.round(t*1e4)/1e4;function hO({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 oo?String(t||"7d"):"7d",i=oo[a];return{key:a,since:i==null?null:r-i,until:r}}var wO=(t,e)=>t!=null&&e-t<=48*In?In:Hb;function Lb(t,e){if(e===In)return Math.floor(t/In)*In;let n=new Date(t);return n.setHours(0,0,0,0),n.getTime()}function gO(t,e){if(e===In)return t+In;let n=new Date(t);return n.setDate(n.getDate()+1),n.setHours(0,0,0,0),n.getTime()}var Fb=()=>({input:0,output:0,cacheRead:0,cacheCreation:0}),Ub=(t,e)=>{for(let n of Object.keys(t))t[n]+=e?.[n]||0},yO=t=>Object.fromEntries(Object.entries(t).map(([e,n])=>[e,so(n)]));async function Wb(t,e={}){let n=e.now??Date.now(),r=hO({...e,now:n}),o=r.since==null?Hb:wO(r.since,r.until),s=v=>(r.since==null||v>=r.since)&&v<r.until,a=r.since!=null?r.until-r.since:null,i=v=>a!=null&&v>=r.since-a&&v<r.since,c={},l={},u=Fb(),f=0,d=0,p=0,m=0,w=0,y=new Set,b=new Map,S=[],_=[],$=[],et=null;for(let v of t||[]){let D=(await L(v)).filter(T=>T.type==="cost.recorded");if(!D.length){v.archivedAt||$.push(v.name||v.id);continue}let lt=[];for(let T of D){let g=T.ts??0;s(g)?lt.push(T):i(g)&&(w+=wa(T).usd)}if(!lt.length)continue;let q=me(lt,{boxLabels:yd(v)});f+=q.totalUsd,d+=q.turns,p+=q.repricedTurns||0,m+=q.unpricedTurns||0;for(let T of q.unpricedModels||[])y.add(T);Ub(u,q.totalTokens);for(let[T,g]of Object.entries(q.byModel))c[T]||={usd:0,events:0,tokens:Fb()},c[T].usd=so(c[T].usd+g.usd),c[T].events+=g.events,Ub(c[T].tokens,g.tokens);for(let[T,g]of Object.entries(q.byProvider))l[T]||={usd:0,events:0},l[T].usd=so(l[T].usd+g.usd),l[T].events+=g.events;for(let T of lt){let{usd:g}=wa(T);if(!g)continue;let h=Lb(T.ts??0,o),A=b.get(h)??{t:h,total:0,byProvider:{}};A.total+=g;let E=T.provider||"unknown";A.byProvider[E]=(A.byProvider[E]||0)+g,b.set(h,A),(et==null||h<et)&&(et=h)}S.push({id:v.id,name:v.name||v.id,archived:!!v.archivedAt,usd:q.totalUsd,turns:q.turns,providers:Object.keys(q.byProvider)});for(let[T,g]of Object.entries(q.byBox))_.push({trackId:v.id,trackName:v.name||v.id,boxId:T,...g})}let I=r.since!=null?Lb(r.since,o):et,C=[];for(let v=I,B=0;v!=null&&v<r.until&&B<pO;v=gO(v,o),B++){let D=b.get(v);C.push(D?{t:v,total:so(D.total),byProvider:yO(D.byProvider)}:{t:v,total:0,byProvider:{}})}return S.sort((v,B)=>B.usd-v.usd),_.sort((v,B)=>B.usd-v.usd),{generatedAt:new Date().toISOString(),pricingBasis:"api-equivalent (subscription usage priced at public API rates)",window:r.key,since:r.since,until:r.until,bucketMs:o,series:C,fleet:{totalUsd:so(f),turns:d,repricedTurns:p,unpricedTurns:m,unpricedModels:[...y],totalTokens:u,byModel:c,byProvider:l,tracksWithCost:S.length,tracksSilentLive:$},prior:a!=null?{totalUsd:so(w),since:r.since-a,until:r.since}:null,tracks:S.slice(0,50),boxes:_.slice(0,mO)}}import ao from"node:fs/promises";import bO from"node:fs";import Mt from"node:path";import kO from"node:os";import{execFile as xO}from"node:child_process";import{promisify as SO}from"node:util";var vO=SO(xO),qb=process.env.BOSTRAT_STATE_DIR||Mt.join(process.env.HOME||kO.tmpdir(),".bostrat"),Ai=!1,bd=null;async function kd(t,e){let n=await ao.stat(t).catch(()=>null);if(!n?.isFile())return!1;let r=await ao.stat(e).catch(()=>null);return r&&r.size===n.size&&r.mtimeMs>=n.mtimeMs?!1:(await ao.mkdir(Mt.dirname(e),{recursive:!0}),await ao.copyFile(t,e),!0)}var TO=new Set([".claude",".codex",".credentials.json","auth.json",".claude.json","integrations.json"]);async function Ei(t,e,{skip:n=null}={}){let r=0,o=await ao.readdir(t,{withFileTypes:!0}).catch(()=>[]);for(let s of o){if(TO.has(s.name)||n?.has(s.name))continue;let a=Mt.join(t,s.name);s.isDirectory()?r+=await Ei(a,Mt.join(e,s.name)):s.isFile()&&(r+=await kd(a,Mt.join(e,s.name))?1:0)}return r}async function Kb({stateDir:t=qb,dest:e=process.env.BOSTRAT_SYNC_DIR||`${qb}-sync`,remote:n=process.env.BOSTRAT_STATE_REMOTE||null,tracks:r=null}={}){try{await ao.mkdir(e,{recursive:!0});let o=0;o+=await kd(Mt.join(t,"tracks.json"),Mt.join(e,"tracks.json"))?1:0,o+=await kd(Mt.join(t,"events.jsonl"),Mt.join(e,"events.jsonl"))?1:0,o+=await Ei(Mt.join(t,"events"),Mt.join(e,"events")),o+=await Ei(Mt.join(t,"memory"),Mt.join(e,"memory"));let s=new Set(["mocks"]);for(let u of r||[])!u?.cwd||!u?.id||(o+=await Ei(Mt.join(u.cwd,".bostrat"),Mt.join(e,"workspaces",u.id,".bostrat"),{skip:s}));let a=(...u)=>vO("git",["-C",e,...u]);bO.existsSync(Mt.join(e,".git"))||(await a("init","-q"),await a("config","user.email","agent@bostrat.local"),await a("config","user.name","bostrat state sync")),n&&/^https:\/\/github\.com\//i.test(n)&&(await a("config","--get","credential.useHttpPath").then(()=>!0).catch(()=>!1)||await Yi(e).catch(()=>{}));let i=!1,{stdout:c}=await a("status","--porcelain");c.trim()&&(await a("add","-A"),await a("commit","-q","-m",`state sync ${new Date().toISOString()}`),i=!0);let l=!1;if(n&&(i||Ai))try{await a("push","-q",n,"HEAD:refs/heads/main"),l=!0,Ai&&console.log("[state-sync] push recovered \u2014 off-box copy is current again"),Ai=!1,bd=null}catch(u){Ai=!0;let f=(u?.stderr||u?.message||String(u)).trim().split(`
|
|
392
|
+
`).slice(-3).join(" | ").slice(0,300);f!==bd&&(bd=f,console.warn(`[state-sync] PUSH FAILING (retrying each cycle; off-box copy is STALE): ${f}`))}return{staged:o,committed:i,pushed:l}}catch(o){return{staged:0,committed:!1,pushed:!1,error:o?.message||String(o)}}}import{execFile as AO}from"node:child_process";import{promisify as EO}from"node:util";var _O=EO(AO),IO=12e4,RO=6e4;function $O(t){return`refs/bostrat/wip/${t}`}function zb(t=process.env){let e=String(t.BOSTRAT_WIP_SYNC_MS??"").trim().toLowerCase();return e!=="0"&&e!=="off"&&e!=="false"}function OO(t=process.env){let e=Number(t.BOSTRAT_WIP_SYNC_MS);return Number.isFinite(e)&&e>0?e:IO}var _i=(t,e,n=15e3)=>_O("git",["-C",t,...e],{timeout:n,maxBuffer:4*1024*1024}),Gb=new Map,Vb=new Set;async function MO(t,{log:e=console}={}){let{id:n,cwd:r}=t??{};if(!n||!r)return{pushed:!1,reason:"no-workspace"};try{await _i(r,["rev-parse","--git-dir"])}catch{return{pushed:!1,reason:"not-a-repo"}}try{await _i(r,["remote","get-url","origin"])}catch{return Vb.has(r)||(Vb.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 Zo(r,"bostrat: wip snapshot"),{stdout:s}=await _i(r,["rev-parse",`${o}^{tree}`]),a=s.trim();if(Gb.get(r)===a)return{pushed:!1,reason:"unchanged"};let i=$O(n);return await _i(r,["push","--force","origin",`${o}:${i}`],RO),Gb.set(r,a),await R({id:n,cwd:r},{type:"code.pushed",actor:"orchestrator",payload:{ref:i,sha:o,wip:!0,ts:Date.now()}}).catch(()=>{}),e.log?.(`[wip-sync] ${n}: pushed ${o.slice(0,7)} \u2192 ${i}`),{pushed:!0,sha:o,ref:i}}catch(o){return e.log?.(`[wip-sync] ${n}: push failed (will retry): ${String(o?.message||o).split(`
|
|
393
|
+
`)[0]}`),{pushed:!1,reason:"push-failed"}}}function Jb({listTracks:t,sweepMs:e=OO(),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 MO(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 CO}from"node:child_process";import{promisify as NO}from"node:util";import{randomUUID as PO}from"node:crypto";import BO from"node:fs";import DO from"node:os";import jO from"node:path";var $n=NO(CO),Ii=t=>new Promise(e=>setTimeout(e,t)),On=pc,xd=process.env.BOSTRAT_USAGE_PROBE_CWD||jO.join(DO.homedir(),".bostrat","usage-probe"),LO=Number(process.env.BOSTRAT_USAGE_IDLE_REAP_MS)||5*6e4,FO=Number(process.env.BOSTRAT_USAGE_PANEL_TIMEOUT_MS)||15e3,UO=Number(process.env.BOSTRAT_USAGE_READY_TIMEOUT_MS)||45e3,Xb=Number(process.env.BOSTRAT_USAGE_CACHE_MS)||2e4,Rn=null,vs=null,Ts=null;async function HO(){try{return await $n(M,P("has-session","-t",On)),!0}catch{return!1}}function WO(){Rn&&clearTimeout(Rn),Rn=setTimeout(()=>qO().catch(()=>{}),LO),Rn.unref&&Rn.unref()}async function qO(){Rn&&(clearTimeout(Rn),Rn=null);try{await $n(M,P("kill-session","-t",On))}catch{}}async function KO(){let t=Date.now()+UO;for(;;){let e=await Sd().catch(()=>"");if(/for agents|for shortcuts|bypass permissions on/i.test(e)){await Ii(500);return}if(Date.now()>t)throw new Error("usage probe didn't reach the prompt (trust/onboarding may be blocking)");await Ii(500)}}async function GO(){if(await HO())return;if(!await nn())throw new Error("host tmux server not running \u2014 start globo-tmux.service");BO.mkdirSync(xd,{recursive:!0}),await Ye(xd);let t=PO();await $n(M,P("new-session","-d","-s",On,"-c",xd,"-x","200","-y","60",...fc()));let e=`${dc} --session-id ${t}`;await $n(M,P("send-keys","-t",On,e,"Enter")),await KO()}async function Sd(){let{stdout:t}=await $n(M,P("capture-pane","-t",On,"-p"));return t}async function VO(){await GO(),await $n(M,P("send-keys","-t",On,"-l","/usage")),await $n(M,P("send-keys","-t",On,"Enter"));let t=Date.now()+FO,e="";for(;await Ii(400),e=await Sd(),!(/Current\s+(session|week)/i.test(e)||/\d+%\s*used/i.test(e)||Date.now()>t););return await Ii(900),e=await Sd(),await $n(M,P("send-keys","-t",On,"Escape")).catch(()=>{}),WO(),e}function zO(t){let e=String(t||"").split(`
|
|
394
|
+
`).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 JO(t){let e=String(t||"").split(`
|
|
395
|
+
`).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 Yb({fresh:t=!1}={}){return!t&&vs&&Date.now()-vs.at<Xb?vs.data:Ts||(Ts=(async()=>{try{let e=await VO(),n=zO(e),r={...n,contributors:JO(e),subscription:!!(n.session||n.week||n.weekModels),capturedAt:Date.now()};return vs={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 vs={at:Date.now()-(Xb-3e3),data:n},n}finally{Ts=null}})(),Ts)}var XO=262144;function YO(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 Zb(t){if(!t||typeof t!="object")return null;let e=Number(t.used_percent);return Number.isFinite(e)?{pct:Math.round(e),resets:YO(t.resets_in_seconds)}:null}function ZO(t){let e=String(t||"").split(`
|
|
396
|
+
`),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:Zb(i.primary),week:Zb(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 vd(t){let e={provider:"codex",supported:!0,contributors:null};try{let n=await Jn(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 Ut(n.file,XO),o=ZO(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 Qb(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 Ri({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 vd(i);if(c.subscription){a=c;break}(!a||a.error&&!c.error)&&(a=c)}r.push(a||await vd(null))}return n.has("cursor")&&r.push(await Ri({agent:"cursor"})),{providers:r,capturedAt:Date.now()}}async function Ri(t,{fresh:e=!1}={}){let n=t?.agent||"claude";return n==="codex"?vd(t):n==="cursor"?{provider:"cursor",supported:!1,subscription:!1,session:null,week:null,contributors:null,capturedAt:Date.now()}:{provider:"claude",supported:!0,...await Yb({fresh:e})}}function QO(t=process.env){return!!(Qt(t)&&t.BOSTRAT_PLANE_TOKEN)}var tM=t=>({authorization:`Bearer ${t.BOSTRAT_PLANE_TOKEN||""}`}),$i=(t,e)=>Object.assign(new Error(t),{status:e});async function tk(t,e){if(!QO(e))throw $i("node is not plane-linked \u2014 Gmail unavailable",503);let n;try{n=await fetch(`${Qt(e)}${t}`,{headers:tM(e)})}catch(o){throw $i(`plane unreachable \u2014 Gmail unavailable (${o?.message||o})`,503)}let r=await n.json().catch(()=>({}));if(!n.ok)throw $i(r?.error||`plane replied ${n.status}`,n.status);return r}async function ek(t,{max:e=10,env:n=process.env}={}){let r=new URLSearchParams({q:t||""});return e&&r.set("max",String(e)),(await tk(`/v1/gmail/messages?${r.toString()}`,n)).messages||[]}async function nk(t,{env:e=process.env}={}){if(!t)throw $i("message id required",400);return(await tk(`/v1/gmail/messages/${encodeURIComponent(t)}`,e)).message}function Td(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 eM(t){let[e,n]=t.split("/"),r=Td(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 rk(t){return(t||"").split(",").map(e=>e.trim()).filter(Boolean).map(eM).filter(Boolean)}function nM(t){let e=t.headers["fly-client-ip"];return typeof e=="string"&&e.trim()?e.trim():(t.socket&&t.socket.remoteAddress||"").replace(/^::ffff:/,"")}function ok(t){let e=rk(t),n=String(t||"").trim().toLowerCase()==="open";return{open:n,clientIp:nM,allowed(r){if(n)return!0;let o=Td((r||"").replace(/^::ffff:/,""));return o==null?!1:e.some(s=>s(o))}}}function sk(t,e){let n=rk(t);return{active:n.length>0,allows(r){if(!n.length)return!1;let o=(r.socket&&r.socket.remoteAddress||"").replace(/^::ffff:/,""),s=Td(o);if(s==null||!n.some(i=>i(s)))return!1;let a=e(r);return!!(a&&a.view)}}}import ak from"node:crypto";import Oi from"node:fs";import rM from"node:path";function ik(t=process.env,{stateDir:e=ks,log:n=console}={}){let r=String(t.BOSTRAT_NODE_API_TOKEN||"").trim();if(r)return r;let o=rM.join(e,"api-token");try{let a=Oi.readFileSync(o,"utf8").trim();if(a)return a}catch{}let s=ak.randomBytes(32).toString("base64url");try{Oi.mkdirSync(e,{recursive:!0}),Oi.writeFileSync(o,s+`
|
|
397
|
+
`,{mode:384}),Oi.chmodSync(o,384)}catch(a){n.error?.(`[bostrat-agent] could not persist the node API token: ${a?.message||a}`)}return s}function As(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 ck(t,e){if(!t||!e)return!1;let n=Buffer.from(String(t)),r=Buffer.from(String(e));return n.length!==r.length?!1:ak.timingSafeEqual(n,r)}function lk(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 oM=[/^\/api\/github\/token\b/],sM=t=>oM.some(e=>e.test(t));function uk({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=lk(o);return ck(c,t)?a():sM(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||As(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 dk(t,e,{enforce:n=!0}={}){return!n||ck(lk(t),e)?!0:As(t)}import aM from"node:http";import iM from"node:net";function fk(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 Mi(t){let e=fk(t.headers.host);return e?{slug:e,view:Gc(e)}:null}var cM=t=>`<!doctype html><meta charset="utf-8">
|
|
400
398
|
<meta http-equiv="refresh" content="3">
|
|
401
399
|
<title>${t.name} \u2014 starting</title>
|
|
402
400
|
<body style="font-family:system-ui;background:#0b0e14;color:#dce3f0;display:grid;place-items:center;height:100vh;margin:0">
|
|
403
401
|
<div style="text-align:center"><p style="font-size:2rem;margin:0">\u23F3</p>
|
|
404
|
-
<p><b>${t.name}</b> is waking up\u2026</p><p style="opacity:.6">auto-refreshing</p></div>`;function
|
|
402
|
+
<p><b>${t.name}</b> is waking up\u2026</p><p style="opacity:.6">auto-refreshing</p></div>`;function pk(t,e,n){let r=Mi(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(cM(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=aM.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 mk(t,e,n){let r=Mi(t);if(!r)return!1;let{view:o,slug:s}=r;if(!o||o.state!=="live"&&o.state!=="unhealthy")return e.write(`HTTP/1.1 404 Not Found\r
|
|
405
403
|
\r
|
|
406
404
|
bostrat: no live view "${s}"\r
|
|
407
|
-
`),e.destroy(),!0;let a=
|
|
405
|
+
`),e.destroy(),!0;let a=iM.connect(o.port,"127.0.0.1",()=>{let c=[`${t.method} ${t.url} HTTP/1.1`];for(let[l,u]of Object.entries(t.headers))l!=="host"&&c.push(`${l}: ${Array.isArray(u)?u.join(", "):u}`);c.push(`host: 127.0.0.1:${o.port}`),a.write(c.join(`\r
|
|
408
406
|
`)+`\r
|
|
409
407
|
\r
|
|
410
|
-
`),n?.length&&a.write(n),e.pipe(a),a.pipe(e)}),i=()=>{a.destroy(),e.destroy()};return a.on("error",i),e.on("error",i),!0}function
|
|
411
|
-
`)){let s=o.trim();if(!s)continue;if(s.startsWith("#")){n==null&&(n=s.replace(/^#+\s*/,"").trim().slice(0,BO)||null);continue}let a=/^[-*]\s+(.*)$/.exec(s);if(!a)continue;let i=a[1].trim(),c=null,l=new RegExp(`^\\[([^\\]]{1,${DO}})\\]\\s+(.*)$`).exec(i);if(l&&(c=l[1].trim(),i=l[2].trim()),!!i){if(i.length>ok)throw new Error(`bullet ${r.length+1} is ${i.length} chars \u2014 over the ${ok}-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>rk)throw new Error(`${r.length} bullets \u2014 over the ${rk}-bullet cap; group related changes into one line`);return{title:n||null,bullets:r}}async function ak(t,{repos:e=hd()}={}){let n=new Set(e);return(await ye()).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 ik(t,{md:e,day:n=null}){let r=FO(e),o=n==null||n===""?LO():String(n);if(!jO.test(o)||Number.isNaN(Date.parse(o)))throw new Error(`invalid day "${n}" \u2014 expected YYYY-MM-DD`);let s=await R({id:t.id,cwd:t.cwd},{type:"changelog.published",actor:"agent",source:"orchestrator",boxId:t.leadBox||null,payload:{ts:Date.now(),day:o,title:r.title,bullets:r.bullets}});return{day:o,title:r.title,bullets:r.bullets,eventId:s.id}}import{execFile as UO}from"node:child_process";import{promisify as HO}from"node:util";var gd=HO(UO),ck=3e4,WO=30*6e4,qO=ck,KO=10*6e4,GO=10080*6e4,VO=60*6e4,zO=8,JO=1440*6e4,XO=3,lk="number,url,title,headRefName,headRefOid,state,mergedAt,closedAt",wd=60*6e4,YO=15*6e4,vi=new Map,Ti=new Map,ZO=/could not resolve|not found|no pull requests?/i,QO=t=>ZO.test(String(t?.message||t||""));function tC(t,e){return[t.ciFix?.delegatedAt,t.reconcile?.delegatedAt,t.ciFixAwait?.since,t.reconcileAwait?.since].some(n=>n&&e-n<wd)}var uk={fast:qO,slow:KO,decay:VO};function dk(t,e){return t.armedAt&&e-t.armedAt<WO||tC(t,e)?"fast":e-(t.firstSeen||0)>GO?"decay":"slow"}function eC(t,e){return uk[dk(t,e)]}function ks(t,e,n,r){let o=t.lanes?.get(H(e.repo,e.number))||{};return Ja({change:n,repo:e.repo,number:Number(e.number),githubAt:r||null,lane:o.lane||null,intervalMs:o.intervalMs||null,source:"watch"})}async function nC(t,e){Le("watch");try{let{stdout:n}=await gd("gh",["pr","view",String(e),"--repo",t,"--json",lk],{timeout:3e4,maxBuffer:4194304,env:{...process.env,...await ae({slug:t})}});return JSON.parse(n||"null")}catch(n){throw fn(n)&&pn(n),n}}async function rC(t){Le("watch");try{let{stdout:e}=await gd("gh",["pr","list","--repo",t,"--state","all","--limit","100","--json",lk],{timeout:3e4,maxBuffer:8388608,env:{...process.env,...await ae({slug:t})}}),n=new Map;for(let r of JSON.parse(e||"[]"))n.set(Number(r.number),r);return n}catch(e){throw fn(e)&&pn(e),e}}async function oC(){let t=new Map;for(let e of await ot().catch(()=>[]))t.set(e.id,e);return t}async function sC(t,e){await R({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"user",source:"orchestrator",boxId:null,payload:{ts:Date.now(),change:"merged",...e}})}async function aC(t,e){let n=await Jn(t);return n?Lr(n,e):null}async function iC(t,e,n){let r=await Jn(t);if(!r)throw new Error(`no local checkout for ${t}`);return Fr(r,e,{method:n})}async function cC(t,e){let n=await Jn(t);if(!n)throw new Error(`no local checkout for ${t}`);return Qo(n,e)}async function lC(t,e){await R({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"user",source:"orchestrator",boxId:null,payload:{ts:Date.now(),change:"auto-canceled",...e}})}async function eo(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 uC=10*6e4;async function dC(t,e,n){let r=t.autoMerge?.armedAt||0;if(r&&(n.now??Date.now())-r>JO){await eo(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 eo(t,o,e,n,`check${o.checks.failed===1?"":"s"} failed: ${a}`);return}if(o.mergeable==="CONFLICTING"){await eo(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<uC)return;await eo(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 eo(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 eo(t,o,e,n,String(a?.message||a).trim()||"merge failed");return}await fk(t,{...o,state:"MERGED"},e,n,{change:"auto-merged",githubAt:o.checks.settledAt||null})}}async function gs(t,e,n){await R({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"user",source:"orchestrator",boxId:null,payload:{ts:Date.now(),change:e,...n}})}async function fC(t,e){try{return await gd("git",["-C",t.cwd,"cat-file","-e",`${e}^{commit}`],{timeout:1e4}),!0}catch{return!1}}async function ys(t,e,n,r=()=>!0){return(await D(t).catch(()=>[])).some(s=>s.type==="pr.state.changed"&&s.payload?.change===e&&s.payload?.number===Number(n)&&r(s))}var bs=(t,e)=>({number:Number(t.number),url:e?.url||t.url,title:e?.title||t.title,branch:e?.headRefName||t.headRef,repo:t.repo});async function pC(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 Aa(t.repo,t.number,{headSha:s}).catch(()=>{}),t.reconcile?.headSha&&s!==t.reconcile.headSha&&await _a(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<YO;await dh(t.repo,t.number,s).catch(()=>{}),t.baseFoldAt&&await uh(t.repo,t.number).catch(()=>{}),!i&&!c&&!l&&a&&!await r.localHasCommit(a,s).catch(()=>!1)&&!await ys(a,"pushed",t.number,f=>f.payload?.sha===s)&&await gs(a,"pushed",{...bs(t,e),sha:s}).catch(()=>{})}if(t.ciFixAwait)if(o-(t.ciFixAwait.since||0)>wd)await Ea(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 Ea(t.repo,t.number).catch(()=>{});else if(i.checks.total>0&&i.checks.pending===0){let c=ks(r,t,"ci-fixed",i.checks.settledAt||null),l=t.ciFixAwait.since||0;a&&!await ys(a,"ci-fixed",t.number,u=>(u.payload?.ts||0)>=l)&&await gs(a,"ci-fixed",{...bs(t,e),passed:i.checks.passed,githubAt:c.githubAt,latencyMs:c.latencyMs,lane:c.lane}).catch(()=>{}),await Ea(t.repo,t.number).catch(()=>{})}}}if(t.reconcileAwait)if(o-(t.reconcileAwait.since||0)>wd)await Ia(t.repo,t.number).catch(()=>{});else{let i=await r.detail(t.repo,t.number).catch(()=>null);if(i){if(i.mergeable==="CONFLICTING")await Ia(t.repo,t.number).catch(()=>{});else if(i.mergeable==="MERGEABLE"){ks(r,t,"reconciled",null);let c=t.reconcileAwait.since||0;a&&!await ys(a,"reconciled",t.number,l=>(l.payload?.ts||0)>=c)&&await gs(a,"reconciled",{...bs(t,e),base:i.baseRefName||null}).catch(()=>{}),await Ia(t.repo,t.number).catch(()=>{})}}}}async function mC(t,e,n,r){let o=ks(r,t,"closed",Date.parse(e?.closedAt||"")||null),s=n.get(t.trackId);s&&!await ys(s,"closed",t.number)&&await gs(s,"closed",{...bs(t,e),githubAt:o.githubAt,latencyMs:o.latencyMs,lane:o.lane}).catch(()=>{}),await r.notifyIfResolved(t.repo,t.number,"closed").catch(()=>{}),await r.mark(t.repo,t.number,"closed").catch(()=>{})}async function hC(t,e,n){ks(n,t,"lost",null);let r=e.get(t.trackId);r&&!await ys(r,"lost",t.number)&&await gs(r,"lost",bs(t,null)).catch(()=>{}),await n.notifyIfResolved(t.repo,t.number,"lost").catch(()=>{}),await n.mark(t.repo,t.number,"lost").catch(()=>{})}async function fk(t,e,n,r,o={}){let s=ks(r,t,o.change||"merged",o.githubAt??(Date.parse(e.mergedAt||"")||null)),a=n.get(t.trackId);a&&((await D(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 wC(t={}){let{now:e=Date.now(),auth:n=hu,view:r=nC,list:o=rC,links:s=ph,trackMap:a=oC,emitMerged:i=sC,mark:c=cn,archiveIfArmed:l=hn,notifyIfArmed:u=wn,notifyIfResolved:f=Wu,batchThreshold:d=XO,detail:p=aC,merge:m=iC,updateBranch:g=cC,lookup:y=$t,disarm:b=jo,emitDisarmed:S=lC,localHasCommit:E=fC}=t;if(!(await n().catch(()=>null))?.authenticated||Ha()>0)return 0;let $=(await s().catch(()=>[])).filter(w=>e-(vi.get(H(w.repo,w.number))||0)>=eC(w,e));if(!$.length)return 0;let j=Date.now(),v=new Map,C=new Map;for(let w of $){let h=dk(w,e);v.set(H(w.repo,w.number),{lane:h,intervalMs:uk[h]}),vi.set(H(w.repo,w.number),e),C.has(w.repo)||C.set(w.repo,[]),C.get(w.repo).push(w)}let q=await a().catch(()=>new Map),rt={now:e,lanes:v,emitMerged:i,mark:c,archiveIfArmed:l,notifyIfArmed:u,notifyIfResolved:f,detail:p,merge:m,updateBranch:g,lookup:y,disarm:b,emitDisarmed:S,localHasCommit:E};for(let[w,h]of C){let T,_=new Set;if(h.length>d)T=await o(w).catch(()=>null);else{T=new Map;for(let O of h)try{let I=await r(w,O.number);I?.number!=null&&T.set(Number(I.number),I)}catch(I){QO(I)&&_.add(H(O.repo,O.number))}}if(T)for(let O of h){let I=H(O.repo,O.number),A=T.get(Number(O.number));if(!A){if(_.has(I)){let G=(Ti.get(I)||0)+1;Ti.set(I,G),G>=zO&&(await hC(O,q,rt),Ti.delete(I),vi.delete(I))}continue}Ti.delete(I),A.state==="MERGED"||A.state==="CLOSED"?(A.state==="MERGED"?await fk(O,A,q,rt):await mC(O,A,q,rt),vi.delete(I)):(await pC(O,A,q,rt).catch(()=>{}),O.autoMerge&&await dC(O,q,rt).catch(()=>{}))}}let st={};for(let{lane:w}of v.values())st[w]=(st[w]||0)+1;return console.log(`[pr-watch] probed ${$.length} link(s) (${Object.entries(st).map(([w,h])=>`${w} ${h}`).join(", ")}) in ${Date.now()-j}ms`),$.length}function pk(){let t=!1;setInterval(async()=>{if(!t){t=!0;try{await wC()}catch{}finally{t=!1}}},ck)}import Ke from"node:fs/promises";import no from"node:path";import kk from"node:os";import mk from"node:fs/promises";import Ai from"node:path";import{execFile as gC}from"node:child_process";import{promisify as yC}from"node:util";import{fileURLToPath as bC}from"node:url";var kC=yC(gC),wk=Ai.dirname(bC(import.meta.url)),xC=Ai.resolve(wk,"..","..","client","dist"),SC="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",vC=new Map([...SC].map((t,e)=>[t,e]));function TC(t){let e=[],n=0,r=0;for(let o of t){let s=vC.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 AC(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=TC(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 EC(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 hk=new Map;async function _C(t){let e;try{e=await mk.stat(t)}catch{return null}let n=hk.get(t);if(n&&n.mtimeMs===e.mtimeMs)return n;try{let r=JSON.parse(await mk.readFile(t,"utf8")),o={mtimeMs:e.mtimeMs,sources:r.sources||[],lines:AC(r.mappings||"")};return hk.set(t,o),o}catch{return null}}var yd=null;function IC(){return yd||(yd=process.env.BOSTRAT_RELEASE?Promise.resolve(process.env.BOSTRAT_RELEASE):kC("git",["rev-parse","--short","HEAD"],{cwd:Ai.resolve(wk,"..","..")}).then(t=>t.stdout.trim()).catch(()=>null)),yd}var RC=/(?:https?:\/\/[^\s()]*)?\/assets\/([^\s()/]+\.js):(\d+):(\d+)/g;async function $C(t,{distDir:e=xC}={}){let n=[],r=0,o=!1;for(let s of t.matchAll(RC)){let[a,i,c,l]=s,u=await _C(Ai.join(e,"assets",`${i}.map`)),f=u?EC(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 gk(t,e={}){try{if(!t?.error?.stack||!t.release||t.release==="unknown")return t;let n=e.release??await IC();if(!n||t.release!==n)return t;let{stack:r,symbolicated:o}=await $C(t.error.stack,e);o&&(t.error.stack=r,t.error.symbolicated=!0)}catch{}return t}var OC=()=>process.env.BOSTRAT_STATE_DIR||no.join(process.env.HOME||kk.tmpdir(),".bostrat"),An=()=>no.join(OC(),"telemetry"),xk=()=>no.join(An(),".relay-offsets.json"),CC=50,MC=14,NC=3e4,yk=500,Gt=(t,e)=>typeof t=="string"?t.slice(0,e):"";function PC(t,e){if(!t||typeof t!="object")return null;let n=Gt(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:Gt(t.anonId,64),sessionId:Gt(t.sessionId,64),userId:Gt(t.userId,64),release:Gt(t.release,64),appVersion:Gt(t.appVersion,32),route:Gt(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:Gt(t.error.kind,256),message:Gt(t.error.message,8192),stack:Gt(t.error.stack,32768),fingerprint:Gt(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:Gt(s?.n,64),r:Gt(s?.r,128)}))),o}var Sk=t=>new Date(t).toISOString().slice(0,10),bk=0;async function vk(t,e=Date.now()){let r=(Array.isArray(t?.events)?t.events.slice(0,CC):[]).map(s=>PC(s,e)).filter(Boolean);if(!r.length)return 0;for(let s of r)s.error?.stack&&await gk(s);await Ke.mkdir(An(),{recursive:!0});let o=no.join(An(),`${Sk(e)}.ndjson`);return await Ke.appendFile(o,r.map(s=>JSON.stringify(s)+`
|
|
412
|
-
`).join(""),"utf8"),e-
|
|
413
|
-
`).filter(Boolean),f=[];for(let d of u)try{f.push(JSON.parse(d))}catch{}for(let d=0;d<f.length;d+=yk){let p=f.slice(d,d+yk).map(g=>DC(g)),m=await t(`${e.url.replace(/\/$/,"")}/v1/logs`,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${e.ingestKey}`},body:JSON.stringify({events:p})}).catch(()=>null);if(!m||!m.ok)return{sent:o,disabled:!1};o+=p.length}r[s]=i+l+1,await Ek(r)}return{sent:o,disabled:!1}}var bd=null,kd=!1;function _k(){bd||(bd=setInterval(async()=>{if(!kd){kd=!0;try{await jC()}catch(t){console.error("[telemetry] relay failed:",t?.message||t)}finally{kd=!1}}},NC),bd.unref?.())}import Ii from"node:fs/promises";import Ck from"node:path";import LC from"node:os";var Mk=()=>process.env.BOSTRAT_STATE_DIR||Ck.join(process.env.HOME||LC.tmpdir(),".bostrat"),Nk=()=>Ck.join(Mk(),"dictation-glossary.json"),FC=100,UC=500,HC=["bostrat","globopi","codex","tmux","worktree","Deepgram","Linear","Firecracker","Tigris","WireGuard","misous","postyx","cedar","gerbil","Anthropic","Opus","Haiku","Sonnet","keyterm","monorepo","dogfood"],Ri=null,xd=null,Sd=Promise.resolve();function Ik(){let t={},e=Date.now();for(let n of HC)t[n.toLowerCase()]={term:n,count:1,lastUsed:e,source:"seed"};return t}async function Pk(){return Ri||(xd??=Ii.readFile(Nk(),"utf8").then(t=>JSON.parse(t)?.terms??Ik()).catch(()=>Ik()).then(t=>Ri=t).finally(()=>xd=null),xd)}async function Rk(){let t=JSON.stringify({terms:Ri},null,2),e=Nk(),n=`${e}.${process.pid}.tmp`;await Ii.mkdir(Mk(),{recursive:!0}),await Ii.writeFile(n,t),await Ii.rename(n,e)}function WC(){return Sd=Sd.then(Rk,Rk),Sd.catch(()=>{})}async function vd(){return Object.values(await Pk()).sort((e,n)=>n.count-e.count||n.lastUsed-e.lastUsed)}var qC=t=>Math.max(1,Math.ceil(t.length/4));async function KC(){let t=[],e=0;for(let{term:n}of await vd()){let r=qC(n);if(!(e+r>UC)&&(t.push(n),e+=r,t.length>=FC))break}return t}async function GC(t,e){let n=await Pk(),r=t.toLowerCase(),o=n[r];n[r]={term:t,count:(o?.count??0)+1,lastUsed:Date.now(),source:o?.source??e}}var $k=t=>(t||"").split(/\s+/).map(e=>e.replace(/^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu,"")).filter(Boolean),_i=t=>t.toLowerCase();function Ok(t){return!new RegExp("\\p{L}","u").test(t)||t.length<3?!1:/^[a-z]+$/.test(t)?t.length>=4:!0}async function Bk(t,e){let n=$k(t),r=$k(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]=_i(n[u])===_i(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;)_i(n[c])===_i(r[l])?(c++,l++):a[c+1][l]>=a[c][l+1]?c++:(Ok(r[l])&&i.add(r[l]),l++);for(;l<s;l++)Ok(r[l])&&i.add(r[l]);if(!i.size)return[];for(let u of i)await GC(u,"learned");return await WC(),[...i]}var xs=null;function VC(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 Dk(){if(!xs)return null;let t=await Oo();if(!t||t.apiKey.slice(-6)!==xs.keyTail)return null;let{keyTail:e,...n}=xs;return n}async function jk(t,e){let n=await Oo();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 KC())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=VC(o.status,i);c.persistent&&(xs={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 xs=null,{transcript:(await o.json())?.results?.channels?.[0]?.alternatives?.[0]?.transcript??""}}var Td={"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 zC(t){if(!t)return null;let e=String(t).trim();if(Td[e])return e;let n=null;for(let r of Object.keys(Td))e.startsWith(r)&&(!n||r.length>n.length)&&(n=r);return n}function JC(t,e={}){let n=Td[zC(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 XC(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 Ss=null;async function YC(){if(!Ss)return null;let t=await ka();if(!t||t.apiKey.slice(-6)!==Ss.keyTail)return null;let{keyTail:e,...n}=Ss;return n}async function ZC(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=XC(e.status,o);s.persistent&&(Ss={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}Ss=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 QC={openai:ZC};async function Lk(){let t=await ka();return t?{configured:!0,provider:t.provider,model:t.model,voice:t.voice,problem:await YC()}:{configured:!1}}async function Fk(){let t=await ka();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 QC[t.provider](t);return{provider:t.provider,model:t.model,voice:t.voice,...e}}async function Uk(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=JC(e,s),i=await R(t,{type:"cost.recorded",actor:"user",provider:"openai-realtime",payload:{model:e||"unknown",usd:a,tokens:{input:s.textIn+s.audioIn,output:s.textOut+s.audioOut,cacheRead:s.textInCached+s.audioInCached,cacheWrite:0},voice:s,seconds:Math.max(0,Math.round(Number(r)||0)),source:"voice-chat"}});return{usd:a,seq:i.seq}}var Vk=lt.dirname(eM(import.meta.url)),Hk=Number(process.env.PORT)||3362,$i=mb(process.env),zk=Number(process.env.BOSTRAT_AUTO_TRIGGER_PCT)||80,ro=Kb(process.env.BOSTRAT_ALLOW_IPS),Rd=Gb(process.env.BOSTRAT_VIEW_FRONT_CIDRS,Si),Jk=zb(),Xk=process.env.BOSTRAT_ALLOW_LAN_TOKENLESS!=="1",x=Se();x.disable("x-powered-by");x.use((t,e,n)=>{if(ws(t)||ro.allowed(ro.clientIp(t))||Rd.allows(t))return n();e.status(403).type("text/plain").send("bostrat: access restricted to the allowlist")});x.use(tk);x.use(Yb({token:Jk,enforce:Xk}));x.use("/api/mocks",Se.json({limit:"1mb"}));x.use("/api/mocks",Se.raw({type:"image/png",limit:"24mb"}));x.use("/api/runbooks",Se.json({limit:"1mb"}));x.use("/api/telemetry",Se.json({limit:"256kb"}));x.use("/api/hook",Se.json({limit:"2mb"}));x.use("/api/dictation",Se.raw({type:["audio/*","video/mp4","application/octet-stream"],limit:"32mb"}));x.use("/api/tracks/:id/upload",Se.raw({type:()=>!0,limit:"25mb"}));x.use(Se.json({limit:"64kb"}));x.get("/api/health",async(t,e)=>{e.json({ok:!0,tmuxSocket:Ut,hostTmuxAlive:await Ye(),autoTriggerPct:zk})});x.get("/api/sessions",async(t,e)=>{e.json({sessions:await Rn()})});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 $n(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(!wt(n))return e.status(400).json({error:"invalid session name"});if(!await Z(n))return e.status(404).json({error:"session not found"});try{await ce(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(!wt(n))return e.status(400).json({error:"invalid session name"});let r=await Z(n);if(!r)return e.status(404).json({error:"session not found"});let o=await bo(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(!wt(n))return e.status(400).json({error:"invalid session name"});try{let r=await Ep(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=oa(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 Wk=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>Wk){let s=new Set([...r].sort((a,i)=>(i.archivedAt||0)-(a.archivedAt||0)).slice(0,Wk).map(a=>a.id));o=n.filter(a=>!a.archivedAt||s.has(a.id))}e.json({tracks:o,archivedTotal:r.length,agents:oi,agentAvailability:await Bu(),tickets:await Sa()})});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 xb(n);if(r.cursorUsage=du(),t.query.format==="text")return e.type("text/plain").send(Sb(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 Ab(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 $b(await ot(),{window:t.query.window,from:t.query.from,to:t.query.to});n.cursorUsage=du(),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 Fb(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 bh(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 kh(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 qy()})}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 Nd(t.get(_s),{byToken:ky,legacy:()=>by(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 fb(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(!Tk(ro.clientIp(t)))return e.json({ok:!0,dropped:!0});let n=await vk(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 ve(),r=new Set((await Promise.all(n.map(a=>tt(a.path)))).filter(Boolean).map(a=>a.toLowerCase())),s=((await co()).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 Cs(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:Os(),calls:Qw(),latency:Ag()})});x.post("/api/github/refresh",(t,e)=>{qd(),e.json({ok:!0})});x.get("/api/gmail/search",async(t,e)=>{try{let n=await Hb(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 Wb(String(t.params.id))})}catch(n){e.status(n?.status||502).json({error:n?.message||String(n)})}});function $d(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 Oi({repo:t=null,number:e=null,branch:n=null}){let r=t?await tt(t):null;if(r&&e!=null){let s=await $t(r,e),a=s&&await Q(s.trackId);if(a)return a}return n?(await ot()).find(s=>s.workspace?.branch===n&&$d(s,t,r))??null:null}async function Yk({repo:t=null,number:e=null,branch:n=null}){let r=await Oi({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&&$d(a,t,o))??null}async function se(t,{number:e,url:n=null,title:r=null,branch:o=null,repo:s=null,track:a=null,extra:i={}}){let c=a||await Oi({repo:s,number:e,branch:o}).catch(()=>null);if(!c)return;let l=i.repo??(s?await tt(s).catch(()=>null):null);await R({id:c.id,cwd:c.cwd},{type:"pr.state.changed",actor:"user",source:"orchestrator",payload:{ts:Date.now(),change:t,number:e,url:n,title:r,branch:o??c.workspace?.branch??null,...l?{repo:l}:{},...i}}).catch(()=>{})}async function sM(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 $t(r,o.number))continue;let s=e.find(a=>a.workspace?.branch===o.headRefName&&$d(a,n.path,r));s&&await Wt({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 Cg();await sM(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 g=o||"merge";if(!Zo.has(g))throw new Error(`invalid merge method: ${g}`);let y=Number(r);if(!Number.isInteger(y)||y<=0)throw new Error("invalid PR number");let b=await tt((await Fe(n)).path);if(!b)throw new Error("repo has no GitHub remote");let S=await Oi({repo:n,number:y,branch:l}).catch(()=>null);if(!S)throw new Error("auto-merge needs a Track to watch this PR \u2014 none owns it");if(a&&await Qo(n,y),await Wt({repo:b,number:y,trackId:S.id,url:f,headRef:l,title:u,source:"matched"}).catch(()=>{}),!await Do(b,y,{merge:{method:g}}))throw new Error("couldn't record the auto-merge arm");return await Tr(b,y,i===!0),await Ra(b,y,c===!0&&i!==!0),ke(),await se("auto-armed",{number:y,url:f,title:u,branch:l,repo:n,track:S,...a?{extra:{updated:!0}}:{}}),e.json({ok:!0,number:y,auto:!0})}let d=await Fr(n,r,{method:o||"squash"});await se("merged",{number:d.number,url:f,title:u,branch:l,repo:n});let p=await tt(n).catch(()=>null),m=!1;if(p&&(i===!0&&await Tr(p,d.number,!0).catch(()=>{}),c===!0&&i!==!0&&await Ra(p,d.number,!0).catch(()=>{}),await wn(p,d.number).catch(()=>{}),m=await hn(p,d.number).catch(()=>!1),await cn(p,d.number,"merged").catch(()=>{})),i===!0&&!m){let g=await Oi({repo:n,number:d.number,branch:l}).catch(()=>null);g&&!g.archivedAt&&await Vr(g.id).catch(()=>{})}e.json(d)}catch(d){let p=d?.message||String(d),m=/invalid|unknown repo|required|needs a Track|no GitHub remote/i.test(p)?400:502;e.status(m).json({error:p})}});x.post("/api/git/prs/disarm-auto",async(t,e)=>{let{repo:n,number:r,headRefName:o,title:s,url:a}=t.body||{};try{let i=Number(r);if(!Number.isInteger(i)||i<=0)throw new Error("invalid PR number");let c=await tt((await Fe(n)).path).catch(()=>null),l=c?await jo(c,i).catch(()=>null):null;try{await yu(n,i)}catch(u){if(!l)throw u}ke(),await se("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 Bg(n,{method:r||"merge",onMerged:a=>{se("merged",{number:a.number,url:a.url,title:a.title,branch:a.headRefName,repo:n}),o&&wn(o,a.number).catch(()=>{}).then(()=>hn(o,a.number)).catch(()=>{}).then(()=>cn(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:Pg()})});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(!Zo.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 ye()).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 Tr(d.repo,d.number,o===!0).catch(()=>{}),await Ra(d.repo,d.number,s===!0&&o!==!0).catch(()=>{});let u=[],f=[];for(let d of l){let p={repo:d.repo,number:d.number,ok:!0,action:"armed"};u.push(p);try{let m=await Jn(d.repo);if(!m)throw new Error(`no local checkout for ${d.repo}`);let g=await Lr(m,d.number);f.push({r:p,l:d,repoPath:m,pr:g,plan:Mg(g,{update:r!==!1})})}catch(m){p.ok=!1,p.action="failed",p.reason=m?.message||String(m)}}for(let{r:d,l:p,repoPath:m,pr:g,plan:y}of Ng(f))try{if(y.action==="skip")d.action="skipped",d.reason=y.reason;else if(y.action==="merge")await Fr(m,p.number,{method:i}),await se("merged",{number:p.number,url:g.url,title:g.title,branch:g.headRefName,repo:m,track:c}),await wn(p.repo,p.number).catch(()=>{}),await hn(p.repo,p.number).catch(()=>{}),await cn(p.repo,p.number,"merged").catch(()=>{}),d.action="merged";else{if(y.action==="update-arm"&&(await Qo(m,p.number).catch(()=>{}),d.action="updated-armed"),!await Do(p.repo,p.number,{merge:{method:i}}))throw new Error("couldn't record the auto-merge arm");await se("auto-armed",{number:p.number,url:g.url,title:g.title,branch:g.headRefName,repo:m,track:c,...d.action==="updated-armed"?{extra:{updated:!0}}:{}})}}catch(b){d.ok=!1,d.action="failed",d.reason=b?.message||String(b)}if(ke(),!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 ye()).filter(a=>a.trackId===r.id&&a.state!=="merged"&&a.state!=="closed"),s=[];for(let a of o){let i=await jo(a.repo,a.number).catch(()=>null),c=await Jn(a.repo).catch(()=>null);c&&await yu(c,a.number).catch(()=>{}),i&&(await se("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}))}ke(),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=Dt(i.workspace),f,d=Array.isArray(a)?a.filter(Boolean):[];if(d.length)f=d.map(g=>{let y=u.find(b=>b.name===g||b.repo===g||b.base===g||b.dir===g);if(!y)throw new Error(`invalid repo "${g}" \u2014 not tagged on this Track`);return y});else if(f=await zo(i),!f.length)return e.status(409).json({error:"no tagged repo has work to ship"});let p=[];for(let g of f)try{let y=await ku(g.dir,{title:r,body:o,base:s}),b=g.repo||await tt(g.dir).catch(()=>null);b&&y.number&&await Wt({repo:b,number:y.number,trackId:i.id,url:y.url,headRef:i.workspace?.branch??null,title:r,source:"api"}),await se("opened",{number:y.number,url:y.url,title:r,track:i,branch:i.workspace?.branch,extra:{repo:b??g.name}}),p.push({repo:g.name,slug:b??null,ok:!0,url:y.url,number:y.number})}catch(y){p.push({repo:g.name,slug:g.repo??null,ok:!1,error:y?.message||String(y)})}let m=p.filter(g=>g.ok);return m.length?e.json({ok:!0,multi:!0,results:p,url:m[0].url,number:m[0].number}):e.status(502).json({error:p.map(g=>`${g.repo}: ${g.error}`).join("; "),results:p})}let c=await ku(i.cwd,{title:r,body:o,base:s}),l=await tt(i.cwd).catch(()=>null);l&&c.number&&await Wt({repo:l,number:c.number,trackId:i.id,url:c.url,headRef:i.workspace?.branch??null,title:r,source:"api"}),await se("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 zo(n),o=new Map(r.map(a=>[a.base,a])),s=Dt(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 Py(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 By(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 Lr(n,r),s=await tt(n).catch(()=>null),a=await Yk({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=Dt(a.workspace).find(b=>b.base===n||s&&b.repo===s)??null,c=a.workspace?.kind==="multi",l=await Dg(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 Lt.promises.mkdir(f,{recursive:!0}),await Lt.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
|
|
408
|
+
`),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 hk(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}.${lM(n.host)}/`}function lM(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 fk(e)&&Gc(n[0])?n.slice(1).join("."):e}var wk=10,gk=200,uM=120,dM=24,fM=/^\d{4}-\d{2}-\d{2}$/;function Ad(t=process.env){return(t.CHANGELOG_REPOS||"globocodes/bostrat").split(",").map(e=>e.trim()).filter(Boolean)}var pM=(t=Date.now())=>new Date(t).toISOString().slice(0,10);function yk(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 mM(t){if(typeof t!="string"||!t.trim())throw new Error("md (markdown string) required");let e=No(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(`
|
|
409
|
+
`)){let s=o.trim();if(!s)continue;if(s.startsWith("#")){n==null&&(n=s.replace(/^#+\s*/,"").trim().slice(0,uM)||null);continue}let a=/^[-*]\s+(.*)$/.exec(s);if(!a)continue;let i=a[1].trim(),c=null,l=new RegExp(`^\\[([^\\]]{1,${dM}})\\]\\s+(.*)$`).exec(i);if(l&&(c=l[1].trim(),i=l[2].trim()),!!i){if(i.length>gk)throw new Error(`bullet ${r.length+1} is ${i.length} chars \u2014 over the ${gk}-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>wk)throw new Error(`${r.length} bullets \u2014 over the ${wk}-bullet cap; group related changes into one line`);return{title:n||null,bullets:r}}async function bk(t,{repos:e=Ad()}={}){let n=new Set(e);return(await Se()).filter(r=>r.state==="merged"&&(r.resolvedAt??0)>=t&&n.has(r.repo)).sort((r,o)=>(r.resolvedAt??0)-(o.resolvedAt??0)).map(r=>({repo:r.repo,number:r.number,title:r.title||null,url:r.url||null,mergedAt:r.resolvedAt??null}))}async function kk(t,{md:e,day:n=null}){let r=mM(e),o=n==null||n===""?pM():String(n);if(!fM.test(o)||Number.isNaN(Date.parse(o)))throw new Error(`invalid day "${n}" \u2014 expected YYYY-MM-DD`);let s=await R({id:t.id,cwd:t.cwd},{type:"changelog.published",actor:"agent",source:"orchestrator",boxId:t.leadBox||null,payload:{ts:Date.now(),day:o,title:r.title,bullets:r.bullets}});return{day:o,title:r.title,bullets:r.bullets,eventId:s.id}}import{execFile as hM}from"node:child_process";import{promisify as wM}from"node:util";var _d=wM(hM),xk=3e4,gM=30*6e4,yM=xk,bM=10*6e4,kM=10080*6e4,xM=60*6e4,SM=8,vM=1440*6e4,TM=3,Sk="number,url,title,headRefName,headRefOid,state,mergedAt,closedAt",Ed=60*6e4,AM=15*6e4,Ci=new Map,Ni=new Map,EM=/could not resolve|not found|no pull requests?/i,_M=t=>EM.test(String(t?.message||t||""));function IM(t,e){return[t.ciFix?.delegatedAt,t.reconcile?.delegatedAt,t.ciFixAwait?.since,t.reconcileAwait?.since].some(n=>n&&e-n<Ed)}var vk={fast:yM,slow:bM,decay:xM};function Tk(t,e){return t.armedAt&&e-t.armedAt<gM||IM(t,e)?"fast":e-(t.firstSeen||0)>kM?"decay":"slow"}function RM(t,e){return vk[Tk(t,e)]}function Rs(t,e,n,r){let o=t.lanes?.get(V(e.repo,e.number))||{};return oi({change:n,repo:e.repo,number:Number(e.number),githubAt:r||null,lane:o.lane||null,intervalMs:o.intervalMs||null,source:"watch"})}async function $M(t,e){qe("watch");try{let{stdout:n}=await _d("gh",["pr","view",String(e),"--repo",t,"--json",Sk],{timeout:3e4,maxBuffer:4194304,env:{...process.env,...await ue({slug:t})}});return JSON.parse(n||"null")}catch(n){throw gn(n)&&yn(n),n}}async function OM(t){qe("watch");try{let{stdout:e}=await _d("gh",["pr","list","--repo",t,"--state","all","--limit","100","--json",Sk],{timeout:3e4,maxBuffer:8388608,env:{...process.env,...await ue({slug:t})}}),n=new Map;for(let r of JSON.parse(e||"[]"))n.set(Number(r.number),r);return n}catch(e){throw gn(e)&&yn(e),e}}async function MM(){let t=new Map;for(let e of await ct().catch(()=>[]))t.set(e.id,e);return t}async function CM(t,e){await R({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"user",source:"orchestrator",boxId:null,payload:{ts:Date.now(),change:"merged",...e}})}async function NM(t,e){let n=await nr(t);return n?qr(n,e):null}async function PM(t,e,n){let r=await nr(t);if(!r)throw new Error(`no local checkout for ${t}`);return Kr(r,e,{method:n})}async function BM(t,e){let n=await nr(t);if(!n)throw new Error(`no local checkout for ${t}`);return ss(n,e)}async function DM(t,e){await R({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"user",source:"orchestrator",boxId:null,payload:{ts:Date.now(),change:"auto-canceled",...e}})}async function io(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 jM=10*6e4;async function LM(t,e,n){let r=t.autoMerge?.armedAt||0;if(r&&(n.now??Date.now())-r>vM){await io(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 io(t,o,e,n,`check${o.checks.failed===1?"":"s"} failed: ${a}`);return}if(o.mergeable==="CONFLICTING"){await io(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<jM)return;await io(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 io(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 io(t,o,e,n,String(a?.message||a).trim()||"merge failed");return}await Ak(t,{...o,state:"MERGED"},e,n,{change:"auto-merged",githubAt:o.checks.settledAt||null})}}async function Es(t,e,n){await R({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"user",source:"orchestrator",boxId:null,payload:{ts:Date.now(),change:e,...n}})}async function FM(t,e){try{return await _d("git",["-C",t.cwd,"cat-file","-e",`${e}^{commit}`],{timeout:1e4}),!0}catch{return!1}}async function _s(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 Is=(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 UM(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 Pa(t.repo,t.number,{headSha:s}).catch(()=>{}),t.reconcile?.headSha&&s!==t.reconcile.headSha&&await Da(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<AM;await vh(t.repo,t.number,s).catch(()=>{}),t.baseFoldAt&&await Sh(t.repo,t.number).catch(()=>{}),!i&&!c&&!l&&a&&!await r.localHasCommit(a,s).catch(()=>!1)&&!await _s(a,"pushed",t.number,f=>f.payload?.sha===s)&&await Es(a,"pushed",{...Is(t,e),sha:s}).catch(()=>{})}if(t.ciFixAwait)if(o-(t.ciFixAwait.since||0)>Ed)await Ba(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 Ba(t.repo,t.number).catch(()=>{});else if(i.checks.total>0&&i.checks.pending===0){let c=Rs(r,t,"ci-fixed",i.checks.settledAt||null),l=t.ciFixAwait.since||0;a&&!await _s(a,"ci-fixed",t.number,u=>(u.payload?.ts||0)>=l)&&await Es(a,"ci-fixed",{...Is(t,e),passed:i.checks.passed,githubAt:c.githubAt,latencyMs:c.latencyMs,lane:c.lane}).catch(()=>{}),await Ba(t.repo,t.number).catch(()=>{})}}}if(t.reconcileAwait)if(o-(t.reconcileAwait.since||0)>Ed)await ja(t.repo,t.number).catch(()=>{});else{let i=await r.detail(t.repo,t.number).catch(()=>null);if(i){if(i.mergeable==="CONFLICTING")await ja(t.repo,t.number).catch(()=>{});else if(i.mergeable==="MERGEABLE"){Rs(r,t,"reconciled",null);let c=t.reconcileAwait.since||0;a&&!await _s(a,"reconciled",t.number,l=>(l.payload?.ts||0)>=c)&&await Es(a,"reconciled",{...Is(t,e),base:i.baseRefName||null}).catch(()=>{}),await ja(t.repo,t.number).catch(()=>{})}}}}async function HM(t,e,n,r){let o=Rs(r,t,"closed",Date.parse(e?.closedAt||"")||null),s=n.get(t.trackId);s&&!await _s(s,"closed",t.number)&&await Es(s,"closed",{...Is(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 WM(t,e,n){Rs(n,t,"lost",null);let r=e.get(t.trackId);r&&!await _s(r,"lost",t.number)&&await Es(r,"lost",Is(t,null)).catch(()=>{}),await n.notifyIfResolved(t.repo,t.number,"lost").catch(()=>{}),await n.mark(t.repo,t.number,"lost").catch(()=>{})}async function Ak(t,e,n,r,o={}){let s=Rs(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 qM(t={}){let{now:e=Date.now(),auth:n=Eu,view:r=$M,list:o=OM,links:s=Ah,trackMap:a=MM,emitMerged:i=CM,mark:c=pn,archiveIfArmed:l=kn,notifyIfArmed:u=xn,notifyIfResolved:f=Qu,batchThreshold:d=TM,detail:p=NM,merge:m=PM,updateBranch:w=BM,lookup:y=Pt,disarm:b=qo,emitDisarmed:S=DM,localHasCommit:_=FM}=t;if(!(await n().catch(()=>null))?.authenticated||Ya()>0)return 0;let I=(await s().catch(()=>[])).filter(T=>e-(Ci.get(V(T.repo,T.number))||0)>=RM(T,e));if(!I.length)return 0;let C=Date.now(),v=new Map,B=new Map;for(let T of I){let g=Tk(T,e);v.set(V(T.repo,T.number),{lane:g,intervalMs:vk[g]}),Ci.set(V(T.repo,T.number),e),B.has(T.repo)||B.set(T.repo,[]),B.get(T.repo).push(T)}let D=await a().catch(()=>new Map),lt={now:e,lanes:v,emitMerged:i,mark:c,archiveIfArmed:l,notifyIfArmed:u,notifyIfResolved:f,detail:p,merge:m,updateBranch:w,lookup:y,disarm:b,emitDisarmed:S,localHasCommit:_};for(let[T,g]of B){let h,A=new Set;if(g.length>d)h=await o(T).catch(()=>null);else{h=new Map;for(let E of g)try{let z=await r(T,E.number);z?.number!=null&&h.set(Number(z.number),z)}catch(z){_M(z)&&A.add(V(E.repo,E.number))}}if(h)for(let E of g){let z=V(E.repo,E.number),W=h.get(Number(E.number));if(!W){if(A.has(z)){let at=(Ni.get(z)||0)+1;Ni.set(z,at),at>=SM&&(await WM(E,D,lt),Ni.delete(z),Ci.delete(z))}continue}Ni.delete(z),W.state==="MERGED"||W.state==="CLOSED"?(W.state==="MERGED"?await Ak(E,W,D,lt):await HM(E,W,D,lt),Ci.delete(z)):(await UM(E,W,D,lt).catch(()=>{}),E.autoMerge&&await LM(E,D,lt).catch(()=>{}))}}let q={};for(let{lane:T}of v.values())q[T]=(q[T]||0)+1;return console.log(`[pr-watch] probed ${I.length} link(s) (${Object.entries(q).map(([T,g])=>`${T} ${g}`).join(", ")}) in ${Date.now()-C}ms`),I.length}function Ek(){let t=!1;setInterval(async()=>{if(!t){t=!0;try{await qM()}catch{}finally{t=!1}}},xk)}import Xe from"node:fs/promises";import co from"node:path";import Ck from"node:os";import _k from"node:fs/promises";import Pi from"node:path";import{execFile as KM}from"node:child_process";import{promisify as GM}from"node:util";import{fileURLToPath as VM}from"node:url";var zM=GM(KM),Rk=Pi.dirname(VM(import.meta.url)),JM=Pi.resolve(Rk,"..","..","client","dist"),XM="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",YM=new Map([...XM].map((t,e)=>[t,e]));function ZM(t){let e=[],n=0,r=0;for(let o of t){let s=YM.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 QM(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=ZM(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 tC(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 Ik=new Map;async function eC(t){let e;try{e=await _k.stat(t)}catch{return null}let n=Ik.get(t);if(n&&n.mtimeMs===e.mtimeMs)return n;try{let r=JSON.parse(await _k.readFile(t,"utf8")),o={mtimeMs:e.mtimeMs,sources:r.sources||[],lines:QM(r.mappings||"")};return Ik.set(t,o),o}catch{return null}}var Id=null;function nC(){return Id||(Id=process.env.BOSTRAT_RELEASE?Promise.resolve(process.env.BOSTRAT_RELEASE):zM("git",["rev-parse","--short","HEAD"],{cwd:Pi.resolve(Rk,"..","..")}).then(t=>t.stdout.trim()).catch(()=>null)),Id}var rC=/(?:https?:\/\/[^\s()]*)?\/assets\/([^\s()/]+\.js):(\d+):(\d+)/g;async function oC(t,{distDir:e=JM}={}){let n=[],r=0,o=!1;for(let s of t.matchAll(rC)){let[a,i,c,l]=s,u=await eC(Pi.join(e,"assets",`${i}.map`)),f=u?tC(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 $k(t,e={}){try{if(!t?.error?.stack||!t.release||t.release==="unknown")return t;let n=e.release??await nC();if(!n||t.release!==n)return t;let{stack:r,symbolicated:o}=await oC(t.error.stack,e);o&&(t.error.stack=r,t.error.symbolicated=!0)}catch{}return t}var sC=()=>process.env.BOSTRAT_STATE_DIR||co.join(process.env.HOME||Ck.tmpdir(),".bostrat"),Mn=()=>co.join(sC(),"telemetry"),Nk=()=>co.join(Mn(),".relay-offsets.json"),aC=50,iC=14,cC=3e4,Ok=500,Zt=(t,e)=>typeof t=="string"?t.slice(0,e):"";function lC(t,e){if(!t||typeof t!="object")return null;let n=Zt(t.name,64);if(!n)return null;let r=Number.isFinite(t.ts)?t.ts:e,o={name:n,ts:Math.abs(r-e)<168*3600*1e3?r:e,receivedAt:e,anonId:Zt(t.anonId,64),sessionId:Zt(t.sessionId,64),userId:Zt(t.userId,64),release:Zt(t.release,64),appVersion:Zt(t.appVersion,32),route:Zt(t.route,128),deviceClass:t.deviceClass==="phone"||t.deviceClass==="desktop"?t.deviceClass:""};return t.props&&typeof t.props=="object"&&!Array.isArray(t.props)&&(JSON.stringify(t.props).length<=8192?o.props=t.props:o.props={clamped:!0}),t.error&&typeof t.error=="object"&&(o.error={kind:Zt(t.error.kind,256),message:Zt(t.error.message,8192),stack:Zt(t.error.stack,32768),fingerprint:Zt(t.error.fingerprint,64)}),Array.isArray(t.breadcrumbs)&&(o.breadcrumbs=t.breadcrumbs.slice(-50).map(s=>({t:Number.isFinite(s?.t)?s.t:0,n:Zt(s?.n,64),r:Zt(s?.r,128)}))),o}var Pk=t=>new Date(t).toISOString().slice(0,10),Mk=0;async function Bk(t,e=Date.now()){let r=(Array.isArray(t?.events)?t.events.slice(0,aC):[]).map(s=>lC(s,e)).filter(Boolean);if(!r.length)return 0;for(let s of r)s.error?.stack&&await $k(s);await Xe.mkdir(Mn(),{recursive:!0});let o=co.join(Mn(),`${Pk(e)}.ndjson`);return await Xe.appendFile(o,r.map(s=>JSON.stringify(s)+`
|
|
410
|
+
`).join(""),"utf8"),e-Mk>6*3600*1e3&&(Mk=e,uC(e).catch(()=>{})),r.length}async function uC(t=Date.now()){let e=Pk(t-iC*24*3600*1e3),n=[];try{n=await Xe.readdir(Mn())}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 Xe.rm(co.join(Mn(),s),{force:!0});let o=await jk();for(let s of r)delete o[s];await Lk(o)}var Bi=new Map;function Dk(t,e=Date.now()){let n=t||"unknown",r=Bi.get(n);return r||(Bi.size>512&&Bi.clear(),r={tokens:40,at:e},Bi.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 jk(){try{return JSON.parse(await Xe.readFile(Nk(),"utf8"))||{}}catch{return{}}}async function Lk(t){await Xe.mkdir(Mn(),{recursive:!0}),await Xe.writeFile(Nk(),JSON.stringify(t),"utf8")}function dC(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||Ck.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 fC({fetchImpl:t=fetch}={}){let e=await qm();if(!e)return{sent:0,disabled:!0};let n;try{n=(await Xe.readdir(Mn())).filter(s=>s.endsWith(".ndjson")).sort()}catch{return{sent:0,disabled:!1}}let r=await jk(),o=0;for(let s of n){let a=co.join(Mn(),s),i=r[s]||0,c;try{let d=await Xe.open(a,"r");try{let p=(await d.stat()).size;if(p<=i)continue;c=Buffer.alloc(p-i),await d.read(c,0,c.length,i)}finally{await d.close()}}catch{continue}let l=c.lastIndexOf(10);if(l<0)continue;let u=c.toString("utf8",0,l+1).split(`
|
|
411
|
+
`).filter(Boolean),f=[];for(let d of u)try{f.push(JSON.parse(d))}catch{}for(let d=0;d<f.length;d+=Ok){let p=f.slice(d,d+Ok).map(w=>dC(w)),m=await t(`${e.url.replace(/\/$/,"")}/v1/logs`,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${e.ingestKey}`},body:JSON.stringify({events:p})}).catch(()=>null);if(!m||!m.ok)return{sent:o,disabled:!1};o+=p.length}r[s]=i+l+1,await Lk(r)}return{sent:o,disabled:!1}}var Rd=null,$d=!1;function Fk(){Rd||(Rd=setInterval(async()=>{if(!$d){$d=!0;try{await fC()}catch(t){console.error("[telemetry] relay failed:",t?.message||t)}finally{$d=!1}}},cC),Rd.unref?.())}import ji from"node:fs/promises";import Kk from"node:path";import pC from"node:os";var Gk=()=>process.env.BOSTRAT_STATE_DIR||Kk.join(process.env.HOME||pC.tmpdir(),".bostrat"),Vk=()=>Kk.join(Gk(),"dictation-glossary.json"),mC=100,hC=500,wC=["bostrat","globopi","codex","tmux","worktree","Deepgram","Linear","Firecracker","Tigris","WireGuard","misous","postyx","cedar","gerbil","Anthropic","Opus","Haiku","Sonnet","keyterm","monorepo","dogfood"],Li=null,Od=null,Md=Promise.resolve();function Uk(){let t={},e=Date.now();for(let n of wC)t[n.toLowerCase()]={term:n,count:1,lastUsed:e,source:"seed"};return t}async function zk(){return Li||(Od??=ji.readFile(Vk(),"utf8").then(t=>JSON.parse(t)?.terms??Uk()).catch(()=>Uk()).then(t=>Li=t).finally(()=>Od=null),Od)}async function Hk(){let t=JSON.stringify({terms:Li},null,2),e=Vk(),n=`${e}.${process.pid}.tmp`;await ji.mkdir(Gk(),{recursive:!0}),await ji.writeFile(n,t),await ji.rename(n,e)}function gC(){return Md=Md.then(Hk,Hk),Md.catch(()=>{})}async function Cd(){return Object.values(await zk()).sort((e,n)=>n.count-e.count||n.lastUsed-e.lastUsed)}var yC=t=>Math.max(1,Math.ceil(t.length/4));async function bC(){let t=[],e=0;for(let{term:n}of await Cd()){let r=yC(n);if(!(e+r>hC)&&(t.push(n),e+=r,t.length>=mC))break}return t}async function kC(t,e){let n=await zk(),r=t.toLowerCase(),o=n[r];n[r]={term:t,count:(o?.count??0)+1,lastUsed:Date.now(),source:o?.source??e}}var Wk=t=>(t||"").split(/\s+/).map(e=>e.replace(/^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu,"")).filter(Boolean),Di=t=>t.toLowerCase();function qk(t){return!new RegExp("\\p{L}","u").test(t)||t.length<3?!1:/^[a-z]+$/.test(t)?t.length>=4:!0}async function Jk(t,e){let n=Wk(t),r=Wk(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]=Di(n[u])===Di(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;)Di(n[c])===Di(r[l])?(c++,l++):a[c+1][l]>=a[c][l+1]?c++:(qk(r[l])&&i.add(r[l]),l++);for(;l<s;l++)qk(r[l])&&i.add(r[l]);if(!i.size)return[];for(let u of i)await kC(u,"learned");return await gC(),[...i]}var $s=null;function xC(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 Xk(){if(!$s)return null;let t=await Bo();if(!t||t.apiKey.slice(-6)!==$s.keyTail)return null;let{keyTail:e,...n}=$s;return n}async function Yk(t,e){let n=await Bo();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 bC())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=xC(o.status,i);c.persistent&&($s={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 $s=null,{transcript:(await o.json())?.results?.channels?.[0]?.alternatives?.[0]?.transcript??""}}var Nd={"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 SC(t){if(!t)return null;let e=String(t).trim();if(Nd[e])return e;let n=null;for(let r of Object.keys(Nd))e.startsWith(r)&&(!n||r.length>n.length)&&(n=r);return n}function vC(t,e={}){let n=Nd[SC(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 TC(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 Os=null;async function AC(){if(!Os)return null;let t=await $a();if(!t||t.apiKey.slice(-6)!==Os.keyTail)return null;let{keyTail:e,...n}=Os;return n}async function EC(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=TC(e.status,o);s.persistent&&(Os={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}Os=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 _C={openai:EC};async function Zk(){let t=await $a();return t?{configured:!0,provider:t.provider,model:t.model,voice:t.voice,problem:await AC()}:{configured:!1}}async function Qk(){let t=await $a();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 _C[t.provider](t);return{provider:t.provider,model:t.model,voice:t.voice,...e}}async function tx(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=vC(e,s),i=await R(t,{type:"cost.recorded",actor:"user",provider:"openai-realtime",payload:{model:e||"unknown",usd:a,tokens:{input:s.textIn+s.audioIn,output:s.textOut+s.audioOut,cacheRead:s.textInCached+s.audioInCached,cacheWrite:0},voice:s,seconds:Math.max(0,Math.round(Number(r)||0)),source:"voice-chat"}});return{usd:a,seq:i.seq}}var ax=ft.dirname(RC(import.meta.url)),ex=Number(process.env.PORT)||3362,Fi=Ab(process.env),ix=Number(process.env.BOSTRAT_AUTO_TRIGGER_PCT)||80,lo=ok(process.env.BOSTRAT_ALLOW_IPS),Ld=sk(process.env.BOSTRAT_VIEW_FRONT_CIDRS,Mi),cx=ik(),lx=process.env.BOSTRAT_ALLOW_LAN_TOKENLESS!=="1",x=Ee();x.disable("x-powered-by");x.use((t,e,n)=>{if(As(t)||lo.allowed(lo.clientIp(t))||Ld.allows(t))return n();e.status(403).type("text/plain").send("bostrat: access restricted to the allowlist")});x.use(pk);x.use(uk({token:cx,enforce:lx}));x.use("/api/mocks",Ee.json({limit:"1mb"}));x.use("/api/mocks",Ee.raw({type:"image/png",limit:"24mb"}));x.use("/api/runbooks",Ee.json({limit:"1mb"}));x.use("/api/telemetry",Ee.json({limit:"256kb"}));x.use("/api/hook",Ee.json({limit:"2mb"}));x.use("/api/dictation",Ee.raw({type:["audio/*","video/mp4","application/octet-stream"],limit:"32mb"}));x.use("/api/tracks/:id/upload",Ee.raw({type:()=>!0,limit:"25mb"}));x.use(Ee.json({limit:"64kb"}));x.get("/api/health",async(t,e)=>{e.json({ok:!0,tmuxSocket:zt,hostTmuxAlive:await nn(),autoTriggerPct:ix})});x.get("/api/sessions",async(t,e)=>{e.json({sessions:await Re()})});x.post("/api/sessions",async(t,e)=>{let{displayName:n,cwd:r}=t.body||{};if(typeof n!="string")return e.status(400).json({error:"displayName (string) required"});try{let o=await Bn(n,r||void 0);e.json({ok:!0,...o})}catch(o){let s=o?.message||String(o),a=/already exists/i.test(s)?409:/must be|invalid/i.test(s)?400:500;e.status(a).json({error:s})}});x.delete("/api/sessions/:name",async(t,e)=>{let n=decodeURIComponent(t.params.name);if(!yt(n))return e.status(400).json({error:"invalid session name"});if(!await nt(n))return e.status(404).json({error:"session not found"});try{await fe(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 nt(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 jp(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=pa(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 nx=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>nx){let s=new Set([...r].sort((a,i)=>(i.archivedAt||0)-(a.archivedAt||0)).slice(0,nx).map(a=>a.id));o=n.filter(a=>!a.archivedAt||s.has(a.id))}e.json({tracks:o,archivedTotal:r.length,agents:fi,agentAvailability:await Vu(),tickets:await Ma()})});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 Nb(n);if(r.cursorUsage=Su(),t.query.format==="text")return e.type("text/plain").send(Pb(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 jb(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 Wb(await ct(),{window:t.query.window,from:t.query.from,to:t.query.to});n.cursorUsage=Su(),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 Qb(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 Oh(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 Mh(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 nb()})}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 qd(t.get(Ps),{byToken:My,legacy:()=>Oy(t.body?.session_id,t.body?.cwd)});if(n==="refused")return Bs("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&&Bs("mismatch",{track:r.track.id,box:r.box.name}),Bs(n,{track:r?.track?.id,hook:t.body?.hook_event_name});let o=await Tb(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(!Dk(lo.clientIp(t)))return e.json({ok:!0,dropped:!0});let n=await Bk(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 _e(),r=new Set((await Promise.all(n.map(a=>ot(a.path)))).filter(Boolean).map(a=>a.toLowerCase())),s=((await ho()).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 Fs(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:Ls(),calls:dg(),latency:Dg()})});x.post("/api/github/refresh",(t,e)=>{tf(),e.json({ok:!0})});x.get("/api/gmail/search",async(t,e)=>{try{let n=await ek(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 nk(String(t.params.id))})}catch(n){e.status(n?.status||502).json({error:n?.message||String(n)})}});function Fd(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 Ui({repo:t=null,number:e=null,branch:n=null}){let r=t?await ot(t):null;if(r&&e!=null){let s=await Pt(r,e),a=s&&await rt(s.trackId);if(a)return a}return n?(await ct()).find(s=>s.workspace?.branch===n&&Fd(s,t,r))??null:null}async function ux({repo:t=null,number:e=null,branch:n=null}){let r=await Ui({repo:t,number:e,branch:n}).catch(()=>null);if(r&&!r.archivedAt)return r;if(!n)return null;let o=t?await ot(t).catch(()=>null):null;return(await ct()).find(a=>!a.archivedAt&&a.workspace?.branch===n&&Fd(a,t,o))??null}async function le(t,{number:e,url:n=null,title:r=null,branch:o=null,repo:s=null,track:a=null,extra:i={}}){let c=a||await Ui({repo:s,number:e,branch:o}).catch(()=>null);if(!c)return;let l=i.repo??(s?await ot(s).catch(()=>null):null);await R({id:c.id,cwd:c.cwd},{type:"pr.state.changed",actor:"user",source:"orchestrator",payload:{ts:Date.now(),change:t,number:e,url:n,title:r,branch:o??c.workspace?.branch??null,...l?{repo:l}:{},...i}}).catch(()=>{})}async function CC(t){let e=(await ct()).filter(n=>!n.archivedAt);for(let n of t??[]){if(!n.prs?.length)continue;let r=await ot(n.path);if(r)for(let o of n.prs){if(await Pt(r,o.number))continue;let s=e.find(a=>a.workspace?.branch===o.headRefName&&Fd(a,n.path,r));s&&await Xt({repo:r,number:o.number,trackId:s.id,url:o.url,headRef:o.headRefName,title:o.title,source:"matched"})}}}x.get("/api/git/prs",async(t,e)=>{try{let n=await qg();await CC(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(!os.has(w))throw new Error(`invalid merge method: ${w}`);let y=Number(r);if(!Number.isInteger(y)||y<=0)throw new Error("invalid PR number");let b=await ot((await Ke(n)).path);if(!b)throw new Error("repo has no GitHub remote");let S=await Ui({repo:n,number:y,branch:l}).catch(()=>null);if(!S)throw new Error("auto-merge needs a Track to watch this PR \u2014 none owns it");if(a&&await ss(n,y),await Xt({repo:b,number:y,trackId:S.id,url:f,headRef:l,title:u,source:"matched"}).catch(()=>{}),!await Ho(b,y,{merge:{method:w}}))throw new Error("couldn't record the auto-merge arm");return await Vn(b,y,i===!0),await Wo(b,y,c===!0&&i!==!0),Te(),await le("auto-armed",{number:y,url:f,title:u,branch:l,repo:n,track:S,...a?{extra:{updated:!0}}:{}}),e.json({ok:!0,number:y,auto:!0})}let d=await Kr(n,r,{method:o||"squash"});await le("merged",{number:d.number,url:f,title:u,branch:l,repo:n});let p=await ot(n).catch(()=>null),m=!1;if(p&&(i===!0&&await Vn(p,d.number,!0).catch(()=>{}),c===!0&&i!==!0&&await Wo(p,d.number,!0).catch(()=>{}),await xn(p,d.number).catch(()=>{}),m=await kn(p,d.number).catch(()=>!1),await pn(p,d.number,"merged").catch(()=>{})),i===!0&&!m){let w=await Ui({repo:n,number:d.number,branch:l}).catch(()=>null);w&&!w.archivedAt&&await Qr(w.id).catch(()=>{})}e.json(d)}catch(d){let p=d?.message||String(d),m=/invalid|unknown repo|required|needs a Track|no GitHub remote/i.test(p)?400:502;e.status(m).json({error:p})}});x.post("/api/git/prs/disarm-auto",async(t,e)=>{let{repo:n,number:r,headRefName:o,title:s,url:a}=t.body||{};try{let i=Number(r);if(!Number.isInteger(i)||i<=0)throw new Error("invalid PR number");let c=await ot((await Ke(n)).path).catch(()=>null),l=c?await qo(c,i).catch(()=>null):null;try{await Ru(n,i)}catch(u){if(!l)throw u}Te(),await le("auto-canceled",{number:i,url:a,title:s,branch:o,repo:n}),e.json({ok:!0,number:i})}catch(i){let c=i?.message||String(i),l=/invalid|unknown repo|required/i.test(c)?400:502;e.status(l).json({error:c})}});x.post("/api/git/prs/merge-all",async(t,e)=>{let{repo:n,method:r}=t.body||{};try{let o=await ot(n).catch(()=>null),s=await zg(n,{method:r||"merge",onMerged:a=>{le("merged",{number:a.number,url:a.url,title:a.title,branch:a.headRefName,repo:n}),o&&xn(o,a.number).catch(()=>{}).then(()=>kn(o,a.number)).catch(()=>{}).then(()=>pn(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:Vg()})});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(!os.has(i))throw new Error(`invalid merge method: ${i}`);if(!n)return e.status(400).json({error:"trackId required"});let c=await rt(n);if(!c)return e.status(404).json({error:`Track ${n} isn't on this node`});let l=(await Se()).filter(d=>d.trackId===c.id&&d.state!=="merged"&&d.state!=="closed");if(!l.length)return e.status(409).json({error:"no open PRs on this Track"});for(let d of l)await Vn(d.repo,d.number,o===!0).catch(()=>{}),await Wo(d.repo,d.number,s===!0&&o!==!0).catch(()=>{});let u=[],f=[];for(let d of l){let p={repo:d.repo,number:d.number,ok:!0,action:"armed"};u.push(p);try{let m=await nr(d.repo);if(!m)throw new Error(`no local checkout for ${d.repo}`);let w=await qr(m,d.number);f.push({r:p,l:d,repoPath:m,pr:w,plan:Kg(w,{update:r!==!1})})}catch(m){p.ok=!1,p.action="failed",p.reason=m?.message||String(m)}}for(let{r:d,l:p,repoPath:m,pr:w,plan:y}of Gg(f))try{if(y.action==="skip")d.action="skipped",d.reason=y.reason;else if(y.action==="merge")await Kr(m,p.number,{method:i}),await le("merged",{number:p.number,url:w.url,title:w.title,branch:w.headRefName,repo:m,track:c}),await xn(p.repo,p.number).catch(()=>{}),await kn(p.repo,p.number).catch(()=>{}),await pn(p.repo,p.number,"merged").catch(()=>{}),d.action="merged";else{if(y.action==="update-arm"&&(await ss(m,p.number).catch(()=>{}),d.action="updated-armed"),!await Ho(p.repo,p.number,{merge:{method:i}}))throw new Error("couldn't record the auto-merge arm");await le("auto-armed",{number:p.number,url:w.url,title:w.title,branch:w.headRefName,repo:m,track:c,...d.action==="updated-armed"?{extra:{updated:!0}}:{}})}}catch(b){d.ok=!1,d.action="failed",d.reason=b?.message||String(b)}if(Te(),!u.some(d=>d.ok)){if(o===!0)for(let d of l)await Vn(d.repo,d.number,!1).catch(()=>{});if(s===!0&&o!==!0)for(let d of l)await Wo(d.repo,d.number,!1).catch(()=>{});return e.status(502).json({error:u.map(d=>`${d.repo}#${d.number}: ${d.reason}`).join("; "),results:u})}e.json({ok:!0,trackId:c.id,results:u})}catch(i){let c=i?.message||String(i),l=/invalid|required/i.test(c)?400:502;e.status(l).json({error:c})}});x.post("/api/git/prs/disarm-track",async(t,e)=>{let{trackId:n}=t.body||{};try{if(!n)return e.status(400).json({error:"trackId required"});let r=await rt(n);if(!r)return e.status(404).json({error:`Track ${n} isn't on this node`});let o=(await Se()).filter(a=>a.trackId===r.id&&a.state!=="merged"&&a.state!=="closed"),s=[];for(let a of o){let i=await qo(a.repo,a.number).catch(()=>null),c=await nr(a.repo).catch(()=>null);c&&await Ru(c,a.number).catch(()=>{}),i&&(await le("auto-canceled",{number:a.number,url:a.url,title:a.title,branch:a.headRef,repo:c,track:r}),s.push({repo:a.repo,number:a.number}))}Te(),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 rt(n);if(!i)return e.status(404).json({error:`Track ${n} isn't on this node`});if(i.workspace?.kind==="multi"){let u=qt(i.workspace),f,d=Array.isArray(a)?a.filter(Boolean):[];if(d.length)f=d.map(w=>{let y=u.find(b=>b.name===w||b.repo===w||b.base===w||b.dir===w);if(!y)throw new Error(`invalid repo "${w}" \u2014 not tagged on this Track`);return y});else if(f=await ts(i),!f.length)return e.status(409).json({error:"no tagged repo has work to ship"});let p=[];for(let w of f)try{let y=await Ou(w.dir,{title:r,body:o,base:s}),b=w.repo||await ot(w.dir).catch(()=>null);b&&y.number&&await Xt({repo:b,number:y.number,trackId:i.id,url:y.url,headRef:i.workspace?.branch??null,title:r,source:"api"}),await le("opened",{number:y.number,url:y.url,title:r,track:i,branch:i.workspace?.branch,extra:{repo:b??w.name}}),p.push({repo:w.name,slug:b??null,ok:!0,url:y.url,number:y.number})}catch(y){p.push({repo:w.name,slug:w.repo??null,ok:!1,error:y?.message||String(y)})}let m=p.filter(w=>w.ok);return m.length?e.json({ok:!0,multi:!0,results:p,url:m[0].url,number:m[0].number}):e.status(502).json({error:p.map(w=>`${w.repo}: ${w.error}`).join("; "),results:p})}let c=await Ou(i.cwd,{title:r,body:o,base:s}),l=await ot(i.cwd).catch(()=>null);l&&c.number&&await Xt({repo:l,number:c.number,trackId:i.id,url:c.url,headRef:i.workspace?.branch??null,title:r,source:"api"}),await le("opened",{number:c.number,url:c.url,title:r,track:i,branch:i.workspace?.branch,extra:{...l?{repo:l}:{}}}),e.json(c)}catch(i){let c=i?.message||String(i),l=/required|invalid/i.test(c)?400:502;e.status(l).json({error:c})}});x.get("/api/tracks/:id/repos",async(t,e)=>{try{let n=await rt(t.params.id);if(!n)return e.status(404).json({error:"track not found"});let r=await ts(n),o=new Map(r.map(a=>[a.base,a])),s=qt(n.workspace).map(a=>{let i=o.get(a.base);return{name:a.name,base:a.base,baseRef:a.baseRef??null,repo:a.repo??null,dirty:i?.dirty??!1,ahead:i?.ahead??0,changed:!!i}});e.json({repos:s,branch:n.workspace?.branch??null,kind:n.workspace?.kind??null})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.post("/api/tracks/:id/repos",async(t,e)=>{let{repo:n}=t.body||{};try{if(!n||typeof n!="string")return e.status(400).json({error:"repo (path or owner/name) required"});let r=await Vy(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 zy(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 qr(n,r),s=await ot(n).catch(()=>null),a=await ux({repo:n,number:r,branch:o.headRefName});if(!a)return e.status(404).json({error:`no active Track owns PR #${o.number} (branch "${o.headRefName}")`});let i=qt(a.workspace).find(b=>b.base===n||s&&b.repo===s)??null,c=a.workspace?.kind==="multi",l=await Jg(n,o.number);if(!l.length)return e.status(409).json({error:"no failing checks on this PR"});let u=c&&i?ft.join("ci",i.name):"ci",f=ft.join(a.cwd,".bostrat",u),d=ft.join(".bostrat",u,`pr-${o.number}-fail.log`);await Gt.promises.mkdir(f,{recursive:!0}),await Gt.promises.writeFile(ft.join(a.cwd,d),l.map(b=>`\u2550\u2550\u2550\u2550 ${b.name}${b.link?` \xB7 ${b.link}`:""} \u2550\u2550\u2550\u2550
|
|
414
412
|
${b.log??"(no fetchable log \u2014 see link)"}
|
|
415
413
|
`).join(`
|
|
416
|
-
`));let p=l.map(b=>b.name).join(", "),m=c&&i?` in the ${i.name}/ checkout`:"",g=await oe(a.id,`CI is red on PR #${o.number} "${o.title}" (${o.url}) \u2014 branch ${o.headRefName}${m}. Failing check${l.length===1?"":"s"}: ${p}. The failure log is at ${d} in this ${c?"workspace":"worktree"} \u2014 read it, fix the root cause${c&&i?` in ${i.name}/`:""}, run the relevant tests locally, then commit and push to update the PR.`,{kind:"ci-fix"}),y=s;y&&(await Wt({repo:y,number:o.number,trackId:a.id,url:o.url,headRef:o.headRefName,title:o.title,source:"matched"}),await ih(y,o.number,{headSha:o.headSha,trackId:a.id}).catch(()=>{})),await se("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:g.leadBox,queued:!!g.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 Lr(n,r);if(o.mergeable!=="CONFLICTING")return e.status(409).json({error:"this PR does not conflict with its base branch"});let s=await tt(n).catch(()=>null),a=await Yk({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=Dt(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 oe(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 Wt({repo:u,number:o.number,trackId:a.id,url:o.url,headRef:o.headRefName,title:o.title,source:"matched"}),await ch(u,o.number,{headSha:o.headSha,trackId:a.id}).catch(()=>{})),await se("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=Fw.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 D(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 oe(a.id,La(o,{branch:c.payload.branch,base:c.payload.base,toPr:c.payload.toPr||null,repos:c.payload.repos||null,offeredRebase:Array.isArray(c.payload.offered)&&c.payload.offered.includes("rebase")}),{kind:o.includes("pr")?"pr-go":"push-go"}));let u=o?o.includes("pr")?"pr":o.includes("push")?"push":o.includes("commit")?"commit":"rebase":"dismiss";await R({id:a.id,cwd:a.cwd},{type:"pr.offer.answered",actor:"user",source:"orchestrator",boxId:c.boxId,payload:{ts:Date.now(),offerId:n,action:u,actions:o,...s?{notify:!0}:{}}}),s&&Ay(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 vs=t=>e=>({...e,url:nk(e,t.headers)});x.get("/api/views",async(t,e)=>{try{let n=await pa({trackId:t.query.track||void 0});e.json({views:n.map(vs(t)),maxLive:rn})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/views/:id",async(t,e)=>{let n=await um(t.params.id);if(!n)return e.status(404).json({error:"view not found"});e.json({view:vs(t)(n),session:br(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 Dc({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:vs(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:vs(t)(await dm(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:vs(t)(await fm(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 pm(t.params.id))}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});var aM=[["linear",Dn,qc],["jira",ba,Hc]],Ad=new Map;x.get("/api/integrations/ticketing",async(t,e)=>{try{let n=await Uc(),r={};for(let[o,s,a]of aM){let i=await s();if(!i){Ad.delete(o),r[o]={configured:!1};continue}let c=Ad.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},Ad.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 Uc()})}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 Om(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 R({id:i.id,cwd:i.cwd},{type:"ticket.flow.changed",actor:s==="user"?"user":"agent",source:"orchestrator",payload:{ts:Date.now(),set:{...o},flow:a}}).catch(()=>{}),e.json({ok:!0,flow:a})}catch(a){let i=a?.message||String(a);e.status(/unknown|nothing to set/i.test(i)?400:500).json({error:i})}});x.post("/api/tickets/link",async(t,e)=>{let{trackId:n,cwd:r,ref:o}=t.body||{};try{let s=n?await Q(n):null;if(!s&&r&&(s=(await 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 vr(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 Gm(),ticket:await ge(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 ge(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 vr(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 nl(t.params.id)})});var Mi=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 Xm(t.params.id))}catch(n){let r=n?.message||String(n);e.status(Mi(r)).json({error:r})}});x.post("/api/tracks/:id/ticket/state",async(t,e)=>{try{e.json({ok:!0,...await rl(t.params.id,(t.body||{}).stateId)})}catch(n){let r=n?.message||String(n);e.status(Mi(r)).json({error:r})}});x.post("/api/tracks/:id/ticket/comment",async(t,e)=>{try{e.json({ok:!0,...await Ym(t.params.id,(t.body||{}).body)})}catch(n){let r=n?.message||String(n);e.status(Mi(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 Zm(o,n,r===!0)})}catch(o){let s=o?.message||String(o);e.status(Mi(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 th(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 Qm()})}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 eh(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 Nn({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 nn(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 nn(t.params.id);if(!n)return e.status(404).json({error:"mock not found"});let r=Ie(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(Ht(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 nn(t.params.id);if(!n)return e.status(404).json({error:"mock not found"});let r=Ie(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(Ht(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(p=>p.cwd&&(a===p.cwd||a.startsWith(p.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 qp({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 Kp(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 Vp(t.params.id),o=r.length?` over ${r.map(s=>`"${s.name}"`).join(", ")}`:"";await oe(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 Zg(t.params.id)).map(r=>({id:r.id,name:r.name}));e.json({...await zp(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 Vg({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 zg(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 Jg({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 Xg(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 Yg(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 wa({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=ym(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(Me(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 xm({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 Sm(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 vm(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 Tm(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=sk(t.query.since||null);e.json({since:n,repos:hd(),prs:await ak(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 ik(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 iM=120,Ed=400,Ci=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>Ed)return e.status(400).json({error:`message is ${i.length} chars \u2014 over the ${Ed} cap; a notification is a nudge, put the detail in chat`});let c=Ro(`${n||""}
|
|
417
|
-
${i}`);if(c.length)return e.status(400).json({error:`notification appears to contain a secret VALUE (${c.join(", ")}) \u2014 notifications ship off-box; never put a credential in one`});let l=a?await
|
|
414
|
+
`));let p=l.map(b=>b.name).join(", "),m=c&&i?` in the ${i.name}/ checkout`:"",w=await ce(a.id,`CI is red on PR #${o.number} "${o.title}" (${o.url}) \u2014 branch ${o.headRefName}${m}. Failing check${l.length===1?"":"s"}: ${p}. The failure log is at ${d} in this ${c?"workspace":"worktree"} \u2014 read it, fix the root cause${c&&i?` in ${i.name}/`:""}, run the relevant tests locally, then commit and push to update the PR.`,{kind:"ci-fix"}),y=s;y&&(await Xt({repo:y,number:o.number,trackId:a.id,url:o.url,headRef:o.headRefName,title:o.title,source:"matched"}),await bh(y,o.number,{headSha:o.headSha,trackId:a.id}).catch(()=>{})),await le("fix-delegated",{number:o.number,url:o.url,title:o.title,branch:o.headRefName,repo:n,track:a,extra:{checks:l.map(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 qr(n,r);if(o.mergeable!=="CONFLICTING")return e.status(409).json({error:"this PR does not conflict with its base branch"});let s=await ot(n).catch(()=>null),a=await ux({repo:n,number:r,branch:o.headRefName});if(!a)return e.status(404).json({error:`no active Track owns PR #${o.number} (branch "${o.headRefName}")`});let i=qt(a.workspace).find(f=>f.base===n||s&&f.repo===s)??null,c=a.workspace?.kind==="multi"&&i?` In the ${i.name}/ checkout:`:"",l=await ce(a.id,`PR #${o.number} "${o.title}" (${o.url}) \u2014 branch ${o.headRefName} \u2014 has merge conflicts with ${o.baseRefName}.${c} Bring ${o.baseRefName} into this branch (fetch origin, then merge origin/${o.baseRefName} \u2014 don't rebase this pushed PR branch), and resolve every conflict so that BOTH sides' intent is preserved. Run the relevant tests, then commit and push to update the PR. If any conflict is a genuine ambiguity \u2014 where you can't confidently tell which side's behavior should win, or reconciling would drop work from either side \u2014 STOP and ask me here rather than guessing, laying out the specific choice and your recommendation.`,{kind:"reconcile"}),u=s;u&&(await Xt({repo:u,number:o.number,trackId:a.id,url:o.url,headRef:o.headRefName,title:o.title,source:"matched"}),await kh(u,o.number,{headSha:o.headSha,trackId:a.id}).catch(()=>{})),await le("reconcile-delegated",{number:o.number,url:o.url,title:o.title,branch:o.headRefName,repo:n,track:a,extra:{base:o.baseRefName}}),e.json({ok:!0,leadBox:l.leadBox,queued:!!l.queued,base:o.baseRefName})}catch(o){let s=o?.message||String(o),a=/unknown repo|invalid|required/i.test(s)?400:/not found|no active Track/i.test(s)?404:502;e.status(a).json({error:s})}});x.post("/api/tracks/:id/pr-offer",async(t,e)=>{let{offerId:n,action:r}=t.body||{};try{let o=null;if(Array.isArray(t.body?.actions)){if(o=Zw.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 rt(t.params.id);if(!a)return e.status(404).json({error:"track not found"});let i=await L(a),c=i.find(f=>f.type==="pr.offered"&&f.payload?.offerId===n);if(!c)return e.status(404).json({error:"unknown offer"});if(i.some(f=>f.type==="pr.offer.answered"&&f.payload?.offerId===n))return e.status(409).json({error:"offer already answered"});let l=null;o&&(l=await ce(a.id,za(o,{branch:c.payload.branch,base:c.payload.base,toPr:c.payload.toPr||null,repos:c.payload.repos||null,offeredRebase:Array.isArray(c.payload.offered)&&c.payload.offered.includes("rebase")}),{kind:o.includes("pr")?"pr-go":"push-go"}));let u=o?o.includes("pr")?"pr":o.includes("push")?"push":o.includes("commit")?"commit":"rebase":"dismiss";await R({id:a.id,cwd:a.cwd},{type:"pr.offer.answered",actor:"user",source:"orchestrator",boxId:c.boxId,payload:{ts:Date.now(),offerId:n,action:u,actions:o,...s?{notify:!0}:{}}}),s&&jy(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 Ms=t=>e=>({...e,url:hk(e,t.headers)});x.get("/api/views",async(t,e)=>{try{let n=await va({trackId:t.query.track||void 0});e.json({views:n.map(Ms(t)),maxLive:ln})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/views/:id",async(t,e)=>{let n=await Sm(t.params.id);if(!n)return e.status(404).json({error:"view not found"});e.json({view:Ms(t)(n),session:Ar(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 rt(n):null;if(!i&&s&&(i=(await ct()).find(u=>u.cwd&&(s===u.cwd||s.startsWith(u.cwd+ft.sep)))||null),!i)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});let c=await Vc({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:Ms(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:Ms(t)(await vm(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:Ms(t)(await Tm(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 Am(t.params.id))}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});var NC=[["linear",Wn,tl],["jira",Ra,Zc]],Pd=new Map;x.get("/api/integrations/ticketing",async(t,e)=>{try{let n=await Yc(),r={};for(let[o,s,a]of NC){let i=await s();if(!i){Pd.delete(o),r[o]={configured:!1};continue}let c=Pd.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},Pd.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 Yc()})}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 Wm(o),i=n?await rt(n):null;!i&&r&&(i=(await ct()).find(l=>l.cwd&&(r===l.cwd||r.startsWith(l.cwd+ft.sep)))||null),i&&await R({id:i.id,cwd:i.cwd},{type:"ticket.flow.changed",actor:s==="user"?"user":"agent",source:"orchestrator",payload:{ts:Date.now(),set:{...o},flow:a}}).catch(()=>{}),e.json({ok:!0,flow:a})}catch(a){let i=a?.message||String(a);e.status(/unknown|nothing to set/i.test(i)?400:500).json({error:i})}});x.post("/api/tickets/link",async(t,e)=>{let{trackId:n,cwd:r,ref:o}=t.body||{};try{let s=n?await rt(n):null;if(!s&&r&&(s=(await ct()).find(c=>c.cwd&&(r===c.cwd||r.startsWith(c.cwd+ft.sep)))||null),!s)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});if(!o)return e.status(400).json({error:"ref (issue URL or key) required"});let a=await Rr(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 rt(r):null;if(!o&&n&&(o=(await ct()).find(a=>a.cwd&&(n===a.cwd||String(n).startsWith(a.cwd+ft.sep)))||null),!o)return e.status(400).json({error:"track (or a cwd inside a Track workspace) required"});e.json({trackId:o.id,configured:await oh(),ticket:await xe(o.id)})}catch(o){e.status(500).json({error:o?.message||String(o)})}});x.get("/api/tracks/:id/ticket",async(t,e)=>{e.json({ticket:await xe(t.params.id)})});x.post("/api/tracks/:id/ticket",async(t,e)=>{try{let n=await rt(t.params.id);if(!n)return e.status(404).json({error:"track not found"});let r=await Rr(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 fl(t.params.id)})});var Wi=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 ch(t.params.id))}catch(n){let r=n?.message||String(n);e.status(Wi(r)).json({error:r})}});x.post("/api/tracks/:id/ticket/state",async(t,e)=>{try{e.json({ok:!0,...await pl(t.params.id,(t.body||{}).stateId)})}catch(n){let r=n?.message||String(n);e.status(Wi(r)).json({error:r})}});x.post("/api/tracks/:id/ticket/comment",async(t,e)=>{try{e.json({ok:!0,...await lh(t.params.id,(t.body||{}).body)})}catch(n){let r=n?.message||String(n);e.status(Wi(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 rt(t.params.id);if(!o)return e.status(404).json({error:"track not found"});e.json({ok:!0,...await uh(o,n,r===!0)})}catch(o){let s=o?.message||String(o);e.status(Wi(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 rt(n):null;if(!s&&r&&(s=(await ct()).find(i=>i.cwd&&(r===i.cwd||r.startsWith(i.cwd+ft.sep)))||null),!s)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});e.json({ok:!0,trackId:s.id,proposal:await fh(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 dh()})}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 rt(t.params.id);if(!s)return e.status(404).json({error:"track not found"});e.json({ok:!0,...await ph(s,n,r===!0,{teamId:o||null})})}catch(s){let a=s?.message||String(s);e.status(/unknown proposal|unknown team/i.test(a)?404:/already answered/i.test(a)?409:/required|no ticketing|pick one/i.test(a)?400:502).json({error:a})}});x.get("/api/mocks",async(t,e)=>{try{e.json({mocks:await Fn({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 cn(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 cn(t.params.id);if(!n)return e.status(404).json({error:"mock not found"});let r=Ce(n,t.query.v??null);if(!r)return e.status(409).json({error:`mock is ${n.state} \u2014 no captured version yet`});e.set("cache-control","private, max-age=31536000, immutable"),e.sendFile(Jt(n,r.v).png,o=>{o&&!e.headersSent&&e.status(404).json({error:"rendered artifact missing"})})});x.get("/api/mocks/:id/html",async(t,e)=>{let n=await cn(t.params.id);if(!n)return e.status(404).json({error:"mock not found"});let r=Ce(n,t.query.v??null);if(!r?.htmlRel)return e.status(404).json({error:"this version has no HTML source (image-sourced)"});e.set("content-security-policy","sandbox"),e.sendFile(Jt(n,r.v).html,o=>{o&&!e.headersSent&&e.status(404).json({error:"source artifact missing"})})});x.post("/api/mocks",async(t,e)=>{let n=Buffer.isBuffer(t.body)?t.body:null,r=n?t.query:t.body||{},{trackId:o,name:s,cwd:a,width:i,group:c}=r;try{let l=o?await rt(o):null;if(!l&&a&&(l=(await ct()).find(p=>p.cwd&&(a===p.cwd||a.startsWith(p.cwd+ft.sep)))||null),!l)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});let u=(l.boxes||[]).find(d=>d.name===l.leadBox),f=await nm({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 rm(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 sm(t.params.id),o=r.length?` over ${r.map(s=>`"${s.name}"`).join(", ")}`:"";await ce(n.trackId,`Design decision: go with "${n.name}" (mock:${n.id})${o} \u2014 proceed with this design.`,{kind:"mock-picked"}).catch(()=>{}),e.json({ok:!0,mock:n,siblings:r})}catch(n){let r=n?.message||String(n),o=/not found/i.test(r)?404:/nothing to pick/i.test(r)?409:500;e.status(o).json({error:r})}});x.delete("/api/mocks/:id",async(t,e)=>{try{let n=(await uy(t.params.id)).map(r=>({id:r.id,name:r.name}));e.json({...await am(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 sy({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 ay(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 rt(n):null;if(!a&&s&&(a=(await ct()).find(u=>u.cwd&&(s===u.cwd||s.startsWith(u.cwd+ft.sep)))||null),!a)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});let i=(a.boxes||[]).find(l=>l.name===a.leadBox),c=await iy({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 cy(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 ly(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+ft.sep)))?.id,!n)return e.json({runbooks:[]})}e.json({runbooks:await Ea({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 Jc(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 Jc(t.params.id);if(!n)return e.status(404).json({error:"runbook not found"});let r=$m(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(je(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 rt(n):null;if(!a&&s&&(a=(await ct()).find(u=>u.cwd&&(s===u.cwd||s.startsWith(u.cwd+ft.sep)))||null),!a)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});let i=(a.boxes||[]).find(l=>l.name===a.leadBox),c=await Cm({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 Nm(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 Pm(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 Bm(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=yk(t.query.since||null);e.json({since:n,repos:Ad(),prs:await bk(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 rt(o):null;if(!a&&s&&(a=(await ct()).find(c=>c.cwd&&(s===c.cwd||String(s).startsWith(c.cwd+ft.sep)))||null),!a)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});e.json({ok:!0,entry:await kk(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 PC=120,Bd=400,Hi=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>Bd)return e.status(400).json({error:`message is ${i.length} chars \u2014 over the ${Bd} cap; a notification is a nudge, put the detail in chat`});let c=No(`${n||""}
|
|
415
|
+
${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 rt(a):null;if(!l&&s&&(l=(await ct()).find(m=>m.cwd&&(s===m.cwd||s.startsWith(m.cwd+ft.sep)))||null),!l)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});let u=(l.boxes||[]).find(p=>p.name===l.leadBox),f=(p,m)=>String(p).replace(/\s+/g," ").trim().slice(0,m),d={title:n?f(n,PC):l.name,body:f(i,Bd),urgency:o==="high"?"high":"normal",trackName:l.name,ts:Date.now()};await R(l,{type:"notify.raised",actor:"agent",boxId:l.leadBox??null,provider:u?.agent||null,payload:d}),e.json({ok:!0,notification:d,delivered:Hi?Hi.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 Yk(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 Bo(),terms:await Cd(),problem:await Xk()})});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 Jk(n,r)})});x.get("/api/tracks/:id/voice-chat",async(t,e)=>{if(!await rt(t.params.id))return e.status(404).json({error:"track not found"});e.json(await Zk())});x.post("/api/tracks/:id/voice-chat/session",async(t,e)=>{try{let n=await rt(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 Qk())}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 rt(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 tx(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 Zu({name:n,cwd:r,repo:o,repos:s,agent:a,skipPermissions:i!==!1}),u=null;if(c)try{await Rr(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 rt(t.params.id);if(!n)return e.status(404).json({error:"track not found"});e.json({track:n,handoff:dx(gs(t.params.id)),subtasks:to(t.params.id).map(Cs)})});x.delete("/api/tracks/:id",async(t,e)=>{try{let n=await tb(t.params.id,{cascadeMocks:t.query.mocks!=="0",cascadeRunbooks:t.query.runbooks==="1"});await fl(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 Qr(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 ob(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 zu(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 rt(t.params.id);if(!n)return e.status(404).json({error:"track not found"});e.set({"content-type":"text/event-stream","cache-control":"no-cache",connection:"keep-alive","x-accel-buffering":"no"}),e.flushHeaders();let r=i=>e.write(`id: ${i.seq}
|
|
418
416
|
data: ${JSON.stringify(i)}
|
|
419
417
|
|
|
420
|
-
`),o=Number(t.query.since??t.headers["last-event-id"])||0;for(let i of await
|
|
418
|
+
`),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=kp(n.id,r),a=setInterval(()=>e.write(`: hb
|
|
421
419
|
|
|
422
|
-
`),25e3);t.on("close",()=>{clearInterval(a),s()})});x.get("/api/tracks/:id/search",async(t,e)=>{let n=await
|
|
423
|
-
`);x.post("/api/tracks/:id/memory/consolidate",async(t,e)=>{try{e.json({ok:!0,...await
|
|
420
|
+
`),25e3);t.on("close",()=>{clearInterval(a),s()})});x.get("/api/tracks/:id/search",async(t,e)=>{let n=await rt(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=Io(await L(n)),a=ia(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 hf()})});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 wf(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 BC=["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(`
|
|
421
|
+
`);x.post("/api/tracks/:id/memory/consolidate",async(t,e)=>{try{e.json({ok:!0,...await ce(t.params.id,BC)})}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 ab(t.params.id)})}catch(n){let r=n?.message||String(n),o=/not found/i.test(r)?404:/mid-turn|terminal|archived/i.test(r)?409:500;e.status(o).json({error:r})}});x.post("/api/tracks/:id/say",async(t,e)=>{let{text:n,attachments:r}=t.body||{};try{e.json(await ce(t.params.id,n,{attachments:r}))}catch(o){let s=o?.message||String(o);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 rt(t.params.id);if(!n)return e.status(404).json({error:`track "${t.params.id}" not found`});if(n.archivedAt)return e.status(409).json({error:"track is archived"});if(!n.cwd)return e.status(409).json({error:"track has no workspace yet"});let r=Buffer.isBuffer(t.body)?t.body:null,o=await wy(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 rt(t.params.id);if(!n)return e.status(404).json({error:`track "${t.params.id}" not found`});if(!n.cwd)return e.status(404).json({error:"track has no workspace"});let r=ft.basename(String(t.params.file||""));if(!r||!Du(ft.extname(r).slice(1)))return e.status(400).json({error:"not an upload path"});e.set("cache-control","private, max-age=31536000, immutable"),e.set("content-security-policy","sandbox"),e.sendFile(ft.join(Bu(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 DC=/^[0-9a-fA-F-]{8,64}$/;x.get("/api/tracks/:id/chat-images/:box/:uuid/:idx",async(t,e)=>{try{let n=await rt(t.params.id);if(!n)return e.status(404).json({error:`track "${t.params.id}" not found`});let{box:r,uuid:o}=t.params,s=Number(t.params.idx);if(!DC.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=$e(a.cwd,a.sessionId);if(!i)return e.status(404).json({error:"transcript not found"});let c=`"${o}"`,l=null,u=IC.createInterface({input:Gt.createReadStream(i,{encoding:"utf8"}),crlfDelay:1/0});for await(let d of u){if(!d.includes(c))continue;let p;try{p=JSON.parse(d)}catch{continue}if(p.uuid===o){l=p,u.close();break}}let f=l?xc(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 Yu(t.params.id))}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:/archived/i.test(r)?409:500).json({error:r})}});x.delete("/api/tracks/:id/queue",async(t,e)=>{try{let{removed:n,restored:r,draftRev:o}=await Py(t.params.id);e.json({ok:!0,removed:n.filter(s=>s.kind!=="ship-notify"),restored:r,draftRev:o})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.post("/api/tracks/:id/answer-prompt",async(t,e)=>{let{promptId:n,answers:r,text:o}=t.body||{};try{e.json(await Ju(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 Zy({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 Ny(t.params.id,{promptId:n,questionIndex:r}))}catch(o){let s=o?.message||String(o),a=/not found/i.test(s)?404:/no pending prompt|changed since|not running|not supported|out of range|review screen/i.test(s)?409:500;e.status(a).json({error:s})}});x.put("/api/tracks/:id/draft",async(t,e)=>{let{text:n,rev:r}=t.body||{};try{e.json(await Hy(t.params.id,typeof n=="string"?n:"",{rev:typeof r=="number"?r:null}))}catch(o){let s=o?.message||String(o),a=/not found/i.test(s)?404:/required/i.test(s)?400:500;e.status(a).json({error:s})}});x.get("/api/tracks/:id/usage",async(t,e)=>{let n=await rt(t.params.id);if(!n)return e.status(404).json({error:"track not found"});let r=n.boxes?.find(s=>s.name===n.leadBox)||null,o=t.query.fresh==="1"||t.query.fresh==="true";e.json(await Ri(r,{fresh:o}))});x.post("/api/tracks/:id/model",async(t,e)=>{let{model:n}=t.body||{};try{e.json(await Wy(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 db(t.params.id,{agent:n,task:r,skipPermissions:o!==!1,reason:s});e.json({ok:!0,subtask:Cs(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:to(t.params.id).map(Cs)})});x.get("/api/tracks/:id/subtask/:sid/messages",async(t,e)=>{let n=await Cy(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:Cs(od(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:Cs(pb(t.params.sid))})}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:409).json({error:r})}});function Cs(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 ed(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 Ly(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(dx(gs(t.params.id))??{running:!1})});function dx(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:Vs(),paths:Mf,trace:await Of({limit:n})})});var Dd=ft.resolve(ax,"..","..","client","dist");Gt.existsSync(Dd)&&(x.use(Ee.static(Dd)),x.get(/^(?!\/api\/).*/,(t,e)=>e.sendFile(ft.join(Dd,"index.html"))));var fx=$C.createServer(x),jC=new MC({noServer:!0}),px=(t,e,n)=>{if(!(As(t)||lo.allowed(lo.clientIp(t))||Ld.allows(t))){e.write(`HTTP/1.1 403 Forbidden\r
|
|
424
422
|
\r
|
|
425
|
-
`),e.destroy();return}if(
|
|
423
|
+
`),e.destroy();return}if(mk(t,e,n))return;if(!dk(t,cx,{enforce:lx})){e.write(`HTTP/1.1 401 Unauthorized\r
|
|
426
424
|
\r
|
|
427
|
-
`),e.destroy();return}let r=
|
|
425
|
+
`),e.destroy();return}let r=Kf(t.url);if(!r.ok){e.write(`HTTP/1.1 ${r.status} ${r.status===400?"Bad Request":"Not Found"}\r
|
|
428
426
|
\r
|
|
429
|
-
`),e.destroy();return}let o=r.sessionName;if(!
|
|
427
|
+
`),e.destroy();return}let o=r.sessionName;if(!yt(o)){e.write(`HTTP/1.1 400 Bad Request\r
|
|
430
428
|
\r
|
|
431
|
-
`),e.destroy();return}let{cols:s,rows:a}=r;
|
|
429
|
+
`),e.destroy();return}let{cols:s,rows:a}=r;jC.handleUpgrade(t,e,n,i=>Hf(i,o,{cols:s,rows:a}))};fx.on("upgrade",px);var mx=process.env.BOSTRAT_CERT_DIR||ft.join(process.env.HOME||jd.tmpdir(),".bostrat","certs"),rx=Number(process.env.BOSTRAT_TLS_PORT)||3443,ox=ft.join(mx,"bostrat.globopi.key"),sx=ft.join(mx,"bostrat.globopi.crt");if(Gt.existsSync(ox)&&Gt.existsSync(sx)){let t=OC.createServer({key:Gt.readFileSync(ox),cert:Gt.readFileSync(sx)},x);t.on("upgrade",px),t.on("error",e=>{console.error(`[bostrat-agent] https listener failed (${e?.code||e?.message}) \u2014 continuing HTTP-only`)}),t.listen(rx,Fi,()=>{console.log(`[bostrat-agent] https listening on ${Fi||"0.0.0.0"}:${rx}`)})}fx.listen(ex,Fi,()=>{console.log(`[bostrat-agent] listening on ${Fi||"0.0.0.0"}:${ex}`),console.log(`[bostrat-agent] tmux socket: ${zt}`),console.log(`[bostrat-agent] auto-trigger default: ${ix}%`),console.log(`[bostrat-agent] ip allowlist: ${lo.open?"OPEN":process.env.BOSTRAT_ALLOW_IPS||"CLOSED (BOSTRAT_ALLOW_IPS unset)"}`+(Ld.active?` \xB7 view front-door: ${process.env.BOSTRAT_VIEW_FRONT_CIDRS} (registered views only)`:"")),lb().then(t=>t&&console.log("[bostrat-agent] reconciled interrupted handoff(s)")).catch(t=>console.error("[bostrat-agent] handoff reconcile failed:",t?.message||t)),ub().then(t=>t&&console.log("[bostrat-agent] reconciled interrupted subtask(s)")).catch(t=>console.error("[bostrat-agent] subtask reconcile failed:",t?.message||t)),sb().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)),Yy().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)),HC(),UC(),zb()&&Jb({listTracks:ct}),Ek(),mh({resolveTrack:rt}),vb({listTracks:ct,createTrack:Zu,archiveTrack:Qr,listRepos:_e,resolveTrack:rt,sayToLead:ce,answerPrompt:Ju,startLeadHandoff:ed,interruptLead:Yu,listSubtaskJobs:to,cancelSubtask:od,readEvents:L,appendEvent:R,knownAgents:fi}),LC(),vo().then(t=>{t||console.warn(`[boot] no host tmux server on ${zt} and auto-start failed \u2014 boxes cannot spawn until one runs`)}),Fk(),process.env.BOSTRAT_PLANE_URL&&process.env.BOSTRAT_PLANE_TOKEN&&(Hi=Rb({url:process.env.BOSTRAT_PLANE_URL,token:process.env.BOSTRAT_PLANE_TOKEN,nodeId:process.env.BOSTRAT_NODE_ID||jd.hostname(),name:process.env.BOSTRAT_NODE_NAME||jd.hostname(),listTracks:ct,detectAgents:Vu,sampleMetrics:Ob}),console.log(`[bostrat-agent] plane link \u2192 ${process.env.BOSTRAT_PLANE_URL}`),dr().catch(()=>{}),Lm().then(t=>t.pushed&&console.log(`[content-plane] seeded ${t.pushed} runbooks to the plane`),()=>{}),hh().then(t=>t.pushed&&console.log(`[content-plane] seeded ${t.pushed} ticket links to the plane`),()=>{}),lm().then(t=>t.pushed&&console.log(`[content-plane] seeded ${t.pushed} mocks to the plane`),()=>{}),py().then(t=>t.pushed&&console.log(`[content-plane] seeded ${t.pushed} flows to the plane`),()=>{}),Eh().then(t=>t.pushed&&console.log(`[content-plane] seeded ${t.pushed} PR links to the plane`),()=>{}))});process.on("SIGTERM",()=>{try{Hi?.stop()}catch{}setTimeout(()=>process.exit(0),150)});function LC(){FC(),Sa().catch(()=>{}),setInterval(()=>Sa().catch(()=>{}),1e4)}function FC(){for(let t of["bostrat-view","bostrat-mock","bostrat-flow","bostrat-ticket","bostrat-runbook","bostrat-notify","bostrat-gmail","bostrat-changelog","gh"])try{let e=ft.resolve(ax,"..","bin",t),n=ft.join(process.env.HOME||"",".local","bin");if(!process.env.HOME||!Gt.existsSync(e))continue;Gt.mkdirSync(n,{recursive:!0});let r=ft.join(n,t);(Gt.existsSync(r)?Gt.readlinkSync(r):null)!==e&&(Gt.rmSync(r,{force:!0}),Gt.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 UC(){let t=!1,e=async()=>{if(!t){t=!0;try{let n=await ct(),r=await Kb({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 HC(){let t=!1;setInterval(async()=>{if(!t){t=!0;try{let e=await ct();for(let n of e)n.archivedAt||!n.boxes?.some(r=>r.state==="live")||(await zu(n.id).catch(()=>{}),await Xg(n).catch(()=>{}))}catch{}finally{t=!1}}},4e3)}
|