bostrat-node 0.2.13 → 0.2.15
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 +44 -44
- package/dist/index.js +150 -145
- 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 tx=Object.defineProperty;var $d=(t,e)=>{for(var n in e)tx(t,n,{get:e[n],enumerable:!0})};import lt from"node:path";import Lt from"node:fs";import JC from"node:readline";import{fileURLToPath as XC}from"node:url";import Se from"express";import YC from"node:http";import ZC from"node:https";import _d from"node:os";import{WebSocketServer as QC}from"ws";import{execFile as b0}from"node:child_process";import{promisify as k0}from"node:util";import tc from"node:fs";import{randomUUID as x0}from"node:crypto";import Ft from"node:fs/promises";import Nd from"node:os";import Ct from"node:path";import{execFile as sx}from"node:child_process";import{promisify as ax}from"node:util";import{createHash as ex,randomBytes as nx,timingSafeEqual as rx}from"node:crypto";var Es="BOSTRAT_HOOK_TOKEN",_s="x-bostrat-hook";function Od(){return nx(32).toString("hex")}function oo(t){let e=typeof t=="string"?t.trim():"";return e?ex("sha256").update(e).digest("hex"):null}function Cd(t,e){return typeof t!="string"||typeof e!="string"||t.length!==e.length||!t?!1:rx(Buffer.from(t),Buffer.from(e))}var Ni=new Map,Pi=new Map,ox=6e4;function Is(t,e={},{now:n=Date.now(),log:r=console.warn}={}){if(Ni.set(t,(Ni.get(t)||0)+1),Pi.has(t)&&n-Pi.get(t)<ox)return!1;Pi.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=${Ni.get(t)}${o?` ${o}`:""}`),!0}async function Md(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 Rs(){return/^(1|true|yes|on)$/i.test(process.env.BOSTRAT_CLAUDE_USE_API_KEY||"")&&process.env.ANTHROPIC_API_KEY?.trim()||null}function ix(){return process.env.CLAUDE_CONFIG_DIR?Ct.join(process.env.CLAUDE_CONFIG_DIR,".claude.json"):Ct.join(Nd.homedir(),".claude.json")}function cx(){return process.env.CLAUDE_CONFIG_DIR?Ct.join(process.env.CLAUDE_CONFIG_DIR,"settings.json"):Ct.join(Nd.homedir(),".claude","settings.json")}async function Ge(t){if(!t||typeof t!="string")return;let e=Ct.resolve(t),n=ix(),r=cx(),o={};try{o=JSON.parse(await Ft.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 Ft.mkdir(Ct.dirname(n),{recursive:!0}),await Ft.writeFile(n,JSON.stringify(o,null,2));let i={};try{i=JSON.parse(await Ft.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 "${_s}: $${Es}" --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 Ft.mkdir(Ct.dirname(r),{recursive:!0}),await Ft.writeFile(r,JSON.stringify(i,null,2)),await lx(e)}async function lx(t){try{let e=Ct.resolve(t),n=Ct.join(e,".bostrat","memory","claude"),r=Ct.join(e,".claude","settings.local.json"),o={};try{o=JSON.parse(await Ft.readFile(r,"utf8"))}catch{}let s=o.autoMemoryDirectory,a=typeof s=="string"&&s.includes(Ct.join(".bostrat","memory"));if(s&&!a||s===n)return;o.autoMemoryDirectory=n,await Ft.mkdir(n,{recursive:!0}),await Ft.mkdir(Ct.dirname(r),{recursive:!0}),await Ft.writeFile(r,JSON.stringify(o,null,2)),await ux(e,["/.claude/settings.local.json"])}catch{}}async function ux(t,e){try{let{stdout:n}=await ax(sx)("git",["-C",t,"rev-parse","--git-path","info/exclude"]),r=Ct.resolve(t,n.trim()),o=await Ft.readFile(r,"utf8").catch(()=>""),s=e.filter(a=>!o.split(`
|
|
2
|
+
`).includes(a));if(!s.length)return;await Ft.mkdir(Ct.dirname(r),{recursive:!0}),await Ft.appendFile(r,(o&&!o.endsWith(`
|
|
3
3
|
`)?`
|
|
4
4
|
`:"")+s.join(`
|
|
5
5
|
`)+`
|
|
6
|
-
`)}catch{}}import
|
|
6
|
+
`)}catch{}}import Bi from"node:fs/promises";import dx from"node:os";import $s from"node:path";function fx(){let t=process.env.CODEX_HOME||$s.join(dx.homedir(),".codex");return $s.join(t,"config.toml")}function Pd(t){return`[projects."${t}"]`}function px(t,e){let n=Pd(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 rr(t){if(!t||typeof t!="string")return;let e=$s.resolve(t),n=fx(),r="";try{r=await Bi.readFile(n,"utf8")}catch{}if(px(r,e))return;let o=Pd(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 Bi.mkdir($s.dirname(n),{recursive:!0}),await Bi.writeFile(n,r)}catch(s){console.warn(`[codex-config] could not seed folder trust for ${e}: ${s?.message||s}`)}}import Bd from"node:fs/promises";import mx from"node:os";import so from"node:path";function hx(t){let n=so.resolve(t).slice(1).replace(/\//g,"-").replace(/\./g,""),r=process.env.CURSOR_CONFIG_DIR||so.join(mx.homedir(),".cursor");return so.join(r,"projects",n)}async function or(t){if(!t||typeof t!="string")return;let e=so.resolve(t),n=hx(e),r=so.join(n,".workspace-trusted");await Bd.mkdir(n,{recursive:!0}),await Bd.writeFile(r,`${JSON.stringify({trustedAt:new Date().toISOString(),workspacePath:e},null,2)}
|
|
14
|
+
`,"utf8")}import B from"node:fs/promises";import Hx from"node:os";import X from"node:path";import{execFile as Wx}from"node:child_process";import{promisify as qx}from"node:util";import Ex from"node:fs";import jd from"node:fs/promises";import io from"node:path";import _x from"node:os";import{fileURLToPath as Ix}from"node:url";import{execFile as Rx}from"node:child_process";import{promisify as $x}from"node:util";import ji from"node:fs/promises";import Ve from"node:path";import{execFile as wx}from"node:child_process";import{promisify as gx}from"node:util";var yx=gx(wx),Dd=process.env.HOME||"/home/globo",bx=Ve.join(process.env.BOSTRAT_STATE_DIR||Ve.join(Dd,".bostrat"),"repos"),kx=[...(process.env.BOSTRAT_REPOS_ROOT||Ve.join(Dd,"Documents")).split(","),bx].map(t=>t.trim()).filter(Boolean),xx=Number(process.env.BOSTRAT_REPOS_DEPTH)||3,Sx=new Set(["node_modules",".git",".cache","dist","build",".next","vendor","__pycache__","venv",".venv","target"]);async function vx(t){try{return(await ji.stat(Ve.join(t,".git"))).isDirectory()?"repo":"worktree"}catch{return null}}async function Tx(t){let e=i=>ji.readFile(Ve.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 ve(){let t=new Map;async function e(o,s){if(t.has(o))return;let a=await vx(o);if(a==="worktree")return;if(a==="repo"){t.set(o,{path:o,name:Ve.basename(o),branch:await Tx(o)});return}if(s>=xx)return;let i;try{i=await ji.readdir(o,{withFileTypes:!0})}catch{return}for(let c of i)!c.isDirectory()||c.name.startsWith(".")||Sx.has(c.name)||await e(Ve.join(o,c.name),s+1)}for(let o of kx)await e(Ve.resolve(o),0);let n=new Set,r=[];for(let o of t.values()){let s=(await tt(o.path))?.toLowerCase();if(s){if(n.has(s))continue;n.add(s)}r.push(o)}return r.sort((o,s)=>o.name.localeCompare(s.name))}function Ax(t){let e=String(t||"").trim().match(/github\.com[:/]+([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/i);return e?`${e[1]}/${e[2]}`:null}var Di=new Map;async function tt(t){if(!t)return null;if(Di.has(t))return Di.get(t);let e=null;try{let{stdout:n}=await yx("git",["-C",t,"config","--get","remote.origin.url"],{timeout:5e3});e=Ax(n)}catch{e=null}return Di.set(t,e),e}async function ao(t){let e=String(t||"").toLowerCase();if(!e)return null;for(let n of await ve())if((await tt(n.path))?.toLowerCase()===e)return n.path;return null}var Ld=$x(Rx),Ox=process.env.BOSTRAT_STATE_DIR||io.join(process.env.HOME||_x.tmpdir(),".bostrat"),Cx=io.join(Ox,"repos"),Fd=/^[^/\s]+\/[^/\s]+$/,Mx=5*6e4,Nx=6e4,Px=10*6e4,Ud=fetch,Li=new Map,En=null;function zt(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 Os(t=process.env){return!!(zt(t)&&t.BOSTRAT_PLANE_TOKEN)}var Hd=t=>({authorization:`Bearer ${t.BOSTRAT_PLANE_TOKEN||""}`}),sr=(t,e)=>Object.assign(new Error(t),{status:e});async function Cs(t,{env:e=process.env}={}){let n=String(t||"").toLowerCase();if(!Fd.test(n))throw sr("repo must be owner/name",400);let r=Li.get(n);if(r&&r.expiresAt-Date.now()>Mx)return r;if(!Os(e))throw sr("node is not plane-linked \u2014 GitHub auth unavailable",503);let o;try{o=await Ud(`${zt(e)}/v1/github/token?repo=${encodeURIComponent(t)}`,{headers:Hd(e)})}catch(i){if(r&&r.expiresAt>Date.now())return r;throw sr(`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 sr(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 Li.set(n,a),a}async function co({env:t=process.env,force:e=!1}={}){if(!Os(t))return{configured:!1,repos:[],username:null};if(!e&&En&&Date.now()-En.at<Nx)return En.data;try{let n=await Ud(`${zt(t)}/v1/github/repos`,{headers:Hd(t)});if(!n.ok)throw new Error(`plane replied ${n.status}`);let r=await n.json();return En={at:Date.now(),data:r},r}catch(n){return En?En.data:{configured:!0,repos:[],username:null,error:n?.message||String(n)}}}function Wd(){Li.clear(),En=null}async function ae({cwd:t=null,slug:e=null,env:n=process.env}={}){try{let r=e||(t?await tt(t):null);if(!r)return{};let{token:o}=await Cs(r,{env:n});return o?{GH_TOKEN:o}:{}}catch{return{}}}async function Fi({env:t=process.env}={}){return(await co({env:t})).username??null}var Bx=()=>Ix(new URL("../bin/bostrat-git-credential",import.meta.url)),Dx=(t=process.env)=>`http://127.0.0.1:${Number(t.PORT)||3362}`;function jx(t){let[e,n]=String(t).split("/");return io.join(Cx,e,n)}async function Ui(t,{env:e=process.env}={}){let n=(...r)=>Ld("git",["-C",t,...r],{timeout:1e4});await n("config","--replace-all","credential.helper",""),await n("config","--add","credential.helper",`!"${Bx()}" ${Dx(e)}`),await n("config","credential.useHttpPath","true")}async function Hi(t,{env:e=process.env,baseUrl:n="https://github.com"}={}){if(!Fd.test(String(t||"")))throw sr(`bad repo slug "${t}"`,400);let r=jx(t);if(Ex.existsSync(io.join(r,".git")))return r;let{token:o}=await Cs(t,{env:e});await jd.mkdir(io.dirname(r),{recursive:!0});let s="!f() { echo username=x-access-token; echo password=$BOSTRAT_GIT_TOKEN; }; f";try{await Ld("git",["-c","credential.helper=","-c",`credential.helper=${s}`,"clone","--no-tags","-q",`${n}/${t}.git`,r],{timeout:Px,env:{...e,BOSTRAT_GIT_TOKEN:o,GIT_TERMINAL_PROMPT:"0"}})}catch(a){throw await jd.rm(r,{recursive:!0,force:!0}).catch(()=>{}),sr(`clone of ${t} failed: ${a?.stderr?.trim?.()||a?.message||a}`,502)}return await Ui(r,{env:e}),r}var Lx=6e4,Fx=5e3,Wi=new Map,Ux=(...t)=>fetch(...t);function qd(t=process.env){return!!(zt(t)&&t.BOSTRAT_PLANE_TOKEN)}var Kd=t=>({authorization:`Bearer ${t.BOSTRAT_PLANE_TOKEN||""}`});async function Gd(t,e,n){let r=zt(n),o=new AbortController,s=setTimeout(()=>o.abort(),Fx);try{return await Ux(`${r}${t}`,{...e,signal:o.signal})}finally{clearTimeout(s)}}async function Vd(t,{env:e=process.env,force:n=!1}={}){if(!qd(e))return{facts:[],source:"unconfigured"};let r=Wi.get(t);if(!n&&r&&Date.now()-r.at<Lx)return{facts:r.facts,source:"cache"};try{let o=await Gd(`/v1/memory/${encodeURIComponent(t)}`,{headers:Kd(e)},e);if(!o.ok)throw new Error(`plane ${o.status}`);let s=await o.json(),a=Array.isArray(s?.facts)?s.facts:[];return Wi.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 Ms(t,e,{env:n=process.env}={}){if(!qd(n))return{pushed:0,source:"unconfigured"};if(!e?.length)return{pushed:0,source:"empty"};try{let r=await Gd(`/v1/memory/${encodeURIComponent(t)}`,{method:"POST",headers:{...Kd(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 Wi.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 Kx=qx(Wx),qi=process.env.BOSTRAT_STATE_DIR||X.join(process.env.HOME||Hx.tmpdir(),".bostrat"),zd=200,Gx=25*1024;function Ns(){return X.join(qi,"memory","repos")}var Te="operator";function _n(t){return t===Te?X.join(qi,"memory",Te):X.join(Ns(),t)}var Zd=".tombstones.json";async function lo(t){try{return JSON.parse(await B.readFile(X.join(t,Zd),"utf8"))}catch{return{}}}async function Ki(t,e){let n=await lo(t);for(let[r,o]of e)n[r]=Math.max(Number(n[r])||0,o);await B.mkdir(t,{recursive:!0}),await B.writeFile(X.join(t,Zd),JSON.stringify(n))}function Jd(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 Xd(t){try{let{stdout:e}=await Kx("git",["-C",t,"config","--get","remote.origin.url"]);return e.trim()||null}catch{return null}}async function Gi(t){if(!t)return[];let e=Jd(await Xd(t));if(e)return[e];let n=[],r=[];try{r=await B.readdir(t,{withFileTypes:!0})}catch{return[]}for(let o of r){if(!o.isDirectory()||o.name.startsWith("."))continue;let s=Jd(await Xd(X.join(t,o.name)));s&&!n.includes(s)&&n.push(s)}return n}function Ps(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 ze(t,e){let n=new RegExp(`^\\s*${e}:\\s*(.+?)\\s*$`,"m").exec(t||"");return n?n[1].replace(/^"|"$/g,""):null}async function ie(t,e=null){let n=e??await B.readFile(t,"utf8").catch(()=>""),{fm:r}=Ps(n),o=ze(r,"modified"),s=o?Date.parse(o):NaN;if(Number.isFinite(s))return s;try{return(await B.stat(t)).mtimeMs}catch{return 0}}function Qd(t,e){let n=ze(t,"scope"),r=/^repo:([a-z0-9][a-z0-9._-]*)$/i.exec(n||"");if(r)return r[1].toLowerCase();if((n||"").toLowerCase()===Te){let o=ze(t,"trust");if(!o||/^operator/i.test(o))return Te}return e}var ar=t=>/\]\(([^)]+\.md)\)/.exec(t)?.[1]||null;function Vi(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=ar(c);l&&c.trim().startsWith("-")&&s.set(l,c)}let a=new Set,i=o.map(c=>{let l=ar(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 tf(t,e){let n=ze(e,"name")||t.replace(/\.md$/,""),r=ze(e,"description")||"";return`- [${n}](${t})${r?` \u2014 ${r}`:""}`}async function uo(t){let e=[];try{e=await B.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 ef(t,e){let n=X.join(t,".bostrat","memory"),r=await uo(n);if(!r.length)return{harvested:0,changedByKey:new Map};let o=new Map,s=new Map,a=0,i=new Map;for(let l of r){let u=X.join(n,l),f;try{f=await B.readFile(u,"utf8")}catch{continue}let{fm:d}=Ps(f),p=Qd(d,e);if(!p)continue;let m=_n(p),g=X.join(m,l),y=await ie(u,f);if(i.has(m)||i.set(m,await lo(m)),(i.get(m)[l]||0)>=y)continue;let b=await ie(g).catch(()=>0);if(await B.access(g).then(()=>!0).catch(()=>!1)&&b>=y)continue;await B.mkdir(m,{recursive:!0}),await B.writeFile(g,f),a++;let E=o.get(m);E||o.set(m,E={won:new Set,generated:new Map}),E.won.add(l),E.generated.set(l,tf(l,d)),s.has(p)||s.set(p,new Set),s.get(p).add(l)}let c=await B.readFile(X.join(n,"MEMORY.md"),"utf8").catch(()=>"");for(let[l,u]of o){let f=X.join(l,"MEMORY.md"),d=await B.readFile(f,"utf8").catch(()=>"");await B.writeFile(f,Vi(d,c,u.won,u.generated))}return{harvested:a,changedByKey:s}}async function Vx(){let t=X.join(qi,"workspaces"),e=[];try{e=await B.readdir(t,{withFileTypes:!0})}catch{return{harvested:0}}let n=0,r=new Map;for(let o of e){if(!o.isDirectory())continue;let s=X.join(t,o.name);if(!await B.access(X.join(s,".bostrat","memory")).then(()=>!0).catch(()=>!1))continue;let[i]=await Gi(s),c=await ef(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 zx=/^[A-Za-z0-9][A-Za-z0-9._-]*\.md$/,Jx=t=>{let e=new Map;for(let n of(t||"").split(`
|
|
20
|
+
`)){let r=ar(n);r&&n.trim().startsWith("-")&&e.set(r,n)}return e};async function Xx(t,e){let n=_n(t),r=await lo(n),o=new Set,s=new Map,a=[],i=0;for(let c of e||[]){let l=String(c?.file||"");if(!zx.test(l)||l==="MEMORY.md")continue;let u=X.join(n,l),f=await B.access(u).then(()=>!0).catch(()=>!1),d=f?await ie(u):0,p=Number(c.modified)||0;if(c.deleted){p>d&&(f&&await B.rm(u,{force:!0}),await Ki(n,[[l,p]]),f&&(a.push(l),i++));continue}f&&d>=p||(r[l]||0)>=p||(await B.mkdir(n,{recursive:!0}),await B.writeFile(u,String(c.content??"")),o.add(l),typeof c.indexLine=="string"&&c.indexLine.trim()&&s.set(l,c.indexLine),i++)}if(o.size||a.length){let c=X.join(n,"MEMORY.md"),l=await B.readFile(c,"utf8").catch(()=>""),u=Vi(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(ar(d))).join(`
|
|
23
|
+
`)}await B.mkdir(n,{recursive:!0}),await B.writeFile(c,u)}return{changed:i,tombstoned:a.length}}async function zi(t,e=null){let n=_n(t),r=e?.length?e:await uo(n);if(!r.length)return{pushed:0,source:"empty"};let o=Jx(await B.readFile(X.join(n,"MEMORY.md"),"utf8").catch(()=>"")),s=[];for(let a of r){let i=X.join(n,a),c;try{c=await B.readFile(i,"utf8")}catch{continue}s.push({file:a,content:c,modified:await ie(i,c),indexLine:o.get(a)??null})}return Ms(t,s)}async function Yx(t){let{facts:e,source:n}=await Vd(t);e.length?await Xx(t,e):n==="plane"&&await zi(t)}function Zx(t){let e=t.split(`
|
|
24
|
+
`);e.length>zd&&(e=e.slice(0,zd));let n=e.join(`
|
|
25
|
+
`);for(;Buffer.byteLength(n,"utf8")>Gx&&e.length;)e.pop(),n=e.join(`
|
|
26
|
+
`);return n}var Qx=`# 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 t0(t,e){if(!t||!e?.length)return{materialized:!1,reason:"no-repo-key"};let n=X.join(t,".bostrat","memory"),r=0,o=0,s=[];for(let u of e){let f=_n(u),d=await lo(f);for(let[y,b]of Object.entries(d)){let S=X.join(n,y);await B.access(S).then(()=>!0).catch(()=>!1)&&await ie(S)<=b&&(await B.rm(S,{force:!0}),o++)}let p=await uo(f);if(!p.length)continue;await B.mkdir(n,{recursive:!0});for(let y of p){let b=X.join(f,y),S=X.join(n,y),E=await ie(b),M=await ie(S).catch(()=>0);await B.access(S).then(()=>!0).catch(()=>!1)&&M>=E||(await B.copyFile(b,S),r++)}let m=await B.readFile(X.join(f,"MEMORY.md"),"utf8").catch(()=>""),g=u===Te?"## operator memory":`## repo: ${u}`;m.trim()&&s.push(e.length>1?`${g}
|
|
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(
|
|
38
|
-
`);await
|
|
39
|
-
`).filter(l=>
|
|
40
|
-
`)).catch(()=>{});let c=await
|
|
41
|
-
`,"utf8");let n=(await
|
|
42
|
-
|
|
36
|
+
`,c=Zx(i);await B.mkdir(n,{recursive:!0}),await B.writeFile(X.join(n,"MEMORY.md"),c);let l=X.join(n,"claude");return await B.mkdir(l,{recursive:!0}),await B.writeFile(X.join(l,"MEMORY.md"),Qx,{flag:"wx"}).catch(()=>{}),{materialized:!0,facts:r,capped:c!==i}}var Yd=0,e0=3e4;async function nf(t){try{let e=await Gi(t);if(!e.length)return{materialized:!1,reason:"no-repo-key"};let n=[...e,Te];if(Date.now()-Yd>e0){Yd=Date.now(),await n0();let{changedByKey:r}=await Vx();for(let[o,s]of r)await zi(o,[...s])}for(let r of n)await Yx(r);return await t0(t,n)}catch(e){return{materialized:!1,reason:"error",error:e?.message||String(e)}}}async function n0(){let t=0,e=[];try{e=await B.readdir(Ns(),{withFileTypes:!0})}catch{return{moved:t}}for(let n of e){if(!n.isDirectory())continue;let r=X.join(Ns(),n.name),o=_n(Te),s=new Set,a=new Map;for(let p of await uo(r)){let m=X.join(r,p),g;try{g=await B.readFile(m,"utf8")}catch{continue}let{fm:y}=Ps(g);if(Qd(y,n.name)!==Te)continue;let b=X.join(o,p),S=await ie(b).catch(()=>0),E=await ie(m,g);await B.mkdir(o,{recursive:!0}),E>S&&await B.writeFile(b,g),await B.rm(m,{force:!0}),s.add(p),a.set(p,tf(p,y)),t++}if(!s.size)continue;let i=Date.now();await Ki(r,[...s].map(p=>[p,i])),await Ms(n.name,[...s].map(p=>({file:p,deleted:!0,modified:i})));let c=X.join(r,"MEMORY.md"),l=await B.readFile(c,"utf8").catch(()=>""),u=l.split(`
|
|
37
|
+
`).filter(p=>!s.has(ar(p))).join(`
|
|
38
|
+
`);await B.writeFile(c,u).catch(()=>{});let f=X.join(o,"MEMORY.md"),d=await B.readFile(f,"utf8").catch(()=>"");await B.writeFile(f,Vi(d,l,s,a))}return{moved:t}}async function rf(){let t=[],e=[Te];try{for(let n of await B.readdir(Ns(),{withFileTypes:!0}))n.isDirectory()&&e.push(n.name)}catch{}for(let n of e){let r=_n(n),o=await uo(r),s=await lo(r);if(!o.length&&!Object.keys(s).length)continue;let a=[];for(let i of o){let c=await B.readFile(X.join(r,i),"utf8").catch(()=>""),{fm:l}=Ps(c);a.push({file:i,name:ze(l,"name")||i.replace(/\.md$/,""),description:ze(l,"description")||"",scope:ze(l,"scope")||null,modified:await ie(X.join(r,i),c)})}t.push({key:n,facts:a,tombstones:Object.keys(s).length})}return t}async function of(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=_n(t),r=X.join(n,e);if(!await B.access(r).then(()=>!0).catch(()=>!1))throw new Error(`fact ${e} not found in ${t}`);let s=Date.now();await B.rm(r,{force:!0}),await Ki(n,[[e,s]]);let a=X.join(n,"MEMORY.md"),i=await B.readFile(a,"utf8").catch(()=>"");await B.writeFile(a,i.split(`
|
|
39
|
+
`).filter(l=>ar(l)!==e).join(`
|
|
40
|
+
`)).catch(()=>{});let c=await Ms(t,[{file:e,deleted:!0,modified:s}]);return{deleted:e,key:t,plane:c.source}}async function sf(t){try{let e=await Gi(t),{harvested:n,changedByKey:r}=await ef(t,e[0]||null);for(let[o,s]of r)await zi(o,[...s]);return{harvested:n}}catch(e){return{harvested:0,error:e?.message||String(e)}}}import Zi from"node:fs/promises";import hf from"node:path";import lf from"node:fs/promises";import uf from"node:os";import df from"node:path";import Ji from"node:fs/promises";import r0 from"node:path";var o0=0;async function Mt(t,e,{mode:n}={}){await Ji.mkdir(r0.dirname(t),{recursive:!0});let r=`${t}.${process.pid}.${++o0}.tmp`;await Ji.writeFile(r,JSON.stringify(e,null,2)+`
|
|
41
|
+
`,n!=null?{mode:n}:void 0),await Ji.rename(r,t)}var s0=1e4,a0=3e4,af=2e3,i0=()=>process.env.BOSTRAT_STATE_DIR||df.join(process.env.HOME||uf.tmpdir(),".bostrat"),ff=()=>df.join(i0(),"content-outbox.json"),c0=()=>process.env.BOSTRAT_NODE_ID||uf.hostname(),l0=(...t)=>fetch(...t),u0=a0;function pt(t=process.env){return!!(zt(t)&&t.BOSTRAT_PLANE_TOKEN)}var po=(t=process.env)=>({authorization:`Bearer ${t.BOSTRAT_PLANE_TOKEN||""}`});async function mo(t,e={},n=process.env){let r=new AbortController,o=setTimeout(()=>r.abort(),s0);try{return await l0(`${zt(n)}${t}`,{...e,signal:r.signal})}finally{clearTimeout(o)}}var Ds=null,Xi=null,Bs=null,fo=null;async function pf(){return Ds||(Xi||=lf.readFile(ff(),"utf8").then(t=>JSON.parse(t)).catch(()=>({ops:[]})).then(t=>Ds={ops:Array.isArray(t?.ops)?t.ops:[]}).finally(()=>Xi=null),Xi)}async function mf(){await Mt(ff(),Ds).catch(()=>{})}function cf(t){return t.op==="blob"?`blob ${t.family} ${t.id} ${t.v} ${t.kind}`:`${t.op} ${t.family} ${t.id}`}async function Yi(t){let e=await pf(),n=cf(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=>cf(o)===n);r>=0?e.ops[r]=t:e.ops.push(t)}e.ops.length>af&&(e.ops=e.ops.slice(-af)),await mf()}function d0(){Bs||(Bs=setTimeout(()=>{Bs=null,ir().catch(()=>{})},u0),Bs.unref?.())}async function f0(t,e=process.env){if(t.op==="record"){let n=await mo(`/v1/content/${encodeURIComponent(t.family)}`,{method:"POST",headers:{...po(e),"content-type":"application/json"},body:JSON.stringify({records:[{id:t.id,trackId:t.trackId??null,data:t.data,updatedAt:t.updatedAt,originNode:c0()}]})},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 lf.readFile(t.path).catch(()=>null);if(!n)return{delivered:!0,dropped:"source file gone"};let r=await mo(`/v1/content/${encodeURIComponent(t.family)}/${encodeURIComponent(t.id)}/blob?v=${t.v}&kind=${encodeURIComponent(t.kind)}`,{method:"POST",headers:{...po(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 mo(`/v1/content/${encodeURIComponent(t.family)}/${encodeURIComponent(t.id)}?deletedAt=${t.deletedAt}`,{method:"DELETE",headers:po(e)},e);if(!n.ok)throw new Error(`plane ${n.status}`);return{delivered:!0}}return{delivered:!0,dropped:"unknown op"}}async function ir({env:t=process.env,log:e=console}={}){return pt(t)?fo||(fo=(async()=>{let n=await pf(),r=0;for(;n.ops.length;){let o=n.ops[0],s;try{s=await f0(o,t)}catch(a){return d0(),{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 mf()}return{flushed:r,pending:0}})().finally(()=>{fo=null}),fo):{flushed:0,source:"unconfigured"}}async function Rt(t,e,{trackId:n=null,env:r=process.env}={}){return pt(r)?(await Yi({op:"record",family:t,id:e.id,trackId:n??e.trackId??null,data:e,updatedAt:Number(e.updatedAt)||Date.now()}),ir({env:r})):{queued:!1,source:"unconfigured"}}async function Je(t,e,n,r,o,{env:s=process.env}={}){return pt(s)?(await Yi({op:"blob",family:t,id:e,v:Number(n),kind:r,path:o}),ir({env:s})):{queued:!1,source:"unconfigured"}}async function Qt(t,e,{deletedAt:n=Date.now(),env:r=process.env}={}){return pt(r)?(await Yi({op:"delete",family:t,id:e,deletedAt:n}),ir({env:r})):{queued:!1,source:"unconfigured"}}async function Tt(t,{env:e=process.env}={}){if(!pt(e))return{records:[],source:"unconfigured"};try{let n=await mo(`/v1/content/${encodeURIComponent(t)}?tombstones=1`,{headers:po(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 js(t,e,n,r,{env:o=process.env}={}){if(!pt(o))return null;try{let s=await mo(`/v1/content/${encodeURIComponent(t)}/${encodeURIComponent(e)}/blob?v=${Number(n)}&kind=${encodeURIComponent(r)}`,{headers:po(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 p0=".bostrat/handoff",m0="latest.md",h0=t=>/workspaces[/\\]([0-9a-f]{8})$/.exec(t||"")?.[1]??null;async function wf(t,e,{box:n=null,agent:r=null}={}){if(!t||!pt())return{pushed:!1};try{let o=Date.now();return await Rt("checkpoints",{id:t,trackId:t,v:o,box:n,agent:r,updatedAt:o},{trackId:t}),await Je("checkpoints",t,o,"md",e),{pushed:!0,v:o}}catch{return{pushed:!1}}}async function gf(t){try{let e=h0(t);if(!e||!pt())return{restored:!1};let n=hf.join(t,p0,m0);if(await Zi.stat(n).then(()=>!0,()=>!1))return{restored:!1};let{records:r,source:o}=await Tt("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 js("checkpoints",e,s.data.v,"md");return typeof a!="string"||!a?{restored:!1}:(await Zi.mkdir(hf.dirname(n),{recursive:!0}),await Zi.writeFile(n,a),{restored:!0,v:s.data.v})}catch{return{restored:!1}}}import ho from"node:fs/promises";import w0 from"node:os";import bf from"node:path";var g0=[/[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 wo(t){let e=String(t??"");for(let n of g0)e=e.replace(n,"\u2039redacted\u203A");return e}var Qi=process.env.BOSTRAT_DEBUG_DIR||bf.join(w0.homedir(),".bostrat","debug"),cr=bf.join(Qi,"handoff.jsonl"),yf=Number(process.env.BOSTRAT_HANDOFF_DEBUG_MAX)||500;function Ls(){let t=process.env.BOSTRAT_HANDOFF_DEBUG;return t==="1"||t==="true"}async function y0(t){try{await ho.mkdir(Qi,{recursive:!0}),await ho.appendFile(cr,`${JSON.stringify(t)}
|
|
42
|
+
`,"utf8");let n=(await ho.readFile(cr,"utf8")).split(`
|
|
43
|
+
`).filter(Boolean);n.length>yf&&await ho.writeFile(cr,`${n.slice(-yf).join(`
|
|
43
44
|
`)}
|
|
44
|
-
`,"utf8")}catch(e){console.warn(`[handoff-debug] failed to write ${
|
|
45
|
-
`).filter(Boolean).slice(-t).map(r=>{try{let o=JSON.parse(r);return typeof o?.paneTail=="string"&&(o.paneTail=
|
|
46
|
-
`).filter(Boolean).map(r=>{let[o,s,a,i,c,l,u,f,d]=r.split("|"),
|
|
47
|
-
`).map(n=>n.trim()).includes(t)}catch{return!1}}async function
|
|
48
|
-
`).filter(Boolean).length}catch{return 0}}async function
|
|
49
|
-
`);for(let n=e.length-1;n>=0;n--){let r=e[n].trim();if(!r||r[0]!=="{")continue;let o;try{o=JSON.parse(r)}catch{continue}let s=o?.message?.usage;if(!s)continue;let a=(s.input_tokens||0)+(s.cache_creation_input_tokens||0)+(s.cache_read_input_tokens||0);if(a>0)return{occupancy:a,model:o.message.model||o.model||null}}return null}async function
|
|
50
|
-
`).trim():""}function
|
|
51
|
-
`)){let i=a.trim();if(!i||i[0]!=="{")continue;let c;try{c=JSON.parse(i)}catch{continue}if(c.type==="attachment"){let
|
|
52
|
-
|
|
53
|
-
`):""}import
|
|
54
|
-
`);if(!
|
|
55
|
-
`);for(let r=n.length-1;r>=0;r--)if(
|
|
56
|
-
`);return e}function
|
|
57
|
-
`),o=[],s=-1;for(let l=0;l<r.length;l++){let u=r[l].match(
|
|
58
|
-
`),o=r.findIndex(i=>
|
|
59
|
-
`).map(a=>a.trim()),o=r.findIndex(a=>/Press any key to log in/.test(a)),s=r.slice(0,o<0?r.length:o).filter(a=>a&&!/^Cursor Agent$/i.test(a)&&!/^v?\d{4}\.\d{2}\.\d{2}/.test(a)).join(" ");return{provider:"cursor",box:e,multiSelect:!1,cursorIndex:0,answerMode:"keys",promptId:"cursor:signin-start",questions:[{question:(s?`${s} `:"")+"Cursor isn't signed in on this box \u2014 start the sign-in to get a login link you can open from any device.",header:"Sign in",multiSelect:!1,options:[{label:"Start sign-in",description:"Shows a login link (and a QR option) \u2014 your login goes to cursor.com, never through bostrat"}]}]}}var
|
|
60
|
-
`),o="",s=r.findIndex(a=>/https:\/\//.test(a));if(s>=0){o=r[s].slice(r[s].indexOf("https://")).trim();for(let a=s+1;a<r.length;a++){let i=r[a].trim();if(!i||!/^[A-Za-z0-9~%&=?/_.-]+$/.test(i))break;o+=i}}return{provider:"cursor",box:e,multiSelect:!1,cursorIndex:0,promptId:`cursor:signin-link:${(o.match(/[?&]challenge=([\w-]+)/)||[])[1]?.slice(0,16)||o.slice(-24)||"pending"}`,questions:[{question:o?`Cursor is waiting for you to finish signing in: open ${o} on any device and log in. Your login goes straight to cursor.com \u2014 bostrat never sees it. This card clears once sign-in completes.`:"Cursor is waiting for you to finish signing in \u2014 open the login link shown in the Terminal tab. This card clears once sign-in completes.",header:"Sign in",multiSelect:!1,options:[]}]}}function
|
|
61
|
-
`))
|
|
62
|
-
`).slice(-
|
|
63
|
-
`);return
|
|
64
|
-
`))if(a.trim())try{r.push(JSON.parse(a))}catch{}}catch{}return
|
|
65
|
-
${o}`.trim()||e.slice(0,2e3)}function
|
|
66
|
-
`):""}async function
|
|
45
|
+
`,"utf8")}catch(e){console.warn(`[handoff-debug] failed to write ${cr}: ${e?.message||e}`)}}async function U(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&&Ls())try{e.paneTail=wo((await ft(e.sessionName,{lines:35})).slice(-4e3))}catch{e.paneTail=null}(Ls()||e.error||/^(checkpoint|spawn|hydrate|commit|done|summarize|clear|resume|ready|send|failed|launch)/.test(String(e.phase||e.step||"")))&&await y0(e)}async function kf({limit:t=80}={}){try{return(await ho.readFile(cr,"utf8")).split(`
|
|
46
|
+
`).filter(Boolean).slice(-t).map(r=>{try{let o=JSON.parse(r);return typeof o?.paneTail=="string"&&(o.paneTail=wo(o.paneTail)),o}catch{return{raw:wo(r)}}})}catch{return[]}}var xf={DEBUG_DIR:Qi,LOG_FILE:cr};var nt=k0(b0);function lr(t){return new Promise(e=>setTimeout(e,t))}function Fs(t,e){if(!e)return t;let n=o=>typeof o=="string"?o.split(e).join("<redacted>"):o,r=new Error(n(t?.message)||"command failed");return t?.code!==void 0&&(r.code=t.code),t?.stderr!==void 0&&(r.stderr=n(t.stderr)),r}var N=process.env.BOSTRAT_TMUX_BIN||"/usr/bin/tmux",Ut=process.env.BOSTRAT_TMUX_SOCKET||"/tmp/tmux-1000/default",S0=process.env.BOSTRAT_DEFAULT_CWD||process.env.HOME||"/",ec=process.env.BOSTRAT_AGENT_CMD||"claude --dangerously-skip-permissions",nc="bostrat-usage-probe",v0=new Set([nc]);function rc(t,e){let n=In(t),r=/(^|\s)(--session-id|--resume|--continue|-r|-c)(\s|=|$)/.test(t);return!n||r?t:`${t} --session-id ${e}`}function In(t){return/(^|\/|\s)claude(\s|$)/.test(String(t||""))}function Xe(t){return/(^|\/|\s)claude(\s|$)/.test(t)?"claude":/cursor/.test(t)?"cursor":/codex/.test(t)?"codex":/gemini/.test(t)?"gemini":"agent"}function T0(t){let e=String(t||"").trim();return!e||/fake-|\.sh(\s|$)/.test(e)?!1:In(e)||/(^|\/|\s)codex(\s|$)/.test(e)||/cursor-agent/.test(e)||/(^|\/|\s)gemini(\s|$)/.test(e)}async function oc(t,{loginShell:e=!1,failOpen:n=!1}={}){if(!t)return!1;let r=e?process.env.SHELL||"/bin/sh":"sh",o=e?"-lic":"-c";try{return await nt(r,[o,'command -v "$1" >/dev/null 2>&1',"_",t],{timeout:5e3}),!0}catch(s){return s?.code===1?!1:n}}async function Us(t){return oc(String(t||"").trim().split(/\s+/)[0])}var sc={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",Ut,...t]}async function Ye(){if(!tc.existsSync(Ut))return!1;try{return await nt(N,P("list-sessions")),!0}catch(t){let e=t&&t.stderr?String(t.stderr):"";return!/no server running/i.test(e)}}async function go(){if(await Ye())return!0;try{tc.mkdirSync(Ut.replace(/\/[^/]+$/,""),{recursive:!0}),await nt(N,P("new-session","-d","-s","keeper","sleep 2147483647"))}catch{}return await Ye()}var A0=/^[A-Za-z0-9_.-]{1,64}$/;function wt(t){return typeof t=="string"&&A0.test(t)}async function Rn(){if(!tc.existsSync(Ut))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 nt(N,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=>!v0.has(r.name)&&!r.name.startsWith("bostrat-view-"))}catch{return[]}}async function Z(t){return(await Rn()).find(n=>n.name===t)||null}var E0=/^[A-Za-z0-9 ._↻-]+$/,_0=64;function I0(t){if(typeof t!="string")return!1;let e=t.trim();return e.length>=1&&e.length<=_0&&E0.test(e)}function ac(t){return t.trim().replace(/\s+/g,"-").toLowerCase().replace(/[^A-Za-z0-9_.-]/g,"").replace(/^-+|-+$/g,"")}async function Sf(t){try{let{stdout:e}=await nt(N,P("list-sessions","-F","#{session_name}"));return e.split(`
|
|
48
|
+
`).map(n=>n.trim()).includes(t)}catch{return!1}}var R0=()=>Number(process.env.BOSTRAT_LAUNCH_VERIFY_MS)||12e3,$0=1e3,O0=/not found|Unknown command/i;function C0(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 M0(t,e){let n=Date.now()+R0(),r=C0(e),o=!1;for(;;){if(await lr($0),!await Sf(t)||!await ur(t))return;let s=await ft(t,{lines:15});if(!o&&O0.test(s)&&r?.test(s)){o=!0,await U({flow:"spawn",phase:"launch-retry",sessionName:t,agent:Xe(e),promptHead:e.slice(0,120)}).catch(()=>{}),await nt(N,P("send-keys","-t",t,"C-u")),await nt(N,P("send-keys","-t",t,"-l",e)),await nt(N,P("send-keys","-t",t,"Enter"));continue}if(Date.now()>n){await U({flow:"spawn",phase:"launch-failed",sessionName:t,agent:Xe(e),retried:o,error:"agent CLI never started after launch was typed"}).catch(()=>{});return}}}async function $n(t,e=S0,n={}){if(!I0(t))throw new Error("displayName must be 1\u201364 chars (letters, digits, space . _ -)");let r=t.trim(),o=ac(r);if(!wt(o))throw new Error("resulting tmux session name is invalid");if(!await go())throw new Error(`host tmux server not running on ${Ut} and auto-start failed \u2014 start one: ${N} -S ${Ut} new-session -d -s keeper "sleep 2147483647"`);if(await Sf(o))throw new Error(`tmux session "${o}" already exists`);let s=n.agentCmd||ec,a=n.agent||Xe(s),i=!!n.resumeSessionId,c=n.resumeSessionId||x0(),l=i?s:rc(s,c),u=i?n.pinned===!0:/(^|\s)--session-id(\s|=)/.test(l),f=a==="claude"&&In(s),d=n.firstRun===!0;d||(f&&await Ge(e),a==="codex"&&await rr(e),a==="cursor"&&await or(e),await nf(e),await gf(e));let p=f&&!d?Rs():null,m=Od(),g=["-e",`${Es}=${m}`];p&&g.push("-e",`ANTHROPIC_API_KEY=${p}`);try{await nt(N,P("new-session","-d","-s",o,"-c",e,...g))}catch(y){throw Fs(p?Fs(y,p):y,m)}return await On(o),await nt(N,P("set-option","-t",o,"@bostrat-session","1")),await nt(N,P("set-option","-t",o,"@bostrat-title",r)),await nt(N,P("set-option","-t",o,"@bostrat-sid",c)),await nt(N,P("set-option","-t",o,"@bostrat-agent",a)),await nt(N,P("send-keys","-t",o,l,"Enter")),T0(l)&&M0(o,l).catch(()=>{}),{name:o,title:r,cwd:e,sessionId:c,agent:a,pinned:u,hookToken:m}}async function ce(t){if(!wt(t))throw new Error("invalid session name");await nt(N,P("kill-session","-t",t))}async function vf(t,e){if(wt(t))try{let n=e?P("set-option","-t",t,"@bostrat-sid",e):P("set-option","-u","-t",t,"@bostrat-sid");await nt(N,n)}catch{}}async function ft(t,{lines:e=40}={}){if(!wt(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 nt(N,n);return String(r||"")}catch{return""}}var N0=Number(process.env.BOSTRAT_PROMPT_CAPTURE_COLS)||100,P0=Number(process.env.BOSTRAT_PROMPT_CAPTURE_ROWS)||40,xt=Number(process.env.BOSTRAT_PROMPT_CAPTURE_LINES)||80,B0=Number(process.env.BOSTRAT_BOX_COLS)||120,D0=Number(process.env.BOSTRAT_BOX_ROWS)||40;async function Tf(t){if(!wt(t))return 0;try{let{stdout:e}=await nt(N,P("list-clients","-t",t,"-F","1"));return String(e||"").trim().split(`
|
|
49
|
+
`).filter(Boolean).length}catch{return 0}}async function On(t,{cols:e=B0,rows:n=D0}={}){if(wt(t)&&!await Tf(t))try{let r=await Af(t);if(r.cols===e&&r.rows===n)return;await nt(N,P("resize-window","-t",t,"-x",String(e),"-y",String(n))),await nt(N,P("set-window-option","-t",t,"window-size","latest")),await lr(80)}catch{}}async function j0(t){if(!wt(t))return 0;try{let{stdout:e}=await nt(N,P("display-message","-t",t,"-p","#{pane_pid}"));return Number(String(e||"").trim())||0}catch{return 0}}async function ur(t){let e=await j0(t);if(!e)return!1;try{return await nt("pgrep",["-P",String(e)]),!1}catch(n){return n?.code===1}}async function Af(t){if(!wt(t))return{cols:0,rows:0};try{let{stdout:e}=await nt(N,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 Hs(t,{cols:e=N0,rows:n=P0}={}){if(wt(t)&&!await Tf(t))try{let r=await Af(t),o=Math.max(r.cols,e),s=Math.max(r.rows,n);(o>r.cols||s>r.rows)&&(await nt(N,P("resize-window","-t",t,"-x",String(o),"-y",String(s))),await nt(N,P("set-window-option","-t",t,"window-size","latest")),await lr(80))}catch{}}async function Ze(t){return await Hs(t),ft(t,{lines:xt})}var L0=new Set(["Up","Down","Left","Right","Space","Enter","Tab","BTab","Escape","BSpace"]);async function bt(t,e,{settleMs:n=40,kind:r}={}){if(!wt(t))throw new Error("invalid session name");let o=(Array.isArray(e)?e:[e]).filter(s=>L0.has(s));for(let s=0;s<o.length;s++)await nt(N,P("send-keys","-t",t,o[s])),n>0&&s<o.length-1&&await lr(n);return U({flow:"tmux",phase:"send-keys",sessionName:t,kind:r||"keys",keys:o.join(" ")}).catch(()=>{}),o.length}async function Ws(t,e,{kind:n}={}){if(!wt(t))throw new Error("invalid session name");let r=Math.max(0,Math.floor(Number(e)||0));return r>0&&await nt(N,P("send-keys","-t",t,"-N",String(r),"BSpace")),U({flow:"tmux",phase:"send-keys",sessionName:t,kind:n||"wipe",keys:`BSpace x${r}`}).catch(()=>{}),r}async function qs(t,e,{perCharDelayMs:n=120,kind:r}={}){if(!wt(t))throw new Error("invalid session name");let o=[...String(e)];for(let s of o)await nt(N,P("send-keys","-t",t,"-l",s)),n>0&&await lr(n);U({flow:"tmux",phase:"type",sessionName:t,kind:r||"type",textLen:o.length,textHead:String(e).slice(0,120)}).catch(()=>{})}async function St(t,e,{enter:n=!0,submitDelayMs:r=0,kind:o}={}){if(!wt(t))throw new Error("invalid session name");if(typeof e=="string"&&e.length>0&&await nt(N,P("send-keys","-t",t,"-l",e)),n){let s=Number(r);Number.isFinite(s)&&s>0&&await lr(s),await nt(N,P("send-keys","-t",t,"Enter"))}U({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 F0 from"node-pty";async function _f(t,e,n={}){if(!wt(e)){Ks(t,`invalid session name: ${e}`),t.close(1008,"invalid session name");return}if(!await Ye()){Ks(t,`host tmux server not running on ${Ut}`),t.close(1011,"tmux server unavailable");return}if(!await Z(e)){Ks(t,`session "${e}" not found`),t.close(1008,"session not found");return}let r=Ef(n.cols,120,If),o=Ef(n.rows,32,Rf),s;try{s=F0.spawn(N,["-S",Ut,"attach-session","-d","-t",e],{name:"xterm-256color",cols:r,rows:o,cwd:process.env.HOME||"/",env:{...process.env,TERM:"xterm-256color"}})}catch(a){Ks(t,`failed to spawn tmux attach: ${a.message}`),t.close(1011,"spawn failed");return}s.onData(a=>{t.readyState===t.OPEN&&ic(t,{type:"data",data:a})}),s.onExit(({exitCode:a,signal:i})=>{t.readyState===t.OPEN&&(ic(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"&&U0(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{}On(e).catch(()=>{})})}var If=20,Rf=6;function U0(t,e){return Number.isFinite(Number(t))&&Number.isFinite(Number(e))&&Number(t)>=If&&Number(e)>=Rf}function Ef(t,e,n){let r=Number(t);return!Number.isFinite(r)||r<n?e:Math.min(500,r|0)}function ic(t,e){try{t.send(JSON.stringify(e))}catch{}}function Ks(t,e){ic(t,{type:"error",message:e})}function $f(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 yo from"node:fs";import H0 from"node:fs/promises";import dr from"node:path";import W0 from"node:os";var Of={"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},q0=2e5,cc=process.env.BOSTRAT_CLAUDE_PROJECTS||dr.join(W0.homedir(),".claude","projects");function lc(t){return t.replace(/[^A-Za-z0-9]/g,"-")}function uc(t){let e=dr.join(cc,lc(t)),n;try{n=yo.readdirSync(e).filter(o=>o.endsWith(".jsonl"))}catch{return null}let r=null;for(let o of n){let s=dr.join(e,o);try{let{mtimeMs:a}=yo.statSync(s);(!r||a>r.mtimeMs)&&(r={full:s,mtimeMs:a})}catch{}}return r?r.full:null}function Ae(t,e){if(!e)return null;let n=dr.join(cc,lc(t),`${e}.jsonl`);return yo.existsSync(n)?n:null}function Cf(t){if(!t)return[];let e=dr.join(cc,lc(t)),n;try{n=yo.readdirSync(e).filter(o=>o.endsWith(".jsonl"))}catch{return[]}let r=[];for(let o of n)try{let s=yo.statSync(dr.join(e,o));r.push({id:o.slice(0,-6),birthMs:s.birthtimeMs||s.ctimeMs||s.mtimeMs})}catch{}return r}async function Nt(t,e=131072){let n=await H0.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 K0(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 bo(t,e=null,{fallbackNewest:n=!0}={}){if(!t)return null;let r,o;if(e){if(r=Ae(t,e),o=!0,!r)return null}else if(n){if(r=uc(t),o=!1,!r)return null}else return null;let s;try{s=K0(await Nt(r))}catch{return null}if(!s)return null;let a=Object.prototype.hasOwnProperty.call(Of,s.model),i=Of[s.model]||q0;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 G0(t,e){return!t||!e?null:Ae(t,e)}function V0(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 z0(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 J0(t){return Array.isArray(t)&&t.length>0&&t.every(e=>e&&e.type==="tool_result")}function dc(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 Mf(t,e){if(!t.uuid)return null;let n=dc(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 X0=/\.(png|jpe?g|gif|webp|bmp|svg|tiff?|avif|heic|heif|ico)$/i;async function fc(t,{maxBytes:e=524288,limit:n=80}={}){let r;try{r=await Nt(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 g=m?.type==="tool_use"?m.input?.file_path||m.input?.path:null;typeof g=="string"&&X0.test(g)&&s.set(m.id,g.split("/").pop())}if(J0(l.content)){let m=Mf(c,s);m&&o.push({role:"assistant",text:"",images:m,ts:c.timestamp?Date.parse(c.timestamp):null,model:null});continue}let f=V0(l.content),d=u==="assistant"?z0(l.content):null,p=u==="user"?Mf(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 Y0=(t,e)=>t.length>e?`${t.slice(0,e).trimEnd()}\u2026`:t;async function Nf(t,e,{turns:n=16}={}){let r=G0(t,e);if(!r)return"";let o=await fc(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}:** ${Y0(s.text||c,700)} ${i}`.trim()}).join(`
|
|
53
|
+
|
|
54
|
+
`):""}import VS from"node:fs";import hr from"node:fs/promises";import wr from"node:path";import Z0 from"node:fs/promises";import{execFile as Q0}from"node:child_process";import{promisify as tS}from"node:util";var pc=tS(Q0),eS=Number(process.env.BOSTRAT_CODEX_UPDATE_TIMEOUT_MS)||5*6e4,Pf=new Set,Bf=Promise.resolve(),mc=null;function jf(t){mc=typeof t=="function"?t:null}function nS(t){let e=String(t||""),n=e.indexOf("/lib/node_modules/");return n>0?e.slice(0,n):null}function Lf({sessionName:t,from:e,to:n}={}){let r=String(n||"").trim();!r||Pf.has(r)||(Pf.add(r),Bf=Bf.then(()=>rS({sessionName:t,from:e,to:r})).catch(()=>{}))}async function rS({sessionName:t,from:e,to:n}){await U({flow:"codex-update",phase:"start",sessionName:t,from:e,to:n}).catch(()=>{});try{let r=(await pc("/bin/sh",["-c","command -v codex"])).stdout.trim();if(!r)throw new Error("codex not on PATH");let o=await Z0.realpath(r),s=nS(o),a=s?{...process.env,PATH:`${s}/bin:${process.env.PATH||""}`}:process.env;await pc("npm",["install","-g","@openai/codex"],{env:a,timeout:eS});let i=(await pc(r,["--version"])).stdout.trim().replace(/^codex-cli\s*/,"")||n;await U({flow:"codex-update",phase:"done",sessionName:t,from:e,to:n,version:i}).catch(()=>{}),await Df({sessionName:t,from:e,to:n,version:i,status:"updated"})}catch(r){let o=String(r?.message||r).slice(0,300);await U({flow:"codex-update",phase:"failed",sessionName:t,from:e,to:n,error:o}).catch(()=>{}),await Df({sessionName:t,from:e,to:n,status:"failed",error:o})}}async function Df(t){if(mc)try{await mc(t)}catch{}}var oS=/^\s*[›>*\-❯]?\s*(\d+)[.)]\s+(.+?)\s*$/,Ff=/\b(allow|approve|apply (this )?patch|run (this )?command|do you want to run|grant)\b/i;function sS(t,{provider:e,box:n}){let r=String(t).replace(/\r/g,"").split(`
|
|
55
|
+
`);if(!Ff.test(t))return null;let o=[],s=!1;for(let l=r.length-1;l>=0;l--){let u=r[l].match(oS);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"&&Ff.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 Gs=/(^|\n)\s*›/,Wf=/^\s*Welcome to Codex\b/;function fr(t){let e=String(t??"").replace(/\r/g,""),n=e.split(`
|
|
56
|
+
`);for(let r=n.length-1;r>=0;r--)if(Wf.test(n[r]))return n.slice(r).join(`
|
|
57
|
+
`);return e}function aS(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 Uf=/^\s*(>)?\s*(\d+)\.\s+(.+?)\s*$/;function cS(t,e=""){let n=fr(t);if(Gs.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(Uf);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(Uf.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&&!Wf.test(r[l])&&aS(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 Hf=/Enter this one-time code/,lS=/^\s*([A-Z0-9]{2,10}-[A-Z0-9]{2,10})\s*$/;function uS(t,e=""){let n=fr(t);if(Gs.test(n)||!Hf.test(n))return null;let r=n.split(`
|
|
59
|
+
`),o=r.findIndex(i=>Hf.test(i)),s="";for(let i=o+1;i<r.length;i++){let c=r[i].match(lS);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 dS(t,e=""){let n=fr(t);return Gs.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 fS(t,e=""){let n=fr(t);return Gs.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 hc=/Run Everything|Add a follow-up|Plan, search, build anything/;function pS(t,e=""){let n=String(t);if(!/Press any key to log in/.test(n)||hc.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 mS=/click this link to log in|Signing in with the browser/;function hS(t,e=""){let n=String(t);if(!mS.test(n)||hc.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 wS(t,e=""){let n=String(t);return!/Scan this QR code to log in/.test(n)||hc.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 qf(t,e,n=""){if(t!=="cursor"&&t!=="codex")return null;if(t==="codex"){let r=uS(e,n)||dS(e,n)||fS(e,n)||cS(e,n);if(r)return r}if(t==="cursor"){let r=hS(e,n)||wS(e,n)||pS(e,n);if(r)return r}return sS(e,{provider:t,box:n})}function Kf(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 gS=()=>Number(process.env.BOSTRAT_AGENT_READY_TIMEOUT_MS)||9e4,wc=Number(process.env.BOSTRAT_AGENT_READY_POLL_MS)||500,ko=Number(process.env.BOSTRAT_AGENT_READY_SETTLE_MS)||800,At={claude:{up:/shift\+tab to cycle|⏵⏵ bypass permissions on|⏸ manual mode on/,busy:/esc to interrupt/,busyAlt:/^[·✢✳✶✻✽✺✹✸✷]\s+[A-Z][\w '’-]{1,40}…/m,auth:/Select login method:|Paste code here if prompted|OAuth error:|Choose the text style that looks best|Quick safety check: Is this a project you created/,block:/Press\s+Enter\s+to\s+continue\s*[.…]{0,3}\s*$/m},codex:{up:/(^|\n)\s*›/,busy:/esc to interrupt|\bWorking \(/,block:/Update available![\s\S]*\b(?:Update now|Skip until next version)\b[\s\S]*Press enter to continue/,auth:/Sign in with ChatGPT[\s\S]*Press enter to continue|Finish signing in via your browser|Paste or type your API key/},cursor:{up:/Run Everything|Add a follow-up|Plan, search, build anything/,busy:/ctrl\+c to stop|^\s*[⠁-⣿]+\s+\S/m,block:/Workspace Trust Required|Trust this workspace|\[a\]\s*Trust|Trusting workspace/,auth:/Press any key to log in|click this link to log in|Scan this QR code to log in|Signing in with the browser/}},yS=/Trust this workspace|\[a\]\s*Trust/,bS=/Update available!\s*([\d][\w.-]*)\s*->\s*([\d][\w.-]*)/;function kS(t){let e=String(t||"");if(!At.codex.block.test(e))return null;let n=fr(e);if(!At.codex.block.test(n))return null;let r=n.match(bS);return{from:r?.[1]||null,to:r?.[2]||null}}function Qe(t){return new Promise(e=>setTimeout(e,t))}var xS=()=>Number(process.env.BOSTRAT_GATE_RESEND_MS)||3e3,Vs=new Map;function Gf(t,e){let n=Date.now(),r=xS();for(let[s,a]of Vs)n-a>=r&&Vs.delete(s);let o=`${t}::${e}`;return Vs.has(o)?!1:(Vs.set(o,n),!0)}var SS=68,vS=/^\s*[─━]{10,}\s*$/;function TS(t){let e=0;for(let n of String(t).split(`
|
|
62
|
+
`))vS.test(n)&&(e=Math.max(e,n.trimEnd().length));return e}function gc(t,e){let n=At[t];if(!n?.busy)return"idle";if(yc(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=TS(e);return t==="claude"&&r>0&&r<SS?"unknown":"idle"}var AS=/\bWaiting for \d+\b[^\n]*\bto finish\b|\/workflows to (?:stop|monitor)\b/,ES=15;function zf(t,e){if(t!=="claude")return!1;let n=String(e||"").split(`
|
|
63
|
+
`).slice(-ES).join(`
|
|
64
|
+
`);return AS.test(n)}function Ee(t,e){return gc(t,e)==="busy"}function zs(t,e){return gc(t,e)!=="idle"}function Js(t,e){let n=At[t];return n?.up?n.up.test(e):!0}function _S(t,e){let n=At[t];return n?yc(t,e)?!1:n.up.test(e)&&gc(t,e)==="idle":!0}function yc(t,e){let n=At[t];return n?n.auth?.test(e)&&!n.up.test(e)?!0:n.block?.test(e)?t==="codex"?!0:!n.up.test(e):!1:!1}async function Vf(t,e,n){let r=Date.now()+n,o=0;for(await On(t).catch(()=>{});;){let s=await ft(t);if(_S(e,s)){await Qe(ko);return}if(e==="cursor"&&yS.test(s)&&!At.cursor.up.test(s)&&o<3){if(!Gf(t,"cursor-trust")){await Qe(wc);continue}o+=1,await U({flow:"agent-ready",phase:"gate-accept",sessionName:t,agent:e,attempt:o}).catch(()=>{}),await St(t,"a",{enter:!1,kind:"cursor-trust-accept"}),await Qe(ko);continue}if(e==="codex"&&o<3){let a=kS(s);if(a){if(!Gf(t,`codex-update:${a.from}->${a.to}`)){await Qe(wc);continue}o+=1,await U({flow:"agent-ready",phase:"gate-accept",sessionName:t,agent:e,attempt:o,update:`${a.from} -> ${a.to}`}).catch(()=>{}),await St(t,"3",{enter:!1,kind:"codex-update-skip"}),Lf({sessionName:t,from:a.from,to:a.to}),await Qe(ko);continue}}if(Date.now()>r){if(yc(e,s)){let a=`${e} is blocked on an interactive prompt`;throw e==="codex"?a=At.codex.auth.test(s)?"codex isn't signed in on this box \u2014 answer the Sign in card in chat (Device Code works from any device) or finish signing in from the Terminal tab, then send again":"codex is stuck on its update prompt (auto-skip didn't clear it) \u2014 open the terminal and choose Skip, then send again":e==="cursor"&&At.cursor.auth.test(s)?a="cursor isn't signed in on this box \u2014 answer the Sign in card in chat (the login link works from any device) or finish signing in from the Terminal tab, then send again":e==="claude"&&At.claude.auth.test(s)?a="claude isn't signed in on this box \u2014 answer the setup cards in chat (or finish signing in from the Terminal tab), then send again":e==="claude"&&At.claude.block.test(s)&&(a='claude is waiting on its "Press Enter to continue" screen \u2014 answer the Continue card in chat (or press Enter in the Terminal tab), then send again'),U({flow:"agent-ready",phase:"blocked",sessionName:t,agent:e,timeoutMs:n,gateAccepts:o,error:a}).catch(()=>{}),new Error(a)}if(await ur(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 U({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.`),U({flow:"agent-ready",phase:"timeout",sessionName:t,agent:e,timeoutMs:n,gateAccepts:o,paneBlocked:!!At[e]?.block?.test(s),paneUp:!!At[e]?.up?.test(s),paneBusy:!!At[e]?.busy?.test(s)}).catch(()=>{}),await Qe(ko);return}await Qe(wc)}}function Jf({agent:t,agentCmd:e}={}){let n=e?Xe(e):Xe(t||"");return{label:n,realClaude:e?In(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 Xs({cwd:t,sessionName:e,agent:n,agentCmd:r}={}){let{realCodex:o,realCursor:s}=Jf({agent:n,agentCmd:r});return!e||!o&&!s?null:tn({cwd:t,sessionName:e,agent:n,agentCmd:r}).catch(()=>{})}async function tn({cwd:t,sessionId:e,sessionName:n,agent:r,agentCmd:o,timeoutMs:s=gS()}={}){let{label:a,realClaude:i,realCodex:c,realCursor:l}=Jf({agent:r,agentCmd:o});if(i&&t&&await Ge(t),c&&t&&await rr(t),l&&t&&await or(t),(i||c||l)&&n){await Vf(n,a,s);return}let u=At[r]?r:At[a]?a:null;if(process.env.BOSTRAT_FAKE_POLL_READY==="1"&&n&&u){await Vf(n,u,s);return}await Qe(ko)}var IS=(()=>{let t=Number(process.env.BOSTRAT_CODEX_SUBMIT_SETTLE_MS??120);return Number.isFinite(t)&&t>=0?t:120})(),RS=(()=>{let t=Number(process.env.BOSTRAT_CLAUDE_SUBMIT_SETTLE_MS??120);return Number.isFinite(t)&&t>=0?t:120})();function _e(t){return t==="codex"?{enter:!0,submitDelayMs:IS}:t==="claude"?{enter:!0,submitDelayMs:RS}:{enter:!0}}import Zs from"node:fs/promises";import ep from"node:path";var $S=["bug-fix","orchestration","design","ops-infra","docs-writing","research","feature-build"],OS={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 pr(t){let e=String(t||"").toLowerCase();if(!e.trim())return null;let n=null;for(let r of $S){let o=0;for(let s of OS[r])s.test(e)&&o++;o>0&&(!n||o>n.score)&&(n={category:r,score:o})}return n}var CS=new Set(["Read","Grep","Glob","LS","WebSearch","WebFetch","NotebookRead"]),MS=new Set(["Edit","Write","MultiEdit","NotebookEdit"]);function Xf(t){let e=0,n=0;for(let[r,o]of Object.entries(t||{}))CS.has(r)&&(e+=o||0),MS.has(r)&&(n+=o||0);return e>=5&&n===0?{category:"research",score:1}:n>=3?{category:"feature-build",score:1}:null}function xo(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 Yf=1.5,Zf=.75,NS=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 tp(t){let e=xo(String(t||"")).toLowerCase(),n=[];for(let r of e.split(/[^a-z0-9]+/))r.length<2||NS.has(r)||n.push(r);return n}function PS(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(Qf(n,"user",o))}else if(n.type==="turn.agent"){if(!o)continue;e.push(Qf(n,"assistant",o))}}return e}function Qf(t,e,n){let r=pr(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 So(t){let e=PS(t),n=new Map,r=new Array(e.length).fill(0),o=0;e.forEach((a,i)=>{let c=tp(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 Ys(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(tp(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,g]of f){let y=t.lengths[m],b=g+Yf*(1-Zf+Zf*(y/(t.avgdl||1))),S=p*(g*(Yf+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 np="Relevant prior discussion (verbatim, retrieved";async function BS(t){let e=ep.join(t,".bostrat","events"),n;try{n=await Zs.readdir(e)}catch{return So([])}let r=[];for(let o of n)if(o.endsWith(".ndjson"))try{let s=await Zs.readFile(ep.join(e,o),"utf8");for(let a of s.split(`
|
|
65
|
+
`))if(a.trim())try{r.push(JSON.parse(a))}catch{}}catch{}return So(r)}function DS(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 jS(t,e,{k:n=6,maxChars:r=4e3,exclude:o=null}={}){let s=Ys(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?["",`## ${np}, 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 rp(t,e,n={}){let{k:r=6,maxChars:o=4e3,exclude:s=null}=n;try{let a=await Zs.readFile(e,"utf8");if(a.includes(np))return{appended:!1,reason:"already-enriched"};let i=await BS(t);if(!i.N)return{appended:!1,reason:"empty-index"};let c=jS(i,DS(a),{k:r,maxChars:o,exclude:s});return c?(await Zs.appendFile(e,`
|
|
67
68
|
`+c),{appended:!0,chars:c.length,chunks:c.split(`
|
|
68
|
-
- `).length-1,indexed:i.N}):{appended:!1,reason:"no-hits",indexed:i.N}}catch(a){return{appended:!1,reason:"error",error:a?.message||String(a)}}}import
|
|
69
|
-
`),
|
|
70
|
-
`))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 bc from"node:fs/promises";import mr from"node:path";import LS from"node:os";import{EventEmitter as FS}from"node:events";import{randomUUID as US}from"node:crypto";var HS=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"]),WS=new Set(["user","agent","orchestrator","system"]),qS=new Set(["hook","otel","statusline","transcript","pane","orchestrator","vendor"]),Jt=new FS;Jt.setMaxListeners(0);var kc=process.env.BOSTRAT_STATE_DIR||mr.join(process.env.HOME||LS.tmpdir(),".bostrat");function ta(t){return t?.cwd?mr.join(t.cwd,".bostrat","events"):mr.join(kc,"events")}var op=(t,e)=>mr.join(t,`${e}.ndjson`),Qs=new Map;async function KS(t,e){if(!Qs.has(e)){let o=(await D(t,e)).reduce((s,a)=>a.seq>s?a.seq:s,0);Qs.set(e,o)}let n=Qs.get(e)+1;return Qs.set(e,n),n}function GS(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||!HS.has(e.type))throw new Error(`appendEvent: unknown event type "${e?.type}"`);if(e.actor&&!WS.has(e.actor))throw new Error(`appendEvent: bad actor "${e.actor}"`);if(e.source&&!qS.has(e.source))throw new Error(`appendEvent: bad source "${e.source}"`);let r=typeof t=="string"?mr.join(kc,"events"):ta(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=GS(c,Number(e.payload?.ts??e.ts)||Date.now())}let s=await KS(r,n),a=e.userId??process.env.BOSTRAT_OWNER??null,i={id:e.id||US(),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 bc.mkdir(r,{recursive:!0}),await bc.appendFile(op(r,n),JSON.stringify(i)+`
|
|
70
|
+
`),Jt.emit("event",i),Jt.emit(`track:${n}`,i),i}async function D(t,e){let n,r;typeof t=="string"&&e?(n=t,r=e):(r=typeof t=="string"?t:t?.id,n=typeof t=="string"?mr.join(kc,"events"):ta(t));let o;try{o=await bc.readFile(op(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 sp(t,e){let n=`track:${t}`;return Jt.on(n,e),()=>Jt.off(n,e)}var lp=".bostrat/handoff",up="latest.md",ap=32*1024;function zS(t){let e=wo(String(t??"")),n=Buffer.byteLength(e,"utf8");return n<=ap?{content:e,bytes:n,truncated:!1}:{content:`${Buffer.from(e,"utf8").subarray(0,ap).toString("utf8").replace(/�+$/,"")}
|
|
71
72
|
|
|
72
|
-
[\u2026 truncated by bostrat at 32 KiB \u2014 full artifact in the workspace]`,bytes:n,truncated:!0}}var Wf="Checkpoint: write a work-in-progress handoff",qf="HANDOFF WRITTEN",Kf="Resume from .bostrat/handoff/latest.md",mS="/clear",hS=t=>`${Wf} 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 ${qf} and take no further action.`,wS=`${Kf}: read it, restate the Objective and the single next step in one line, then continue the work from 'Next steps'.`,Gf="Beat: write a",zf="BEAT WRITTEN",gS=".bostrat/arc",yS="beat.txt",Lf=200,Vf=4,Jf=5,bS=36,kS=t=>`${Gf} 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 ${Vf} further lines, each starting with "- " and naming ONE piece in at most ${Jf} 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 ${zf} and take no further action.`,Ff=/^(?:[-–—*•·]|\d+[.)])\s+/,xS=(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 SS(t){let e=String(t||"").split(/\r?\n/).map(s=>s.trim()).filter(Boolean),n=[],r=[];for(let s of e)if(Ff.test(s)){if(r.length>=Vf)continue;let a=xS(s.replace(Ff,"").replace(/[.;,:]+$/,""),Jf,bS);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>Lf&&(o=`${o.slice(0,Lf-1).trimEnd()}\u2026`),{text:o,items:r.length>1?r:[]}}async function Xf(t,e,{timeoutMs:n=9e4}={}){let r=fr.join(e,gS),o=fr.join(r,yS);await dr.mkdir(r,{recursive:!0});let s=await Zs(o),a=await Y(t);if(!a)throw new Error(`session "${t}" not found`);return await Qs(t,a,kS(o)),await op(o,s,n),SS(await dr.readFile(o,"utf8"))}function Yf(t,e){let n=(e||"").trim();return n?t==="assistant"?n===qf||n===zf:n.startsWith(Wf)||n===mS||n.startsWith(Kf)||n.startsWith(Gf):!1}var vS=6e5;function Zf(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-vS;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 Qf(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 tp(t,{windows:e=[]}={}){if(Yf(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 TS=3500,AS=3e5,pc=new Map;function ta(t){return pc.get(t)||null}async function ep(t){let e=pc.get(t);if(e&&!e.finishedAt)throw new Error("a handoff is already running for this session");let n=await Y(t);if(!n)throw new Error(`session "${t}" not found`);if(!n.cwd)throw new Error(`can't resolve cwd for "${t}"`);let r={name:t,step:"summarize",startedAt:Date.now(),finishedAt:null,error:null,file:fr.join(n.cwd,Uf,Hf)};return pc.set(t,r),ES(r,n.cwd).catch(o=>{r.error=o?.message||String(o),r.finishedAt=Date.now()}),r}async function ES(t,e){t.step="summarize",await np(t.name,e),t.step="clear";let n=await Y(t.name);await Qs(t.name,n,"/clear"),await sp(TS),t.step="resume",await Qs(t.name,n,wS),t.step="done",t.finishedAt=Date.now()}async function np(t,e){let n=fr.join(e,Uf),r=fr.join(n,Hf);await dr.mkdir(n,{recursive:!0});let o=await Zs(r),s=await Y(t);await G({flow:"checkpoint",phase:"summarize-start",sessionName:t,cwd:e,file:r,beforeMtime:o}),await Qs(t,s,hS(r)),await op(r,o,AS),await G({flow:"checkpoint",phase:"summarize-done",sessionName:t,file:r,afterMtime:await Zs(r)});let a=await Pf(e,r,{exclude:c=>Yf(c.role,c.text)});await G({flow:"checkpoint",phase:"seed-enriched",sessionName:t,file:r,...a});let i=/workspaces[/\\]([0-9a-f]{8})$/.exec(e||"")?.[1];if(i){await $({id:i,cwd:e},{type:"memory.seed",actor:"orchestrator",payload:{...a,tokensEst:a.chars?Math.round(a.chars/4):0,ts:Date.now()}}).catch(()=>{});try{let c=await dr.readFile(r,"utf8");await $({id:i,cwd:e},{type:"handoff.artifact",actor:"orchestrator",payload:{...pS(c),ts:Date.now()}})}catch{}}return await Vd(e),await _S(n,r),r}async function rp(t){let e=await Y(t);if(!e)throw new Error(`session "${t}" not found`);if(!e.cwd)throw new Error(`can't resolve cwd for "${t}"`);return np(t,e.cwd)}async function Zs(t){try{return(await dr.stat(t)).mtimeMs}catch{return 0}}async function op(t,e,n){let r=Date.now()+n;for(;;){if(await Zs(t)>e)return;if(Date.now()>r)throw new Error(`timed out waiting for the handoff file to be written (${t})`);await sp(2e3)}}async function _S(t,e){try{if(!fS.existsSync(e))return;let n=new Date().toISOString().replace(/[:.]/g,"-");await dr.copyFile(e,fr.join(t,`${n}.md`))}catch{}}function sp(t){return new Promise(e=>setTimeout(e,t))}async function Qs(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 Qe({cwd:e.cwd,sessionId:e.sessionId,sessionName:t,agent:e.agent,agentCmd:o})}let r=await Y(t);if(!r)throw new Error(`session "${t}" disappeared before the orchestration prompt could be sent`);await vt(t,n,{kind:"orchestration-drive",..._e(r.agent)})}import U from"node:fs/promises";import H from"node:path";import{randomUUID as cs}from"node:crypto";import{execFile as z_}from"node:child_process";import{promisify as V_}from"node:util";import Ym from"node:path";var mc={"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}},ap={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 ip(t){if(!t)return null;let e=String(t).trim();return mc[e]?e:ap[e]?ap[e]:Object.keys(mc).find(r=>e.startsWith(r))||null}var ea=t=>ip(t)!==null;function le(t,e={}){let n=mc[ip(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 cp=()=>({input:0,output:0,cacheRead:0,cacheCreation:0});function IS(t){return t?{input:t.input||0,output:t.output||0,cacheRead:t.cacheRead||0,cacheWrite:t.cacheWrite??t.cacheCreation??0}:null}function lp(t,e){e&&(t.input+=e.input,t.output+=e.output,t.cacheRead+=e.cacheRead,t.cacheCreation+=e.cacheWrite)}function ra(t){let e=t.payload?.model||"unknown",n=IS(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&&!ea(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=cp();for(let f of n){let{usd:d,model:m,tokens:h,repriced:w,unpriced:g}=ra(f),b=f.provider||"unknown",S=f.boxId||"unattributed";if(w&&i++,g&&(c++,l.add(m)),a+=d,lp(u,h),r[m]||={usd:0,tokens:cp(),events:0},r[m].usd+=d,r[m].events+=1,lp(r[m].tokens,h),o[b]||={usd:0,events:0},o[b].usd+=d,o[b].events+=1,s[S]||={usd:0,events:0,provider:b,models:new Set,label:e[S]||S},s[S].usd+=d,s[S].events+=1,m!=="unknown"&&s[S].models.add(m),f.epoch!=null){let A=(s[S].epochs||={})[f.epoch]||={usd:0,events:0,provider:b,models:new Set};A.usd+=d,A.events+=1,A.provider=b,m!=="unknown"&&A.models.add(m)}}return{totalUsd:xo(a),totalTokens:u,turns:n.length,repricedTurns:i,unpricedTurns:c,unpricedModels:[...l],byModel:na(r,f=>({...f,usd:xo(f.usd)})),byProvider:na(o,f=>({...f,usd:xo(f.usd)})),byBox:na(s,f=>({usd:xo(f.usd),events:f.events,provider:f.provider,label:f.label,models:[...f.models],...f.epochs?{epochs:na(f.epochs,d=>({usd:xo(d.usd),events:d.events,provider:d.provider,models:[...d.models]}))}:{}}))}}var xo=t=>Math.round(t*1e4)/1e4,na=(t,e)=>Object.fromEntries(Object.entries(t).map(([n,r])=>[n,e(r)]));import St from"node:fs/promises";import Nt from"node:path";import Ao from"node:os";import{randomUUID as LS}from"node:crypto";import{execFile as FS}from"node:child_process";import{promisify as US}from"node:util";import{chromium as HS}from"playwright-core";import hc from"node:fs/promises";import RS from"node:path";var $S=0;async function Ct(t,e,{mode:n}={}){await hc.mkdir(RS.dirname(t),{recursive:!0});let r=`${t}.${process.pid}.${++$S}.tmp`;await hc.writeFile(r,JSON.stringify(e,null,2)+`
|
|
73
|
-
`,n!=null?{mode:n}:void 0),await hc.rename(r,t)}import fp from"node:fs/promises";import pp from"node:os";import mp from"node:path";var OS=1e4,MS=3e4,up=2e3,CS=()=>process.env.BOSTRAT_STATE_DIR||mp.join(process.env.HOME||pp.tmpdir(),".bostrat"),hp=()=>mp.join(CS(),"content-outbox.json"),NS=()=>process.env.BOSTRAT_NODE_ID||pp.hostname(),PS=(...t)=>fetch(...t),BS=MS;function Tt(t=process.env){return!!(Gt(t)&&t.BOSTRAT_PLANE_TOKEN)}var vo=(t=process.env)=>({authorization:`Bearer ${t.BOSTRAT_PLANE_TOKEN||""}`});async function To(t,e={},n=process.env){let r=new AbortController,o=setTimeout(()=>r.abort(),OS);try{return await PS(`${Gt(n)}${t}`,{...e,signal:r.signal})}finally{clearTimeout(o)}}var sa=null,wc=null,oa=null,So=null;async function wp(){return sa||(wc||=fp.readFile(hp(),"utf8").then(t=>JSON.parse(t)).catch(()=>({ops:[]})).then(t=>sa={ops:Array.isArray(t?.ops)?t.ops:[]}).finally(()=>wc=null),wc)}async function gp(){await Ct(hp(),sa).catch(()=>{})}function dp(t){return t.op==="blob"?`blob ${t.family} ${t.id} ${t.v} ${t.kind}`:`${t.op} ${t.family} ${t.id}`}async function gc(t){let e=await wp(),n=dp(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=>dp(o)===n);r>=0?e.ops[r]=t:e.ops.push(t)}e.ops.length>up&&(e.ops=e.ops.slice(-up)),await gp()}function DS(){oa||(oa=setTimeout(()=>{oa=null,pr().catch(()=>{})},BS),oa.unref?.())}async function jS(t,e=process.env){if(t.op==="record"){let n=await To(`/v1/content/${encodeURIComponent(t.family)}`,{method:"POST",headers:{...vo(e),"content-type":"application/json"},body:JSON.stringify({records:[{id:t.id,trackId:t.trackId??null,data:t.data,updatedAt:t.updatedAt,originNode:NS()}]})},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 fp.readFile(t.path).catch(()=>null);if(!n)return{delivered:!0,dropped:"source file gone"};let r=await To(`/v1/content/${encodeURIComponent(t.family)}/${encodeURIComponent(t.id)}/blob?v=${t.v}&kind=${encodeURIComponent(t.kind)}`,{method:"POST",headers:{...vo(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 To(`/v1/content/${encodeURIComponent(t.family)}/${encodeURIComponent(t.id)}?deletedAt=${t.deletedAt}`,{method:"DELETE",headers:vo(e)},e);if(!n.ok)throw new Error(`plane ${n.status}`);return{delivered:!0}}return{delivered:!0,dropped:"unknown op"}}async function pr({env:t=process.env,log:e=console}={}){return Tt(t)?So||(So=(async()=>{let n=await wp(),r=0;for(;n.ops.length;){let o=n.ops[0],s;try{s=await jS(o,t)}catch(a){return DS(),{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 gp()}return{flushed:r,pending:0}})().finally(()=>{So=null}),So):{flushed:0,source:"unconfigured"}}async function de(t,e,{trackId:n=null,env:r=process.env}={}){return Tt(r)?(await gc({op:"record",family:t,id:e.id,trackId:n??e.trackId??null,data:e,updatedAt:Number(e.updatedAt)||Date.now()}),pr({env:r})):{queued:!1,source:"unconfigured"}}async function mr(t,e,n,r,o,{env:s=process.env}={}){return Tt(s)?(await gc({op:"blob",family:t,id:e,v:Number(n),kind:r,path:o}),pr({env:s})):{queued:!1,source:"unconfigured"}}async function Yt(t,e,{deletedAt:n=Date.now(),env:r=process.env}={}){return Tt(r)?(await gc({op:"delete",family:t,id:e,deletedAt:n}),pr({env:r})):{queued:!1,source:"unconfigured"}}async function Zt(t,{env:e=process.env}={}){if(!Tt(e))return{records:[],source:"unconfigured"};try{let n=await To(`/v1/content/${encodeURIComponent(t)}?tombstones=1`,{headers:vo(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 yp(t,e,n,r,{env:o=process.env}={}){if(!Tt(o))return null;try{let s=await To(`/v1/content/${encodeURIComponent(t)}/${encodeURIComponent(e)}/blob?v=${Number(n)}&kind=${encodeURIComponent(r)}`,{headers:vo(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 kp=US(FS),xp=process.env.BOSTRAT_STATE_DIR||Nt.join(process.env.HOME||Ao.tmpdir(),".bostrat"),Sp=Nt.join(xp,"mocks.json"),WS=process.env.BOSTRAT_MOCK_ENGINE||"chromium",qS=process.env.BOSTRAT_CHROMIUM_BIN||"/usr/bin/chromium",KS=process.env.BOSTRAT_FIREFOX_BIN||"firefox",GS=1300,hr=2,zS=32,vp=1500,wr=Number(process.env.BOSTRAT_MOCK_TIMEOUT_MS)||75e3,VS=700*1024*1024;async function JS(){if(process.platform!=="darwin")return Ao.freemem();try{let{stdout:t}=await kp("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:Ao.freemem()}catch{return Ao.freemem()}}var Qt=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 dp="Checkpoint: write a work-in-progress handoff",fp="HANDOFF WRITTEN",pp="Resume from .bostrat/handoff/latest.md",JS="/clear",XS=t=>`${dp} 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 ${fp} and take no further action.`,YS=`${pp}: read it, restate the Objective and the single next step in one line, then continue the work from 'Next steps'.`,mp="Beat: write a",hp="BEAT WRITTEN",ZS=".bostrat/arc",QS="beat.txt",ip=200,wp=4,gp=5,tv=36,ev=t=>`${mp} 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 ${wp} further lines, each starting with "- " and naming ONE piece in at most ${gp} 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 ${hp} and take no further action.`,cp=/^(?:[-–—*•·]|\d+[.)])\s+/,nv=(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 rv(t){let e=String(t||"").split(/\r?\n/).map(s=>s.trim()).filter(Boolean),n=[],r=[];for(let s of e)if(cp.test(s)){if(r.length>=wp)continue;let a=nv(s.replace(cp,"").replace(/[.;,:]+$/,""),gp,tv);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>ip&&(o=`${o.slice(0,ip-1).trimEnd()}\u2026`),{text:o,items:r.length>1?r:[]}}async function yp(t,e,{timeoutMs:n=9e4}={}){let r=wr.join(e,ZS),o=wr.join(r,QS);await hr.mkdir(r,{recursive:!0});let s=await ea(o),a=await Z(t);if(!a)throw new Error(`session "${t}" not found`);return await na(t,a,ev(o)),await Ep(o,s,n),rv(await hr.readFile(o,"utf8"))}function bp(t,e){let n=(e||"").trim();return n?t==="assistant"?n===fp||n===hp:n.startsWith(dp)||n===JS||n.startsWith(pp)||n.startsWith(mp):!1}var ov=6e5;function kp(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-ov;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 xp(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 Sp(t,{windows:e=[]}={}){if(bp(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 sv=3500,av=3e5,xc=new Map;function ra(t){return xc.get(t)||null}async function vp(t){let e=xc.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,lp,up)};return xc.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 Tp(t.name,e),t.step="clear";let n=await Z(t.name);await na(t.name,n,"/clear"),await _p(sv),t.step="resume",await na(t.name,n,YS),t.step="done",t.finishedAt=Date.now()}async function Tp(t,e){let n=wr.join(e,lp),r=wr.join(n,up);await hr.mkdir(n,{recursive:!0});let o=await ea(r),s=await Z(t);await U({flow:"checkpoint",phase:"summarize-start",sessionName:t,cwd:e,file:r,beforeMtime:o}),await na(t,s,XS(r)),await Ep(r,o,av),await U({flow:"checkpoint",phase:"summarize-done",sessionName:t,file:r,afterMtime:await ea(r)});let a=await rp(e,r,{exclude:l=>bp(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:{...zS(l),ts:Date.now()}})}catch{}}await sf(e);let c=await cv(n,r);return i&&await wf(i,c||r,{box:t,agent:s?.agent??null}),r}async function Ap(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 Tp(t,e.cwd)}async function ea(t){try{return(await hr.stat(t)).mtimeMs}catch{return 0}}async function Ep(t,e,n){let r=Date.now()+n;for(;;){if(await ea(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 cv(t,e){try{if(!VS.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 na(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 bI}from"node:child_process";import{promisify as kI}from"node:util";import fh from"node:path";var Sc={"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}},Ip={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 Rp(t){if(!t)return null;let e=String(t).trim();return Sc[e]?e:Ip[e]?Ip[e]:Object.keys(Sc).find(r=>e.startsWith(r))||null}var oa=t=>Rp(t)!==null;function le(t,e={}){let n=Sc[Rp(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 $p=()=>({input:0,output:0,cacheRead:0,cacheCreation:0});function lv(t){return t?{input:t.input||0,output:t.output||0,cacheRead:t.cacheRead||0,cacheWrite:t.cacheWrite??t.cacheCreation??0}:null}function Op(t,e){e&&(t.input+=e.input,t.output+=e.output,t.cacheRead+=e.cacheRead,t.cacheCreation+=e.cacheWrite)}function aa(t){let e=t.payload?.model||"unknown",n=lv(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&&!oa(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=$p();for(let f of n){let{usd:d,model:p,tokens:m,repriced:g,unpriced:y}=aa(f),b=f.provider||"unknown",S=f.boxId||"unattributed";if(g&&i++,y&&(c++,l.add(p)),a+=d,Op(u,m),r[p]||={usd:0,tokens:$p(),events:0},r[p].usd+=d,r[p].events+=1,Op(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:sa(r,f=>({...f,usd:vo(f.usd)})),byProvider:sa(o,f=>({...f,usd:vo(f.usd)})),byBox:sa(s,f=>({usd:vo(f.usd),events:f.events,provider:f.provider,label:f.label,models:[...f.models],...f.epochs?{epochs:sa(f.epochs,d=>({usd:vo(d.usd),events:d.events,provider:d.provider,models:[...d.models]}))}:{}}))}}var vo=t=>Math.round(t*1e4)/1e4,sa=(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 uv}from"node:crypto";import{execFile as dv}from"node:child_process";import{promisify as fv}from"node:util";import{chromium as pv}from"playwright-core";var Mp=fv(dv),Np=process.env.BOSTRAT_STATE_DIR||Pt.join(process.env.HOME||To.tmpdir(),".bostrat"),Pp=Pt.join(Np,"mocks.json"),mv=process.env.BOSTRAT_MOCK_ENGINE||"chromium",hv=process.env.BOSTRAT_CHROMIUM_BIN||"/usr/bin/chromium",wv=process.env.BOSTRAT_FIREFOX_BIN||"firefox",gv=1300,gr=2,yv=32,Bp=1500,yr=Number(process.env.BOSTRAT_MOCK_TIMEOUT_MS)||75e3,bv=700*1024*1024;async function kv(){if(process.platform!=="darwin")return To.freemem();try{let{stdout:t}=await Mp("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}
|
|
74
74
|
${n.trim().split(`
|
|
75
75
|
`).slice(-6).join(`
|
|
76
|
-
`)}`:e),this.name="MockRenderError"}},Rn=null,yc=null;async function pe(){return Rn||(yc||=(async()=>{try{Rn=JSON.parse(await St.readFile(Sp,"utf8"))}catch{Rn={mocks:[]}}finally{yc=null}return Rn.mocks||=[],Rn})(),yc)}async function fe(){await Ct(Sp,Rn)}var tn=t=>de("mocks",{...t},{trackId:t.trackId}).catch(()=>{}),$n=(t,e,n)=>mr("mocks",t.id,e,n,Ut(t,e)[n]).catch(()=>{});function te(t,e,n={}){return $({id:t.trackId,cwd:t.cwd},{type:"mock.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box||null,provider:t.agent||null,payload:{ts:Date.now(),change:e,mockId:t.id,name:t.name,...t.group?{group:t.group}:{},...n}}).catch(()=>{})}var aa=t=>Nt.join(t,".bostrat","mocks");function Ut(t,e){let n=aa(t.cwd);return{html:Nt.join(n,`${t.id}.v${e}.html`),png:Nt.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 kc(t){return t.length<24||t.readUInt32BE(12)!==1229472850?null:{width:t.readUInt32BE(16),height:t.readUInt32BE(20)}}var XS=Buffer.from([137,80,78,71,13,10,26,10]);function Tp(t){return Buffer.isBuffer(t)&&t.length>24&&t.subarray(0,8).equals(XS)}async function YS(t){let e=Buffer.alloc(24),n=await St.open(t,"r");try{await n.read(e,0,24,0)}finally{await n.close()}return e}function ZS({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 QS(t,e){return t.evaluate(async n=>{let r=new Image;await new Promise((h,w)=>{r.onload=h,r.onerror=()=>w(new Error("decode failed")),r.src=`data:image/png;base64,${n}`});let o=r.naturalWidth,s=r.naturalHeight;if(!o||!s||o*s>24e6)return null;let a=document.createElement("canvas");a.width=o,a.height=s;let i=a.getContext("2d",{willReadFrequently:!0});i.drawImage(r,0,0);let c=i.getImageData(0,0,o,s).data,l=[c[0],c[1],c[2]],u=o,f=s,d=-1,m=-1;for(let h=0;h<s;h++)for(let w=h*o*4,g=0;g<o;g++,w+=4)Math.abs(c[w]-l[0])+Math.abs(c[w+1]-l[1])+Math.abs(c[w+2]-l[2])>24&&(g<u&&(u=g),g>d&&(d=g),h<f&&(f=h),m=h);return d<0?null:{imgW:o,imgH:s,bg:l,bbox:{left:u,top:f,right:d,bottom:m}}},e.toString("base64"))}async function tv(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 ev({htmlPath:t,outPath:e,width:n}){let r=Date.now(),o;try{o=await HS.launch({executablePath:qS,args:["--disable-gpu"],timeout:3e4})}catch(s){throw new Qt("chromium failed to launch",s?.message||"")}try{let a=await(await o.newContext({viewport:{width:n,height:800},deviceScaleFactor:hr})).newPage();a.setDefaultTimeout(wr),await a.goto(`file://${t}`,{waitUntil:"load",timeout:wr}),await a.waitForTimeout(250);let i;for(let l=1;;l++)try{i=await a.screenshot({fullPage:!0,timeout:wr});break}catch(u){if(l>=3||!/captureScreenshot|Unable to capture/i.test(u?.message||""))throw u;await a.waitForTimeout(500)}try{let l=await QS(a,i),u=l&&ZS({...l,pad:zS*hr,align:hr});u&&(i=await tv(a,i,{bg:l.bg,crop:u}))}catch{}if(i.length<vp)throw new Qt(`capture produced a ${i.length}-byte image`);let c=kc(i);if(!c)throw new Qt("output is not a PNG");return await St.writeFile(e,i),{width:Math.round(c.width/hr),height:Math.round(c.height/hr),dpr:hr,renderMs:Date.now()-r}}catch(s){throw s instanceof Qt?s:new Qt(/timeout/i.test(s?.message||"")?`render timed out after ${wr}ms`:"chromium render failed",s?.message||"")}finally{await o.close().catch(()=>{})}}async function bp({htmlPath:t,outPath:e,width:n}){let r=await St.mkdtemp(Nt.join(Ao.tmpdir(),"bostrat-mock-profile-")),o=Date.now(),s="";try{try{s=(await kp(KS,["--headless","--no-remote","--profile",r,`--window-size=${n}`,"--screenshot",e,`file://${t}`],{env:{...process.env,MOZ_HEADLESS:"1"},timeout:wr,killSignal:"SIGKILL",maxBuffer:4194304})).stderr||""}catch(c){throw s=c?.stderr||"",new Qt(c?.killed?`render timed out after ${wr}ms`:"firefox exited non-zero",s)}let a=await St.stat(e).catch(()=>null);if(!a||a.size<vp)throw new Qt(`render produced ${a?`a ${a.size}-byte file`:"no file"} (silent no-op \u2014 another firefox holding the profile?)`,s);let i=kc(await YS(e));if(!i)throw new Qt("output is not a PNG",s);return{width:i.width,height:i.height,dpr:1,renderMs:Date.now()-o}}finally{await St.rm(r,{recursive:!0,force:!0}).catch(()=>{})}}async function nv(t){if(WS==="firefox")return bp(t);try{return await ev(t)}catch(e){if(/failed to launch/i.test(e?.message||""))return bp(t);throw e}}var rv=nv;var bc=Promise.resolve();function Ap(t){return bc=bc.then(t,t),bc}async function Ep(t,e){let n=await pe(),r=n.mocks.find(o=>o.id===t);if(r){r.state="rendering",await fe(),await te(r,"rendering",{version:e});try{let o=await JS();if(o<VS)throw new Qt(`box is low on memory (${Math.round(o/1e6)} MB available) \u2014 not spawning a browser`);let s=Ut(r,e),a=await rv({htmlPath:s.html,outPath:s.png,width:r.width});if(!n.mocks.some(i=>i.id===t)){await St.rm(s.png,{force:!0}).catch(()=>{});return}_p(r,{v:e,source:"html",htmlRel:Nt.relative(r.cwd,s.html),pngRel:Nt.relative(r.cwd,s.png),width:a.width,height:a.height,dpr:a.dpr,renderMs:a.renderMs,createdAt:Date.now()}),await fe(),await te(r,"ready",{version:e,width:a.width,height:a.height,dpr:a.dpr}),tn(r),$n(r,e,"png")}catch(o){if(!n.mocks.some(s=>s.id===t))return;r.state="error",r.error=o?.message||String(o),await fe(),await te(r,"error",{version:e,error:r.error}),tn(r)}}}function _p(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 ov({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?GS:Number(r);if(!Number.isInteger(o)||o<320||o>4e3)throw new Error("invalid width (320-4000)");return o}async function On({trackId:t,group:e}={}){let n=await pe(),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 en(t){let n=(await pe()).mocks.find(r=>r.id===t);return n?{...n}:null}async function Ip({trackId:t,name:e,html:n,image:r,cwd:o,width:s,group:a,box:i,agent:c,prompt:l}){let u=ov({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&&!Tp(r))throw new Error("image must be a PNG");let d=await pe(),m={id:LS().slice(0,8),trackId:t,box:i||null,agent:c||null,name:e.trim(),prompt:l?String(l).slice(0,500):null,group:a?String(a).trim():null,cwd:o,width:u,height:null,dpr:null,state:"queued",source:f?"html":"image",version:1,readyVersion:null,versions:[],error:null,renderMs:null,createdAt:Date.now(),updatedAt:Date.now()};return await St.mkdir(aa(o),{recursive:!0}),d.mocks.push(m),f?(await St.writeFile(Ut(m,1).html,n),await fe(),await te(m,"created"),tn(m),$n(m,1,"html"),Ap(()=>Ep(m.id,1))):(await $p(m,1,r),await fe(),await te(m,"created"),await te(m,"ready",{version:1,width:m.width,height:m.height,dpr:m.dpr}),tn(m),$n(m,1,"png")),{...m}}async function Rp(t,{html:e,image:n,width:r}={}){let s=(await pe()).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&&!Tp(n))throw new Error("image must be a PNG");if(r!=null){let c=Number(r);if(!Number.isInteger(c)||c<320||c>4e3)throw new Error("invalid width (320-4000)");s.width=c}let i=s.version+1;return s.version=i,s.updatedAt=Date.now(),s.source=a?"html":"image",await St.mkdir(aa(s.cwd),{recursive:!0}),a?(s.state="queued",await St.writeFile(Ut(s,i).html,e),await fe(),await te(s,"updated",{version:i}),tn(s),$n(s,i,"html"),Ap(()=>Ep(s.id,i))):(await $p(s,i,n),await fe(),await te(s,"updated",{version:i}),await te(s,"ready",{version:i,width:s.width,height:s.height,dpr:s.dpr}),tn(s),$n(s,i,"png")),{...s}}async function $p(t,e,n){let r=kc(n);if(!r)throw new Error("image must be a PNG");let o=Ut(t,e);await St.writeFile(o.png,n),_p(t,{v:e,source:"image",htmlRel:null,pngRel:Nt.relative(t.cwd,o.png),width:r.width,height:r.height,dpr:1,renderMs:null,createdAt:Date.now()}),t.width=r.width}async function Op(t){let e=await pe(),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 te(n,"picked",{version:n.readyVersion,siblings:r}),{mock:{...n},siblings:r}}async function Mp(t){let e=await pe(),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=Ut(r,o);await St.rm(s.html,{force:!0}).catch(()=>{}),await St.rm(s.png,{force:!0}).catch(()=>{})}return await fe(),await te(r,"deleted"),Yt("mocks",r.id).catch(()=>{}),{ok:!0,id:t,name:r.name}}async function Cp(t){let n=(await pe()).mocks.filter(i=>i.trackId===t.id);if(!n.length)return 0;let r=Nt.join(xp,"mock-archive",t.id),o=aa(t.cwd),s=Nt.join(r,".bostrat","mocks");await St.mkdir(s,{recursive:!0});let a=await St.readdir(o).catch(()=>[]);for(let i of a)await St.copyFile(Nt.join(o,i),Nt.join(s,i));for(let i of n){for(let c of i.versions||[])for(let l of[c.htmlRel,c.pngRel]){if(!l)continue;if(!(await St.stat(Nt.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(),tn(i)}return await fe(),n.length}async function Np(t){let e=await pe(),n=e.mocks.filter(r=>r.trackId===t);if(n.length){e.mocks=e.mocks.filter(r=>r.trackId!==t),await fe();for(let r of n)Yt("mocks",r.id).catch(()=>{})}return n.length}async function Pp(){if(!Tt())return{pushed:0,source:"unconfigured"};let{records:t,source:e}=await Zt("mocks");if(e!=="plane"||t.length)return{pushed:0,source:e};let n=await pe();for(let r of n.mocks){await tn(r);for(let o of r.versions||[])o.htmlRel&&await $n(r,o.v,"html"),o.pngRel&&await $n(r,o.v,"png")}return{pushed:n.mocks.length,source:"plane"}}import _o from"node:fs/promises";import Pt from"node:path";import sv from"node:os";import av from"node:net";import{randomUUID as iv}from"node:crypto";import{execFile as cv}from"node:child_process";import{promisify as lv}from"node:util";var he=lv(cv),uv=process.env.BOSTRAT_STATE_DIR||Pt.join(process.env.HOME||sv.tmpdir(),".bostrat"),Fp=Pt.join(uv,"views.json"),[Bp,Dp]=(process.env.BOSTRAT_VIEW_PORTS||"4300-4349").split("-").map(t=>Number(t)||0),nn=Number(process.env.BOSTRAT_VIEW_MAX)||3,dv="bostrat-view-",gr=t=>`${dv}${t}`,jp=new Set(["bostrat","www","api","views","app"]),ia=t=>Object.assign(new Error(t),{status:400}),fv=new Set(["npm","pnpm","yarn","bun"]),vc="package.json",pv=/[|;&`]|\$\(/,mv=new Set(["sh","bash","zsh","dash","env","exec","nohup","time"]),hv=/(^|\s)(-C|--cwd|--manifest-path|--dir)(=|\s)/,Up=t=>String(t).trim().split(/\s+/).filter(Boolean),wv=/^[A-Za-z_][A-Za-z0-9_]*=/;function gv(t){for(let e of Up(t))if(!wv.test(e))return e;return null}function Hp(t){let e=Up(t),n=e.indexOf("--");return n===-1?e:e.slice(0,n)}function yv(t,e){let n=Hp(e);for(let r=0;r<n.length;r++){let o=/^--prefix=(.+)$/.exec(n[r]);if(o)return Pt.resolve(t,o[1]);if(n[r]==="--prefix"&&n[r+1])return Pt.resolve(t,n[r+1])}return t}var bv=t=>_o.stat(t).then(e=>e.isDirectory(),()=>!1),Wp=t=>_o.stat(t).then(e=>e.isFile(),()=>!1),ca=t=>_o.realpath(t).catch(()=>Pt.resolve(t));async function kv(t,e){if(Pt.resolve(t)===Pt.resolve(e))return!0;let[n,r]=await Promise.all([ca(t),ca(e)]);return n===r}async function xv(t){let n=(await _o.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 Wp(Pt.join(t,o.name,vc))?`./${o.name}`:null))).filter(Boolean)}async function Sv(t,e){let[n,r]=await Promise.all([ca(t),ca(e)]),o=Pt.relative(n,r);if(o&&(o.startsWith("..")||Pt.isAbsolute(o)))throw ia(`serve directory escapes the Track workspace: ${e}`)}async function vv(t,e){if(!await bv(t))throw ia(`serve directory does not exist: ${t}`);let n=gv(e);if(!n)return;if(!n.includes("/")&&!await Xi(n,{loginShell:!0,failOpen:!0}))throw ia(`\`${n}\` is not on the box's PATH \u2014 install it in the box first`);if(pv.test(e)||mv.has(n)||!fv.has(n)||!Hp(e).includes("run")||hv.test(e))return;let r=yv(t,e);if(await Wp(Pt.join(r,vc)))return;let o=await xv(r),s=o.length?` \u2014 found one in ${o.join(", ")}; run bostrat-view from there`:"";throw ia(`no ${vc} in ${r}${s}`)}var me=null,xc=null;async function Oe(){return me||(xc||=(async()=>{try{me=JSON.parse(await _o.readFile(Fp,"utf8"))}catch{me={views:[],adoptWatermarks:{}}}finally{xc=null}return me.views||=[],me.adoptWatermarks||={},me})(),xc)}async function we(){await Ct(Fp,me)}function $e(t,e,n={}){return $({id:t.trackId,cwd:t.cwd},{type:"view.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box||null,payload:{ts:Date.now(),change:e,viewId:t.id,name:t.name,slug:t.slug,port:t.port,...n}}).catch(()=>{})}var Eo=new Map,Ec=Number(process.env.BOSTRAT_VIEW_JOB_TIMEOUT)||3e5,qp=12e3;function _c(t){return Eo.get(t)||null}function Kp(t,{name:e,box:n}={}){let r=Eo.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 Eo.set(t,o),o}function Tc(t,e){let n=Eo.get(t);n&&!n.finishedAt&&!n.viewId&&(n.viewId=e,n.step="booting")}function la(t,{error:e=null}={}){let n=Eo.get(t);return!n||n.finishedAt?null:(n.finishedAt=Date.now(),e&&(n.error=e),n)}function Gp(t){let e=[];for(let n of t?.events||[])n.type!=="view-spinup"||!n.box||!n.startedAt||!n.endedAt||e.push({box:n.box,from:n.startedAt,to:n.endedAt});return e}function Ac(t){return String(t||"").trim().toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,40).replace(/-+$/g,"")||"view"}function Tv(t,e){let n=Ac(e),r=new Set(t.views.map(o=>o.slug));if(!r.has(n)&&!jp.has(n))return n;for(let o=2;;o++){let s=`${n}-${o}`;if(!r.has(s)&&!jp.has(s))return s}}function Io(t,{timeoutMs:e=1200}={}){return new Promise(n=>{let r=av.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 Ic(t){if(process.platform!=="darwin"){await he("fuser",["-k",`${t}/tcp`]).catch(()=>{});return}let e="";try{({stdout:e}=await he("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 zp(t=process.env){return new Set([Number(t.PORT)||3362,Number(t.BOSTRAT_TLS_PORT)||3443])}async function Av(t,e){let n=new Set(t.views.map(o=>o.port)),r=zp();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=Bp;o<=Dp;o++)if(!(n.has(o)||r.has(o))&&!await Io(o,{timeoutMs:250}))return o;throw new Error(`no free view port in ${Bp}-${Dp}`)}async function Ev(t){try{let{stdout:e}=await he(N,B("list-sessions","-F","#{session_name}"));return e.split(`
|
|
77
|
-
`).some(n=>n.trim()===
|
|
76
|
+
`)}`:e),this.name="MockRenderError"}},Cn=null,vc=null;async function fe(){return Cn||(vc||=(async()=>{try{Cn=JSON.parse(await kt.readFile(Pp,"utf8"))}catch{Cn={mocks:[]}}finally{vc=null}return Cn.mocks||=[],Cn})(),vc)}async function de(){await Mt(Pp,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 ia=t=>Pt.join(t,".bostrat","mocks");function Ht(t,e){let n=ia(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 Ac(t){return t.length<24||t.readUInt32BE(12)!==1229472850?null:{width:t.readUInt32BE(16),height:t.readUInt32BE(20)}}var xv=Buffer.from([137,80,78,71,13,10,26,10]);function Dp(t){return Buffer.isBuffer(t)&&t.length>24&&t.subarray(0,8).equals(xv)}async function Sv(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 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 Tv(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 Av(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 Ev({htmlPath:t,outPath:e,width:n}){let r=Date.now(),o;try{o=await pv.launch({executablePath:hv,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 Tv(a,i),u=l&&vv({...l,pad:yv*gr,align:gr});u&&(i=await Av(a,i,{bg:l.bg,crop:u}))}catch{}if(i.length<Bp)throw new te(`capture produced a ${i.length}-byte image`);let c=Ac(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 Cp({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 Mp(wv,["--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<Bp)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=Ac(await Sv(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 _v(t){if(mv==="firefox")return Cp(t);try{return await Ev(t)}catch(e){if(/failed to launch/i.test(e?.message||""))return Cp(t);throw e}}var Iv=_v;var Tc=Promise.resolve();function jp(t){return Tc=Tc.then(t,t),Tc}async function Lp(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 kv();if(o<bv)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 Iv({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}Fp(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 Fp(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 Rv({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?gv: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 Up({trackId:t,name:e,html:n,image:r,cwd:o,width:s,group:a,box:i,agent:c,prompt:l}){let u=Rv({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&&!Dp(r))throw new Error("image must be a PNG");let d=await fe(),p={id:uv().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(ia(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"),jp(()=>Lp(p.id,1))):(await Wp(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 Hp(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&&!Dp(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(ia(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"),jp(()=>Lp(s.id,i))):(await Wp(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 Wp(t,e,n){let r=Ac(n);if(!r)throw new Error("image must be a PNG");let o=Ht(t,e);await kt.writeFile(o.png,n),Fp(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 qp(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 Kp(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 Gp(t){let n=(await fe()).mocks.filter(i=>i.trackId===t.id);if(!n.length)return 0;let r=Pt.join(Np,"mock-archive",t.id),o=ia(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 Vp(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 zp(){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 $v from"node:os";import Ov from"node:net";import{randomUUID as Cv}from"node:crypto";import{execFile as Mv}from"node:child_process";import{promisify as Nv}from"node:util";var me=Nv(Mv),Pv=process.env.BOSTRAT_STATE_DIR||Bt.join(process.env.HOME||$v.tmpdir(),".bostrat"),Qp=Bt.join(Pv,"views.json"),[Jp,Xp]=(process.env.BOSTRAT_VIEW_PORTS||"4300-4349").split("-").map(t=>Number(t)||0),rn=Number(process.env.BOSTRAT_VIEW_MAX)||3,Bv="bostrat-view-",br=t=>`${Bv}${t}`,Yp=new Set(["bostrat","www","api","views","app"]),ca=t=>Object.assign(new Error(t),{status:400}),Dv=new Set(["npm","pnpm","yarn","bun"]),Ic="package.json",jv=/[|;&`]|\$\(/,Lv=new Set(["sh","bash","zsh","dash","env","exec","nohup","time"]),Fv=/(^|\s)(-C|--cwd|--manifest-path|--dir)(=|\s)/,tm=t=>String(t).trim().split(/\s+/).filter(Boolean),Uv=/^[A-Za-z_][A-Za-z0-9_]*=/;function Hv(t){for(let e of tm(t))if(!Uv.test(e))return e;return null}function em(t){let e=tm(t),n=e.indexOf("--");return n===-1?e:e.slice(0,n)}function Wv(t,e){let n=em(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 qv=t=>Eo.stat(t).then(e=>e.isDirectory(),()=>!1),nm=t=>Eo.stat(t).then(e=>e.isFile(),()=>!1),la=t=>Eo.realpath(t).catch(()=>Bt.resolve(t));async function Kv(t,e){if(Bt.resolve(t)===Bt.resolve(e))return!0;let[n,r]=await Promise.all([la(t),la(e)]);return n===r}async function Gv(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 nm(Bt.join(t,o.name,Ic))?`./${o.name}`:null))).filter(Boolean)}async function Vv(t,e){let[n,r]=await Promise.all([la(t),la(e)]),o=Bt.relative(n,r);if(o&&(o.startsWith("..")||Bt.isAbsolute(o)))throw ca(`serve directory escapes the Track workspace: ${e}`)}async function zv(t,e){if(!await qv(t))throw ca(`serve directory does not exist: ${t}`);let n=Hv(e);if(!n)return;if(!n.includes("/")&&!await oc(n,{loginShell:!0,failOpen:!0}))throw ca(`\`${n}\` is not on the box's PATH \u2014 install it in the box first`);if(jv.test(e)||Lv.has(n)||!Dv.has(n)||!em(e).includes("run")||Fv.test(e))return;let r=Wv(t,e);if(await nm(Bt.join(r,Ic)))return;let o=await Gv(r),s=o.length?` \u2014 found one in ${o.join(", ")}; run bostrat-view from there`:"";throw ca(`no ${Ic} in ${r}${s}`)}var pe=null,Ec=null;async function Oe(){return pe||(Ec||=(async()=>{try{pe=JSON.parse(await Eo.readFile(Qp,"utf8"))}catch{pe={views:[],adoptWatermarks:{}}}finally{Ec=null}return pe.views||=[],pe.adoptWatermarks||={},pe})(),Ec)}async function he(){await Mt(Qp,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,Oc=Number(process.env.BOSTRAT_VIEW_JOB_TIMEOUT)||3e5,rm=12e3;function Cc(t){return Ao.get(t)||null}function om(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 Rc(t,e){let n=Ao.get(t);n&&!n.finishedAt&&!n.viewId&&(n.viewId=e,n.step="booting")}function ua(t,{error:e=null}={}){let n=Ao.get(t);return!n||n.finishedAt?null:(n.finishedAt=Date.now(),e&&(n.error=e),n)}function sm(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 $c(t){return String(t||"").trim().toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,40).replace(/-+$/g,"")||"view"}function Jv(t,e){let n=$c(e),r=new Set(t.views.map(o=>o.slug));if(!r.has(n)&&!Yp.has(n))return n;for(let o=2;;o++){let s=`${n}-${o}`;if(!r.has(s)&&!Yp.has(s))return s}}function _o(t,{timeoutMs:e=1200}={}){return new Promise(n=>{let r=Ov.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 Mc(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 am(t=process.env){return new Set([Number(t.PORT)||3362,Number(t.BOSTRAT_TLS_PORT)||3443])}async function Xv(t,e){let n=new Set(t.views.map(o=>o.port)),r=am();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=Jp;o<=Xp;o++)if(!(n.has(o)||r.has(o))&&!await _o(o,{timeoutMs:250}))return o;throw new Error(`no free view port in ${Jp}-${Xp}`)}async function Yv(t){try{let{stdout:e}=await me(N,P("list-sessions","-F","#{session_name}"));return e.split(`
|
|
77
|
+
`).some(n=>n.trim()===br(t))}catch{return!1}}async function Io(t){try{await me(N,P("kill-session","-t",br(t)))}catch{}}async function Nc(t){if(!await go())throw new Error("host tmux server not running and auto-start failed \u2014 cannot spawn a view server");let e=br(t.slug),n=t.serveCwd??t.cwd,r=Bt.dirname(process.execPath);await me(N,P("new-session","-d","-s",e,"-c",n)),await me(N,P("send-keys","-t",e,"-l",`export PORT=${t.port} PATH="${r}:$PATH"`)),await me(N,P("send-keys","-t",e,"Enter")),await me(N,P("send-keys","-t",e,"-l",t.cmd)),await me(N,P("send-keys","-t",e,"Enter"))}async function Zp(t,e=12){try{let{stdout:n}=await me(N,P("capture-pane","-t",br(t),"-p"));return String(n||"").trimEnd().split(`
|
|
78
78
|
`).filter(Boolean).slice(-e).join(`
|
|
79
|
-
`)}catch{return""}}var Re=new Map,_v=12e4;async function Iv(t){if(t.state==="stopped"||t.state==="error")return!1;let e=t.origin!=="detected",n=e?await Ev(t.slug):!0;if(e&&!n){let s=t.state==="starting";return t.state=s?"error":"stopped",s&&(t.error=await Lp(t.slug)||"server exited before listening"),Re.delete(t.id),await $e(t,t.state,t.error?{error:t.error}:{}),!0}if(await Io(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>_v?(t.state="error",t.error=await Lp(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 Sc=!1;async function ua(){if(!Sc){Sc=!0;try{let t=await Oe(),e=!1;for(let n of t.views)await Iv(n)&&(e=!0);e&&await we()}finally{Sc=!1}}}async function da({trackId:t}={}){await ua();let e=await Oe();return(t?e.views.filter(r=>r.trackId===t):e.views).map(r=>({...r}))}async function Vp(t){let n=(await Oe()).views.find(r=>r.id===t);return n?{...n}:null}function $c(t){return me&&me.views.find(e=>e.slug===t)||null}async function Oc({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=Pt.resolve(r,o??"."),f=i==="detected"?null:async()=>{await Sv(r,u),await vv(u,n)},d=await Oe();if(i!=="detected"){let w=Ac(e),g=String(n).trim(),b=d.views.find(S=>S.trackId===t&&(S.slug===w||Ac(S.name)===w||S.cmd===g&&Pt.resolve(S.serveCwd??S.cwd)===u));if(b)return Rv(d,b,{cmd:g,box:a,branch:c,serveCwd:l?u:void 0,validate:f})}if(f&&await f(),d.views.filter(w=>w.state==="live"||w.state==="starting").length>=nn)throw new Error(`view cap reached (${nn} live) \u2014 stop one first`);let h={id:iv().slice(0,8),trackId:t,box:a||null,name:e.trim(),slug:Tv(d,e),cmd:n?String(n).trim():null,cwd:r,serveCwd:u,branch:c,port:await Av(d,s),origin:i,state:"starting",error:null,createdAt:Date.now(),lastHealthyAt:null};return i!=="detected"&&await Rc(h),d.views.push(h),await we(),await $e(h,"created",{origin:i}),Tc(t,h.id),i==="detected"&&await Io(h.port)&&(h.state="live",h.lastHealthyAt=Date.now(),await we(),await $e(h,"live")),{...h}}async function Rv(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 kv(e.serveCwd??e.cwd,s);if((e.state==="live"||e.state==="starting")&&!i&&!c&&await Io(e.port))return await we(),Tc(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>=nn)throw new Error(`view cap reached (${nn} live) \u2014 stop one first`);return e.origin==="detected"?(await Ic(e.port),e.origin="agent"):await Ro(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 Rc(e),await we(),await $e(e,"created",{restarted:!0,reused:!0}),Tc(e.trackId,e.id),{...e,reused:!0}}async function Jp(t){let n=(await Oe()).views.find(r=>r.id===t);if(!n)throw new Error("view not found");return n.origin==="detected"?await Ic(n.port):await Ro(n.slug),n.state!=="stopped"&&(n.state="stopped",Re.delete(n.id),await we(),await $e(n,"stopped")),{...n}}async function Xp(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>=nn)throw new Error(`view cap reached (${nn} live) \u2014 stop one first`);return await Ro(n.slug),n.state="starting",n.error=null,n.createdAt=Date.now(),Re.delete(n.id),await Rc(n),await we(),await $e(n,"created",{restarted:!0}),{...n}}async function Yp(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 Ic(r.port):await Ro(r.slug),e.views.splice(n,1),Re.delete(r.id),await we(),await $e(r,"deleted"),{ok:!0,id:t,slug:r.slug}}async function Zp(t){let e=await Oe(),n=e.views.filter(r=>r.trackId===t);for(let r of n)r.origin!=="detected"&&await Ro(r.slug),Re.delete(r.id);return n.length&&(e.views=e.views.filter(r=>r.trackId!==t),await we()),n.length}var $v=/https?:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0)(?::(\d{2,5}))?(?:\/|\b)/g;async function Qp(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 we()),!o.length)return[];let a=new Set;for(let c of o)for(let l of String(c.text||"").matchAll($v)){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(!zp().has(c)&&!n.views.some(l=>l.port===c)&&await Io(c))try{i.push(await Oc({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 Ov from"node:os";import{randomUUID as Mv}from"node:crypto";var Mn=process.env.BOSTRAT_STATE_DIR||Pe.join(process.env.HOME||Ov.tmpdir(),".bostrat"),em=Pe.join(Mn,"runbooks.json"),fa=Pe.join(Mn,"runbooks"),tm=256*1024,Cv=[{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 $o(t){return Cv.filter(({re:e})=>e.test(t)).map(({label:e})=>e)}var Me=null,Mc=null;async function Be(){return Me||(Mc||=(async()=>{let t=!0;try{Me=JSON.parse(await Ne.readFile(em,"utf8"))}catch{Me={runbooks:[]},t=!1}finally{Mc=null}return Me.runbooks||=[],!t&&Tt()&&await Nv().catch(()=>{}),Me})(),Mc)}async function Cn(){await Ct(em,Me)}var Oo=t=>Promise.resolve(t).catch(()=>{});function pa(t){return Oo(de("runbooks",{...t},{trackId:t.trackId}))}async function Nv(){let{records:t,source:e}=await Zt("runbooks");if(!(e!=="plane"||!t.length)){await Ne.mkdir(fa,{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 yp("runbooks",r.id,o.v,"md");typeof s=="string"&&await Ne.writeFile(Ce(r,o.v),s).catch(()=>{})}Me.runbooks.push(r)}Me.runbooks.length&&await Cn()}}function yr(t,e,n={}){return $({id:t.trackId,cwd:t.cwd},{type:"runbook.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box||null,provider:t.agent||null,payload:{ts:Date.now(),change:e,runbookId:t.id,name:t.name,status:t.status,...n}}).catch(()=>{})}function Ce(t,e){return Pe.join(fa,`${t.id}.v${e}.md`)}function nm(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 rm(t){if(typeof t!="string"||!t.trim())throw new Error("md (markdown string) required");let e=Buffer.byteLength(t,"utf8");if(e>tm)throw new Error(`runbook is ${Math.round(e/1024)} KB \u2014 over the ${tm/1024} KB cap; a doc that size belongs in the repo, not a runbook`);let n=$o(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 om(t,e){if(t.trackDeletedAt)throw new Error(`runbook is orphaned (its Track was deleted) \u2014 cannot ${e} it`)}async function ma({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 Cc(t){let n=(await Be()).runbooks.find(r=>r.id===t);return n?{...n}:null}async function sm({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=rm(r),c=await Be(),l={id:Mv().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(fa,{recursive:!0}),await Ne.writeFile(Ce(l,1),r),l.versions.push({v:1,mdRel:Pe.relative(Mn,Ce(l,1)),bytes:i,note:null,createdAt:Date.now()}),c.runbooks.push(l),await Cn(),await yr(l,"created",{version:1}),pa(l),Oo(mr("runbooks",l.id,1,"md",Ce(l,1))),{...l}}async function am(t,{md:e,note:n}={}){let o=(await Be()).runbooks.find(i=>i.id===t);if(!o)throw new Error("runbook not found");om(o,"update");let s=rm(e),a=o.version+1;return o.version=a,o.updatedAt=Date.now(),await Ne.mkdir(fa,{recursive:!0}),await Ne.writeFile(Ce(o,a),e),o.versions.push({v:a,mdRel:Pe.relative(Mn,Ce(o,a)),bytes:s,note:n?String(n).slice(0,200):null,createdAt:Date.now()}),await Cn(),await yr(o,"updated",{version:a,...n?{note:String(n).slice(0,200)}:{}}),pa(o),Oo(mr("runbooks",o.id,a,"md",Ce(o,a))),{...o}}async function im(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 om(r,e==="done"?"complete":"reopen"),r.status===e?{...r}:(r.status=e,r.updatedAt=Date.now(),await Cn(),await yr(r,e==="done"?"done":"reopened"),pa(r),{...r})}async function cm(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(Mn,o.mdRel),{force:!0}).catch(()=>{});return await Cn(),r.trackDeletedAt||await yr(r,"deleted"),Oo(Yt("runbooks",r.id)),{ok:!0,id:t,name:r.name}}async function lm(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 yr(r,"orphaned"),r.cwd=null,pa(r);return n.length&&await Cn(),n.length}async function um(t){let e=await Be(),n=e.runbooks.filter(r=>r.trackId===t);for(let r of n){await yr(r,"deleted");for(let o of r.versions||[])await Ne.rm(Pe.join(Mn,o.mdRel),{force:!0}).catch(()=>{});Oo(Yt("runbooks",r.id))}return n.length&&(e.runbooks=e.runbooks.filter(r=>r.trackId!==t),await Cn()),n.length}async function dm(){if(!Tt())return{pushed:0,source:"unconfigured"};let{records:t,source:e}=await Zt("runbooks");if(e!=="plane"||t.length)return{pushed:0,source:e};let n=await Be();for(let r of n.runbooks){await de("runbooks",{...r},{trackId:r.trackId}).catch(()=>{});for(let o of r.versions||[])await mr("runbooks",r.id,o.v,"md",Pe.join(Mn,o.mdRel)).catch(()=>{})}return{pushed:n.runbooks.length,source:"plane"}}import No from"node:fs/promises";import Am from"node:path";import uT from"node:os";import{randomUUID as Em}from"node:crypto";import Pv from"node:fs/promises";import fm from"node:path";import Bv from"node:os";var pm=()=>fm.join(process.env.BOSTRAT_STATE_DIR||fm.join(process.env.HOME||Bv.tmpdir(),".bostrat"),"integrations.json");async function De(){try{return JSON.parse(await Pv.readFile(pm(),"utf8"))||{}}catch{return{}}}var ha=["linked","prOpened","prMerged","create"],Nc=["prompt","auto","off"],Dv={create:["prompt","off"]},mm=t=>Dv[t]??Nc;function wa(t){let e=t.linear||{},n=e.transitions===!0?"auto":"prompt",r={};for(let o of ha){let s=mm(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 Nn(){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:wa(t),defaultTeam:n}:null}async function ga(){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:wa(t)}}async function Pc(){return wa(await De())}async function hm(t){let e=Object.entries(t||{});if(!e.length)throw new Error(`nothing to set \u2014 phases: ${ha.join(", ")}`);for(let[r,o]of e){if(!ha.includes(r))throw new Error(`unknown phase "${r}" \u2014 configurable phases: ${ha.join(", ")}`);let s=mm(r);if(!s.includes(o))throw new Error(s.length<Nc.length?`mode "${o}" not allowed for "${r}" \u2014 ${r} is always confirmed (modes: ${s.join(", ")})`:`unknown mode "${o}" \u2014 modes: ${Nc.join(", ")}`)}let n=await De();return n.ticketFlow={...wa(n),...Object.fromEntries(e)},n.linear&&(delete n.linear.ticketFlow,delete n.linear.transitions),await Ct(pm(),n,{mode:384}),n.ticketFlow}async function Mo(){let t=(await De()).deepgram||{},e=process.env.DEEPGRAM_API_KEY||t.apiKey;return e?{apiKey:e}:null}async function jv(){let t=(await De()).openai||{},e=process.env.BOSTRAT_OPENAI_API_KEY||t.apiKey;return e?{apiKey:e}:null}var Lv={openai:{model:"gpt-realtime-2.1-mini",voice:"marin"}};async function ya(){let e=(await De()).voiceChat||{},n=e.provider||"openai",r=Lv[n];if(!r)return null;let o=n==="openai"?await jv():null;return o?{provider:n,model:e.model||r.model,voice:e.voice||r.voice,apiKey:o.apiKey}:null}async function wm(){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 Pn(){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 Dc={};bd(Dc,{createComment:()=>Vv,findIssueUrl:()=>Wv,issueStates:()=>Yv,mdToAdf:()=>bm,moveIssue:()=>Zv,parseIssueRef:()=>Hv,resolveIssue:()=>qv,uploadImage:()=>Jv,upsertCard:()=>Kv,whoami:()=>Bc});var br=t=>String(t.baseUrl||"").replace(/\/$/,""),ym=t=>`Basic ${Buffer.from(`${t.email}:${t.apiToken}`).toString("base64")}`;async function Bn(t,e,n,r){let o=await fetch(`${br(t)}${n}`,{method:e,headers:{authorization:ym(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 Fv=/\/browse\/([A-Za-z][A-Za-z0-9]{0,9}-\d+)/,Uv=/^([A-Z][A-Z0-9]{0,9}-\d+)$/;function Hv(t){let e=String(t||"").trim();if(/^https?:\/\//i.test(e)){let r=e.match(Fv);return r?r[1].toUpperCase():null}let n=e.match(Uv);return n?n[1]:null}function Wv(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 qv(t,e){let n;try{n=await Bn(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:`${br(t)}/browse/${n.key}`,state:n.fields?.status?.name??null}}async function Kv(t,e,{url:n,title:r,subtitle:o}){await Bn(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 Gv=/(!\[[^\]]*\]\([^)\s]+\)|\*\*[^*\n]+\*\*|`[^`\n]+`|_[^_\n]+_|https?:\/\/[^\s)]+)/g;function zv(t){let e=[],n=0;for(let r of t.matchAll(Gv)){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 bm(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 Re=new Map,Zv=12e4;async function Qv(t){if(t.state==="stopped"||t.state==="error")return!1;let e=t.origin!=="detected",n=e?await Yv(t.slug):!0;if(e&&!n){let s=t.state==="starting";return t.state=s?"error":"stopped",s&&(t.error=await Zp(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>Zv?(t.state="error",t.error=await Zp(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 _c=!1;async function da(){if(!_c){_c=!0;try{let t=await Oe(),e=!1;for(let n of t.views)await Qv(n)&&(e=!0);e&&await he()}finally{_c=!1}}}async function fa({trackId:t}={}){await da();let e=await Oe();return(t?e.views.filter(r=>r.trackId===t):e.views).map(r=>({...r}))}async function im(t){let n=(await Oe()).views.find(r=>r.id===t);return n?{...n}:null}function Pc(t){return pe&&pe.views.find(e=>e.slug===t)||null}async function Bc({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 Vv(r,u),await zv(u,n)},d=await Oe();if(i!=="detected"){let g=$c(e),y=String(n).trim(),b=d.views.find(S=>S.trackId===t&&(S.slug===g||$c(S.name)===g||S.cmd===y&&Bt.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(g=>g.state==="live"||g.state==="starting").length>=rn)throw new Error(`view cap reached (${rn} live) \u2014 stop one first`);let m={id:Cv().slice(0,8),trackId:t,box:a||null,name:e.trim(),slug:Jv(d,e),cmd:n?String(n).trim():null,cwd:r,serveCwd:u,branch:c,port:await Xv(d,s),origin:i,state:"starting",error:null,createdAt:Date.now(),lastHealthyAt:null};return i!=="detected"&&await Nc(m),d.views.push(m),await he(),await $e(m,"created",{origin:i}),Rc(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 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 Kv(e.serveCwd??e.cwd,s);if((e.state==="live"||e.state==="starting")&&!i&&!c&&await _o(e.port))return await he(),Rc(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 Mc(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 Nc(e),await he(),await $e(e,"created",{restarted:!0,reused:!0}),Rc(e.trackId,e.id),{...e,reused:!0}}async function cm(t){let n=(await Oe()).views.find(r=>r.id===t);if(!n)throw new Error("view not found");return n.origin==="detected"?await Mc(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 lm(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 Nc(n),await he(),await $e(n,"created",{restarted:!0}),{...n}}async function um(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 Mc(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 dm(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 eT=/https?:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0)(?::(\d{2,5}))?(?:\/|\b)/g;async function fm(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(eT)){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(!am().has(c)&&!n.views.some(l=>l.port===c)&&await _o(c))try{i.push(await Bc({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 nT from"node:os";import{randomUUID as rT}from"node:crypto";var Pn=process.env.BOSTRAT_STATE_DIR||Pe.join(process.env.HOME||nT.tmpdir(),".bostrat"),mm=Pe.join(Pn,"runbooks.json"),pa=Pe.join(Pn,"runbooks"),pm=256*1024,oT=[{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 oT.filter(({re:e})=>e.test(t)).map(({label:e})=>e)}var Ce=null,Dc=null;async function Be(){return Ce||(Dc||=(async()=>{let t=!0;try{Ce=JSON.parse(await Ne.readFile(mm,"utf8"))}catch{Ce={runbooks:[]},t=!1}finally{Dc=null}return Ce.runbooks||=[],!t&&pt()&&await sT().catch(()=>{}),Ce})(),Dc)}async function Bn(){await Mt(mm,Ce)}var $o=t=>Promise.resolve(t).catch(()=>{});function ma(t){return $o(Rt("runbooks",{...t},{trackId:t.trackId}))}async function sT(){let{records:t,source:e}=await Tt("runbooks");if(!(e!=="plane"||!t.length)){await Ne.mkdir(pa,{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(pa,`${t.id}.v${e}.md`)}function hm(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 wm(t){if(typeof t!="string"||!t.trim())throw new Error("md (markdown string) required");let e=Buffer.byteLength(t,"utf8");if(e>pm)throw new Error(`runbook is ${Math.round(e/1024)} KB \u2014 over the ${pm/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 gm(t,e){if(t.trackDeletedAt)throw new Error(`runbook is orphaned (its Track was deleted) \u2014 cannot ${e} it`)}async function ha({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 jc(t){let n=(await Be()).runbooks.find(r=>r.id===t);return n?{...n}:null}async function ym({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=wm(r),c=await Be(),l={id:rT().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(pa,{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}),ma(l),$o(Je("runbooks",l.id,1,"md",Me(l,1))),{...l}}async function bm(t,{md:e,note:n}={}){let o=(await Be()).runbooks.find(i=>i.id===t);if(!o)throw new Error("runbook not found");gm(o,"update");let s=wm(e),a=o.version+1;return o.version=a,o.updatedAt=Date.now(),await Ne.mkdir(pa,{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)}:{}}),ma(o),$o(Je("runbooks",o.id,a,"md",Me(o,a))),{...o}}async function km(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 gm(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"),ma(r),{...r})}async function xm(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 Sm(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,ma(r);return n.length&&await Bn(),n.length}async function vm(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 Tm(){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 jm from"node:path";import PT from"node:os";import{randomUUID as Lm}from"node:crypto";import aT from"node:fs/promises";import Am from"node:path";import iT from"node:os";var Em=()=>Am.join(process.env.BOSTRAT_STATE_DIR||Am.join(process.env.HOME||iT.tmpdir(),".bostrat"),"integrations.json");async function De(){try{return JSON.parse(await aT.readFile(Em(),"utf8"))||{}}catch{return{}}}var wa=["linked","prOpened","prMerged","create"],Lc=["prompt","auto","off"],cT={create:["prompt","off"]},_m=t=>cT[t]??Lc;function ga(t){let e=t.linear||{},n=e.transitions===!0?"auto":"prompt",r={};for(let o of wa){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:ga(t),defaultTeam:n}:null}async function ya(){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:ga(t)}}async function Fc(){return ga(await De())}async function Im(t){let e=Object.entries(t||{});if(!e.length)throw new Error(`nothing to set \u2014 phases: ${wa.join(", ")}`);for(let[r,o]of e){if(!wa.includes(r))throw new Error(`unknown phase "${r}" \u2014 configurable phases: ${wa.join(", ")}`);let s=_m(r);if(!s.includes(o))throw new Error(s.length<Lc.length?`mode "${o}" not allowed for "${r}" \u2014 ${r} is always confirmed (modes: ${s.join(", ")})`:`unknown mode "${o}" \u2014 modes: ${Lc.join(", ")}`)}let n=await De();return n.ticketFlow={...ga(n),...Object.fromEntries(e)},n.linear&&(delete n.linear.ticketFlow,delete n.linear.transitions),await Mt(Em(),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 lT(){let t=(await De()).openai||{},e=process.env.BOSTRAT_OPENAI_API_KEY||t.apiKey;return e?{apiKey:e}:null}var uT={openai:{model:"gpt-realtime-2.1-mini",voice:"marin"}};async function ba(){let e=(await De()).voiceChat||{},n=e.provider||"openai",r=uT[n];if(!r)return null;let o=n==="openai"?await lT():null;return o?{provider:n,model:e.model||r.model,voice:e.voice||r.voice,apiKey:o.apiKey}:null}async function Rm(){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 Hc={};$d(Hc,{createComment:()=>bT,findIssueUrl:()=>mT,issueStates:()=>ST,mdToAdf:()=>Cm,moveIssue:()=>vT,parseIssueRef:()=>pT,resolveIssue:()=>hT,uploadImage:()=>kT,upsertCard:()=>wT,whoami:()=>Uc});var xr=t=>String(t.baseUrl||"").replace(/\/$/,""),Om=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:Om(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 dT=/\/browse\/([A-Za-z][A-Za-z0-9]{0,9}-\d+)/,fT=/^([A-Z][A-Z0-9]{0,9}-\d+)$/;function pT(t){let e=String(t||"").trim();if(/^https?:\/\//i.test(e)){let r=e.match(dT);return r?r[1].toUpperCase():null}let n=e.match(fT);return n?n[1]:null}function mT(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 hT(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 wT(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 gT=/(!\[[^\]]*\]\([^)\s]+\)|\*\*[^*\n]+\*\*|`[^`\n]+`|_[^_\n]+_|https?:\/\/[^\s)]+)/g;function yT(t){let e=[],n=0;for(let r of t.matchAll(gT)){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 Cm(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(...yT(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 bT(t,e,n){let r=await Ln(t,"POST",`/rest/api/3/issue/${encodeURIComponent(e.key)}/comment`,{body:Cm(n)});return r?.id?`${xr(t)}/browse/${e.key}?focusedCommentId=${r.id}`:null}async function Uc(t){let e=await Ln(t,"GET","/rest/api/3/myself"),n=null;try{n=new URL(xr(t)).host}catch{n=t.baseUrl??null}return{user:e?.displayName??e?.emailAddress??null,org:n}}async function kT(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(`${xr(t)}/rest/api/3/issue/${encodeURIComponent(e)}/attachments`,{method:"POST",headers:{authorization:Om(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??`${xr(t)}/rest/api/3/attachment/content/${c.id}`}var xT={new:"unstarted",indeterminate:"started",done:"completed"},$m=t=>xT[t?.key]??"started";async function ST(t,e){let n=encodeURIComponent(e.key),[r,o]=await Promise.all([Ln(t,"GET",`/rest/api/3/issue/${n}?fields=status`),Ln(t,"GET",`/rest/api/3/issue/${n}/transitions`)]),s=r?.fields?.status??null,a=s?{id:`status:${s.id}`,name:s.name,type:$m(s.statusCategory)}:null,i=(o?.transitions??[]).map((c,l)=>({id:c.id,name:c.to?.name??c.name,type:$m(c.to?.statusCategory),position:l}));return{current:a,states:i}}async function vT(t,e,n){await Ln(t,"POST",`/rest/api/3/issue/${encodeURIComponent(e.key)}/transitions`,{transition:{id:String(n)}})}var Gc={};$d(Gc,{createComment:()=>OT,createIssue:()=>Kc,createProject:()=>qc,findIssueUrl:()=>IT,issueStates:()=>MT,moveIssue:()=>NT,parseIssueRef:()=>_T,resolveIssue:()=>RT,teams:()=>Sr,uploadImage:()=>CT,upsertCard:()=>$T,whoami:()=>Wc});var TT=()=>process.env.BOSTRAT_LINEAR_API_URL||"https://api.linear.app/graphql";async function we(t,e,n={}){let r=await fetch(TT(),{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 AT=/linear\.app\/[^/\s]+\/issue\/([A-Za-z][A-Za-z0-9]{0,9}-\d+)/,ET=/^([A-Z][A-Z0-9]{0,9}-\d+)$/;function _T(t){let e=String(t||"").trim(),n=e.match(AT);if(n)return n[1].toUpperCase();let r=e.match(ET);return r?r[1]:null}function IT(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 RT(t,e){let r=(await we(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 $T(t,e,{url:n,title:r,subtitle:o}){let s=await we(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 OT(t,e,n){let r=await we(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 Wc(t){let e=await we(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 CT(t,{filename:e,contentType:n,buffer:r}){let o=await we(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 cT(t,e){let n=await ge(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 ge(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 kr(t){return((await ge(t,"query { teams(first: 100) { nodes { id key name } } }")).teams?.nodes??[]).map(n=>({id:n.id,key:n.key,name:n.name}))}var km=255;async function Lc(t,{teamId:e,name:n,description:r}){let o=r||null,s=!!o&&o.length>km,a=s?`${o.slice(0,km-1).trimEnd()}\u2026`:o,i=await ge(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 Fc(t,{teamId:e,title:n,description:r,projectId:o,priority:s}){let a=await ge(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 _m={linear:{api:Uc,config:Nn},jira:{api:Dc,config:ga}};async function Vc(){let t=[];for(let[e,{api:n,config:r}]of Object.entries(_m)){let o=await r();o&&t.push({name:e,api:n,cfg:o})}return t}async function Dn(t){let e=_m[t?.provider];if(!e)return null;let n=await e.config();return n?{name:t.provider,api:e.api,cfg:n}:null}function Jc(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 Im=()=>process.env.BOSTRAT_STATE_DIR||Am.join(process.env.HOME||uT.tmpdir(),".bostrat"),Kc=()=>Am.join(Im(),"tickets.json"),Rm=()=>(process.env.BOSTRAT_PUBLIC_URL||"http://bostrat.globopi").replace(/\/$/,""),ba=t=>`${Rm()}/track/${t}`,rn=null,Hc=null,Wc=Promise.resolve();async function Bo(){return rn||(Hc??=No.readFile(Kc(),"utf8").then(t=>({tickets:JSON.parse(t)?.tickets??{},existed:!0})).catch(()=>({tickets:{},existed:!1})).then(async({tickets:t,existed:e})=>{if(rn=t,!e&&Tt()){let{records:n,source:r}=await Zt("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;rn[s]=i}Object.keys(rn).length&&await Gc().catch(()=>{})}}return rn}).finally(()=>Hc=null),Hc)}async function Gc(){let t=JSON.stringify({tickets:rn},null,2),e=`${Kc()}.${process.pid}.tmp`;await No.mkdir(Im(),{recursive:!0}),await No.writeFile(e,t),await No.rename(e,Kc())}async function on(t=null){if(Wc=Wc.then(Gc,Gc),await Wc.catch(()=>{}),!t)return;let e=rn?.[t];e?de("tickets",{id:t,updatedAt:Date.now(),...e},{trackId:t}).catch(()=>{}):Yt("tickets",t).catch(()=>{})}async function ye(t){return(await Bo())[t]??null}async function ka(){return{...await Bo()}}async function $m(){return(await Vc()).length>0}async function xr(t,e,{source:n="api"}={}){let r=await Vc();if(!r.length)throw new Error("no ticketing integration configured \u2014 add a linear or jira entry to ~/.bostrat/integrations.json");let o=null,s=null,a=null;for(let u of r){let f=u.api.parseIssueRef(e);if(f)try{s=await u.api.resolveIssue(u.cfg,f),o=u;break}catch(d){a=d}}if(!o)throw a||new Error(`"${e}" is not a Linear or Jira issue URL or key (ABC-123)`);let i=(t.boxes||[]).find(u=>u.name===t.leadBox),c={provider:o.name,key:s.key,issueId:s.issueId,url:s.url,title:s.title,state:s.state,source:n,linkedAt:Date.now(),track:{name:t.name,branch:t.workspace?.branch??null,leadAgent:i?.agent??null,cwd:t.cwd??null,...t.workspace?.kind==="multi"?{repos:(t.workspace.repos??[]).map(u=>u?.name).filter(Boolean)}:{}},pr:null};(await Bo())[t.id]=c,await on(t.id),await $({id:t.id,cwd:t.cwd},{type:"ticket.linked",actor:"user",source:"orchestrator",payload:{ts:Date.now(),provider:c.provider,key:c.key,url:c.url,title:c.title,linkSource:n}}).catch(()=>{}),await Po(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 Cm(t.id,c,"linked"),c}async function Xc(t){let e=await Bo();return e[t]?(delete e[t],await on(t),!0):!1}var zc=t=>`$${t>=100?Math.round(t):t.toFixed(2)}`;async function Om(t,e){if(!t.track?.cwd)return null;let n=ue(await L({id:e,cwd:t.track.cwd}));return!n.totalUsd||n.totalUsd<.01?null:n}function dT(t){let e=Object.entries(t.byProvider).sort((r,o)=>o[1].usd-r[1].usd).map(([r,o])=>`${r} ${zc(o.usd)}`).join(" \xB7 "),n=Object.keys(t.byBox).length;return`${zc(t.totalUsd)}${e?` (${e})`:""}${n>1?` across ${n} boxes`:""}`}function fT(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(zc(e.totalUsd)),n.join(" \xB7 ")||"linked"}async function Po(t,e){let n=await Dn(e);if(!n)return;let r=await Om(e,t).catch(()=>null);await n.api.upsertCard(n.cfg,e,{url:ba(t),title:`Bostrat Track: ${e.track.name}`,subtitle:fT(e,r)})}function xa(t){let e=new URL(Rm());return`${e.protocol}//${t}.${e.host}/`}var pT={linked:"linked",opened:"prOpened",merged:"prMerged"};function mT(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 Mm(t,e,{from:n,to:r,mode:o,phase:s=null,actor:a="user"}){e.state=r,await on(t),await $({id:t,cwd:e.track?.cwd},{type:"ticket.state.changed",actor:a,source:"orchestrator",payload:{ts:Date.now(),key:e.key,url:e.url,from:n??null,to:r,mode:o,...s?{phase:s}:{}}}).catch(()=>{})}async function Cm(t,e,n){let r=await Dn(e);if(!r)return;let o=r.cfg.ticketFlow?.[pT[n]]??"prompt";if(o==="off")return;let{current:s,states:a}=await r.api.issueStates(r.cfg,e),i=Jc(s,a),c=mT(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 Mm(t,e,{from:l,to:c.name,mode:"auto",phase:n,actor:"system"});return}await $({id:t,cwd:e.track?.cwd},{type:"ticket.move.offered",actor:"system",source:"orchestrator",payload:{ts:Date.now(),offerId:Em(),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 Nm(t){let e=await ye(t);if(!e)throw new Error("no ticket linked to this Track");let n=await Dn(e);if(!n)throw new Error("no ticketing integration configured");let{current:r,states:o}=await n.api.issueStates(n.cfg,e),s=Jc(r,o);return s?.name&&e.state!==s.name&&(e.state=s.name,await on(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 Yc(t,e,{mode:n="manual"}={}){if(!e)throw new Error("stateId required");let r=await ye(t);if(!r)throw new Error("no ticket linked to this Track");let o=await Dn(r);if(!o)throw new Error("no ticketing integration configured");let{current:s,states:a}=await o.api.issueStates(o.cfg,r),i=Jc(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 on(t)),{key:r.key,from:l,to:c.name,moved:!1}):(await o.api.moveIssue(o.cfg,r,c.id),await Mm(t,r,{from:l,to:c.name,mode:n}),{key:r.key,from:l,to:c.name,moved:!0})}async function Pm(t,e){let n=await ye(t);if(!n)throw new Error("no ticket linked to this Track");let r=await Dn(n);if(!r)throw new Error("no ticketing integration configured");let o=String(e||"").trim();if(!o)throw new Error("comment body required");let s=await r.api.createComment(r.cfg,n,o);return await $({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 Bm(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 Yc(t.id,o.payload.to?.id,{mode:"offer"})),await $({id:t.id,cwd:t.cwd},{type:"ticket.move.answered",actor:"user",source:"orchestrator",payload:{ts:Date.now(),offerId:e,action:n?"moved":"dismiss",...s?{from:s.from,to:s.to}:{}}}),{offerId:e,action:n?"moved":"dismiss",...s??{}}}var xm=50;async function Dm(){let t=await Nn();if(!t)throw new Error("no ticketing integration configured");return kr(t)}function hT(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>xm)throw new Error(`too many issues (${n.length}); cap is ${xm} per proposal`);return{epic:r,issues:n,team:e.team?String(e.team).trim():null,linkCurrentTrack:e.linkCurrentTrack===!0}}async function wT(t,e,n,r){let o=r??await kr(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 ye(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 jm(t,e){let n=await Nn();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=hT(e),o=await kr(n),s=await wT(n,r,t,o),a=s?{id:s.id,key:s.key,name:s.name}:null,i=Em();return await $({id:t.id,cwd:t.cwd},{type:"ticket.create.offered",actor:"system",source:"orchestrator",payload:{ts:Date.now(),proposalId:i,provider:"linear",epic:r.epic,issues:r.issues,team:a,needsTeam:!s,linkCurrentTrack:r.linkCurrentTrack}}),{proposalId:i,epic:r.epic,issues:r.issues,team:a,needsTeam:!s}}async function Lm(t,e,n,{teamId:r=null}={}){if(!e)throw new Error("proposalId required");let o=await L({id:t.id,cwd:t.cwd}),s=o.find(h=>h.type==="ticket.create.offered"&&h.payload?.proposalId===e);if(!s)throw new Error("unknown proposal");if(o.some(h=>h.type==="ticket.create.answered"&&h.payload?.proposalId===e))throw new Error("proposal already answered");if(!n)return await $({id:t.id,cwd:t.cwd},{type:"ticket.create.answered",actor:"user",source:"orchestrator",payload:{ts:Date.now(),proposalId:e,action:"dismiss"}}),{proposalId:e,action:"dismiss"};let a=await Nn();if(!a)throw new Error("no ticketing integration configured");let i=s.payload,c=i.team??null;if(!c&&r){let h=(await kr(a)).find(w=>w.id===r);if(!h)throw new Error("unknown team");c={id:h.id,key:h.key,name:h.name}}if(!c)throw new Error("a team is required to create \u2014 pick one");let l=[],u=[],f=null;if(i.epic){let h=await Lc(a,{teamId:c.id,name:i.epic.name,description:i.epic.description});f=h.id,l.push({kind:"epic",title:h.name,url:h.url})}for(let h of i.issues||[])try{let w=await Fc(a,{teamId:c.id,title:h.title,description:h.description,projectId:f,priority:h.priority});l.push({kind:"issue",key:w.key,title:w.title,url:w.url})}catch(w){u.push({title:h.title,message:w?.message||String(w)})}await $({id:t.id,cwd:t.cwd},{type:"ticket.created",actor:"user",source:"orchestrator",payload:{ts:Date.now(),proposalId:e,team:{key:c.key,name:c.name},items:l,failed:u}}),await $({id:t.id,cwd:t.cwd},{type:"ticket.create.answered",actor:"user",source:"orchestrator",payload:{ts:Date.now(),proposalId:e,action:"created",createdCount:l.length,failedCount:u.length}});let d=l.filter(h=>h.kind==="issue"),m=null;return i.linkCurrentTrack&&d.length===1&&!await ye(t.id)&&(m=await xr(t,d[0].key,{source:"created"}).catch(()=>null)),{proposalId:e,action:"created",created:l,failed:u,linked:m?m.key:null}}async function Co(t,e){let n=await Dn(t);n&&await n.api.createComment(n.cfg,t,e)}async function gT(t,e,n){let r=await Dn(t);if(!r)return null;let o=await en(e);if(!o)return null;let s=Ie(o,n);if(!s)return null;let a=await No.readFile(Ut(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 Sm=()=>Number(process.env.BOSTRAT_TICKET_COST_SYNC_MS??12e4),qc=new Map;function yT(t,e){if(Sm()<=0)return Po(t,e);if(qc.has(t))return;let n=setTimeout(()=>{qc.delete(t),ye(t).then(r=>r?Po(t,r):null).catch(()=>{})},Sm());n.unref?.(),qc.set(t,n)}var vm=new Map;async function Tm(t,e,n){let r=vm.get(t)??0;Date.now()-r<600*1e3||(vm.set(t,Date.now()),await $({id:t,cwd:e},{type:"ticket.sync.failed",actor:"system",source:"orchestrator",payload:{ts:Date.now(),message:n?.message||String(n)}}).catch(()=>{}))}async function bT(t,{resolveTrack:e=null}={}){let n=await ye(t.trackId),r=t.payload||{};if(!n){if(t.type!=="turn.user"||t.actor!=="user")return;let o=null;for(let a of await Vc())if(o=a.api.findIssueUrl(r.text,a.cfg),o)break;if(!o)return;let s=e?await e(t.trackId):null;if(!s)return;try{await xr(s,o,{source:"chat"})}catch(a){await Tm(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 on(t.trackId))}try{switch(t.type){case"handoff.done":n.track.leadAgent=r.toAgent??n.track.leadAgent,await on(t.trackId),await Po(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 on(t.trackId),await Po(t.trackId,n),r.change==="opened"||r.change==="merged"){let o=r.change==="merged"?await Om(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 Co(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 Co(n,`**Bostrat** \u2014 live preview **${r.name}**: ${
|
|
90
|
-
_(runs on the Track's branch; reachable on the operator's private network only)_`);break}case"cost.recorded":await yT(t.trackId,n);break;default:break}}catch(o){let s=e?await e(t.trackId).catch(()=>null):null;await Tm(t.trackId,s?.cwd??null,o)}}function Fm({resolveTrack:t}){zt.on("event",e=>{bT(e,{resolveTrack:t}).catch(()=>{})})}async function Um(){if(!Tt())return{pushed:0,source:"unconfigured"};let{records:t,source:e}=await Zt("tickets");if(e!=="plane"||t.length)return{pushed:0,source:e};let n=await Bo(),r=0;for(let[o,s]of Object.entries(n))await de("tickets",{id:o,updatedAt:Date.now(),...s},{trackId:o}).catch(()=>{}),r++;return{pushed:r,source:"plane"}}import Sa from"node:fs/promises";import Hm from"node:path";import kT from"node:os";var Wm=process.env.BOSTRAT_STATE_DIR||Hm.join(process.env.HOME||kT.tmpdir(),".bostrat"),Qc=Hm.join(Wm,"pr-links.json"),ft=(t,e)=>`${t}#${Number(e)}`,va=null,Zc=null,D=Promise.resolve();async function kt(){return va||(Zc??=Sa.readFile(Qc,"utf8").then(t=>JSON.parse(t)?.links??{}).catch(()=>({})).then(t=>va=t).finally(()=>Zc=null),Zc)}async function Z(){let t=JSON.stringify({links:va},null,2),e=`${Qc}.${process.pid}.tmp`;await Sa.mkdir(Wm,{recursive:!0}),await Sa.writeFile(e,t),await Sa.rename(e,Qc)}var xT=new Set(["api","observed","reconciled"]);async function Ht({repo:t,number:e,trackId:n,url:r=null,headRef:o=null,title:s=null,source:a="matched"}){if(!t||!Number.isInteger(Number(e))||!n)return{link:null,created:!1};let i=await kt(),c=ft(t,e);return i[c]?{link:i[c],created:!1}:(i[c]={repo:t,number:Number(e),trackId:n,url:r,headRef:o,title:s,source:a,announced:xT.has(a),firstSeen:Date.now()},D=D.then(Z,Z),await D.catch(()=>{}),{link:i[c],created:!0})}async function Do(t,e){if(!t||e==null)return null;let n=(await kt())[ft(t,e)];return!n||n.announced?null:(n.announced=!0,D=D.then(Z,Z),await D.catch(()=>{}),n)}async function Rt(t,e){return!t||e==null?null:(await kt())[ft(t,e)]??null}async function sn(t,e,n){if(!t||e==null)return null;let r=(await kt())[ft(t,e)];return r?(r.state=n,r.resolvedAt=Date.now(),delete r.armedAt,delete r.autoMerge,delete r.archiveOnMerge,delete r.notifyOnMerge,delete r.notifyPending,D=D.then(Z,Z),await D.catch(()=>{}),r):null}async function qm(t,e,{headSha:n=null,trackId:r=null}={}){if(!t||e==null)return null;let o=(await kt())[ft(t,e)];return o?(o.ciFix={delegatedAt:Date.now(),headSha:n,trackId:r},D=D.then(Z,Z),await D.catch(()=>{}),o):null}async function Km(t,e,{headSha:n=null,trackId:r=null}={}){if(!t||e==null)return null;let o=(await kt())[ft(t,e)];return o?(o.reconcile={delegatedAt:Date.now(),headSha:n,trackId:r},D=D.then(Z,Z),await D.catch(()=>{}),o):null}async function Ta(t,e,{headSha:n=null}={}){if(!t||e==null)return null;let r=(await kt())[ft(t,e)];return!r||!r.ciFix?null:(r.ciFixAwait={since:Date.now(),trackId:r.ciFix.trackId??null,headSha:n},delete r.ciFix,D=D.then(Z,Z),await D.catch(()=>{}),r)}async function Aa(t,e){if(!t||e==null)return null;let n=(await kt())[ft(t,e)];return!n||!n.ciFixAwait?null:(delete n.ciFixAwait,D=D.then(Z,Z),await D.catch(()=>{}),n)}async function Ea(t,e,{headSha:n=null}={}){if(!t||e==null)return null;let r=(await kt())[ft(t,e)];return!r||!r.reconcile?null:(r.reconcileAwait={since:Date.now(),trackId:r.reconcile.trackId??null,headSha:n},delete r.reconcile,D=D.then(Z,Z),await D.catch(()=>{}),r)}async function _a(t,e){if(!t||e==null)return null;let n=(await kt())[ft(t,e)];return!n||!n.reconcileAwait?null:(delete n.reconcileAwait,D=D.then(Z,Z),await D.catch(()=>{}),n)}async function Gm(t,e,{at:n=Date.now()}={}){if(!t||e==null)return null;let r=(await kt())[ft(t,e)];return r?(r.baseFoldAt=n,D=D.then(Z,Z),await D.catch(()=>{}),r):null}async function zm(t,e){if(!t||e==null)return null;let n=(await kt())[ft(t,e)];return!n||!n.baseFoldAt?null:(delete n.baseFoldAt,D=D.then(Z,Z),await D.catch(()=>{}),n)}async function Vm(t,e,n){if(!t||e==null||!n)return null;let r=(await kt())[ft(t,e)];return r?(r.lastHeadSha=n,D=D.then(Z,Z),await D.catch(()=>{}),r):null}async function jo(t,e,{merge:n=null}={}){if(!t||e==null)return null;let r=(await kt())[ft(t,e)];return r?(r.armedAt=Date.now(),n&&(r.autoMerge={method:n.method||"merge",armedAt:r.armedAt}),D=D.then(Z,Z),await D.catch(()=>{}),r):null}async function Sr(t,e,n=!0){if(!t||e==null)return null;let r=(await kt())[ft(t,e)];return r?(n?r.archiveOnMerge=!0:delete r.archiveOnMerge,D=D.then(Z,Z),await D.catch(()=>{}),r):null}async function Ia(t,e,n=!0){if(!t||e==null)return null;let r=(await kt())[ft(t,e)];return r?(n?r.notifyOnMerge=!0:delete r.notifyOnMerge,D=D.then(Z,Z),await D.catch(()=>{}),r):null}async function Jm(t,e,n){if(!t||e==null)return null;let r=(await kt())[ft(t,e)];if(!r)return null;r.notifyOnMerge=!0;let o=new Map((r.notifyPending||[]).map(s=>[`${s.repo}#${s.number}`,s]));for(let s of n||[])s&&s.number!=null&&o.set(`${s.repo}#${s.number}`,s);return r.notifyPending=[...o.values()],D=D.then(Z,Z),await D.catch(()=>{}),r}async function Lo(t,e){if(!t||e==null)return null;let n=(await kt())[ft(t,e)];return!n||!n.armedAt&&!n.autoMerge?null:(delete n.armedAt,delete n.autoMerge,delete n.archiveOnMerge,delete n.notifyOnMerge,delete n.notifyPending,D=D.then(Z,Z),await D.catch(()=>{}),n)}var ST=new Set(["merged","closed","lost"]);async function Xm(){return Object.values(await kt()).filter(t=>!ST.has(t.state))}async function be(){return Object.values(await kt()).sort((t,e)=>e.firstSeen-t.firstSeen)}var Zm=t=>Math.round(t*100)/100;function vT(t){return!t||t.kind==="setup"?[]:Bt(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():Ym.basename(e.base)}))}function Qm(t){let e=new Map;for(let n of t||[])for(let r of vT(n)){let o=e.get(r.key);o||e.set(r.key,o={...r,tracks:[]}),o.tracks.push(n)}return e}function Ra(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 tl=(t,e)=>`${t} ${e}${t===1?"":"s"}`;function TT(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 el(t,e=[]){let n=[],r=t?.events||[],o=new Map,s=0;for(let u of e){if(u.type!=="pr.state.changed")continue;let f=u.payload||{};!f.repo||f.number==null||(f.change==="merged"?o.set(`${f.repo}#${f.number}`,f):f.change==="opened"&&s++)}if(o.size){let u=[...o.keys()].map(f=>{let[d,m]=f.split("#");return`${d.split("/").pop()}#${m}`});n.push(`Merged ${u.slice(0,4).join(", ")}${u.length>4?` +${u.length-4}`:""}`)}else s&&n.push(tl(s,"PR")+" opened");let a=Object.keys(t?.subtasks||{}).length||r.filter(u=>u.type==="subtask-delegated").length;a&&n.push(tl(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(`${tl(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)?TT(t.archivedAt-t.createdAt):null;return n.length&&l&&n.push(l),n.length?n.join(" \xB7 "):null}function AT(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 th(t,e,n){let r=new Set(t.tracks.map(d=>d.id)),o=0,s=0,a=0,i=null,c=null,l=0;for(let d of t.tracks){d.archivedAt?s++:o++,d.beat?.text&&a++;let m=Ra(d);m&&(!i||m>i)&&(i=m),d.createdAt&&(!c||d.createdAt<c)&&(c=d.createdAt),l+=e.get(d.id)||0}let u=0,f=0;for(let d of n)r.has(d.trackId)&&(t.slug&&d.repo!==t.slug||t.slug&&(d.state==="merged"?f++:d.state!=="closed"&&u++));return{key:t.key,slug:t.slug,base:t.base,name:t.name,tracks:{total:t.tracks.length,live:o,archived:s,withBeat:a},prs:{open:u,merged:f},usd:Zm(l),firstActivityAt:c,lastActivityAt:i}}async function eh(t){let e=Qm(t),n=await be().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,ue(c).totalUsd)}let s=[...e.values()].map(a=>th(a,r,n)).sort((a,i)=>(i.lastActivityAt??0)-(a.lastActivityAt??0));return{generatedAt:Date.now(),arcs:s}}async function nh(t,e){let r=Qm(t).get(e);if(!r)return null;let[o,s,a,i,c]=await Promise.all([be().catch(()=>[]),On().catch(()=>[]),da().catch(()=>[]),ma().catch(()=>[]),ka().catch(()=>({}))]),l=new Map,u=[],f=(d,m)=>(Ra(m)||m.createdAt||0)-(Ra(d)||d.createdAt||0);for(let d of[...r.tracks].sort(f)){let m=await L(d).catch(()=>[]),h=ue(m);l.set(d.id,h.totalUsd);let w=c[d.id]||null;u.push({trackId:d.id,name:d.name,createdAt:d.createdAt??null,archivedAt:d.archivedAt??null,live:!d.archivedAt,beat:d.beat??null,agents:AT(d),handoffs:(d.events||[]).filter(g=>g.type==="lead-handoff").length,subtasks:Object.keys(d.subtasks||{}).length,repos:Bt(d.workspace).map(g=>g.repo||Ym.basename(g.base||"")),prs:o.filter(g=>g.trackId===d.id&&(!r.slug||g.repo===r.slug)).map(g=>({repo:g.repo,number:g.number,state:g.state??null,title:g.title??null,url:g.url??null})),mocks:s.filter(g=>g.trackId===d.id).length,views:a.filter(g=>g.trackId===d.id).length,runbooks:i.filter(g=>g.trackId===d.id).map(g=>({name:g.name,status:g.status})),ticket:w?{provider:w.provider,key:w.key,url:w.url??null}:null,usd:Zm(h.totalUsd),lastActivityAt:Ra(d)})}return{...th(r,l,o),generatedAt:Date.now(),segments:u}}import{execFile as ET}from"node:child_process";import{promisify as _T}from"node:util";import{randomUUID as IT}from"node:crypto";var jn=_T(ET),$a=t=>new Promise(e=>setTimeout(e,t)),RT=Number(process.env.BOSTRAT_SWAP_EXIT_TIMEOUT_MS)||15e3,rh=Number(process.env.BOSTRAT_SWAP_SHELL_TIMEOUT_MS)||8e3,$T=250,OT=400,oh={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 ah(t){let e=await ef(t);if(!e)return!1;try{return await jn("pgrep",["-P",String(e)]),!1}catch(n){return n?.code===1}}async function nl(t,e){let n=Date.now()+e;for(;;){if(await ah(t))return!0;if(Date.now()>n)return!1;await $a($T)}}async function MT(t,e){let n=oh[e]||oh.default;n.escape&&(await xt(t,["Escape"],{kind:"swap-exit"}),await $a(150)),n.wipe&&await Fs(t,400,{kind:"swap-exit"}),await Us(t,n.text,{perCharDelayMs:90,kind:"swap-exit"});let r=_e(e).submitDelayMs||0;await $a(Math.max(r,250)),await xt(t,["Enter"],{kind:"swap-exit"})}async function CT(t,e){try{let{stdout:n}=await jn(N,B("show-environment","-t",t,e));return!String(n||"").trimStart().startsWith("-")}catch{return!1}}async function sh(t){await jn(N,B("respawn-pane","-k","-t",t))}async function rl(t,e={}){if(!yt(t))throw new Error("invalid session name");let n=String(e.agentCmd||"").trim();if(!n)throw new Error("agentCmd is required");let r=await Y(t);if(!r)throw new Error(`session "${t}" not found`);if(!r.cwd)throw new Error(`can't resolve cwd for "${t}"`);let o=r.agent||"agent",s=e.agent||ar(n),a=Number(e.exitTimeoutMs)||RT;await G({flow:"swap",phase:"start",sessionName:t,from:o,to:s,cwd:r.cwd});let i=!1;if(!await ah(t)&&(await MT(t,o),!await nl(t,a)&&(i=!0,await G({flow:"swap",phase:"respawn-forced",sessionName:t,from:o}),await sh(t),!await nl(t,rh))))throw new Error(`pane shell did not come back after respawn-pane in "${t}"`);let c=s==="claude"&&sr(n),l=c?Rs():null,u=await CT(t,"ANTHROPIC_API_KEY"),f=!1;try{l?(await jn(N,B("set-environment","-t",t,"ANTHROPIC_API_KEY",l)),f=!0):u&&(await jn(N,B("set-environment","-u","-t",t,"ANTHROPIC_API_KEY")),f=!0)}catch(g){throw l?Ds(g,l):g}if(f&&!i&&(await sh(t),!await nl(t,rh)))throw new Error(`pane shell did not come back after env respawn in "${t}"`);let d=!!e.resumeSessionId,m=e.resumeSessionId||IT(),h=d?n:Ji(n,m),w=d?e.pinned===!0:/(^|\s)--session-id(\s|=)/.test(h);return c&&await Ge(r.cwd),s==="codex"&&await tr(r.cwd),s==="cursor"&&await er(r.cwd),await In(t),await jn(N,B("set-option","-t",t,"@bostrat-sid",m)),await jn(N,B("set-option","-t",t,"@bostrat-agent",s)),await $a(OT),await vt(t,h,{kind:"swap-launch"}),await G({flow:"swap",phase:"done",sessionName:t,from:o,to:s,sid:m,forced:i,envRespawned:f}),{name:t,cwd:r.cwd,sessionId:m,agent:s,pinned:w,forced:i,envRespawned:f}}import fA from"node:fs/promises";function NT(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
|
|
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
|
|
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 MT(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 NT(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 Mm=255;async function qc(t,{teamId:e,name:n,description:r}){let o=r||null,s=!!o&&o.length>Mm,a=s?`${o.slice(0,Mm-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 Kc(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 Fm={linear:{api:Gc,config:Dn},jira:{api:Hc,config:ya}};async function Qc(){let t=[];for(let[e,{api:n,config:r}]of Object.entries(Fm)){let o=await r();o&&t.push({name:e,api:n,cfg:o})}return t}async function Fn(t){let e=Fm[t?.provider];if(!e)return null;let n=await e.config();return n?{name:t.provider,api:e.api,cfg:n}:null}function tl(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 Um=()=>process.env.BOSTRAT_STATE_DIR||jm.join(process.env.HOME||PT.tmpdir(),".bostrat"),Xc=()=>jm.join(Um(),"tickets.json"),Hm=()=>(process.env.BOSTRAT_PUBLIC_URL||"http://bostrat.globopi").replace(/\/$/,""),ka=t=>`${Hm()}/track/${t}`,on=null,Vc=null,zc=Promise.resolve();async function Po(){return on||(Vc??=Mo.readFile(Xc(),"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 Yc().catch(()=>{})}}return on}).finally(()=>Vc=null),Vc)}async function Yc(){let t=JSON.stringify({tickets:on},null,2),e=`${Xc()}.${process.pid}.tmp`;await Mo.mkdir(Um(),{recursive:!0}),await Mo.writeFile(e,t),await Mo.rename(e,Xc())}async function sn(t=null){if(zc=zc.then(Yc,Yc),await zc.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 xa(){return{...await Po()}}async function Wm(){return(await Qc()).length>0}async function vr(t,e,{source:n="api"}={}){let r=await Qc();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 Gm(t.id,c,"linked"),c}async function el(t){let e=await Po();return e[t]?(delete e[t],await sn(t),!0):!1}var Zc=t=>`$${t>=100?Math.round(t):t.toFixed(2)}`;async function qm(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 BT(t){let e=Object.entries(t.byProvider).sort((r,o)=>o[1].usd-r[1].usd).map(([r,o])=>`${r} ${Zc(o.usd)}`).join(" \xB7 "),n=Object.keys(t.byBox).length;return`${Zc(t.totalUsd)}${e?` (${e})`:""}${n>1?` across ${n} boxes`:""}`}function DT(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(Zc(e.totalUsd)),n.join(" \xB7 ")||"linked"}async function No(t,e){let n=await Fn(e);if(!n)return;let r=await qm(e,t).catch(()=>null);await n.api.upsertCard(n.cfg,e,{url:ka(t),title:`Bostrat Track: ${e.track.name}`,subtitle:DT(e,r)})}function Sa(t){let e=new URL(Hm());return`${e.protocol}//${t}.${e.host}/`}var jT={linked:"linked",opened:"prOpened",merged:"prMerged"};function LT(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 Km(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 Gm(t,e,n){let r=await Fn(e);if(!r)return;let o=r.cfg.ticketFlow?.[jT[n]]??"prompt";if(o==="off")return;let{current:s,states:a}=await r.api.issueStates(r.cfg,e),i=tl(s,a),c=LT(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 Km(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:Lm(),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 Vm(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=tl(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 nl(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=tl(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 Km(t,r,{from:l,to:c.name,mode:n}),{key:r.key,from:l,to:c.name,moved:!0})}async function zm(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 Jm(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 nl(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 Nm=50;async function Xm(){let t=await Dn();if(!t)throw new Error("no ticketing integration configured");return Sr(t)}function FT(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>Nm)throw new Error(`too many issues (${n.length}); cap is ${Nm} per proposal`);return{epic:r,issues:n,team:e.team?String(e.team).trim():null,linkCurrentTrack:e.linkCurrentTrack===!0}}async function UT(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 Ym(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=FT(e),o=await Sr(n),s=await UT(n,r,t,o),a=s?{id:s.id,key:s.key,name:s.name}:null,i=Lm();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 Zm(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 qc(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 Kc(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 HT(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 Pm=()=>Number(process.env.BOSTRAT_TICKET_COST_SYNC_MS??12e4),Jc=new Map;function WT(t,e){if(Pm()<=0)return No(t,e);if(Jc.has(t))return;let n=setTimeout(()=>{Jc.delete(t),ge(t).then(r=>r?No(t,r):null).catch(()=>{})},Pm());n.unref?.(),Jc.set(t,n)}var Bm=new Map;async function Dm(t,e,n){let r=Bm.get(t)??0;Date.now()-r<600*1e3||(Bm.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 qT(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 Qc())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 Dm(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 qm(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: ${BT(o)}.`:"")),await Gm(t.trackId,n,r.change)}break}case"mock.state.changed":{if(r.change==="ready"&&r.mockId){let o=await HT(n,r.mockId,r.version??null);if(!o)break;let s=(r.version??1)>1?` revised (v${r.version})`:"";await Co(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 Co(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 Co(n,`**Bostrat** \u2014 live preview **${r.name}**: ${Sa(r.slug)}
|
|
90
|
+
_(runs on the Track's branch; reachable on the operator's private network only)_`);break}case"cost.recorded":await WT(t.trackId,n);break;default:break}}catch(o){let s=e?await e(t.trackId).catch(()=>null):null;await Dm(t.trackId,s?.cwd??null,o)}}function Qm({resolveTrack:t}){Jt.on("event",e=>{qT(e,{resolveTrack:t}).catch(()=>{})})}async function th(){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 KT from"node:crypto";import va from"node:fs/promises";import eh from"node:path";import GT from"node:os";var nh=process.env.BOSTRAT_STATE_DIR||eh.join(process.env.HOME||GT.tmpdir(),".bostrat"),sl=eh.join(nh,"pr-links.json"),H=(t,e)=>`${t}#${Number(e)}`,rh=t=>{let e=t.replace(/[^A-Za-z0-9._-]+/g,"-").replace(/^[^A-Za-z0-9]+/,"").slice(0,55),n=KT.createHash("sha256").update(t).digest("hex").slice(0,8);return`${e||"pr"}-${n}`},an=null,rl=null,ol=Promise.resolve();async function gt(){return an||(rl??=va.readFile(sl,"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 al().catch(()=>{})}}return an}).finally(()=>rl=null),rl)}async function al(){let t=JSON.stringify({links:an},null,2),e=`${sl}.${process.pid}.tmp`;await va.mkdir(nh,{recursive:!0}),await va.writeFile(e,t),await va.rename(e,sl)}async function vt(t){ol=ol.then(al,al),await ol.catch(()=>{});let e=an?.[t];e&&Rt("prlinks",{id:rh(t),updatedAt:Date.now(),...e},{trackId:e.trackId??null}).catch(()=>{})}var VT=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:VT.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 oh(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 sh(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 Ta(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 Aa(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 Ea(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 _a(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 ah(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 ih(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 ch(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 Ia(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 lh(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 zT=new Set(["merged","closed","lost"]);async function uh(){return Object.values(await gt()).filter(t=>!zT.has(t.state))}async function ye(){return Object.values(await gt()).sort((t,e)=>e.firstSeen-t.firstSeen)}async function dh(){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:rh(o),updatedAt:Date.now(),...s},{trackId:s.trackId??null}).catch(()=>{}),r++;return{pushed:r,source:"plane"}}var ph=t=>Math.round(t*100)/100;function JT(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():fh.basename(e.base)}))}function mh(t){let e=new Map;for(let n of t||[])for(let r of JT(n)){let o=e.get(r.key);o||e.set(r.key,o={...r,tracks:[]}),o.tracks.push(n)}return e}function Ra(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 il=(t,e)=>`${t} ${e}${t===1?"":"s"}`;function XT(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 cl(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(il(s,"PR")+" opened");let a=Object.keys(t?.subtasks||{}).length||r.filter(u=>u.type==="subtask-delegated").length;a&&n.push(il(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(`${il(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)?XT(t.archivedAt-t.createdAt):null;return n.length&&l&&n.push(l),n.length?n.join(" \xB7 "):null}function YT(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 hh(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=Ra(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:ph(l),firstActivityAt:c,lastActivityAt:i}}async function wh(t){let e=mh(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=>hh(a,r,n)).sort((a,i)=>(i.lastActivityAt??0)-(a.lastActivityAt??0));return{generatedAt:Date.now(),arcs:s}}async function gh(t,e){let r=mh(t).get(e);if(!r)return null;let[o,s,a,i,c]=await Promise.all([ye().catch(()=>[]),Nn().catch(()=>[]),fa().catch(()=>[]),ha().catch(()=>[]),xa().catch(()=>({}))]),l=new Map,u=[],f=(d,p)=>(Ra(p)||p.createdAt||0)-(Ra(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:YT(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||fh.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:ph(m.totalUsd),lastActivityAt:Ra(d)})}return{...hh(r,l,o),generatedAt:Date.now(),segments:u}}import{execFile as ZT}from"node:child_process";import{promisify as QT}from"node:util";import{randomUUID as tA}from"node:crypto";var Ar=QT(ZT),$a=t=>new Promise(e=>setTimeout(e,t)),eA=Number(process.env.BOSTRAT_SWAP_EXIT_TIMEOUT_MS)||15e3,yh=Number(process.env.BOSTRAT_SWAP_SHELL_TIMEOUT_MS)||8e3,nA=250,rA=400,bh={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 ll(t,e){let n=Date.now()+e;for(;;){if(await ur(t))return!0;if(Date.now()>n)return!1;await $a(nA)}}async function oA(t,e){let n=bh[e]||bh.default;n.escape&&(await bt(t,["Escape"],{kind:"swap-exit"}),await $a(150)),n.wipe&&await Ws(t,400,{kind:"swap-exit"}),await qs(t,n.text,{perCharDelayMs:90,kind:"swap-exit"});let r=_e(e).submitDelayMs||0;await $a(Math.max(r,250)),await bt(t,["Enter"],{kind:"swap-exit"})}async function sA(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 kh(t){await Ar(N,P("respawn-pane","-k","-t",t))}async function ul(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)||eA;await U({flow:"swap",phase:"start",sessionName:t,from:o,to:s,cwd:r.cwd});let i=!1;if(!await ur(t)&&(await oA(t,o),!await ll(t,a)&&(i=!0,await U({flow:"swap",phase:"respawn-forced",sessionName:t,from:o}),await kh(t),!await ll(t,yh))))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 sA(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 kh(t),!await ll(t,yh)))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:rc(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 $a(rA),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 jA from"node:fs/promises";function aA(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 Lo(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 Fo(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 _r=(t,e)=>t.length>e?`${t.slice(0,e).trimEnd()}\u2026`:t,Et=t=>t&&!/[$`]/.test(t)?t:null,mt=t=>t.replace(/\/+$/,"").split("/").pop()||t,Er=t=>{let e=t.replace(/\/+$/,"").split("/").filter(Boolean);return e.length>3?e.slice(-2).join("/"):t},vh=t=>t.startsWith("-"),xh=new Set(["-g","--glob","-t","--type","-A","-B","-C","-m","--max-count","-e","--regexp","-f","--file"]);function at(t,e=new Set){let n=[];for(let r=1;r<t.length;r++){let o=t[r];if(o==="<<")break;if(vh(o)){e.has(o)&&r++;continue}n.push(o)}return n}var iA={status:()=>"Check git status",diff:()=>"Review changes",log:()=>"Review commit history",show:()=>"Inspect a commit",add:()=>"Stage files",commit:()=>"Commit changes",push:()=>"Push to remote",pull:()=>"Pull from remote",fetch:()=>"Fetch from remote",merge:()=>"Merge branches",rebase:()=>"Rebase branch",stash:()=>"Stash changes",clone:t=>t[1]?`Clone ${mt(t[1])}`:"Clone repository",checkout:()=>"Switch branches",switch:()=>"Switch branches",restore:()=>"Restore files",reset:()=>"Reset git state",branch:(t,e)=>e.includes("--show-current")?"Check current branch":"List branches",remote:()=>"Check git remotes","rev-parse":()=>"Resolve a git ref",worktree:()=>"Manage git worktrees","merge-base":()=>"Find the merge base","ls-files":()=>"List tracked files"},cA={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 Th(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 lA(t){let e=Th(t),n=Et(at(t).find(r=>!/^\d+(,\d+)?p$/.test(r)));return e&&n?e.from===e.to?`Read line ${e.from} of ${mt(n)}`:`Read lines ${e.from}\u2013${e.to} of ${mt(n)}`:t.includes("-i")&&n?`Edit ${mt(n)}`:null}function dl(t){if(t.includes("--files")){let s=Et(at(t,xh)[0]);return s&&s!=="."?`List files in ${Er(s)}`:"List files"}let e=at(t,xh),n=e[0];if(!n)return null;let r=Et(e.slice(1).find(s=>s!==".")),o=_r(n,40);return r?`Search for \u201C${o}\u201D in ${Er(r)}`:`Search for \u201C${o}\u201D`}function fl(t){let e=at(t,new Set(["--prefix"])),n=e[0];return n?n==="test"||n==="t"?"Run tests":n==="run"||n==="run-script"?e[1]?`Run ${e[1]}`:null:n==="ci"||n==="install"||n==="i"||n==="add"?"Install dependencies":null:null}function pl(t,e){if(t.includes("-e")||t.includes("--eval")||t.includes("-c")||t[1]==="-"||t.includes("<<"))return`Run an inline ${e} script`;if(t.includes("--test"))return"Run tests";let n=at(t)[0];return n?`Run ${mt(n)}`:null}function uA(t){let e=at(t),n=e[0],r=e.find(a=>/\.(service|timer|socket|target)$/.test(a))||e[1],o=r?r.replace(/\.service$/,""):"a service";return{status:`Check ${o} status`,show:`Inspect ${o}`,restart:`Restart ${o}`,start:`Start ${o}`,stop:`Stop ${o}`,"daemon-reload":"Reload systemd"}[n]||null}function Sh(t){return t.find(e=>/^https?:\/\//.test(e))||null}var dA={git:t=>{let e=at(t,new Set(["-C","-c"])),n=iA[e[0]];return n?n(e,t):null},gh:t=>{let e=at(t),n=cA[e[0]];return n?n(e):null},sed:lA,rg:dl,grep:dl,egrep:dl,nl:t=>{let e=Et(at(t)[0]);return e?`Read ${mt(e)}`:null},cat:t=>{let e=Et(at(t)[0]);return e?`Read ${mt(e)}`:null},head:t=>{let e=Et(at(t,new Set(["-n","-c"])).find(n=>!/^\d+$/.test(n)));return e?`Read the start of ${mt(e)}`:null},tail:t=>{let e=Et(at(t,new Set(["-n","-c"])).find(n=>!/^\d+$/.test(n)));return e?`Read the end of ${mt(e)}`:null},wc:t=>{let e=Et(at(t)[0]);return e?`Count lines in ${mt(e)}`:"Count input lines"},ls:t=>{let e=Et(at(t)[0]);return e&&e!=="."?`List files in ${Er(e)}`:"List files"},find:t=>Et(t[1])&&!vh(t[1])&&t[1]!=="."?`Find files in ${Er(t[1])}`:"Find files",tree:()=>"List the directory tree",mkdir:t=>{let e=Et(at(t)[0]);return e?`Create directory ${Er(e)}`:null},touch:t=>{let e=Et(at(t)[0]);return e?`Create ${mt(e)}`:null},rm:t=>{let e=Et(at(t)[0]);return e?`Delete ${Er(e)}`:null},mv:t=>{let e=Et(at(t)[0]);return e?`Move ${mt(e)}`:null},cp:t=>{let e=Et(at(t)[0]);return e?`Copy ${mt(e)}`:null},chmod:t=>{let e=Et(at(t)[1]);return e?`Change permissions on ${mt(e)}`:null},diff:t=>{let e=at(t);return e.length>=2?`Compare ${mt(e[0])} and ${mt(e[1])}`:null},curl:t=>{let e=Sh(t);return e?`Fetch ${_r(e,56)}`:"Fetch a URL"},wget:t=>{let e=Sh(t);return e?`Fetch ${_r(e,56)}`:"Fetch a URL"},npm:fl,pnpm:fl,yarn:fl,npx:t=>{let e=at(t)[0];return e?`Run ${e}`:null},node:t=>pl(t,"Node"),python:t=>pl(t,"Python"),python3:t=>pl(t,"Python"),make:t=>{let e=at(t)[0];return e?`Run make ${e}`:"Run make"},systemctl:uA,journalctl:t=>{let e=t.findIndex(r=>r==="-u"||r==="--unit"||r==="--user-unit"),n=e!==-1?t[e+1]:null;return n?`Read ${n.replace(/\.service$/,"")} logs`:"Read system logs"},tmux:t=>{let e=at(t,new Set(["-S","-L","-t","-f"]))[0];return{ls:"List tmux sessions","list-sessions":"List tmux sessions","kill-session":"Kill a tmux session","send-keys":"Send keys to a tmux session","capture-pane":"Capture a tmux pane","new-session":"Start a tmux session"}[e]||null},ps:()=>"List processes",which:t=>{let e=at(t)[0];return e?`Locate ${e}`:null},command:t=>t[1]==="-v"&&t[2]?`Locate ${t[2]}`:null,fly:t=>{let e=at(t)[0];return e==="deploy"?"Deploy to Fly":e?`Run fly ${e}`:null}},fA=new Set(["pwd","echo","cd","sleep","true","false","test","[","set","export","exit","read"]);function pA(t){let e=Fo(Lo(t));if(!e.length)return null;if(/^(ba|z)?sh$/.test(mt(e[0]))&&(e[1]==="-lc"||e[1]==="-c")&&e[2])return ml(e[2]);let n=mt(e[0]);if(fA.has(n))return null;if(e.includes("--version"))return`Check ${n} version`;if(n.endsWith(".sh"))return`Run ${n}`;let r=dA[n];return r?r(e.map((o,s)=>s===0?n:o)):null}function ml(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=aA(e);for(let i=0;i<r.length-1;i++){let c=Fo(Lo(r[i])),l=Fo(Lo(r[i+1]));if(!(!c.length||!l.length)&&["nl","cat"].includes(mt(c[0]))&&mt(l[0])==="sed"){let u=Th(l),f=at(c)[0];if(u&&f)return _r(u.from===u.to?`Read line ${u.from} of ${mt(f)}`:`Read lines ${u.from}\u2013${u.to} of ${mt(f)}`,88)}}let o=[];for(let i of r){let c=pA(i);c&&o.push({d:c,head:mt(Fo(Lo(i))[0]||""),sub:at(Fo(Lo(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":_r(s?s.d:o[0].d,88)}var wl=new Set(["Bash"]);function Ir(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 ln(t,e=""){let n=e.trim();return n||ml(t)||Ir(t)}function hl(t,e){if(t){if(Array.isArray(t)){for(let n of t)hl(n,e);return}if(typeof t=="object"){if(t.type==="tool_use"&&wl.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))hl(n,e)}}}async function Ah(t,{maxBytes:e=1048576,limit:n=400}={}){let r;try{r=await Nt(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(hl(c.content,l),!l.length)continue;let u=i.timestamp?Date.parse(i.timestamp):null;for(let f of l)o.push({...f,summary:ln(f.command,f.description),ts:u})}return o.slice(-n)}function Eh(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,g)=>g>=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 mA=/^Async (?:agent|task) launched/i,hA=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 _h(t,{maxBytes:e=2097152,limit:n=100}={}){let r;try{r=await Nt(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?Ir(p):null),detail:p?Ir(p):null,ts:f,proven:!0})}}else d.type==="tool_result"&&d.tool_use_id&&mA.test(hA(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 Uo from"node:fs";import gA from"node:fs/promises";import Un from"node:path";import yA from"node:os";import bA from"node:crypto";import kA from"node:zlib";var Oa=new Set(["Shell","Read","Grep","Glob","StrReplace","Write","ReadLints","Delete","ApplyPatch","Await"]),ne=(t,e=88)=>t.length>e?`${t.slice(0,e).trimEnd()}\u2026`:t,Rr=t=>String(t).replace(/\/+$/,"").split("/").pop()||String(t);function un(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 gl=t=>`"${String(t).replace(/"/g,'\\"')}"`;function wA(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 Ih(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:ne(`read ${un(r,n)}${o?` (lines ${o}\u2013${s??"end"})`:""}`,140),description:o?`Read lines ${o}\u2013${s??"end"} of ${Rr(r)}`:`Read ${Rr(r)}`}}case"Grep":{let r=e?.pattern;if(!r)return null;let o=e?.path?un(e.path,n):null,s=e?.glob?` --glob ${gl(e.glob)}`:"",a=ne(String(r),40);return{command:ne(`grep ${gl(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?un(e.target_directory,n):null;return{command:ne(`glob ${gl(r)}${o&&o!=="."?` ${o}`:""}`,140),description:`Find files matching \u201C${ne(String(r),40)}\u201D`}}case"StrReplace":return e?.path?{command:ne(`edit ${un(e.path,n)}`,140),description:`Edit ${Rr(e.path)}`}:null;case"Write":return e?.path?{command:ne(`write ${un(e.path,n)}`,140),description:`Write ${Rr(e.path)}`}:null;case"Delete":return e?.path?{command:ne(`delete ${un(e.path,n)}`,140),description:`Delete ${Rr(e.path)}`}:null;case"ReadLints":{let o=(Array.isArray(e?.paths)?e.paths:[]).map(s=>un(s,n)).filter(Boolean);return{command:ne(o.length?`lints ${o.join(" ")}`:"lints",140),description:"Check lints"}}case"ApplyPatch":{let r=wA(e);if(!r.length)return null;let o=r.length>1?` (+${r.length-1} more)`:"";return{command:ne(`apply_patch ${un(r[0],n)}${o}`,140),description:r.length>1?`Patch ${r.length} files`:`Patch ${Rr(r[0])}`}}case"Await":{let r=e?.shell_id??e?.task_id;return{command:ne(`await ${r??"background output"}`,140),description:"Wait for a background command"}}default:return null}}var xA=process.env.BOSTRAT_CURSOR_CHATS||Un.join(yA.homedir(),".cursor","chats");function Oh(t){let e=bA.createHash("md5").update(String(t)).digest("hex");return Un.join(xA,e)}function Ch(t){try{let e=JSON.parse(Uo.readFileSync(Un.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 SA(t){let e=Oh(t),n;try{n=Uo.readdirSync(e,{withFileTypes:!0}).filter(o=>o.isDirectory())}catch{return null}let r=null;for(let o of n){let s=Un.join(e,o.name),a=Un.join(s,"store.db");if(!Uo.existsSync(a))continue;let i=Ch(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 Ca(t,e){if(!t)return null;if(e){let n=Un.join(Oh(t),e),r=Un.join(n,"store.db");if(Uo.existsSync(r))return{id:e,dir:n,store:r,...Ch(n)||{createdAtMs:null,updatedAtMs:null}}}return SA(t)}function Rh(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=kA.gunzipSync(e)}catch{}try{return JSON.parse(e.toString("utf8"))}catch{return null}}function TA(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 AA(t,e){if(!Array.isArray(t))return[];let n=[];for(let r of t){if(!r||r.type!=="tool-call"||!Oa.has(r.toolName))continue;let o=Ih(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 EA=t=>typeof t=="string"?t.trim():Array.isArray(t)?t.filter(e=>e&&e.type==="text"&&typeof e.text=="string").map(e=>e.text).join(`
|
|
96
|
+
`).trim():"",_A=t=>Array.isArray(t)?t.filter(e=>e&&e.type==="tool-call"&&e.toolName).map(e=>e.toolName):[],$h=process.getBuiltinModule?.("node:sqlite"),$r={turns:[],trailingCommands:[],model:null,requestIds:[]};async function yl(t,e){if(!$h)return $r;let{DatabaseSync:n}=$h,r;try{r=new n(t,{readOnly:!0})}catch{return $r}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 $r;let s;try{s=JSON.parse(Buffer.from(String(o.value),"hex").toString("utf8"))}catch{return $r}let a=s.lastUsedModel||null,i=s.latestRootBlobId?Rh(r,s.latestRootBlobId):null;if(!i)return{...$r,model:a};let c=[],l=[],u=[],f=null,d=[];for(let p of TA(i)){let m=vA(Rh(r,p));if(!m)continue;let g=m.providerOptions?.cursor;if(g?.requestId&&d.push(g.requestId),g?.modelProviderMessageId&&d.push(g.modelProviderMessageId),m.role!=="assistant")continue;if(Array.isArray(m.content))for(let S of m.content){let E=S?.providerOptions?.cursor?.modelName;E&&(f=E)}l.push(...AA(m.content,e).map((S,E)=>({...S,id:S.id||`${p}:${E}`}))),u.push(..._A(m.content));let y=EA(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 $r}finally{try{r.close()}catch{}}}function Mh(t,e){let n=t.updatedAtMs;if(!n)try{n=Math.round(Uo.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 Nh(t,e,{limit:n=80}={}){let r=Ca(t,e);if(!r)return[];let{turns:o,model:s}=await yl(r.store,t);if(!o.length)return[];let a=o.slice(-n),{tsAt:i}=Mh(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 Ph(t,e){let n=Ca(t,e);if(!n)return null;let{turns:r,requestIds:o,model:s}=await yl(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 Bh(t,e,{limit:n=80}={}){let r=Ca(t,e);if(!r)return[];let{turns:o,trailingCommands:s}=await yl(r.store,t);if(!o.length&&!s.length)return[];let a=o.slice(-n),{end:i,tsAt:c}=Mh(r,a.length||1),l=[];a.forEach((u,f)=>{for(let d of u.commands)l.push({...d,summary:ln(d.command,d.description),ts:c(f)})});for(let u of s)l.push({...u,summary:ln(u.command,u.description),ts:i+1});return l}async function Dh(t,e){let n=Ca(t,e);if(!n)return null;if(n.updatedAtMs)return n.updatedAtMs;try{return Math.round((await gA.stat(n.store)).mtimeMs)}catch{return null}}import jh from"node:fs";import kl from"node:fs/promises";import bl from"node:path";import IA from"node:os";var RA=process.env.BOSTRAT_CODEX_SESSIONS||bl.join(process.env.CODEX_HOME||bl.join(IA.homedir(),".codex"),"sessions");async function Lh(t){try{return await kl.realpath(t)}catch{return t}}function $A(t=RA){let e=[],n=r=>{let o;try{o=jh.readdirSync(r,{withFileTypes:!0})}catch{return}for(let s of o){let a=bl.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:jh.statSync(a).mtimeMs})}catch{}}}};return n(t),e.sort((r,o)=>o.mtimeMs-r.mtimeMs)}async function OA(t,e=1048576){let n;try{n=await kl.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 CA(t){let e=await OA(t);if(!e)return null;let n;try{n=JSON.parse(e)}catch{return null}return n?.type==="session_meta"&&n?.payload?.cwd||null}async function Hn(t,e){if(!t&&!e)return null;let n=$A();if(e){let o=n.find(s=>s.id===e);return o||null}let r=await Lh(t);for(let o of n){let s=await CA(o.file);if(s&&(s===t||await Lh(s)===r))return o}return null}var MA=new Set(["output_text","text"]),NA=t=>typeof t=="string"?t.trim():Array.isArray(t)?t.filter(e=>e&&typeof e.text=="string"&&MA.has(e.type)).map(e=>e.text).join(`
|
|
98
|
+
`).trim():"";async function Fh(t,e,{limit:n=80,maxBytes:r=524288}={}){let o=await Hn(t,e);if(!o)return[];let s;try{s=await Nt(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:Sl,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 g=m.payload;if(!g)continue;if((g.type==="function_call"||g.type==="custom_tool_call")&&g.name){u=!0,xl.has(g.name)||c.push(g.name);continue}if(g.type!=="message"||g.role!=="assistant")continue;u=!0;let y=NA(g.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 Ho=new Set(["exec_command","shell","exec"]),xl=new Set(["wait","write_stdin"]),Sl="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 PA(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 BA(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 DA(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=BA(i.slice(c.index+c[0].length).trimStart());l&&l.trim()&&o.push(l.trim())}),o}async function Uh(t,e,{limit:n=400,maxBytes:r=524288}={}){let o=await Hn(t,e);if(!o)return[];let s;try{s=await Nt(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"&&Ho.has(u.name)){let p;try{p=JSON.parse(u.arguments||"{}")}catch{continue}let m=PA(p);m&&(f=[m])}else u.type==="custom_tool_call"&&u.name==="exec"&&(f=DA(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:ln(p),ts:l.timestamp?Date.parse(l.timestamp):null})})}return a.slice(-n)}async function Hh(t,e){let n=await Hn(t,e);if(!n)return null;try{return Math.round((await kl.stat(n.file)).mtimeMs)}catch{return n.mtimeMs||null}}var Or=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}},vl=class extends Or{transcript(e){return!e?.cwd||!e?.sessionId?null:Ae(e.cwd,e.sessionId)}async readMessages(e,n){let r=this.transcript(e);return r?fc(r,n):[]}async readCommands(e,n){let r=this.transcript(e);return r?Ah(r,n):[]}async readJobs(e,n){let r=this.transcript(e);return r?_h(r,n):[]}async updatedAt(e){let n=this.transcript(e);if(!n)return null;try{return Math.round((await jA.stat(n)).mtimeMs)}catch{return null}}},Tl=class extends Or{async readMessages(e,n){return Nh(e.cwd,e.sessionId,n)}async readCommands(e,n){return Bh(e.cwd,e.sessionId,n)}async updatedAt(e){return Dh(e.cwd,e.sessionId)}},Al=class extends Or{sessionId(e){return e?.pinned?e.sessionId:null}async readMessages(e,n){return Fh(e.cwd,this.sessionId(e),n)}async readCommands(e,n){return Uh(e.cwd,this.sessionId(e),n)}async updatedAt(e){return Hh(e.cwd,this.sessionId(e))}},LA=new Or("unknown"),Wh=new Map;function El(t){return Wh.set(t.agent,t),t}function Wo(t){return Wh.get(t)||LA}El(new vl("claude",wl));El(new Tl("cursor",Oa));El(new Al("codex",Ho));var _l=t=>e=>({...e,box:t.name,agent:t.agent,boxRole:t.role});async function Il(t,e={}){return(await Wo(t.agent).readMessages(t,e)).map(_l(t))}async function qh(t,e={}){return(await Wo(t.agent).readCommands(t,e)).map(_l(t))}async function Kh(t,e={}){return(await Wo(t.agent).readJobs(t,e)).map(_l(t))}var zh=/to navigate\b.*\bEsc to cancel|Enter to select\b/,Ol=/Chat about this/i,Wn=/^\s*(❯)?\s*(\d+)\.\s+(?:\[([ xX✔])\]\s+)?(.+?)\s*$/,Gh=/^(type something|chat about this|submit)\b/i,qn=/^[─—-]{6,}\s*$/,Cl=/[\u2500-\u257F\u2550-\u256F┌┐└┘├┤│┬┴┼─✂]/;function Kn(t){let e=t.search(Cl);return(e>=0?t.slice(0,e):t).trim()}function FA(t){if(!t)return"";let e=t.replace(/[│┌└├─┘┤┬┴┼─✂].*$/,"").trim();return!e||/hidden|\d+\s+lines?/i.test(e)?"":e}function UA(t){let e=String(t).trim();return!!(!e||qn.test(t)||Cl.test(e)||/hidden|\d+\s+lines?/i.test(e)||/notespress|press n to add/i.test(e)||/^\s*notes?\s*$/i.test(e))}function HA(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=WA(n,e);return n.forEach((a,i)=>{a.current=i===s}),{tabs:n,questionIndex:s,questionTotal:n.length,currentTabLabel:n[s].label}}function WA(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 Vh(t){return t.replace(/[←→✔☐☒●▶]/g," ").replace(/\bSubmit\b/g," ").replace(/\s+/g," ").trim()}var Jh=/^\s*Switch model\?\s*$/m;function qA(t,e=""){let n=String(t);if(!Jh.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(Wn);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&&!qn.test(r[u])&&a.push(d)}}if(s.length<2)return null;let i=s.map(u=>({label:Kn(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 Xh=/^\s*Enter to confirm\s*·\s*Esc to cancel\s*$/,KA=/^\s*[─—▔-]{6,}\s*$/;function GA(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||!Xh.test(n[r]))return null;let o=r-1;for(;o>=0&&n[o].trim()==="";)o--;let s=[];for(;o>=0;o--){let c=n[o].match(Wn);if(!c)break;s.unshift({cursor:!!c[1],n:Number(c[2]),label:c[4].trim()})}if(s.length<2||s.some((c,l)=>c.n!==l+1))return null;let a=[];for(let c=o;c>=0&&a.length<6&&!(KA.test(n[c])||qn.test(n[c]));c--){let l=n[c].trim();l&&a.unshift(l)}let i=s.map(c=>({label:Kn(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 VA(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(Wn);l&&o.push({cursor:!!l[1],label:l[4].trim()})}let s=o.filter(c=>/^(submit answers|cancel)$/i.test(c.label));if(s.length<2)return null;let a=s.map(c=>({label:c.label,description:""})),i=Math.max(0,s.findIndex(c=>c.cursor));return{provider:"claude",box:e,multiSelect:!1,cursorIndex:i,review:!0,promptId:"claude:review:submit-answers",questions:[{question:"Ready to submit your answers?",header:"Review",multiSelect:!1,options:a}]}}var Rl=/Resume from summary/i,zA=/^\s*This session is\b/;function JA(t,e=""){let n=String(t);if(!Rl.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
105
|
+
`),o=-1;for(let u=r.length-1;u>=0;u--)if(zA.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(Wn);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&&!qn.test(r[u])&&a.push(d)}}if(s.length<2||!s.some(u=>Rl.test(u.label)))return null;let i=s.map(u=>({label:Kn(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 Gn=/shift\+tab to cycle|⏵⏵ bypass permissions on|⏸ manual mode on/;function Ml(t){let e=[];for(let r=0;r<t.length;r++){let o=t[r].match(Wn);o&&e.push({line:r,cursor:!!o[1],n:Number(o[2]),label:o[4].trim()})}let n=e.sort((r,o)=>r.n-o.n);return n.length<2||n.some((r,o)=>r.n!==o+1)?null:n}var Yh=/Choose the text style that looks best with your terminal/,Zh=/Let's get started\./;function XA(t,e=""){let n=String(t);if(!Yh.test(n)||!Zh.test(n)||Gn.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
106
|
+
`),o=Ml(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:Kn(s.label),description:""}))}]}:null}var Qh=/Select login method:/;function YA(t,e=""){let n=String(t);if(!Qh.test(n)||Gn.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
107
|
+
`),o=Ml(r);if(!o)return null;let s=o.map(a=>{let[i,...c]=a.label.split("\xB7");return{label:Kn(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 Nl=/Paste code here if prompted/,ZA=/^[A-Za-z0-9%&=+._~#?/:\-]+$/;function QA(t,e){let n=t[e].trim();for(let r=e+1;r<t.length;r++){let o=t[r].trim();if(!o||!ZA.test(o)||Nl.test(t[r]))break;n+=o}return n}function tE(t,e=""){let n=String(t);if(!Nl.test(n)||Gn.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=QA(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 $l=/OAuth error:/,eE=/Press Enter to retry\./;function nE(t,e=""){let n=String(t);if(!$l.test(n)||!eE.test(n)||Gn.test(n))return null;let s=([...n.replace(/\r/g,"").split(`
|
|
109
|
+
`)].reverse().find(a=>$l.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 tw=/Quick safety check: Is this a project you created or one you trust/;function rE(t,e=""){let n=String(t);if(!tw.test(n)||Gn.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
110
|
+
`),o=Ml(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:Kn(s.label),description:""}))}]}:null}var ew=/^(.{0,60}?)Press\s+Enter\s+to\s+continue\s*[.…]{0,3}\s*$/;function oE(t,e=""){let n=String(t);if(Gn.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(ew);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(qn.test(r[l])||Cl.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 sE(t){let e=-1;for(let r=t.length-1;r>=0;r--)if(Ol.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 aE(t,e=""){let n=String(t);if(!zh.test(n)||!Ol.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
112
|
+
`),o=sE(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 C=r[v].match(Wn);C&&(u<0&&(u=v),l.push({line:v,cursor:!!C[1],checked:C[3]!=null&&C[3]!==" ",hasCheckbox:C[3]!=null,label:C[4].trim(),desc:""}))}if(!l.length)return null;for(let v of l)for(let C=v.line+1;C<Math.min(r.length,v.line+4)&&!(Wn.test(r[C])||qn.test(r[C]));C++){let q=r[C].trim();if(!(!q||UA(r[C])||Gh.test(q))){v.desc=FA(q);break}}let f=l.filter(v=>!Gh.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,g=[];for(let v=m;v<u;v++){let C=r[v].trim();C&&!qn.test(r[v])&&!/[☐☒]/.test(r[v])&&g.push(C)}let y=g.join(" ")||(s>=0?Vh(r[s]):"")||"The agent is asking a question",b=s>=0?HA(r[s],y):null,S=b?.currentTabLabel||(s>=0?Vh(r[s]):""),E=f.map(v=>({label:Kn(v.label),description:v.desc})),M=E.map(v=>v.label).join("|"),$=`claude:${b?`q${b.questionIndex+1}/${b.questionTotal}`:"q1/1"}:${S}:${y}:${M}`.slice(0,240),j=b?`claude:ask:${b.tabs.map(v=>v.label).join("|")}`.slice(0,240):$;return{provider:"claude",box:e,multiSelect:d,cursorIndex:p,promptId:$,groupId:j,...b?{questionIndex:b.questionIndex,questionTotal:b.questionTotal,currentTabLabel:b.currentTabLabel,tabs:b.tabs}:{},questions:[{question:y,header:S,multiSelect:d,options:E}]}}function Cr(t,e){let n=String(e||"");return n?t==="claude"?zh.test(n)&&Ol.test(n)||Jh.test(n)||Rl.test(n)||Xh.test(n.trimEnd().split(`
|
|
113
|
+
`).pop()||"")?!0:Gn.test(n)?!1:Yh.test(n)&&Zh.test(n)||Qh.test(n)||Nl.test(n)||$l.test(n)||tw.test(n)||ew.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 Vn(t,e){return!t||!e?null:t.agent==="claude"?VA(e,t.name)||aE(e,t.name)||qA(e,t.name)||JA(e,t.name)||nE(e,t.name)||tE(e,t.name)||YA(e,t.name)||XA(e,t.name)||rE(e,t.name)||oE(e,t.name)||GA(e,t.name):t.agent==="cursor"||t.agent==="codex"?qf(t.agent,e,t.name):null}async function Pl(t){if(!t)return null;let e=await Ze(t.name).catch(()=>"");return Vn(t,e)}function nw(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 rw(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 Mr(t){return typeof t=="string"?t.trim():""}var ow=/^\s*─{8,}\s*$/,iE=/^❯[ ]?(.*)$/;function qo(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(ow.test(n[c])){r=c;break}if(r<=0)return null;let o=-1;for(let c=r-1;c>=0;c--)if(ow.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(iE);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 qt(t){return String(t||"").replace(/\s+/g," ").trim()}function sw(t,e,{slackMs:n=2e4}={}){let r=qt(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&&qt(o.text).includes(r))}function aw(t,e,n,{busy:r=!1}={}){let o=qo(t,n);return o&&Bl(e,o)?"stuck":r||o?"submitted":"unknown"}function Bl(t,e){let n=qt(t),r=qt(e).replace(/^…\s*/,"").replace(/\s*…$/,"");return!n||!r||r.length<Math.min(n.length,12)?!1:n===r||n.includes(r)}import cE from"node:crypto";var Dl=new Set(["cursor","codex"]),cw=4,lE=4,uE=2,dE=24,iw=/^[ \t]{0,3}(`{3,})bostrat-question[ \t]*\r?\n([\s\S]*?)\r?\n[ \t]{0,3}\1`*[ \t]*$/gm,Ko=t=>typeof t=="string"?t.trim():"";function fE(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,cw)){let o=Ko(r?.question);if(!o)return null;let s=Array.isArray(r?.options)?r.options:[],a=[];for(let i of s.slice(0,lE)){let c=Ko(i?.label);if(!c)return null;let l=Ko(i?.description);a.push(l?{label:c,description:l}:{label:c})}if(a.length<uE)return null;n.push({question:o,header:Ko(r?.header).slice(0,dE),multiSelect:r?.multiSelect===!0,options:a})}return n.length?n:null}function jl(t){if(typeof t!="string"||!t.includes("```bostrat-question"))return null;iw.lastIndex=0;let e=null;for(let o;o=iw.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=fE(n);return r?{text:t.slice(0,e.index).trimEnd(),questions:r}:null}function pE(t,e,n){let r=cE.createHash("sha256").update(JSON.stringify(n)).digest("hex").slice(0,8);return`${t}:ask:${e||"turn"}:${r}`}function Ll(t,{leadBoxId:e,provider:n,isAnswered:r,maxQuestions:o=cw}={}){if(!Array.isArray(t)||!e||!Dl.has(n))return null;let s=null;for(let l=t.length-1;l>=0;l--){let u=t[l];if(u?.kind==="prompt")return null;if(u?.kind==="message"){if(u.role!=="assistant")return null;s=u;break}}if(!s||s.box!==e||s.agent!==n)return null;let a=s.ask?.questions||jl(s.text)?.questions;if(!a?.length)return null;let i=a.slice(0,Math.max(1,o)),c=pE(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 lw(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=Ko(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 uw=`
|
|
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,40 +135,44 @@ 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 uw(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&&!iw.has(o))throw new Error(`unknown codex effort "${o}" (low | medium | high | xhigh)`);return{model:r,effort:o||null}}if(t==="cursor"){let r=cw.find(o=>o.alias===n);return{model:n,filter:r?r.label:n}}if(!YA.has(n))throw new Error(`unknown model "${n}"`);return{model:n}}async function Vo(t,e,{timeoutMs:n=JA}={}){let r=Date.now()+n;for(;;){let o=await gt(t,{lines:80}).catch(()=>""),s=e(o);if(s)return s;if(Date.now()>r)return null;await Pa(XA)}}async function tE(t,e){return await vt(t,`/model ${e.model}`,_e("claude")),{verified:!1,note:"confirmed when the next turn's transcript model flips"}}async function eE(t,e){await Ls(t),await vt(t,"/model",{enter:!1}),await Pa(250),await xt(t,["Enter"],{kind:"model-switch"});let n=await Vo(t,rw);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=QA(i);return!c||c.model!==e.model||e.effort&&c.effort&&c.effort.toLowerCase()!==e.effort.toLowerCase()?null:c};await vt(t,String(r.n),{enter:!1,kind:"model-switch"});let s=await Vo(t,i=>ow(i)||!rw(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?iw.get(e.effort):null,c=i?s.find(l=>l.label.toLowerCase()===i.toLowerCase()):s.find(l=>l.isDefault)||s[0];if(!c)throw await xt(t,["Escape","Escape"],{kind:"model-switch"}).catch(()=>{}),new Error(`codex doesn't offer effort "${e.effort}" for ${e.model}`);await vt(t,String(c.n),{enter:!1,kind:"model-switch"})}let a=s.done||await Vo(t,i=>!ow(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 nE(){let t=nw.join(process.env.CURSOR_CONFIG_DIR||nw.join(VA.homedir(),".cursor"),"cli-config.json");try{return JSON.parse(await zA.readFile(t,"utf8"))?.model||null}catch{return null}}async function rE(t,e){await Ls(t),await Us(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 xt(t,["Down"],{kind:"model-switch"}),await Pa(250)}if(!n||!s||!o(s)){await xt(t,["Escape"],{kind:"model-switch"}).catch(()=>{});let c=Array(`/model ${e.filter}`.length).fill("BSpace");throw await xt(t,c,{settleMs:15,kind:"model-switch"}).catch(()=>{}),new Error(n?`cursor picker has no model matching "${e.filter}"`:"cursor model picker didn't open")}await xt(t,["Enter"],{kind:"model-switch"}),await Pa(800);let a=await nE();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 dw(t,e,n){return t==="codex"?eE(e,n):t==="cursor"?rE(e,n):tE(e,n)}var Wt=t=>t||null;function Da(t){let e=t||{},n=oE(e);return n&&(n.payload={ts:e.at,...n.payload}),n}function oE(t){switch(t.type){case"said":return{type:"turn.user",actor:"user",source:"orchestrator",boxId:t.box,provider:Wt(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:Wt(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:Wt(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:Wt(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:Wt(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:Wt(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:Wt(t.agent),payload:{...Mr(t)}};case"subtask-failed":return{type:"error",actor:"system",source:"orchestrator",boxId:t.box||t.to,provider:Wt(t.agent||t.toAgent),payload:{kind:"subtask-failed",...Mr(t)}};case"subtask-resumed":return{type:"box.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Wt(t.agent),payload:{change:"subtask-resumed",...Mr(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",...Mr(t)}};case"archived":case"unarchived":case"track-deleted":return{type:"box.state.changed",actor:"user",source:"orchestrator",payload:{change:t.type,...Mr(t)}};case"rehydrated":return{type:"box.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Wt(t.agent),payload:{change:"rehydrated"}};case"retagged":return{type:"box.state.changed",actor:"user",source:"orchestrator",payload:{change:"retagged",...Mr(t)}};case"rehydrate-failed":return{type:"error",actor:"system",source:"orchestrator",boxId:t.box,provider:Wt(t.agent),payload:{kind:"rehydrate-failed",reason:t.reason}};default:return null}}var Ba=Promise.resolve(),Wl=t=>(Ba=Ba.then(t).catch(()=>{}),Ba),pw=()=>Ba;function mw(t,e){let n=Da(e);if(!n)return;let r=e?.type==="track-deleted"?{id:t.id}:t;Wl(()=>$(r,n))}var fw=new Set;async function hw(t){if(!(!t?.id||fw.has(t.id))){fw.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=Da(r);!o||n.has(`${o.type}|${o.payload?.ts??"?"}`)||await $(t,o)}}catch{}}}function Mr(t){let{type:e,at:n,...r}=t;return r}var Hl=new Map;function ww(t,e,n){!n?.promptId||Hl.get(t.id)===n.promptId||(Hl.set(t.id,n.promptId),Wl(()=>$(t,{type:"prompt.raised",actor:"agent",source:"pane",boxId:e?.name,provider:Wt(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 ql(t,e,n,r={}){Hl.delete(t.id),Wl(()=>$(t,{type:"prompt.answered",actor:"user",source:"orchestrator",boxId:e?.name,provider:Wt(n?.provider||e?.agent),payload:{promptId:n?.promptId,...r}}))}import{randomUUID as BE}from"node:crypto";var yw=/(?<!\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,kw=/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,sE=new RegExp(`\\b(?:${yw}|${bw}|${kw}|${xw})\\b`,"i"),aE=/\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,iE=/^(?:commit|push|open|create|raise|rebase|ready|ok(?:ay)?\s+to|good\s+to)\b/i,cE=90;function lE(t){return String(t).split(/(?<=[.?!])\s+|\n+/).map(e=>e.trim()).filter(Boolean)}function Sw(t){let e=String(t||"").trim();if(!e)return null;let n=e.slice(-600);for(let r of lE(n).reverse()){if(!sE.test(r))continue;let o=r.endsWith("?")&&r.length<=cE&&iE.test(r);if(aE.test(r)||o)return r.length>200?r.slice(0,197)+"\u2026":r}return null}var vw=["commit","rebase","push","pr"],uE=new RegExp(`\\b(?:${yw})\\b`,"i"),dE=new RegExp(`\\b(?:${xw})\\b`,"i"),fE=new RegExp(`\\b(?:${bw})\\b`,"i"),pE=new RegExp(`\\b(?:${kw})\\b`,"i"),mE=/\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 Tw(t){let e=String(t||""),n=[];uE.test(e)&&n.push("commit"),dE.test(e)&&n.push("rebase"),fE.test(e)&&n.push("push");let r=mE.test(e);!r&&pE.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 ja(t,{branch:e=null,base:n=null,toPr:r=null,repos:o=null,offeredRebase:s=!1}={}){Array.isArray(t)||(t=t==="push"?["commit","push"]:["commit","push","pr"]);let a=new Set(t),i=Array.isArray(o)&&o.length>0,c=i?o.map(m=>`${m.name}/`).join(", "):null,l=e?` on this branch (${e})`:"",u=[];if(a.has("commit")&&u.push(i?`commit the work in each changed checkout (${c})${l} with a clear commit message per repo`:`commit the work${l} with a clear commit message`),a.has("rebase")){let m=a.has("commit")?"the branch":`this branch${e?` (${e})`:""}`;u.push(i?`rebase ${m} in each changed checkout (${c}) onto its own base branch (fetch origin first), resolving any conflicts so both sides' intent is preserved`:`rebase ${m} onto ${n?`the latest ${n}`:"its base branch"} (fetch origin first), resolving any conflicts so both sides' intent is preserved`)}if(a.has("push")||a.has("pr")){let m=r&&!a.has("pr")?` (the open PR${r.number?` #${r.number}`:""} picks it up)`:"",h=a.has("rebase")?" with `--force-with-lease` (the rebase rewrote history)":"";i?u.push(a.has("commit")||a.has("rebase")?`push each one to its remote${h}${m}`:`push this branch${e?` (${e})`:""} from each changed checkout (${c}) to its remote${m}`):u.push(a.has("commit")||a.has("rebase")?`push it to the remote${h}${m}`:`push this branch${l} to the remote${m}`)}a.has("pr")&&u.push(i?"open a pull request in each changed repo with a concise title and a description of what changed and why (run `gh pr create` inside each checkout)":`open a pull request${n?` against ${n}`:""} with a concise title and a description of what changed and why (use \`gh pr create\`)`);let f=[];a.has("commit")||f.push("don't create any new commits \u2014 ship what's already committed"),s&&!a.has("rebase")&&f.push("don't rebase \u2014 leave the branch's history as it is"),!a.has("push")&&!a.has("pr")&&f.push("don't push"),a.has("pr")||f.push("don't open a pull request");let d=`Yes \u2014 go ahead: ${u.join(", then ")}.`;return f.length&&(d+=` ${f.join(", and ").replace(/^./,m=>m.toUpperCase())}.`),a.has("pr")&&(d+=i?" If a repo's PR for this branch is already open, just push that repo's update instead.":" If a PR for this branch is already open, just push the update instead."),d}var hE=3e4;function Aw(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<=hE&&n.push({box:r.box,from:r.from,to:e+1e12}),n}function Ew(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 _w(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 kE}from"node:child_process";import{promisify as xE}from"node:util";import wE from"node:fs";import Kl from"node:fs/promises";import Ow from"node:path";import gE from"node:os";var Mw=process.env.BOSTRAT_STATE_DIR||Ow.join(process.env.HOME||gE.tmpdir(),".bostrat"),La=Ow.join(Mw,"github-metrics.json"),Cw=60*6e4,Iw=5*6e4,yE=6e4,Cr=Date.now(),je=new Map,ln=null,Gn=0,Rw=0,Fa=t=>[...t.values()].reduce((e,n)=>e+n,0);try{let t=JSON.parse(wE.readFileSync(La,"utf8"));t?.windowStart&&(Date.now()-t.windowStart<Cw?(Cr=t.windowStart,je=new Map(Object.entries(t.byCaller||{})),ln=t.lastHour||null):Fa(new Map(Object.entries(t.byCaller||{})))&&(ln={start:t.windowStart,total:Fa(new Map(Object.entries(t.byCaller||{}))),byCaller:t.byCaller||{}}))}catch{}var $w=Promise.resolve();function Nw(t,e=!1){if(!e&&t-Rw<yE)return;Rw=t;let n=JSON.stringify({windowStart:Cr,byCaller:Object.fromEntries(je),lastHour:ln});$w=$w.then(()=>Kl.mkdir(Mw,{recursive:!0})).then(()=>Kl.writeFile(`${La}.tmp`,n)).then(()=>Kl.rename(`${La}.tmp`,La)).catch(()=>{})}function Pw(t){t-Cr<Cw||(ln={start:Cr,total:Fa(je),byCaller:Object.fromEntries(je)},ln.total&&console.log(`[github] ${ln.total} calls last hour`,ln.byCaller),Cr=t,je=new Map,Nw(t,!0))}function Le(t="other",e=Date.now()){Pw(e),je.set(t,(je.get(t)||0)+1),Nw(e)}function Bw(t=Date.now()){return Pw(t),{windowStart:Cr,total:Fa(je),byCaller:Object.fromEntries(je),lastHour:ln,backoffUntil:Gn>t?Gn:null}}var bE=/rate limit|too quickly|abuse detection|HTTP 429/i;function un(t){return bE.test(String(t?.message||t||""))}function dn(t,e=Date.now()){return Gn<=e&&console.warn(`[github] rate limited \u2014 backing off ${Iw/6e4} min:`,String(t?.message||t||"").slice(0,200)),Gn=Math.max(Gn,e+Iw),Gn}function Ua(t=Date.now()){return Math.max(0,Gn-t)}async function Gl(t,e){let n={working:!1,prompted:!1};if(!t)return n;let r=await gt(t,{lines:At}).catch(()=>"");if(!Ze(e,r))return Ef(e,r)?{working:!0,prompted:!1}:n;if(Rr(e,r)){let o=await Xe(t).catch(()=>r);if(Kn({name:t,agent:e},o))return{working:!1,prompted:!0}}return{working:!0,prompted:!1}}async function Ha(t,e){return(await Gl(t,e)).working}var Dw=xE(kE),jw=[4e3,15e3,45e3],SE=2500,vE=5*6e4;async function zl(t,e,{midTurn:n=Ha,pollMs:r=SE,capMs:o=vE}={}){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 TE=6e4,AE=9e4,EE=2e3,_E=/(?:^|[;&|]\s*|\)\s*)gh\s+(?:[^\s;&|"'`$]+\s+)*?pr\s+create\b/,IE=/(?:^|[;&|]\s*|\)\s*)gh\s+(?:[^\s;&|"'`$]+\s+)*?pr\s+merge\b/,RE=/\s--web\b/,Lw=/(?:--repo|-R)[\s=]+([\w.-]+\/[\w.-]+)/;function Vl(t,e){return t.slice(e).replace(/^[\s;&|)]+/,"").replace(/"[^"]*"|'[^']*'/g,'""').split(/[;&|]/)[0]}function Fw(t){let e=String(t||""),n=e.match(_E);if(!n)return null;let r=Vl(e,n.index);return RE.test(r)?null:{repo:r.match(Lw)?.[1]??null}}var $E=new Set(["--subject","-t","--body","-b","--body-file","-F","--match-head-commit","--author-email","-A","--repo","-R"]);function Uw(t){let e=String(t||""),n=e.match(IE);if(!n)return null;let r=Vl(e,n.index),o=Vl(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("-")){$E.has(c)&&i++;continue}s=c.replace(/^["']+|["']+$/g,"")||null;break}return{repo:r.match(Lw)?.[1]??null,selector:s,auto:/\s--auto\b/.test(r)}}var OE="number,url,title,headRefName,createdAt";async function ME(t,e){let n=async a=>{Le("observer");let{stdout:i}=await Dw("gh",["pr","list","--repo",t,"--author","@me","--state","all","--limit","10","--json",OE],{timeout:3e4,maxBuffer:4*1024*1024,env:{...process.env,...a}}).catch(c=>{throw un(c)&&dn(c),c});return JSON.parse(i||"[]")},r=a=>(a||[]).filter(i=>Date.parse(i.createdAt)>=e-TE),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 CE(t,e){if(e)return[e];let n=[];for(let r of Hw(t)){let o=await tt(r).catch(()=>null);o&&!n.includes(o)&&n.push(o)}for(let r of await Te().catch(()=>[])){let o=await tt(r.path).catch(()=>null);o&&!n.includes(o)&&n.push(o)}return n}function Hw(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 NE(t,e,n){let r=t.workspace?.branch,o=[];for(let s of await CE(t,e)){let a;try{a=await ME(s,n)}catch{continue}for(let i of a){let c=await Rt(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 fn=new Set;function Ww(t,e,{delays:n=jw,turnEnd:r}={}){let o=Fw(e.command);if(!o)return null;let s=!!Uw(e.command),a=`${t.id}|${e.id??e.ts??e.command}`;if(fn.has(a))return null;fn.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 NE(t,o.repo,i).catch(()=>null);if(!u)continue;let{slug:f,pr:d}=u,{link:m,created:h}=await Ht({repo:f,number:d.number,trackId:t.id,url:d.url,headRef:d.headRefName,title:d.title,source:"observed"});return!h&&!(m?.trackId===t.id&&await Do(f,d.number))?null:(await zl(e.box,e.agent,r),await $({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:Date.now(),change:"opened",number:d.number,url:d.url,title:d.title,branch:d.headRefName,repo:f,...s?{selfMerge:!0}:{}}}),d)}return null}finally{fn.delete(a)}})();return c.catch(()=>fn.delete(a)),c}var PE="number,url,title,headRefName,state";function qw(t,e,{delays:n=jw,turnEnd:r,openedWait:o}={}){let s=Uw(e.command);if(!s)return null;let a=`merge|${t.id}|${e.id??e.ts??e.command}`;if(fn.has(a))return null;fn.add(a);let i=(async()=>{try{let c=s.repo,l=s.selector,u=l?.match(/github\.com\/([\w.-]+\/[\w.-]+)\/pull\/(\d+)/);u&&([,c,l]=u);let f=[];if(c)f=[c];else{let h=Hw(t);if(h.length>1&&/^\d+$/.test(String(s.selector??"")))return null;for(let w of h){let g=await tt(w).catch(()=>null);g&&!f.includes(g)&&f.push(g)}}if(l||=t.workspace?.branch,!f.length||!l)return null;c=f[0];let d=async(h,w)=>{let g=await Rt(c,h.number);return g&&g.trackId!==t.id?null:(g||await Ht({repo:c,number:h.number,trackId:t.id,url:h.url,headRef:h.headRefName,title:h.title,source:"merge-observed"}),await jo(c,h.number),w&&((await L(t).catch(()=>[])).some(A=>A.type==="pr.state.changed"&&(A.payload?.change==="auto-armed"||A.payload?.change==="merged")&&A.payload?.number===h.number)||(await zl(e.box,e.agent,r),await $({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:Date.now(),change:"auto-armed",number:h.number,url:h.url,title:h.title,branch:h.headRefName,repo:c}}))),h)},m=null;for(let h of n){await new Promise(O=>setTimeout(O,h));let w=[];for(let O of f)try{Le("observer");let{stdout:F}=await Dw("gh",["pr","view",String(l),"--repo",O,"--json",PE],{timeout:3e4,maxBuffer:4*1024*1024,env:{...process.env,...await ae({slug:O})}}),T=JSON.parse(F||"null");T?.number&&w.push({slug:O,pr:T})}catch(F){un(F)&&dn(F)}let g=w.find(O=>O.pr.state==="MERGED")??(w.length===1?w[0]:null);if(!g)continue;c=g.slug;let b=g.pr;if(!b?.number)continue;if(b.state==="CLOSED")return null;if(b.state!=="MERGED"){if(m=b,s.auto)return d(b,!0);continue}let S=await Rt(c,b.number);if(S&&S.trackId!==t.id)return null;S||await Ht({repo:c,number:b.number,trackId:t.id,url:b.url,headRef:b.headRefName,title:b.title,source:"merge-observed"}),await zl(e.box,e.agent,r);let A=await L(t).catch(()=>[]);if(A.some(O=>O.type==="pr.state.changed"&&O.payload?.change==="merged"&&O.payload?.number===b.number))return null;let C=O=>O.some(F=>F.type==="pr.state.changed"&&F.payload?.change==="opened"&&F.payload?.number===b.number),z=0;if(!C(A)){if(Fw(e.command)&&await Do(c,b.number)){let O=Date.now();z=O+1,await $({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:O,change:"opened",number:b.number,url:b.url,title:b.title,branch:b.headRefName,repo:c,selfMerge:!0}})}else if((await Rt(c,b.number))?.announced){let O=o?.capMs??AE,F=o?.pollMs??EE,T=Date.now()+O;for(;Date.now()<T&&(await new Promise(M=>setTimeout(M,F)),!C(await L(t).catch(()=>[]))););}}return await $({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:Math.max(Date.now(),z),change:"merged",number:b.number,url:b.url,title:b.title,branch:b.headRefName,repo:c}}),await mn(c,b.number).catch(()=>{}),await pn(c,b.number).catch(()=>{}),await sn(c,b.number,"merged"),b}return m?d(m,!1):null}finally{fn.delete(a)}})();return i.catch(()=>fn.delete(a)),i}function Xl(t){return t.sourceId?`${t.role}|id:${t.sourceId}`:`${t.role}|${t.ts??"?"}|${String(t.text||"").slice(0,40)}`}function Kw(t){return t.id?`cmd|${t.box}|id:${t.id}`:`cmd|${t.box}|${t.ts??"?"}|${String(t.command||"").slice(0,40)}`}function Gw(t){return`job|${t.box}|id:${t.id}`}var Jl=new Map,Wa=new Map;async function Yl(t){return Jl.has(t.id)?Jl.get(t.id):(Wa.has(t.id)||Wa.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(Xl({role:o,sourceId:r.payload.sourceId})),n.keys.add(Xl({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(Kw({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(Gw({id:r.payload.id,box:r.boxId}));return Jl.set(t.id,n),n}).finally(()=>Wa.delete(t.id))),Wa.get(t.id))}async function zw(t,e){try{let n=(await Yl(t)).keys;for(let r of e||[]){if(!r||r.ts==null||!String(r.text||"").trim()&&!r.images?.length)continue;let o=Xl(r);if(!n.has(o)&&(n.add(o),await $(t,{type:r.role==="user"?"turn.user":"turn.agent",actor:r.role==="user"?"user":"agent",source:"transcript",boxId:r.box,provider:r.agent||null,payload:{text:r.text,ts:r.ts,model:r.model||null,tools:r.tools||void 0,boxRole:r.boxRole,sourceId:r.sourceId||void 0,note:r.note||void 0,images:r.images?.length?r.images:void 0}}),r.role==="assistant"&&r.box===t.leadBox&&t.workspace?.branch)){let s=Sw(r.text);if(s){let{offered:a,toPr:i}=Tw(s),c;if(t.workspace?.kind==="multi"){let l=await Jo(t).catch(()=>[]);c=l.length?l.map(u=>({name:u.name,slug:u.repo??null})):void 0}await $(t,{type:"pr.offered",actor:"agent",source:"transcript",boxId:r.box,provider:r.agent||null,payload:{offerId:BE(),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 Vw(t,e){try{let n=await Yl(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=Kw(o);n.keys.has(s)||(n.keys.add(s),n.cmdN+=1,await $(t,{type:"tool.called",actor:"agent",source:"transcript",boxId:o.box,provider:o.agent||null,payload:{id:o.id??null,tool:o.tool||null,command:o.command,description:o.description||"",summary:o.summary||o.command,ts:o.ts??null,boxRole:o.boxRole,n:n.cmdN}}),Ww(t,o),qw(t,o))}}catch{}}async function Jw(t,e){try{let n=await Yl(t),r=[...e||[]].filter(o=>o&&o.id).sort((o,s)=>(o.ts||0)-(s.ts||0));for(let o of r){let s=Gw(o);n.keys.has(s)||(n.keys.add(s),await $(t,{type:"job.started",actor:"agent",source:"transcript",boxId:o.box,provider:o.agent||null,payload:{id:o.id,kind:o.kind||"agent",name:o.name||null,detail:o.detail||null,ts:o.ts??null,boxRole:o.boxRole}}))}}catch{}}var DE=/^Base directory for this skill:\s*(\S+)/,jE=/^Run the "([^"]+)" workflow\./,LE=/<command-name>\s*\/?([a-z0-9:_-]+)\s*<\/command-name>/i,FE=/^<local-command-(?:stdout|caveat)>/i,UE=/^\[Image: original \d+x\d+, displayed at \d+x\d+\.\s*Multiply coordinates by [\d.]+ to map to original image\.\]$/,HE=/^Continue from where you left off\.$/,WE=/^<task-notification>/i,qE=/<summary>([\s\S]*?)<\/summary>/i,KE=/<task-id>([\s\S]*?)<\/task-id>/i,GE=/<tool-use-id>([\s\S]*?)<\/tool-use-id>/i,zE=/<status>([\s\S]*?)<\/status>/i,VE=/<result>([\s\S]*?)(?:<\/result>|$)/i;var Zl=new Set(["model","clear","compact","login","exit","quit"]);function Xw(t){return Zl.has(String(t||"").replace(/^\//,"").toLowerCase())}var JE=/^\/([a-z0-9:_-]+)$/i;function qa(t){let e=String(t||"").trim();if(!e)return null;let n=e.match(DE);if(n)return{kind:"skill",name:n[1].split("/").filter(Boolean).pop()||"skill"};let r=e.match(jE);if(r)return{kind:"skill",name:r[1]};let o=e.match(LE);if(o){let a=o[1].toLowerCase();return Zl.has(a)?{kind:"suppress"}:{kind:"command",command:`/${a}`}}let s=e.match(JE);if(s&&Zl.has(s[1].toLowerCase()))return{kind:"suppress"};if(FE.test(e))return{kind:"suppress"};if(HE.test(e))return{kind:"suppress"};if(UE.test(e))return{kind:"suppress"};if(WE.test(e)){let a=c=>XE(e.match(c)?.[1].trim()||"")||null,i=a(VE);return{kind:"task",summary:a(qE)||"",taskId:a(KE),toolUseId:a(GE),status:a(zE)?.toLowerCase()||null,resultHead:i?i.length>600?`${i.slice(0,600).trimEnd()}\u2026`:i:null}}return null}function XE(t){return t.replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/'/g,"'").replace(/&/g,"&")}function Yw(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 Zw(t){return t?.kind==="skill"?"skill-run":t?.kind==="task"?"task-done":"command-run"}var it=t=>t.payload?.ts??t.ts,YE=new Set(["opened","ci-fixed","reconciled","pushed"]),Qw=t=>`${t.repo??""}#${t.number}`;function ZE(t,e,n){let r=Qw(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 Xo(t){let e=[],n=new Set,r=new Set,o=new Map,s=new Map,a=new Set,i=new Map,c=new Set,l=new Map,u=/^Agent "([\s\S]+)" finished$/,f=new Map;for(let y of t||[]){if(y.type!=="tool.called"||y.source!=="transcript")continue;let p=String(y.payload?.command||"");if(/\bbostrat-mock\s+(?:add|update)\b/.test(p))for(let v of p.matchAll(/[\w./~-]+\.(?:png|jpe?g|gif|webp|bmp|svg)\b/gi)){let E=v[0].split("/").pop().toLowerCase();f.has(E)||f.set(E,[]),f.get(E).push(y.payload?.ts??y.ts)}}let d=3*6e4,m=20*6e4,h=(y,p)=>!!(y?.name&&f.get(String(y.name).toLowerCase()))?.some(E=>E>=p-d&&E<=p+m);for(let y of t||[])if(y.type==="turn.retracted"){r.add(`${y.boxId}|${y.payload?.saidTs}`);let p=ne(y.payload?.text);p&&(o.has(y.boxId)||o.set(y.boxId,[]),o.get(y.boxId).push(p))}else if(y.type==="turn.user"&&y.source==="orchestrator"){let p=ne(y.payload?.text);p&&(s.has(y.boxId)||s.set(y.boxId,new Set),s.get(y.boxId).add(p))}else if(y.type==="job.started"&&y.payload?.id)a.add(y.payload.id),(y.payload.kind||"agent")==="agent"&&y.payload.name&&c.add(y.payload.name);else if(y.type==="turn.user"&&y.payload?.note?.kind==="task"){let p=y.payload.note,v={status:p.status||null,summary:p.summary||null,resultHead:p.resultHead??null,ts:it(y)};p.toolUseId&&i.set(p.toolUseId,v);let E=String(p.summary||"").match(u);E&&l.set(E[1],v)}let w=new Map,g=new Map,b=null,S=new Map,A=new Map,C=new Map,z=new Map,O=new Map,F=new Map,T=new Map,M=6e4,W=new Map,st=(y,p)=>{let v=o.get(y);if(!v?.length)return!1;let E=ne(p),rt=s.get(y);return v.some(I=>{if(!E.startsWith(I))return!1;let R=ne(E.slice(I.length));return!!R&&!!rt?.has(R)})};for(let y of t||[]){let p=y.payload||{};switch(y.type){case"turn.user":{if(y.source==="hook"||!String(p.text||"").trim()&&!p.images?.length)break;{let v=p.note||qa(p.text);if(v){if(v.kind==="task"){if(v.toolUseId&&a.has(v.toolUseId))break;let E=String(v.summary||"").match(u);if(!v.toolUseId&&E&&c.has(E[1]))break}if(v.kind==="command"&&Xw(v.command))break;v.kind!=="suppress"&&e.push({id:y.id,kind:"event",type:Zw(v),name:v.name??null,command:v.command??null,summary:v.summary??null,ts:it(y),box:y.boxId,agent:y.provider});break}}if(r.has(`${y.boxId}|${it(y)}`)||y.source==="transcript"&&st(y.boxId,p.text))break;if(p.kind==="merge-notify"||p.kind==="ship-notify"){e.push({id:y.id,kind:"event",type:p.kind==="ship-notify"?"ship-notified":"merge-notified",text:p.text,ts:it(y),box:y.boxId,agent:y.provider});break}if(p.kind==="mock-picked"||p.kind==="ci-fix"||p.kind==="reconcile"||p.kind==="pr-go"||p.kind==="push-go"||p.kind==="spin-up-view"||p.kind==="prompt-answer")break;for(let v of S.values())v.status="stale";S.clear(),e.push({id:y.id,kind:"message",role:"user",text:p.text,ts:it(y),box:y.boxId,agent:y.provider,boxRole:p.boxRole,...y.actor==="orchestrator"?{orchestration:p.kind||!0}:{},...p.sourceId?{sourceId:p.sourceId}:{},...p.attachments?.length?{attachments:p.attachments}:{},...p.images?.length?{images:p.images}:{}});break}case"turn.agent":{if(p.model==="<synthetic>"&&/^No response requested\.$/.test(String(p.text||"").trim()))break;T.clear();let v=(p.images||[]).filter(R=>!h(R,it(y)));if(!String(p.text||"").trim()&&!v.length)break;if(y.provider==="cursor"){let R=`${y.boxId}|${p.text}`;if(n.has(R))break;n.add(R)}let E=y.provider==="codex"?(p.tools||[]).filter(R=>!ml.has(R)&&!Wo.has(R)):p.tools||[],rt=p.text,I=null;if($l.has(y.provider)){let R=Ol(String(p.text||""));R&&(rt=R.text,I={questions:R.questions})}e.push({id:y.id,kind:"message",role:"assistant",text:rt,ts:it(y),box:y.boxId,agent:y.provider,boxRole:p.boxRole,...I?{ask:I}:{},...E.length?{tools:E}:{},...p.model?{model:p.model}:{},...p.sourceId?{sourceId:p.sourceId}:{},...v.length?{images:v}:{}});break}case"job.started":{if(!p.id)break;let v=it(y),E=i.get(p.id);if(!E&&(p.kind||"agent")==="agent"&&p.name){let P=l.get(p.name);P&&P.ts>=v&&(E=P)}let rt={jobId:p.id,jobKind:p.kind||"agent",name:p.name||null,detail:p.detail||null,status:E?E.status&&E.status!=="completed"?"failed":"done":"running",startedTs:v,finishedTs:E?.ts??null,summary:E?.summary??null,resultHead:E?.resultHead??null},I=W.get(y.boxId),R=I?.jobs[I.jobs.length-1]?.startedTs;if(I&&R!=null&&v-R<=M)I.jobs.push(rt);else{let P={id:y.id,kind:"job-dock",dockId:p.id,ts:v,box:y.boxId,agent:y.provider,jobs:[rt]};W.set(y.boxId,P),e.push(P)}break}case"handoff.done":e.push(hn(y,"lead-handoff"));break;case"subtask.delegated":e.push({...hn(y,"subtask-delegated"),to:y.boxId,toAgent:y.provider});break;case"subtask.merged":e.push(hn(y,"subtask-merged"));break;case"pr.state.changed":{if(!p.change)break;if(p.change==="opened"){for(let E of S.values())E.status="stale";S.clear()}let v=hn(y,`pr-${p.change}`);if(YE.has(p.change)&&p.number!=null){let E=ZE(w,g,p);if(w.size>=2){for(let I of g.values())for(let R of I)R.grouped=!0;v.groupPrs=[...w.values()];let rt=[v];for(let I of w.keys())g.set(I,rt)}else{let rt=g.get(E)??[];rt.push(v),g.set(E,rt)}}else if(p.change==="merged"||p.change==="closed"||p.change==="lost"){let E=Qw(p),rt=b&&!b.complete||w.size>=2;if(w.has(E))w.delete(E),g.delete(E);else if(p.number!=null)for(let I of[...w.keys()])I.endsWith(`#${p.number}`)&&(w.delete(I),g.delete(I));if(rt&&p.number!=null){(!b||b.complete)&&(b={id:y.id,kind:"event",type:"pr-group-settled",prs:[],remaining:[],complete:!1,at:it(y),ts:it(y),box:y.boxId,agent:y.provider},e.push(b)),b.prs.push({number:p.number,url:p.url??null,title:p.title??null,repo:p.repo??null,branch:p.branch??null,change:p.change,ts:it(y)}),b.remaining=[...w.values()],b.complete=w.size===0;break}}e.push(v);break}case"pr.offered":{if(!p.offerId)break;for(let E of S.values())E.status="stale";S.clear();let v={id:y.id,kind:"pr-offer",offerId:p.offerId,excerpt:p.excerpt||null,branch:p.branch||null,base:p.base||null,offered:Array.isArray(p.offered)&&p.offered.length?p.offered:null,toPr:p.toPr&&typeof p.toPr=="object"?{number:p.toPr.number??null}:null,repos:Array.isArray(p.repos)&&p.repos.length?p.repos:null,status:"open",ts:it(y),box:y.boxId,agent:y.provider};S.set(p.offerId,v),A.set(p.offerId,v),e.push(v);break}case"pr.offer.answered":{let v=A.get(p.offerId);v&&(v.status=p.action||"dismiss",v.actions=Array.isArray(p.actions)?p.actions:null,v.notify=p.notify===!0,S.delete(p.offerId));break}case"box.state.changed":{let v=p.change==="model"?"model-change":p.change;if(!v)break;let E=hn(y,v);v==="model-change"&&(E.to=p.model),e.push(E);break}case"view.state.changed":{if(p.change!=="created"||p.restarted)break;e.push({id:y.id,kind:"view-card",viewId:p.viewId,name:p.name,slug:p.slug,port:p.port,origin:p.origin,ts:it(y),box:y.boxId,agent:y.provider});break}case"view.spinup.failed":{e.push({id:y.id,kind:"view-spinup-failed",name:p.name||null,reason:p.reason||"no-view",ts:it(y),box:y.boxId,agent:y.provider});break}case"mock.state.changed":{if(p.change==="picked"){e.push({id:y.id,kind:"mock-picked",mockId:p.mockId,name:p.name,group:p.group??null,version:p.version??null,siblings:p.siblings??[],ts:it(y),box:y.boxId,agent:y.provider});break}if(p.change!=="created")break;e.push({id:y.id,kind:"mock-card",mockId:p.mockId,name:p.name,group:p.group??null,ts:it(y),box:y.boxId,agent:y.provider});break}case"flow.state.changed":{if(p.change!=="created")break;e.push({id:y.id,kind:"flow-card",flowId:p.flowId,name:p.name,stepCount:p.stepCount??null,mockIds:p.mockIds??[],ts:it(y),box:y.boxId,agent:y.provider});break}case"runbook.state.changed":{if(p.change!=="created")break;e.push({id:y.id,kind:"runbook-card",runbookId:p.runbookId,name:p.name,ts:it(y),box:y.boxId,agent:y.provider});break}case"ticket.linked":{e.push({id:y.id,kind:"ticket-card",provider:p.provider||"linear",key:p.key,url:p.url,title:p.title??null,linkSource:p.linkSource??null,ts:it(y),box:y.boxId,agent:y.provider});break}case"ticket.move.offered":{if(!p.offerId)break;for(let E of C.values())E.key===p.key&&(E.status="stale",C.delete(E.offerId));let v={id:y.id,kind:"ticket-offer",offerId:p.offerId,provider:p.provider||"linear",key:p.key,url:p.url??null,phase:p.phase??null,from:p.from??null,to:p.to?.name??null,prNumber:p.prNumber??null,status:"open",ts:it(y),box:y.boxId,agent:y.provider};C.set(p.offerId,v),z.set(p.offerId,v),e.push(v);break}case"ticket.move.answered":{let v=z.get(p.offerId);v&&(v.status=p.action==="moved"?"moved":"dismiss",C.delete(p.offerId));break}case"ticket.state.changed":{for(let v of C.values())v.key===p.key&&v.to===p.to&&(v.status="stale",C.delete(v.offerId));if(p.mode==="offer")break;e.push(hn(y,"ticket-state"));break}case"ticket.commented":{e.push({id:y.id,kind:"ticket-comment",key:p.key,url:p.url??null,excerpt:p.excerpt??null,ts:it(y),box:y.boxId,agent:y.provider});break}case"ticket.flow.changed":e.push(hn(y,"ticket-flow"));break;case"ticket.create.offered":{if(!p.proposalId)break;for(let E of O.values())E.status="stale";O.clear();let v={id:y.id,kind:"ticket-create-offer",proposalId:p.proposalId,provider:p.provider||"linear",epic:p.epic??null,issues:Array.isArray(p.issues)?p.issues:[],team:p.team??null,needsTeam:!!p.needsTeam,linkCurrentTrack:!!p.linkCurrentTrack,status:"open",ts:it(y),box:y.boxId,agent:y.provider};O.set(p.proposalId,v),F.set(p.proposalId,v),e.push(v);break}case"ticket.create.answered":{let v=F.get(p.proposalId);v&&(v.status=p.action==="created"?"created":"dismiss",v.createdCount=p.createdCount??null,v.failedCount=p.failedCount??null,O.delete(p.proposalId));break}case"ticket.created":{let v={items:Array.isArray(p.items)?p.items:[],failed:Array.isArray(p.failed)?p.failed:[],team:p.team??null},E=p.proposalId?F.get(p.proposalId):null;if(E){E.created=v;break}e.push({id:y.id,kind:"ticket-created",...v,ts:it(y),box:y.boxId,agent:y.provider});break}case"error":{if(!p.kind)break;e.push(hn(y,p.kind));break}case"provider.connected":{e.push({id:y.id,kind:"event",type:"provider-connected",provider:p.provider||y.provider,ts:it(y),at:it(y),box:y.boxId,agent:y.provider});break}case"prompt.answered":{if(p.control||!p.question)break;let v=p.groupId||p.promptId;if(!v)break;let E=T.get(v);E||(E={id:y.id,kind:"prompt",groupId:v,provider:y.provider,box:y.boxId,agent:y.provider,questionTotal:p.questionTotal??1,questions:[],ts:it(y)},T.set(v,E));let rt=p.questionIndex??0,I={index:rt,header:p.header||"",question:p.question,multiSelect:!!p.multiSelect,chosen:Array.isArray(p.chosen)?p.chosen:[]},R=E.questions.findIndex(P=>P.index===rt);R>=0?E.questions[R]=I:E.questions.push(I),E.questions.sort((P,et)=>P.index-et.index),E.questionTotal=Math.max(E.questionTotal,p.questionTotal??1),new Set(E.questions.map(P=>P.index)).size>=E.questionTotal&&(e.push(E),T.delete(v));break}default:break}}return t_(e.sort((y,p)=>(y.ts??0)-(p.ts??0)))}var QE=2*6e4;function t_(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<=QE){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 Ql(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:e_(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??Xo(t)).filter(o=>o.kind==="message");return fh(n,r)}function e_(t){let e=String(t.summary||"").trim();return e&&e!==t.command&&e!==Ar(t.command)?e:an(t.command,t.description||"")}function hn(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 n_(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 cg(){let t={};for(let[e,n]of ou)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 k_(t){let e=/workspaces[/\\]([0-9a-f]{8})(?:[/\\]|$)/.exec(t||"")?.[1];if(e)try{let[{stdout:n},{stdout:r}]=await Promise.all([Pr("git",["-C",t,"rev-parse","HEAD"]),Pr("git",["-C",t,"rev-parse","--abbrev-ref","HEAD"])]);await $({id:e,cwd:t},{type:"code.pushed",actor:"orchestrator",payload:{ref:r.trim(),sha:n.trim(),wip:!1,ts:Date.now()}})}catch{}}var Pr=b_(y_),x_={timeout:3e4,maxBuffer:8*1024*1024};async function Jt(t,e,n={}){let{caller:r="prs",...o}=n,s=await ae({cwd:t});Le(r);try{let{stdout:a}=await Pr("gh",e,{cwd:t,...x_,...o,env:{...process.env,...s,...o.env||{}}});return String(a||"")}catch(a){throw un(a)&&dn(a),a}}var Ja=null;async function iu(){if(Ja&&Date.now()-Ja.at<6e4)return Ja.data;let t;try{Le("auth");let e=await Pr("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 Ja={at:Date.now(),data:t},t}async function S_(t){try{let{stdout:e}=await Pr("git",["-C",t,"config","--get","remote.origin.url"],{timeout:5e3});return/github\.com/.test(e)}catch{return!1}}function ug(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 su(t){let e=(t||[]).map(ug),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 cu="number,title,url,headRefName,headRefOid,baseRefName,isDraft,author,reviewDecision,mergeable,mergeStateStatus,statusCheckRollup,autoMergeRequest,additions,deletions,createdAt,updatedAt",dg=cu.replace("statusCheckRollup,","");function lu(t){return/Resource not accessible by personal access token/.test(String(t?.stderr||t?.message||""))}async function fg(t,e){if(!e)return null;let n=await tt(t);if(!n)return null;try{let r=await Jt(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 pg(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 mg(t,e){let n=await fg(t,e);return n===null?{...su([]),unavailable:!0}:su(pg(n))}function Xa(t,e=su(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 v_=9e4,Nr=new Map;function T_(t,e,n){let r=new Set;for(let o of e){let s=`${t}#${o.number}`;if(o.mergeable!=="UNKNOWN"&&o.mergeStateStatus!=="UNKNOWN"){Nr.delete(s);continue}r.add(s),Nr.has(s)||Nr.set(s,n),o.mergeabilityPending=n-Nr.get(s)<v_}for(let o of Nr.keys())o.startsWith(`${t}#`)&&!r.has(o)&&Nr.delete(o);return e}async function Br(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 Jt(n.path,["pr","view",String(r),"--json",cu]);return Xa(JSON.parse(o))}catch(o){if(!lu(o))throw o;let s=await Jt(n.path,["pr","view",String(r),"--json",dg]),a=JSON.parse(s);return Xa(a,await mg(n.path,a.headRefOid))}}var lg=(t,e)=>t.number-e.number;async function Zo(t,{caller:e="prs"}={}){try{let n=await Jt(t,["pr","list","--json",cu,"--limit","50"],{caller:e});return JSON.parse(n||"[]").map(r=>Xa(r)).sort(lg)}catch(n){if(!lu(n))throw n;let r=await Jt(t,["pr","list","--json",dg,"--limit","50"],{caller:e});return(await Promise.all(JSON.parse(r||"[]").map(async o=>Xa(o,await mg(t,o.headRefOid))))).sort(lg)}}var A_=3e4,E_=1500,Yo=new Map;function xe(){Yo.clear()}async function __(t,e){if(!t)return e;for(let n of e){let r=await Rt(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 Ta(t,n.number,{headSha:n.headSha}).catch(()=>{}):n.ciFix={delegatedAt:r.ciFix.delegatedAt,trackId:r.ciFix.trackId??null}),r.reconcile&&(o(r.reconcile)?await Ea(t,n.number,{headSha:n.headSha}).catch(()=>{}):n.reconcile={delegatedAt:r.reconcile.delegatedAt,trackId:r.reconcile.trackId??null})}return e}async function hg(){let t=await iu();if(!t.authenticated)return{auth:t,repos:[]};let e=[];for(let a of await Te())await S_(a.path)&&e.push(a);let n=Date.now(),r=Ua(n)>0,o=await Promise.all(e.map(async a=>{let i=Yo.get(a.path),c=i?.pending?E_:A_;if(i&&(r||n-i.at<c))return i.entry;try{let l=await tt(a.path).catch(()=>null),u=T_(a.path,await Zo(a.path,{caller:"tab"}),n);if(await __(l,u),i?.entry?.prs)for(let m of u)i.entry.prs.find(w=>w.number===m.number)?.checks?.pending>0&&m.checks.total>0&&m.checks.pending===0&&Va({change:m.checks.failed>0?"checks-failed":"checks-green",repo:l||a.name,number:m.number,githubAt:m.checks.settledAt,source:"tab"});let f={name:a.name,path:a.path,prs:u},d=u.some(m=>m.mergeabilityPending||m.checks.pending>0||m.autoMergeArmed);return Yo.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:ke(l)}}})),s=new Set(e.map(a=>a.path));for(let a of Yo.keys())s.has(a)||Yo.delete(a);return{auth:t,repos:o}}async function Fe(t){let e=(await Te()).find(n=>n.path===t);if(!e)throw new Error(`unknown repo: ${t}`);return e}async function zn(t){return ao(t)}var Qo=new Set(["squash","merge","rebase"]);async function Dr(t,e,{method:n="squash"}={}){let r=await Fe(t);if(!Qo.has(n))throw new Error(`invalid merge method: ${n}`);let o=Number(e);if(!Number.isInteger(o)||o<=0)throw new Error("invalid PR number");let s=["pr","merge",String(o),`--${n}`];if(n!=="rebase"&&(await ae({cwd:r.path})).GH_TOKEN){let a=await Bi().catch(()=>null);a&&s.push("--body",`Merged by bostrat on behalf of @${a}`)}try{await Jt(r.path,s)}catch(a){throw new Error(ke(a))}finally{xe()}return{ok:!0,number:o}}async function ts(t,e){let n=await Fe(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");try{await Jt(n.path,["api","-X","PUT",`repos/{owner}/{repo}/pulls/${r}/update-branch`])}catch(s){throw new Error(ke(s))}finally{xe()}let o=await tt(n.path).catch(()=>null);return o&&await Gm(o,r).catch(()=>{}),{ok:!0,number:r}}async function uu(t,e){let n=await Fe(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");try{await Jt(n.path,["pr","merge",String(r),"--disable-auto"])}catch(o){throw new Error(ke(o))}finally{xe()}return{ok:!0,number:r}}var I_=2500,R_=9e4,$_=t=>new Promise(e=>setTimeout(e,t));function du(t){return t.green&&!t.isDraft}function O_(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 wg(t,{update:e=!0}={}){return t.isDraft?{action:"skip",reason:"draft"}:du(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 yg(){let t={};for(let[e,n]of au)t[e]={...n};return t}async function bg(t,{method:e="merge",onMerged:n=null}={}){let r=await Fe(t);if(!Qo.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 Zo(r.path)).filter(du).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),C_(s,o,{listPrs:()=>Zo(r.path),merge:a=>Dr(r.path,a,{method:e}),sleep:$_,onMerged:n}).catch(()=>{}),{ok:!0,repo:r.path,total:s.total,queue:o.map(a=>a.number)}}async function M_(t,e){let n=Date.now()+(e.settleMaxMs??R_),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(I_)}}async function C_(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 M_(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: ${ke(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(!du(a)){t.skipped.push({number:a.number,title:a.title,reason:O_(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:ke(i)})}}}finally{t.current=null,t.running=!1,t.finishedAt=Date.now(),xe()}}async function fu(t,{title:e,body:n="",base:r=null}={}){if(typeof e!="string"||!e.trim())throw new Error("title required");try{await Pr("git",["-C",t,"push","-u","origin","HEAD"],{timeout:12e4,maxBuffer:4*1024*1024})}catch(l){throw new Error(`push failed: ${ke(l)}`)}await k_(t);let o=n||"";if((await ae({cwd:t})).GH_TOKEN){let l=await Bi().catch(()=>null);l&&(o=`${o?`${o}
|
|
148
|
-
|
|
149
|
-
`:""}Opened by bostrat on behalf of @${l}`)}let s=["pr","create","--title",e.trim(),"--body",o];r&&s.push("--base",r);let a;try{a=await
|
|
138
|
+
`;import{execFile as mE}from"node:child_process";import{promisify as hE}from"node:util";var Ul=hE(mE),dw=64*1024*1024;async function ht(t,e,{input:n}={}){let r=Ul("git",["-C",t,...e],{maxBuffer:dw});return n!==void 0&&r.child.stdin&&(r.child.stdin.on("error",()=>{}),r.child.stdin.end(n)),r}async function Ma(t){if(!t)return!1;try{let{stdout:e}=await ht(t,["rev-parse","--is-inside-work-tree"]);return e.trim()==="true"}catch{return!1}}async function fw(t){let{stdout:e}=await ht(t,["rev-parse","--show-toplevel"]);return e.trim()}async function wE(t){try{return await ht(t,["rev-parse","--verify","HEAD"]),!0}catch{return!1}}async function Go(t,e="bostrat: subtask snapshot"){let{stdout:n}=await ht(t,["rev-parse","--absolute-git-dir"]),r=`${n.trim()}/bostrat-snapshot-index`,o={...process.env,GIT_INDEX_FILE:r},s=(a,i)=>Ul("git",["-C",t,...a],{maxBuffer:dw,env:o,...i});try{let a=await wE(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 Ul("rm",["-f",r])}catch{}}}async function Hl(t,e){let{stdout:n}=await ht(t,["status","--porcelain"]);return n.trim()?(await ht(t,["add","-A"]),await ht(t,["-c","user.email=agent@bostrat.local","-c","user.name=bostrat","commit","-m",e]),!0):!1}async function Wl(t,{ignorePlumbing:e=!1}={}){let{stdout:n}=await ht(t,["status","--porcelain"]),r=n.split(`
|
|
139
|
+
`).filter(Boolean);return e&&(r=r.filter(o=>{let s=o.slice(3);return!(s===".bostrat"||s===".bostrat/"||s.startsWith(".bostrat/")||o.startsWith("??")&&(s==="AGENTS.md"||s==="CLAUDE.md"))})),r.length>0}async function ql(t,e){if(!e)return 0;try{let{stdout:n}=await ht(t,["rev-list","--count",`${e}..HEAD`]);return Number(n.trim())||0}catch{return 0}}async function Nr(t,e,n,r){return await ht(t,["worktree","add","--no-track","-b",n,e,r]),e}async function pw(t,e){try{return await ht(t,["rev-parse","--verify","--quiet",`refs/heads/${e}`]),!0}catch{return!1}}async function mw(t,e,n){return await ht(t,["worktree","add",e,n]),e}async function Na(t){if(process.env.BOSTRAT_TRACK_BASE_REF)return process.env.BOSTRAT_TRACK_BASE_REF;try{let{stdout:e}=await ht(t,["symbolic-ref","--short","refs/remotes/origin/HEAD"]);if(e.trim())return e.trim()}catch{}for(let[e,n]of[["refs/remotes/origin/main","origin/main"],["refs/remotes/origin/master","origin/master"],["refs/heads/main","main"],["refs/heads/master","master"]])try{return await ht(t,["rev-parse","--verify","--quiet",e]),n}catch{}return"HEAD"}async function hw(t){await ht(t,["init","-q"]),await ht(t,["-c","user.email=agent@bostrat.local","-c","user.name=bostrat","commit","--allow-empty","-qm","bostrat: track workspace"])}async function Xt(t,e){try{await ht(t,["worktree","remove","--force",e])}catch{try{await ht(t,["worktree","prune"])}catch{}}}function gE(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 Kl(t,e){return!e||!e.length?t:[...t,"--",".",...e.map(n=>`:(exclude)${n}`)]}async function Gl(t,e,n,r){let{stdout:o}=await ht(t,Kl(["diff","--shortstat",e,n],r));return gE(o)}async function Vl(t,e,n,r){let{stdout:o}=await ht(t,Kl(["diff","--name-only",e,n],r));return o.split(`
|
|
140
|
+
`).map(s=>s.trim()).filter(Boolean)}async function zl(t,e,n,r){let{stdout:o}=await ht(t,Kl(["diff","--binary",e,n],r));if(!o.trim())return{applied:!0,empty:!0,conflicts:[]};if((await Fl(t,o,["--whitespace=nowarn"])).ok)return{applied:!0,empty:!1,conflicts:[]};let s=await Fl(t,o,["--3way","--whitespace=nowarn"]),a=await yE(t);return a.length>0?{applied:!1,empty:!1,conflicts:a}:s.ok?{applied:!0,empty:!1,conflicts:[]}:(await Fl(t,o,["--reject","--whitespace=nowarn"]),{applied:!1,empty:!1,conflicts:[`patch did not apply cleanly: ${s.err}`]})}async function Fl(t,e,n){try{return await ht(t,["apply",...n,"-"],{input:e}),{ok:!0}}catch(r){return{ok:!1,err:r?.stderr?.trim()||r?.message||String(r)}}}async function yE(t){try{let{stdout:e}=await ht(t,["diff","--name-only","--diff-filter=U"]);return e.split(`
|
|
141
|
+
`).map(n=>n.trim()).filter(Boolean)}catch{return[]}}import bE from"node:fs/promises";import kE from"node:os";import ww from"node:path";var Pa=t=>new Promise(e=>setTimeout(e,t)),xE=Number(process.env.BOSTRAT_MODEL_PICKER_TIMEOUT_MS)||8e3,SE=300,kw=[{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"}],vE=new Set([...kw.map(t=>t.alias),"best","opus[1m]","sonnet[1m]","claude-opus-4-8","claude-sonnet-4-6","claude-haiku-4-5","claude-fable-5"]),TE=[{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"}],xw=new Map([["low","Low"],["medium","Medium"],["high","High"],["xhigh","Extra high"]]),Sw=[{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 vw(t){return t==="codex"?TE:t==="cursor"?Sw:kw}function gw(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 yw(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 AE(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 bw(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 Tw(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&&!xw.has(o))throw new Error(`unknown codex effort "${o}" (low | medium | high | xhigh)`);return{model:r,effort:o||null}}if(t==="cursor"){let r=Sw.find(o=>o.alias===n);return{model:n,filter:r?r.label:n}}if(!vE.has(n))throw new Error(`unknown model "${n}"`);return{model:n}}async function Vo(t,e,{timeoutMs:n=xE}={}){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 Pa(SE)}}async function EE(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 _E(t,e){await Hs(t),await St(t,"/model",{enter:!1}),await Pa(250),await bt(t,["Enter"],{kind:"model-switch"});let n=await Vo(t,gw);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=AE(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=>yw(i)||!gw(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?xw.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=>!yw(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 IE(){let t=ww.join(process.env.CURSOR_CONFIG_DIR||ww.join(kE.homedir(),".cursor"),"cli-config.json");try{return JSON.parse(await bE.readFile(t,"utf8"))?.model||null}catch{return null}}async function RE(t,e){await Hs(t),await qs(t,`/model ${e.filter}`,{kind:"model-switch"});let n=await Vo(t,bw),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,bw,{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 Pa(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 Pa(800);let a=await IE();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 Aw(t,e,n){return t==="codex"?_E(e,n):t==="cursor"?RE(e,n):EE(e,n)}var Kt=t=>t||null;function Da(t){let e=t||{},n=$E(e);return n&&(n.payload={ts:e.at,...n.payload}),n}function $E(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 Ba=Promise.resolve(),Xl=t=>(Ba=Ba.then(t).catch(()=>{}),Ba),_w=()=>Ba;function Iw(t,e){let n=Da(e);if(!n)return;let r=e?.type==="track-deleted"?{id:t.id}:t;Xl(()=>R(r,n))}var Ew=new Set;async function Rw(t){if(!(!t?.id||Ew.has(t.id))){Ew.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=Da(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 Jl=new Map;function $w(t,e,n){!n?.promptId||Jl.get(t.id)===n.promptId||(Jl.set(t.id,n.promptId),Xl(()=>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 Yl(t,e,n,r={}){Jl.delete(t.id),Xl(()=>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 c_}from"node:crypto";var Cw=/(?<!\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,Mw=/(?<!\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,Nw=/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,Pw=/(?<!\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,OE=new RegExp(`\\b(?:${Cw}|${Mw}|${Nw}|${Pw})\\b`,"i"),CE=/\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,ME=/^(?:commit|push|open|create|raise|rebase|ready|ok(?:ay)?\s+to|good\s+to)\b/i,NE=90;function PE(t){return String(t).split(/(?<=[.?!])\s+|\n+/).map(e=>e.trim()).filter(Boolean)}function Bw(t){let e=String(t||"").trim();if(!e)return null;let n=e.slice(-600);for(let r of PE(n).reverse()){if(!OE.test(r))continue;let o=r.endsWith("?")&&r.length<=NE&&ME.test(r);if(CE.test(r)||o)return r.length>200?r.slice(0,197)+"\u2026":r}return null}var Dw=["commit","rebase","push","pr"],BE=new RegExp(`\\b(?:${Cw})\\b`,"i"),DE=new RegExp(`\\b(?:${Pw})\\b`,"i"),jE=new RegExp(`\\b(?:${Mw})\\b`,"i"),LE=new RegExp(`\\b(?:${Nw})\\b`,"i"),FE=/\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 jw(t){let e=String(t||""),n=[];BE.test(e)&&n.push("commit"),DE.test(e)&&n.push("rebase"),jE.test(e)&&n.push("push");let r=FE.test(e);!r&&LE.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 ja(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 UE=3e4;function Lw(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<=UE&&n.push({box:r.box,from:r.from,to:e+1e12}),n}function Fw(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 Ow={commit:"commit",rebase:"rebase",push:"push",pr:"open a PR"};function Uw(t){let e=(Array.isArray(t)?t:[]).filter(r=>Ow[r]).map(r=>Ow[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 GE}from"node:child_process";import{promisify as VE}from"node:util";import HE from"node:fs";import Zl from"node:fs/promises";import Kw from"node:path";import WE from"node:os";var Gw=process.env.BOSTRAT_STATE_DIR||Kw.join(process.env.HOME||WE.tmpdir(),".bostrat"),La=Kw.join(Gw,"github-metrics.json"),Vw=60*6e4,Hw=5*6e4,qE=6e4,Br=Date.now(),je=new Map,dn=null,zn=0,Ww=0,Fa=t=>[...t.values()].reduce((e,n)=>e+n,0);try{let t=JSON.parse(HE.readFileSync(La,"utf8"));t?.windowStart&&(Date.now()-t.windowStart<Vw?(Br=t.windowStart,je=new Map(Object.entries(t.byCaller||{})),dn=t.lastHour||null):Fa(new Map(Object.entries(t.byCaller||{})))&&(dn={start:t.windowStart,total:Fa(new Map(Object.entries(t.byCaller||{}))),byCaller:t.byCaller||{}}))}catch{}var qw=Promise.resolve();function zw(t,e=!1){if(!e&&t-Ww<qE)return;Ww=t;let n=JSON.stringify({windowStart:Br,byCaller:Object.fromEntries(je),lastHour:dn});qw=qw.then(()=>Zl.mkdir(Gw,{recursive:!0})).then(()=>Zl.writeFile(`${La}.tmp`,n)).then(()=>Zl.rename(`${La}.tmp`,La)).catch(()=>{})}function Jw(t){t-Br<Vw||(dn={start:Br,total:Fa(je),byCaller:Object.fromEntries(je)},dn.total&&console.log(`[github] ${dn.total} calls last hour`,dn.byCaller),Br=t,je=new Map,zw(t,!0))}function Le(t="other",e=Date.now()){Jw(e),je.set(t,(je.get(t)||0)+1),zw(e)}function Xw(t=Date.now()){return Jw(t),{windowStart:Br,total:Fa(je),byCaller:Object.fromEntries(je),lastHour:dn,backoffUntil:zn>t?zn:null}}var KE=/rate limit|too quickly|abuse detection|HTTP 429/i;function fn(t){return KE.test(String(t?.message||t||""))}function pn(t,e=Date.now()){return zn<=e&&console.warn(`[github] rate limited \u2014 backing off ${Hw/6e4} min:`,String(t?.message||t||"").slice(0,200)),zn=Math.max(zn,e+Hw),zn}function Ua(t=Date.now()){return Math.max(0,zn-t)}async function Ql(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 zf(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 Ha(t,e){return(await Ql(t,e)).working}var Yw=VE(GE),Zw=[4e3,15e3,45e3],zE=2500,JE=5*6e4;async function tu(t,e,{midTurn:n=Ha,pollMs:r=zE,capMs:o=JE}={}){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 XE=6e4,YE=9e4,ZE=2e3,QE=/(?:^|[;&|]\s*|\)\s*)gh\s+(?:[^\s;&|"'`$]+\s+)*?pr\s+create\b/,t_=/(?:^|[;&|]\s*|\)\s*)gh\s+(?:[^\s;&|"'`$]+\s+)*?pr\s+merge\b/,e_=/\s--web\b/,Qw=/(?:--repo|-R)[\s=]+([\w.-]+\/[\w.-]+)/;function eu(t,e){return t.slice(e).replace(/^[\s;&|)]+/,"").replace(/"[^"]*"|'[^']*'/g,'""').split(/[;&|]/)[0]}function tg(t){let e=String(t||""),n=e.match(QE);if(!n)return null;let r=eu(e,n.index);return e_.test(r)?null:{repo:r.match(Qw)?.[1]??null}}var n_=new Set(["--subject","-t","--body","-b","--body-file","-F","--match-head-commit","--author-email","-A","--repo","-R"]);function eg(t){let e=String(t||""),n=e.match(t_);if(!n)return null;let r=eu(e,n.index),o=eu(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("-")){n_.has(c)&&i++;continue}s=c.replace(/^["']+|["']+$/g,"")||null;break}return{repo:r.match(Qw)?.[1]??null,selector:s,auto:/\s--auto\b/.test(r)}}var r_="number,url,title,headRefName,createdAt";async function o_(t,e){let n=async a=>{Le("observer");let{stdout:i}=await Yw("gh",["pr","list","--repo",t,"--author","@me","--state","all","--limit","10","--json",r_],{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-XE),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 s_(t,e){if(e)return[e];let n=[];for(let r of ng(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 ng(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 a_(t,e,n){let r=t.workspace?.branch,o=[];for(let s of await s_(t,e)){let a;try{a=await o_(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 rg(t,e,{delays:n=Zw,turnEnd:r}={}){let o=tg(e.command);if(!o)return null;let s=!!eg(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 a_(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 tu(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 i_="number,url,title,headRefName,state";function og(t,e,{delays:n=Zw,turnEnd:r,openedWait:o}={}){let s=eg(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=ng(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 tu(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 Yw("gh",["pr","view",String(l),"--repo",$,"--json",i_],{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 tu(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(tg(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??YE,j=o?.pollMs??ZE,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 ru(t){return t.sourceId?`${t.role}|id:${t.sourceId}`:`${t.role}|${t.ts??"?"}|${String(t.text||"").slice(0,40)}`}function sg(t){return t.id?`cmd|${t.box}|id:${t.id}`:`cmd|${t.box}|${t.ts??"?"}|${String(t.command||"").slice(0,40)}`}function ag(t){return`job|${t.box}|id:${t.id}`}var nu=new Map,Wa=new Map;async function ou(t){return nu.has(t.id)?nu.get(t.id):(Wa.has(t.id)||Wa.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(ru({role:o,sourceId:r.payload.sourceId})),n.keys.add(ru({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(sg({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(ag({id:r.payload.id,box:r.boxId}));return nu.set(t.id,n),n}).finally(()=>Wa.delete(t.id))),Wa.get(t.id))}async function ig(t,e){try{let n=(await ou(t)).keys;for(let r of e||[]){if(!r||r.ts==null||!String(r.text||"").trim()&&!r.images?.length)continue;let o=ru(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=Bw(r.text);if(s){let{offered:a,toPr:i}=jw(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:c_(),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 cg(t,e){try{let n=await ou(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=sg(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}}),rg(t,o),og(t,o))}}catch{}}async function lg(t,e){try{let n=await ou(t),r=[...e||[]].filter(o=>o&&o.id).sort((o,s)=>(o.ts||0)-(s.ts||0));for(let o of r){let s=ag(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 l_=/^Base directory for this skill:\s*(\S+)/,u_=/^Run the "([^"]+)" workflow\./,d_=/<command-name>\s*\/?([a-z0-9:_-]+)\s*<\/command-name>/i,f_=/^<local-command-(?:stdout|caveat)>/i,p_=/^\[Image: original \d+x\d+, displayed at \d+x\d+\.\s*Multiply coordinates by [\d.]+ to map to original image\.\]$/,m_=/^Continue from where you left off\.$/,h_=/^<task-notification>/i,w_=/<summary>([\s\S]*?)<\/summary>/i,g_=/<task-id>([\s\S]*?)<\/task-id>/i,y_=/<tool-use-id>([\s\S]*?)<\/tool-use-id>/i,b_=/<status>([\s\S]*?)<\/status>/i,k_=/<result>([\s\S]*?)(?:<\/result>|$)/i;var su=new Set(["model","clear","compact","login","exit","quit"]);function ug(t){return su.has(String(t||"").replace(/^\//,"").toLowerCase())}var x_=/^\/([a-z0-9:_-]+)$/i;function qa(t){let e=String(t||"").trim();if(!e)return null;let n=e.match(l_);if(n)return{kind:"skill",name:n[1].split("/").filter(Boolean).pop()||"skill"};let r=e.match(u_);if(r)return{kind:"skill",name:r[1]};let o=e.match(d_);if(o){let a=o[1].toLowerCase();return su.has(a)?{kind:"suppress"}:{kind:"command",command:`/${a}`}}let s=e.match(x_);if(s&&su.has(s[1].toLowerCase()))return{kind:"suppress"};if(f_.test(e))return{kind:"suppress"};if(m_.test(e))return{kind:"suppress"};if(p_.test(e))return{kind:"suppress"};if(h_.test(e)){let a=c=>S_(e.match(c)?.[1].trim()||"")||null,i=a(k_);return{kind:"task",summary:a(w_)||"",taskId:a(g_),toolUseId:a(y_),status:a(b_)?.toLowerCase()||null,resultHead:i?i.length>600?`${i.slice(0,600).trimEnd()}\u2026`:i:null}}return null}function S_(t){return t.replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/'/g,"'").replace(/&/g,"&")}function dg(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 fg(t){return t?.kind==="skill"?"skill-run":t?.kind==="task"?"task-done":"command-run"}var it=t=>t.payload?.ts??t.ts,v_=new Set(["opened","ci-fixed","reconciled","pushed"]),pg=t=>`${t.repo??""}#${t.number}`;function T_(t,e,n){let r=pg(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||qa(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"&&ug(T.command))break;T.kind!=="suppress"&&e.push({id:w.id,kind:"event",type:fg(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=>!xl.has(A)&&!Ho.has(A)):h.tools||[],O=h.text,I=null;if(Dl.has(w.provider)){let A=jl(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(v_.has(h.change)&&h.number!=null){let _=T_(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 _=pg(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 E_(e.sort((w,h)=>(w.ts??0)-(h.ts??0)))}var A_=2*6e4;function E_(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<=A_){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 au(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:__(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 Eh(n,r)}function __(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 I_(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 iu=new Map;async function R_(t,e){let n=`${t.id}:${e.name}`;if(!iu.has(n)){let r=await D(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);iu.set(n,o)}return iu.get(n)}async function mg(t,e){try{if(e?.agent!=="codex"||!e.cwd)return;let n=await Hn(e.cwd,e.pinned?e.sessionId:null);if(!n)return;let r=await R_(t,e),o=I_(await Nt(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:le(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 Ka from"node:fs";import lu from"node:path";import $_ from"node:os";var O_=process.env.BOSTRAT_STATE_DIR||lu.join(process.env.HOME||$_.tmpdir(),".bostrat"),wg=(t=process.env)=>!!t.BOSTRAT_CURSOR_USAGE_COOKIE,C_="https://cursor.com/api/dashboard/get-filtered-usage-events",hg=100;async function M_({sinceMs:t,cookie:e,fetchImpl:n=fetch}){let r=[];for(let o=1;o<=20;o++){let s=await n(C_,{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:hg})});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<hg)break}return r}function N_(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 P_=5*6e4,yn={at:0,rows:null,error:null},Ga=()=>lu.join(O_,"cursor-usage.json");function gg(){try{return JSON.parse(Ka.readFileSync(Ga(),"utf8"))}catch{return{sinceMs:0,seen:[]}}}function B_(t){try{Ka.mkdirSync(lu.dirname(Ga()),{recursive:!0});let e=Ga()+".tmp";Ka.writeFileSync(e,JSON.stringify(t)),Ka.renameSync(e,Ga())}catch{}}var D_=5e3;async function j_({fetchImpl:t,now:e=Date.now()}={}){if(yn.rows&&e-yn.at<P_)return yn.rows;let n=process.env.BOSTRAT_CURSOR_USAGE_COOKIE;if(!n)return null;let r=gg(),o=Math.max(r.sinceMs-6e4,e-24*36e5);try{let s=await M_({sinceMs:o,cookie:n,fetchImpl:t});yn={at:e,rows:s.map(N_).filter(Boolean),error:null}}catch(s){yn={at:e,rows:null,error:String(s?.message||s)}}return yn.rows}var uu=()=>({enabled:wg(),lastPollAt:yn.at||null,error:yn.error}),cu=new Map;async function L_(t,e){let n=`${t.id}:${e.name}`;if(!cu.has(n)){let r=await D(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);cu.set(n,o)}return cu.get(n)}async function yg(t,e,{fetchImpl:n}={}){try{if(e?.agent!=="cursor"||!e.cwd||!wg())return;let r=await j_({fetchImpl:n});if(!r||!r.length)return;let o=await Ph(e.cwd,e.pinned?e.sessionId:null);if(!o?.chatId)return;let s=await L_(t,e),a=gg(),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=le(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}})}B_({sinceMs:c,seen:[...i].slice(-D_)})}catch{}}import{execFile as q_}from"node:child_process";import{promisify as K_}from"node:util";import bg from"node:fs/promises";import kg from"node:path";import F_ from"node:os";var xg=process.env.BOSTRAT_STATE_DIR||kg.join(process.env.HOME||F_.tmpdir(),".bostrat"),U_=kg.join(xg,"github-latency.ndjson"),H_=100,Va=[],du=new Map;function za({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};Va.push(l),Va.length>H_&&Va.shift();let u=du.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)),du.set(t,u),W_(l),l}async function W_(t){try{await bg.mkdir(xg,{recursive:!0}),await bg.appendFile(U_,JSON.stringify(t)+`
|
|
147
|
+
`)}catch{}}function Sg(){let t={};for(let[e,n]of du)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:Va.slice(-20)}}async function G_(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=K_(q_),V_={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,...V_,...o,env:{...process.env,...s,...o.env||{}}});return String(a||"")}catch(a){throw fn(a)&&pn(a),a}}var Ja=null;async function mu(){if(Ja&&Date.now()-Ja.at<6e4)return Ja.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 Ja={at:Date.now(),data:t},t}async function z_(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 Tg(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 fu(t){let e=(t||[]).map(Tg),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 hu="number,title,url,headRefName,headRefOid,baseRefName,isDraft,author,reviewDecision,mergeable,mergeStateStatus,statusCheckRollup,autoMergeRequest,additions,deletions,createdAt,updatedAt",Ag=hu.replace("statusCheckRollup,","");function wu(t){return/Resource not accessible by personal access token/.test(String(t?.stderr||t?.message||""))}async function Eg(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 Ig(t,e){let n=await Eg(t,e);return n===null?{...fu([]),unavailable:!0}:fu(_g(n))}function Xa(t,e=fu(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 J_=9e4,Dr=new Map;function X_(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)<J_}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",hu]);return Xa(JSON.parse(o))}catch(o){if(!wu(o))throw o;let s=await Yt(n.path,["pr","view",String(r),"--json",Ag]),a=JSON.parse(s);return Xa(a,await Ig(n.path,a.headRefOid))}}var vg=(t,e)=>t.number-e.number;async function Yo(t,{caller:e="prs"}={}){try{let n=await Yt(t,["pr","list","--json",hu,"--limit","50"],{caller:e});return JSON.parse(n||"[]").map(r=>Xa(r)).sort(vg)}catch(n){if(!wu(n))throw n;let r=await Yt(t,["pr","list","--json",Ag,"--limit","50"],{caller:e});return(await Promise.all(JSON.parse(r||"[]").map(async o=>Xa(o,await Ig(t,o.headRefOid))))).sort(vg)}}var Y_=3e4,Z_=1500,Xo=new Map;function ke(){Xo.clear()}async function Q_(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 Ta(t,n.number,{headSha:n.headSha}).catch(()=>{}):n.ciFix={delegatedAt:r.ciFix.delegatedAt,trackId:r.ciFix.trackId??null}),r.reconcile&&(o(r.reconcile)?await Ea(t,n.number,{headSha:n.headSha}).catch(()=>{}):n.reconcile={delegatedAt:r.reconcile.delegatedAt,trackId:r.reconcile.trackId??null})}return e}async function Rg(){let t=await mu();if(!t.authenticated)return{auth:t,repos:[]};let e=[];for(let a of await ve())await z_(a.path)&&e.push(a);let n=Date.now(),r=Ua(n)>0,o=await Promise.all(e.map(async a=>{let i=Xo.get(a.path),c=i?.pending?Z_:Y_;if(i&&(r||n-i.at<c))return i.entry;try{let l=await tt(a.path).catch(()=>null),u=X_(a.path,await Yo(a.path,{caller:"tab"}),n);if(await Q_(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&&za({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 Fi().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 ah(o,r).catch(()=>{}),{ok:!0,number:r}}async function gu(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 tI=2500,eI=9e4,nI=t=>new Promise(e=>setTimeout(e,t));function yu(t){return t.green&&!t.isDraft}function rI(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 $g(t,{update:e=!0}={}){return t.isDraft?{action:"skip",reason:"draft"}:yu(t)?{action:"merge"}:e&&t.mergeStateStatus==="BEHIND"&&t.mergeable!=="CONFLICTING"&&t.checks.failed===0?{action:"update-arm"}:{action:"arm"}}function Og(t){return[...t.filter(e=>e.plan.action!=="merge"),...t.filter(e=>e.plan.action==="merge")]}var pu=new Map;function Cg(){let t={};for(let[e,n]of pu)t[e]={...n};return t}async function Mg(t,{method:e="merge",onMerged:n=null}={}){let r=await Fe(t);if(!Zo.has(e))throw new Error(`invalid merge method: ${e}`);if(pu.get(r.path)?.running)throw new Error("a merge-all is already running for this repo");let o=(await Yo(r.path)).filter(yu).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 pu.set(r.path,s),sI(s,o,{listPrs:()=>Yo(r.path),merge:a=>Fr(r.path,a,{method:e}),sleep:nI,onMerged:n}).catch(()=>{}),{ok:!0,repo:r.path,total:s.total,queue:o.map(a=>a.number)}}async function oI(t,e){let n=Date.now()+(e.settleMaxMs??eI),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 sI(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 oI(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(!yu(a)){t.skipped.push({number:a.number,title:a.title,reason:rI(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 bu(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 G_(t);let o=n||"";if((await ae({cwd:t})).GH_TOKEN){let l=await Fi().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 Yt(t,s)}catch(l){throw new Error(be(l))}finally{ke()}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 Ng(t,e){let n=await Fe(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");let o;try{let i;try{i=await Yt(n.path,["pr","checks",String(r),"--json","name,state,link,bucket"])}catch(c){if(i=String(c?.stdout||""),!i.trim())throw new Error(be(c))}o=JSON.parse(i||"[]").filter(c=>c.bucket==="fail")}catch(i){if(!wu(i))throw i;let c=await Yt(n.path,["pr","view",String(r),"--json","headRefOid"]),l=await Eg(n.path,JSON.parse(c).headRefOid);if(l===null)throw new Error(be(i));o=_g(l).map(Tg).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=aI(i.link),l=null;c&&!s.has(c)&&(s.add(c),l=await Yt(n.path,["run","view",c,"--log-failed"]).then(u=>iI(u)).catch(()=>null)),a.push({name:i.name,link:i.link||null,log:l})}return a}function aI(t){return String(t||"").match(/\/actions\/runs\/(\d+)/)?.[1]??null}function iI(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 B_=6e3,Ya=new Map;function pu(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(pu(n)==null)return;let r=(t.boxes||[]).find(c=>c.name===t.leadBox);if(await Ha(t.leadBox,r?.agent).catch(()=>!1))return;let o=Date.now();if(o-(Ya.get(t.id)||0)<B_)return;Ya.set(t.id,o);let s;try{s=await Zo(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 Ht({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 Do(i,a.number))){Ya.delete(t.id);return}}await $(t,{type:"pr.state.changed",actor:"agent",source:"orchestrator",boxId:t.leadBox,payload:{ts:Date.now(),change:"opened",number:a.number,url:a.url,title:a.title,branch:e,...i?{repo:i}:{}}}).catch(()=>{}),Ya.delete(t.id)}import D_ from"node:fs/promises";import Eg from"node:path";import j_ from"node:os";import{randomUUID as _g}from"node:crypto";var L_=process.env.BOSTRAT_STATE_DIR||Eg.join(process.env.HOME||j_.tmpdir(),".bostrat"),Ig=Eg.join(L_,"flows.json"),Sg=20,vg=80,Tg=500,Ag=80,Ue=null,mu=null;async function He(){return Ue||(mu||=(async()=>{let t=!0;try{Ue=JSON.parse(await D_.readFile(Ig,"utf8"))}catch{Ue={flows:[]},t=!1}finally{mu=null}if(Ue.flows||=[],!t&&Tt()){let{records:e,source:n}=await Zt("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 jr().catch(()=>{})}}return Ue})(),mu)}async function jr(){await Ct(Ig,Ue)}var Za=t=>de("flows",{...t},{trackId:t.trackId}).catch(()=>{});function hu(t,e,n={}){return $({id:t.trackId,cwd:t.cwd},{type:"flow.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box||null,provider:t.agent||null,payload:{ts:Date.now(),change:e,flowId:t.id,name:t.name,stepCount:t.steps.length,mockIds:[...new Set(t.steps.map(r=>r.mockId))],...n}}).catch(()=>{})}async function Rg({trackId:t,steps:e,prevSteps:n=[]}){if(!Array.isArray(e)||e.length<1||e.length>Sg)throw new Error(`steps must be an array of 1-${Sg}`);let r=await On({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>Tg)throw new Error(`${l}: caption over ${Tg} chars`);let f=c.title==null?null:String(c.title).trim()||null;if(f&&f.length>Ag)throw new Error(`${l}: title over ${Ag} chars`);let d=String(c.mock??"").trim().replace(/^mock:/,"");if(!d)throw new Error(`${l}: mock (name or id) required`);let m=r.filter(S=>S.id===d);if(m.length||(m=r.filter(S=>S.name.toLowerCase()===d.toLowerCase())),!m.length)throw new Error(`${l}: no mock matching "${d}" in this Track \u2014 see \`bostrat-mock ls\``);if(m.length>1)throw new Error(`${l}: "${d}" is ambiguous (${m.map(S=>`mock:${S.id}`).join(", ")}) \u2014 use the id`);let h=m[0],w=null;if(c.v!=null&&(w=Number(c.v),!Number.isInteger(w)||!(h.versions||[]).some(S=>S.v===w)))throw new Error(`${l}: "${h.name}" has no captured v${c.v} to pin`);let g=o.findIndex(S=>S.mockId===h.id),b=g>=0?o.splice(g,1)[0].id:_g().slice(0,8);s.push({id:b,mockId:h.id,caption:u,title:f,v:w})}let a={};for(let i of s)i.mockId in a||(a[i.mockId]=r.find(c=>c.id===i.mockId)?.readyVersion??null);return{steps:s,resolved:a}}function F_(t){if(typeof t!="string"||!t.trim())throw new Error("name (string) required");if(t.trim().length>vg)throw new Error(`name over ${vg} chars`);return t.trim()}async function $g({trackId:t}={}){let e=await He();return(t?e.flows.filter(r=>r.trackId===t):e.flows).map(r=>({...r}))}async function U_(t){let n=(await He()).flows.find(r=>r.id===t);return n?{...n}:null}async function Og(t){let e=await U_(t);if(!e)return null;let n=new Map((await On({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 Mg({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=F_(e),i=await He(),c=i.flows.find(m=>m.trackId===t&&m.name.toLowerCase()===a.toLowerCase());if(c)throw new Error(`flow "${c.name}" already exists in this Track (flow:${c.id}) \u2014 revise it with \`bostrat-flow update\``);let{steps:l,resolved:u}=await Rg({trackId:t,steps:n}),f=Date.now(),d={id:_g().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 jr(),await hu(d,"created"),Za(d),{...d}}async function Cg(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 Rg({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 jr(),await hu(o,"updated",{version:o.version,...n?{note:String(n).slice(0,200)}:{}}),Za(o),{...o}}async function Ng(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 jr(),await hu(r,"deleted"),Yt("flows",r.id).catch(()=>{}),{ok:!0,id:t,name:r.name}}async function Pg(t){return(await He()).flows.filter(n=>n.steps.some(r=>r.mockId===t)).map(n=>({...n}))}async function Bg(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(),Za(r);return n.length&&await jr(),n.length}async function Dg(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 jr();for(let r of n)Yt("flows",r.id).catch(()=>{})}return n.length}async function jg(){if(!Tt())return{pushed:0,source:"unconfigured"};let{records:t,source:e}=await Zt("flows");if(e!=="plane"||t.length)return{pushed:0,source:e};let n=await He();for(let r of n.flows)await Za(r);return{pushed:n.flows.length,source:"plane"}}import wu from"node:fs/promises";import es from"node:path";import{randomUUID as H_}from"node:crypto";var Fg=".bostrat/uploads",gu=t=>es.join(t,Fg),Lg=Number(process.env.BOSTRAT_MAX_UPLOAD_BYTES)||24*1024*1024,W_=new Set(["png","jpg","jpeg","gif","webp","svg","bmp","tif","tiff","heic","heif","avif","ico"]),q_=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 yu(t){let e=String(t||"").toLowerCase().replace(/^\./,"");return W_.has(e)?"image":q_.has(e)?"doc":null}function K_(t){let r=es.basename(String(t||"")).replace(/[\x00-\x1f]/g,"").replace(/[^A-Za-z0-9._-]+/g,"_").replace(/^_+|_+$/g,"").slice(-80)||"file";return r.startsWith(".")?`f${r}`:r}async function Ug(t,{filename:e,buffer:n}){if(!t)throw new Error("cwd required");if(!Buffer.isBuffer(n)||n.length===0)throw new Error("empty upload");if(n.length>Lg)throw new Error(`file too large \u2014 max ${Math.floor(Lg/(1024*1024))} MB`);let r=K_(e),o=es.extname(r).slice(1),s=yu(o);if(!s)throw new Error(`unsupported file type "${o||"unknown"}" \u2014 attach an image or a document`);let a=H_().slice(0,8),i=gu(t);await wu.mkdir(i,{recursive:!0});let c=`${a}-${r}`;return await wu.writeFile(es.join(i,c),n,{mode:384}),await wu.chmod(es.join(i,c),384).catch(()=>{}),{id:a,name:r,rel:`${Fg}/${c}`,kind:s,size:n.length}}function Hg(t){let e=t.map(r=>`\`${r}\``).join(", ");return`[Attached \u2014 please read ${t.length===1?"this file":`these ${t.length} files`}: ${e}]`}var G_=/\[Attached — please read (?:this file|these \d+ files): [^\]]*\]$/;function bu(t){return t.role==="user"&&G_.test((t.text||"").trim())}function Lr(t,e,n=Date.now()){return e?t.idleSince==null?(t.idleSince=n,!1):n-t.idleSince>=6500:(t.idleSince=null,!1)}var J_=V_(z_),We=process.env.BOSTRAT_STATE_DIR||H.join(process.env.HOME||"/tmp",".bostrat"),Yg=H.join(We,"tracks.json"),X_=H.join(We,"events.jsonl"),Eu=H.join(We,"workspaces"),Y_=process.env.BOSTRAT_DEFAULT_REPO||null,ei={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"}},ni=Object.keys(ei);function Z_(t,{skipPermissions:e=!0}={}){let n=ei[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 Zg(t,e,{skipPermissions:n=!0}={}){let r=ei[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=ei[t];if(e)return process.env[e.envVar]||e.base}var Vn={at:0,value:null};async function Iu({maxAgeMs:t=6e4}={}){if(Vn.value&&Date.now()-Vn.at<t)return Vn.value;let e=await Promise.all(ni.map(async n=>[n,await js(ls(n))]));return Vn.value=Object.fromEntries(e),Vn.at=Date.now(),Vn.value}async function ri(t,e=!0){let n=Z_(t,{skipPermissions:e});if(!await js(n)){Vn.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.":Yi[r]?`Install it on this node: ${Yi[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 oi=".bostrat/handoff/latest.md";function Q_(t,e=oi){return`${t.trim()}
|
|
153
|
+
`+r.slice(-n)),r}function be(t){return String(t?.stderr||t?.message||t||"").trim().split(`
|
|
154
|
+
`).find(n=>n.trim())||"gh failed"}var cI=6e3,Ya=new Map;function ku(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 Pg(t){let e=t?.workspace?.branch;if(!e)return;let n;try{n=await D(t)}catch{return}if(ku(n)==null)return;let r=(t.boxes||[]).find(c=>c.name===t.leadBox);if(await Ha(t.leadBox,r?.agent).catch(()=>!1))return;let o=Date.now();if(o-(Ya.get(t.id)||0)<cI)return;Ya.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))){Ya.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(()=>{}),Ya.delete(t.id)}import lI from"node:fs/promises";import Fg from"node:path";import uI from"node:os";import{randomUUID as Ug}from"node:crypto";var dI=process.env.BOSTRAT_STATE_DIR||Fg.join(process.env.HOME||uI.tmpdir(),".bostrat"),Hg=Fg.join(dI,"flows.json"),Bg=20,Dg=80,jg=500,Lg=80,Ue=null,xu=null;async function He(){return Ue||(xu||=(async()=>{let t=!0;try{Ue=JSON.parse(await lI.readFile(Hg,"utf8"))}catch{Ue={flows:[]},t=!1}finally{xu=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})(),xu)}async function Ur(){await Mt(Hg,Ue)}var Za=t=>Rt("flows",{...t},{trackId:t.trackId}).catch(()=>{});function Su(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 Wg({trackId:t,steps:e,prevSteps:n=[]}){if(!Array.isArray(e)||e.length<1||e.length>Bg)throw new Error(`steps must be an array of 1-${Bg}`);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>jg)throw new Error(`${l}: caption over ${jg} chars`);let f=c.title==null?null:String(c.title).trim()||null;if(f&&f.length>Lg)throw new Error(`${l}: title over ${Lg} 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:Ug().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 fI(t){if(typeof t!="string"||!t.trim())throw new Error("name (string) required");if(t.trim().length>Dg)throw new Error(`name over ${Dg} chars`);return t.trim()}async function qg({trackId:t}={}){let e=await He();return(t?e.flows.filter(r=>r.trackId===t):e.flows).map(r=>({...r}))}async function pI(t){let n=(await He()).flows.find(r=>r.id===t);return n?{...n}:null}async function Kg(t){let e=await pI(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 Gg({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=fI(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 Wg({trackId:t,steps:n}),f=Date.now(),d={id:Ug().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 Su(d,"created"),Za(d),{...d}}async function Vg(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 Wg({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 Su(o,"updated",{version:o.version,...n?{note:String(n).slice(0,200)}:{}}),Za(o),{...o}}async function zg(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 Su(r,"deleted"),Qt("flows",r.id).catch(()=>{}),{ok:!0,id:t,name:r.name}}async function Jg(t){return(await He()).flows.filter(n=>n.steps.some(r=>r.mockId===t)).map(n=>({...n}))}async function Xg(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(),Za(r);return n.length&&await Ur(),n.length}async function Yg(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 Zg(){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 Za(r);return{pushed:n.flows.length,source:"plane"}}import vu from"node:fs/promises";import ts from"node:path";import{randomUUID as mI}from"node:crypto";var ty=".bostrat/uploads",Tu=t=>ts.join(t,ty),Qg=Number(process.env.BOSTRAT_MAX_UPLOAD_BYTES)||24*1024*1024,hI=new Set(["png","jpg","jpeg","gif","webp","svg","bmp","tif","tiff","heic","heif","avif","ico"]),wI=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 Au(t){let e=String(t||"").toLowerCase().replace(/^\./,"");return hI.has(e)?"image":wI.has(e)?"doc":null}function gI(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 ey(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>Qg)throw new Error(`file too large \u2014 max ${Math.floor(Qg/(1024*1024))} MB`);let r=gI(e),o=ts.extname(r).slice(1),s=Au(o);if(!s)throw new Error(`unsupported file type "${o||"unknown"}" \u2014 attach an image or a document`);let a=mI().slice(0,8),i=Tu(t);await vu.mkdir(i,{recursive:!0});let c=`${a}-${r}`;return await vu.writeFile(ts.join(i,c),n,{mode:384}),await vu.chmod(ts.join(i,c),384).catch(()=>{}),{id:a,name:r,rel:`${ty}/${c}`,kind:s,size:n.length}}function ny(t){let e=t.map(r=>`\`${r}\``).join(", ");return`[Attached \u2014 please read ${t.length===1?"this file":`these ${t.length} files`}: ${e}]`}var yI=/\[Attached — please read (?:this file|these \d+ files): [^\]]*\]$/;function Eu(t){return t.role==="user"&&yI.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 xI=kI(bI),We=process.env.BOSTRAT_STATE_DIR||F.join(process.env.HOME||"/tmp",".bostrat"),dy=F.join(We,"tracks.json"),SI=F.join(We,"events.jsonl"),Mu=F.join(We,"workspaces"),vI=process.env.BOSTRAT_DEFAULT_REPO||null,ni={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"}},ri=Object.keys(ni);function TI(t,{skipPermissions:e=!0}={}){let n=ni[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 fy(t,e,{skipPermissions:n=!0}={}){let r=ni[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=ni[t];if(e)return process.env[e.envVar]||e.base}var Xn={at:0,value:null};async function Pu({maxAgeMs:t=6e4}={}){if(Xn.value&&Date.now()-Xn.at<t)return Xn.value;let e=await Promise.all(ri.map(async n=>[n,await Us(ls(n))]));return Xn.value=Object.fromEntries(e),Xn.at=Date.now(),Xn.value}async function oi(t,e=!0){let n=TI(t,{skipPermissions:e});if(!await Us(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.":sc[r]?`Install it on this node: ${sc[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 si=".bostrat/handoff/latest.md";function AI(t,e=si){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 EI=/\(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 ry(t){return t.role==="user"&&EI.test((t.text||"").trim())}var Qa=".bostrat/handoff/subtask-result.md",ns=".bostrat/handoff/lead-context.md",_I=(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 ${ns} \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 ${Qa} (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 ${
|
|
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 ${Qa} (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 ${ns} \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 ${Qa} 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.`,
|
|
165
|
-
`;return xu=xu.then(()=>U.mkdir(We,{recursive:!0})).then(()=>U.appendFile(X_,n)).catch(()=>{}),xu}var ct=(t,e,n={})=>{let r={at:Date.now(),type:e,...n};return t.events.push(r),rI(t.id,r),mw(t,r),r};wf(async({sessionName:t,from:e,to:n,version:r,status:o,error:s})=>{let i=(await q()).tracks.find(c=>c.leadBox===t||(c.boxes||[]).some(l=>l.name===t));i&&(ct(i,"codex-update",{box:t,from:e,to:r||n,status:o,...s?{error:s}:{}}),await V().catch(()=>{}))});var Su=(t,e,n)=>e!=null&&n.some(r=>t===r.box&&e>=r.from&&e<=r.to),oI=new Set(["stuck","wrong-approach","too-slow","want-different-model","context-refresh","cost","parallelize","other"]);function Qg(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=>oI.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 ty(t,e,n,{kind:r}={}){let o=await Y(e);if(!o)throw new Error(`box "${e}" is not running`);let s=ls(o.agent);await Qe({cwd:o.cwd,sessionId:o.sessionId,sessionName:e,agent:o.agent,agentCmd:s}),await vt(e,n,{..._e(o.agent),kind:r||"orchestration"}),await G({flow:"orchestration",phase:"sent",trackId:t,sessionName:e,agent:o.agent,kind:r||"orchestration",promptHead:n.slice(0,160)});let a=await q(),i=nt(a,t);if(!i)return;let c=i.boxes.find(l=>l.name===e);ct(i,"orchestration-prompt",{box:e,agent:c?.agent||o.agent,boxRole:c?.role,prompt:n,kind:r||"orchestration"}),await V()}var Jn=new Set(["merged","conflict","canceled","failed","handed-off"]),qg=new Map;async function sI(t){let e=H.join(Ys(t),`${t.id}.ndjson`),n;try{n=await U.stat(e)}catch{return null}let r=qg.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 qg.set(t.id,{mtimeMs:n.mtimeMs,at:o}),o}async function aI(t,e){let n=e.find(r=>r.name===t.leadBox);return n?.state!=="live"?{working:!1,prompted:!1}:Gl(n.name,n.agent)}async function gn(t,e=null){let n=e??new Map((await En()).map(i=>[i.name,i])),r=t.boxes.map(i=>{let c=n.get(i.name),l=Jn.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([sI(t),aI(t,r)]);return{...t,boxes:r,lastResponseAt:o,working:s,prompted:a}}var iI=5e3;function cI(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-iI,d=r[u+1]?.createdAt??1/0,m=o.find(h=>!s.has(h.id)&&h.birthMs>=f&&h.birthMs<d);m&&(s.add(m.id),c.push({box:l.name,sessionId:m.id}))}return c}async function ey(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=uf(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=cI(u,a,d=>i.has(d));for(let d of f){let m=u.find(h=>h.name===d.box);m&&(m.sessionId=d.sessionId),await Qd(d.box,d.sessionId),n=!0}}return n}async function ot(){let t=await q();await ey(t)&&await V();let e=new Map((await En()).map(n=>[n.name,n]));return Promise.all(t.tracks.map(n=>gn(n,e)))}async function ny(t,e){let n=await q();if(t)for(let r of n.tracks||[]){let o=r.boxes.find(s=>s.sessionId&&s.sessionId===t);if(o)return{track:r,box:o}}if(e)for(let r of n.tracks||[]){let o=r.boxes.find(s=>s.cwd===e);if(o)return{track:r,box:o}}return null}async function ry(t){let e=oo(t);if(!e)return null;let n=await q();for(let r of n.tracks||[])for(let o of r.boxes||[])if(o.hookTokenHash&&xd(o.hookTokenHash,e))return{track:r,box:o};return null}async function Q(t){let e=await q(),n=nt(e,t);return n?(await ey(e)&&await V(),gn(n)):null}var lI={claude:[/^\[Request interrupted by user(?: for tool use)?\]$/i],codex:[/^\[?Request interrupted/i,/^Conversation interrupted/i],cursor:[/^\[?Request interrupted/i]};function Kg(t){if(t.role!=="user")return!1;let e=(t.text||"").trim();return(lI[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 Gg(t){let e=(t.text||"").trim();return e?uI.some(n=>n.test(e)):!1}var dI=10*6e4;async function Ru(t,e={}){let n=await Q(t);if(!n)return null;let r=new Set([...n.events.filter(k=>k.type==="orchestration-prompt"&&k.prompt).map(k=>String(k.prompt).trim()),...n.events.filter(k=>k.type==="said"&&k.text).map(k=>String(k.text).trim())]),o=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=_c(n.id),c=!!(i&&!i.finishedAt),l=c&&i.startedAt&&i.box?[{box:i.box,from:i.startedAt,to:Date.now()+1e12}]:[],u=[...Gp(n),...l],d={windows:[...Zf(n),...Qf(n,ta),...a,...u]},h=(await Promise.all(n.boxes.map(k=>xl(k,e)))).flat(),w=h.map(k=>{let K={kind:"message",...k};if(K.role==="assistant"&&(K.text||"").trim()&&K.tools?.length){let Zn=qo(K.agent).cliTools,As=K.tools.filter(Qn=>!Zn.has(Qn));return{...K,tools:As.length?As:void 0}}return K}).map(k=>{if(k.role!=="user")return k;let K=qa(k.text);return K?K.kind==="suppress"?null:{...k,note:K,text:Yw(K)}:k}).filter(Boolean).filter(k=>!(k.role==="user"&&r.has((k.text||"").trim()))).filter(k=>!Kg(k)).filter(k=>!Gg(k)).filter(k=>!Wg(k)).filter(k=>!bu(k)).filter(k=>k.role!=="assistant"||!!(k.text||"").trim()||!!k.images?.length).filter(k=>!tp(k,d)),g=w.filter(k=>k.failed&&k.text===hl);if(g.length){let k=new Map;for(let K of g){if(!k.has(K.box)){let Qn=[...(await gt(K.box,{lines:At}).catch(()=>"")).matchAll(/^\s*■\s+(.+?)\s*$/gm)].map(Ri=>Ri[1]);k.set(K.box,Qn.length?Qn[Qn.length-1]:null)}let Zn=k.get(K.box);Zn&&(K.text=`\u25A0 ${Zn}`)}}let b=await Promise.all(n.boxes.map(k=>Oh(k,e)));await Jw(n,b.flat()),await zw(n,w),await hw(n),await Qp(n,w).catch(()=>{});for(let k of n.boxes)k.agent==="codex"&&k.state==="live"&&await tg(n,k),k.agent==="cursor"&&k.state==="live"&&await og(n,k);let S=await Promise.all(n.boxes.map(k=>$h(k,e)));await Vw(n,S.flat().filter(k=>!Su(k.box,k.ts,u)));let A=await L(n),C=c&&i.viewId?i.viewId:null,z=Aw(A),O=Xo(A).filter(k=>!(k.kind==="message"&&(Kg(k)||Gg(k)||Wg(k)||bu(k)))&&!(k.kind==="view-card"&&C&&k.viewId===C)),F=Ew(O,z),T=O.filter(k=>!(k.kind==="message"&&k.role==="assistant"&&!F.has(k)&&Su(k.box,k.ts,z))),M=Ql(A,T).map(k=>({kind:"command-batch",id:k.id,ts:k.pending?k.ts:(k.anchorTs??k.ts)-1,anchorTs:k.anchorTs,anchorBox:k.anchorBox,agent:k.agent,boxRole:k.boxRole,commands:k.commands,pending:k.pending})).filter(k=>!Su(k.anchorBox,k.anchorTs??k.ts,u)),W=[...T,...M].filter(k=>k.ts!=null).sort((k,K)=>k.ts-K.ts),st=fI(n),y=st.size?W.filter(k=>k.kind==="event"||!st.has(k.box??k.anchorBox)):W,p=n.boxes.find(k=>k.name===n.leadBox)||null,v=p?await go(p.cwd,p.sessionId,{fallbackNewest:!1}):null;if(!v&&p){let k=[...A].reverse().find(K=>K.type==="context.usage"&&K.boxId===p.name);k?.payload?.window&&(v={pct:k.payload.pct??(k.payload.occupancy!=null?k.payload.occupancy/k.payload.window:null),occupancy:k.payload.occupancy??null,window:k.payload.window,windowKnown:!0,model:k.payload.model||null})}let E=[...h].reverse().find(k=>k.role==="assistant"&&k.model),rt=E?.model||null,I=[...n.events||[]].reverse().find(k=>k.type==="model-change"),R=v?.model||rt;I&&(!E?.ts||I.at>=E.ts)&&(R=I.to);let _=p?await gt(p.name,{lines:At}).catch(()=>""):"",P=p&&Rr(p.agent,_)?await Xe(p.name).catch(()=>_):_,et=p?Kn(p,P):null;!et&&p&&!Ze(p.agent,_)&&!zg(n.id).length&&(et=Ml(y,{leadBoxId:p.name,provider:p.agent,isAnswered:k=>A.some(K=>K.type==="prompt.answered"&&K.payload?.promptId===k)})),et&&ww(n,p,et);let wt=y.filter(k=>!(k.kind==="message"&&k.role==="assistant"&&k.ask&&!String(k.text||"").trim()&&!k.images?.length)),bt=!!p&&!et&&Ze(p.agent,_);p&&!p.readyAt&&(bt||et||_&&Gs(p.agent,_))&&(p.readyAt=Date.now(),await V());let $t=!!p&&!p.readyAt&&Date.now()-(n.createdAt||0)<dI;if(n.kind==="setup"&&n.setup?.provider&&!n.setup.connectedAt&&p&&Gs(p.agent,_)&&!et){let k=await q(),K=nt(k,n.id);K?.setup&&!K.setup.connectedAt&&(K.setup.connectedAt=Date.now(),n.setup.connectedAt=K.setup.connectedAt,await $(K,{type:"provider.connected",actor:"system",source:"pane",boxId:p.name,provider:p.agent,payload:{provider:p.agent,ts:K.setup.connectedAt}}).catch(()=>{}),await V())}let Kt=s?{running:!0,step:o.step,toAgent:o.toAgent||null}:null,Xt=null;if(c){let k=Date.now()-i.startedAt;if(i.viewId&&!bt){let K=la(n.id);K&&(ct(n,"view-spinup",{box:i.box,startedAt:i.startedAt,endedAt:K.finishedAt}),await V())}else if(et||k>qp&&!bt&&!i.viewId||k>Ec){let K=et?"needs-input":k>Ec?"timeout":"no-view";la(n.id,{error:K})&&await $(n,{type:"view.spinup.failed",actor:"orchestrator",source:"orchestrator",boxId:i.box,provider:p?.agent||null,payload:{ts:i.startedAt,name:i.name,reason:K}}).catch(()=>{})}else Xt={running:!0,step:i.step,name:i.name}}return{trackId:n.id,leadBox:n.leadBox,items:wt,context:v?{pct:v.pct,occupancy:v.occupancy,window:v.window,windowKnown:v.windowKnown,model:v.model}:null,models:{current:R,options:lw(p?.agent)},pendingPrompt:et,working:bt,leadStarting:$t,handoff:Kt,view:Xt,queued:zg(n.id).filter(k=>k.kind!=="ship-notify"&&k.kind!=="prompt-answer")}}function fI(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 oy(t,e){let n=await Q(t);if(!n)return null;let r=Kr(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=Xo(s).filter(f=>f.box===o&&!(f.kind==="message"&&f.role==="assistant"&&f.ask&&!String(f.text||"").trim()&&!f.images?.length)),i=Ql(s.filter(f=>f.boxId===o),a.filter(f=>f.kind==="message")).map(f=>({kind:"command-batch",id:f.id,ts:f.pending?f.ts:(f.anchorTs??f.ts)-1,anchorTs:f.anchorTs,anchorBox:f.anchorBox,agent:f.agent,boxRole:f.boxRole,commands:f.commands,pending:f.pending})),c=[...a,...i].filter(f=>f.ts!=null).sort((f,d)=>f.ts-d.ts),l=n.boxes.find(f=>f.name===o)||null,u=!1;if(l&&l.state==="live"){let f=await gt(o,{lines:At}).catch(()=>"");u=Ze(l.agent||r.agent,f)}return{items:c,working:u,box:o,agent:r.agent,task:r.task}}async function $u(t,{promptId:e,answers:n,text:r}={}){let o=nt(await q(),t);if(!o)throw new Error(`track "${t}" not found`);let s=o.boxes.find(d=>d.name===o.leadBox);if(!s)throw new Error(`track "${t}" has no lead box`);if(!await Y(o.leadBox))throw new Error(`lead box "${o.leadBox}" is not running`);let i=await Il(s);if(i||(i=await pI(o,s)),!i)throw new Error("no pending prompt to answer");if(e&&e!==i.promptId)throw new Error("prompt changed since it was shown \u2014 reload and answer the current prompt");let c;if(i.answerMode==="text"){let d=typeof r=="string"?r.trim():"";if(!d)throw new Error("this prompt needs a code \u2014 paste it into the card");if(/\s/.test(d)||d.length>2048)throw new Error("that doesn't look like a sign-in code \u2014 paste the code exactly as shown");await vt(o.leadBox,d,{enter:!0,kind:"answer-prompt"}),c={mode:"text-input",value:"\u2039code\u203A",count:1,redacted:!0}}else if(i.answerMode==="esc")await xt(o.leadBox,["Escape"],{kind:"answer-prompt"}),c={mode:"keys",value:"Escape",count:1};else if(i.answerMode==="say"){let d=Jh(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=Hh(i,n||[]);await xt(o.leadBox,d,{kind:"answer-prompt"}),c={mode:"keys",value:d.join(" "),count:d.length}}else{let d=vf(i,n||[]);await vt(o.leadBox,d,{enter:!0,kind:"answer-prompt"}),c={mode:"text",value:d,count:1}}if(i.answerMode==="say"){let d=i.questions||[];for(let m=0;m<d.length;m++){let h=d[m],w=(Array.isArray(n?.[m])?n[m]:[]).map(g=>h.options?.[g]).filter(Boolean).map(g=>({label:g.label,description:g.description||""}));ql(o,s,i,{...c,groupId:i.groupId||i.promptId,questionIndex:m,questionTotal:i.questionTotal??d.length,header:h.header||"",question:h.question||"",multiSelect:!!h.multiSelect,chosen:w})}return await G({flow:"prompt",phase:"answered",sessionName:o.leadBox,agent:s.agent,provider:i.provider,promptId:i.promptId,...c}).catch(()=>{}),{ok:!0,promptId:i.promptId,sent:c.count}}let l=i.questions?.[0]||{},u=(Array.isArray(n?.[0])?n[0]:[]).map(d=>l.options?.[d]).filter(Boolean).map(d=>({label:d.label,description:d.description||""})),f=i.review?"review":/^claude:model-confirm:/.test(i.promptId||"")?"model-confirm":/^claude:resume-confirm:/.test(i.promptId||"")?"resume-confirm":void 0;return c={...c,groupId:i.groupId||i.promptId,questionIndex:i.questionIndex??0,questionTotal:i.questionTotal??1,header:l.header||"",question:l.question||"",multiSelect:!!l.multiSelect,chosen:u,...f?{control:f}:{}},await G({flow:"prompt",phase:"answered",sessionName:o.leadBox,agent:s.agent,provider:i.provider,promptId:i.promptId,...c}).catch(()=>{}),ql(o,s,i,c),{ok:!0,promptId:i.promptId,sent:c.count}}async function pI(t,e){if(!e)return null;let n=await L(t).catch(()=>[]);return Ml(Xo(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=nt(await q(),t);if(!r)throw new Error(`track "${t}" not found`);let o=r.boxes.find(l=>l.name===r.leadBox);if(!o)throw new Error(`track "${t}" has no lead box`);if(!await Y(r.leadBox))throw new Error(`lead box "${r.leadBox}" is not running`);let a=await Il(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=Wh(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 mI=1500,os=new Map;function Ou(t){let e=os.get(t);return e||(e={items:[],timer:null,flushing:!1,idleSince:null},os.set(t,e)),e}function zg(t){return(os.get(t)?.items??[]).map(e=>({...e}))}function si(t){let e=os.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 ay(t){let e=Ou(t);e.timer||(e.timer=setInterval(()=>hI(t).catch(()=>{}),mI))}async function hI(t){let e=os.get(t);if(!(!e||e.flushing)){if(!e.items.length){e.timer&&(clearInterval(e.timer),e.timer=null);return}e.flushing=!0;try{let n=nt(await q(),t);if(!n||n.archivedAt){si(t);return}let r=us(t);if(r&&!r.finishedAt&&r.step!=="done"&&!r.error){Lr(e,!1);return}let o=await Y(n.leadBox);if(!o){Lr(e,!1);return}let s=n.boxes.find(c=>c.name===n.leadBox),a=await gt(n.leadBox,{lines:At}).catch(()=>"");if(Ks(s?.agent,a)){Lr(e,!1);return}if(Rr(s?.agent,a)){let c=await Xe(n.leadBox).catch(()=>a);if(Kn(s,c)){Lr(e,!1);return}}if(!Lr(e,!0))return;let i=e.items.shift();e.idleSince=null,await iy(n,s,o,i.text,i.kind??null,i.attachments??null),await V()}finally{e.flushing=!1}}}async function iy(t,e,n,r,o=null,s=null){let a=r,i=!1;t.pendingHandoffHydrate&&(a=Q_(r,t.pendingHandoffHydrate.artifact||oi),delete t.pendingHandoffHydrate,i=!0);let c=Array.isArray(s)?s.filter(u=>u&&u.rel):[];if(c.length){let u=Hg(c.map(f=>f.rel));a=a.trim()?`${a.trim()}
|
|
164
|
+
Work ONLY inside this directory. When you are finished: (1) make your code changes, (2) commit them with git and a clear message, (3) LAST, write a short markdown digest to ${Qa} 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.`,II=(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."),xe=null,_u=null;async function W(){return xe||(_u||=(async()=>{try{xe=JSON.parse(await L.readFile(dy,"utf8"))}catch{xe={tracks:[]}}finally{_u=null}return xe})(),_u)}async function z(){await Mt(dy,xe)}var et=(t,e)=>t.tracks.find(n=>n.id===e)||null,Iu=Promise.resolve();function RI(t,e){let n=JSON.stringify({trackId:t,...e})+`
|
|
165
|
+
`;return Iu=Iu.then(()=>L.mkdir(We,{recursive:!0})).then(()=>L.appendFile(SI,n)).catch(()=>{}),Iu}var ct=(t,e,n={})=>{let r={at:Date.now(),type:e,...n};return t.events.push(r),RI(t.id,r),Iw(t,r),r};jf(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 Ru=(t,e,n)=>e!=null&&n.some(r=>t===r.box&&e>=r.from&&e<=r.to),$I=new Set(["stuck","wrong-approach","too-slow","want-different-model","context-refresh","cost","parallelize","other"]);function py(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=>$I.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 my(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"]),oy=new Map;async function OI(t){let e=F.join(ta(t),`${t.id}.ndjson`),n;try{n=await L.stat(e)}catch{return null}let r=oy.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 oy.set(t.id,{mtimeMs:n.mtimeMs,at:o}),o}async function CI(t,e){let n=e.find(r=>r.name===t.leadBox);return n?.state!=="live"?{working:!1,prompted:!1}:Ql(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([OI(t),CI(t,r)]);return{...t,boxes:r,lastResponseAt:o,working:s,prompted:a}}var MI=5e3;function NI(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-MI,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 hy(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=Cf(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=NI(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 vf(d.box,d.sessionId),n=!0}}return n}async function ot(){let t=await W();await hy(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 wy(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 gy(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&&Cd(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 hy(e)&&await z(),bn(n)):null}var PI={claude:[/^\[Request interrupted by user(?: for tool use)?\]$/i],codex:[/^\[?Request interrupted/i,/^Conversation interrupted/i],cursor:[/^\[?Request interrupted/i]};function sy(t){if(t.role!=="user")return!1;let e=(t.text||"").trim();return(PI[t.agent]||[]).some(n=>n.test(e))}var BI=[/^\/model(?:\s|$)/i,/<command-(?:name|message|args)>[^<]*\bmodel\b/i,/^Model changed to\b/i];function ay(t){let e=(t.text||"").trim();return e?BI.some(n=>n.test(e)):!1}var DI=10*6e4;async function Bu(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=Cc(n.id),c=!!(i&&!i.finishedAt),l=c&&i.startedAt&&i.box?[{box:i.box,from:i.startedAt,to:Date.now()+1e12}]:[],u=[...sm(n),...l],d={windows:[...kp(n),...xp(n,ra),...a,...u]},m=(await Promise.all(n.boxes.map(k=>Il(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=qa(k.text);return V?V.kind==="suppress"?null:{...k,note:V,text:dg(V)}:k}).filter(Boolean).filter(k=>!(k.role==="user"&&r.has((k.text||"").trim()))).filter(k=>!sy(k)).filter(k=>!ay(k)).filter(k=>!ry(k)).filter(k=>!Eu(k)).filter(k=>k.role!=="assistant"||!!(k.text||"").trim()||!!k.images?.length).filter(k=>!Sp(k,d)),y=g.filter(k=>k.failed&&k.text===Sl);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(Mi=>Mi[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=>Kh(k,e)));await lg(n,b.flat()),await ig(n,g),await Rw(n),await fm(n,g).catch(()=>{});for(let k of n.boxes)k.agent==="codex"&&k.state==="live"&&await mg(n,k),k.agent==="cursor"&&k.state==="live"&&await yg(n,k);let S=await Promise.all(n.boxes.map(k=>qh(k,e)));await cg(n,S.flat().filter(k=>!Ru(k.box,k.ts,u)));let E=await D(n),M=c&&i.viewId?i.viewId:null,J=Lw(E),$=Jo(E).filter(k=>!(k.kind==="message"&&(sy(k)||ay(k)||ry(k)||Eu(k)))&&!(k.kind==="view-card"&&M&&k.viewId===M)),j=Fw($,J),v=$.filter(k=>!(k.kind==="message"&&k.role==="assistant"&&!j.has(k)&&Ru(k.box,k.ts,J))),C=au(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=>!Ru(k.anchorBox,k.anchorTs??k.ts,u)),q=[...v,...C].filter(k=>k.ts!=null).sort((k,V)=>k.ts-V.ts),rt=jI(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)&&!iy(n.id).length&&(K=Ll(st,{leadBoxId:w.name,provider:w.agent,isAnswered:k=>E.some(V=>V.type==="prompt.answered"&&V.payload?.promptId===k)})),K&&$w(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&&Js(w.agent,A))&&(w.readyAt=Date.now(),await z());let Ot=!!w&&!w.readyAt&&Date.now()-(n.createdAt||0)<DI;if(n.kind==="setup"&&n.setup?.provider&&!n.setup.connectedAt&&w&&Js(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=ua(n.id);V&&(ct(n,"view-spinup",{box:i.box,startedAt:i.startedAt,endedAt:V.finishedAt}),await z())}else if(K||k>rm&&!yt&&!i.viewId||k>Oc){let V=K?"needs-input":k>Oc?"timeout":"no-view";ua(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:vw(w?.agent)},pendingPrompt:K,working:yt,stopping:qI(n.id),leadStarting:Ot,handoff:Vt,view:Zt,queued:iy(n.id).filter(k=>k.kind!=="ship-notify"&&k.kind!=="prompt-answer")}}function jI(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 yy(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=au(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 Du(t,{promptId:e,answers:n,text:r}={}){let o=et(await W(),t);if(!o)throw new Error(`track "${t}" not found`);Fu(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 Pl(s);if(i||(i=await LI(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=lw(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=nw(i,n||[]);await bt(o.leadBox,d,{kind:"answer-prompt"}),c={mode:"keys",value:d.join(" "),count:d.length}}else{let d=Kf(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||""}));Yl(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(()=>{}),Yl(o,s,i,c),{ok:!0,promptId:i.promptId,sent:c.count}}async function LI(t,e){if(!e)return null;let n=await D(t).catch(()=>[]);return Ll(Jo(n),{leadBoxId:e.name,provider:e.agent,isAnswered:r=>n.some(o=>o.type==="prompt.answered"&&o.payload?.promptId===r)})}async function by(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 Pl(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=rw(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 FI=1500,rs=new Map;function ju(t){let e=rs.get(t);return e||(e={items:[],timer:null,flushing:!1,idleSince:null},rs.set(t,e)),e}function iy(t){return(rs.get(t)?.items??[]).map(e=>({...e}))}function ai(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 ky(t){let e=ju(t);e.timer||(e.timer=setInterval(()=>UI(t).catch(()=>{}),FI))}async function UI(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){ai(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(zs(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 xy(n,s,o,i.text,i.kind??null,i.attachments??null),await z()}finally{e.flushing=!1}}}async function xy(t,e,n,r,o=null,s=null){let a=ti.get(t.id)||0,i=r,c=!1;t.pendingHandoffHydrate&&(i=AI(r,t.pendingHandoffHydrate.artifact||si),delete t.pendingHandoffHydrate,c=!0);let l=Array.isArray(s)?s.filter(f=>f&&f.rel):[];if(l.length){let f=ny(l.map(d=>d.rel));i=i.trim()?`${i.trim()}
|
|
166
166
|
|
|
167
|
-
${
|
|
167
|
+
${f}`:f}let u=ct(t,"said",{box:t.leadBox,agent:e?.agent,boxRole:e?.role,text:r,...o?{kind:o}:{},...l.length?{attachments:l}:{}});delete t.draft;try{if(await tn({cwd:n.cwd,sessionId:n.sessionId,sessionName:t.leadBox,agent:n.agent,agentCmd:ls(n.agent)}),(ti.get(t.id)||0)!==a){!o&&r.trim()&&(t.draft={text:[t.draft?.text,r].filter(Boolean).join(`
|
|
168
168
|
|
|
169
|
-
|
|
169
|
+
`),updatedAt:Date.now()},await z());let f=new Error("delivery stopped \u2014 the message was handed back as the draft");throw f.interrupted=!0,f}await St(t.leadBox,i,{..._e(e?.agent),kind:c?"lead-handoff-first-prompt":"say"})}catch(f){throw ct(t,"unsaid",{box:t.leadBox,agent:e?.agent,boxRole:e?.role,saidTs:u.at,text:r,...f?.interrupted?{reason:"interrupted"}:{}}),f}await eR(t,e,i)||(await Ey(t.leadBox,e?.agent,i.length+8).catch(()=>{}),ct(t,"unsaid",{box:t.leadBox,agent:e?.agent,boxRole:e?.role,saidTs:u.at,text:r,reason:"unsubmitted"}),o||(t.draft={text:r,updatedAt:Date.now()})),c&&await U({flow:"lead-handoff",phase:"first-prompt",trackId:t.id,sessionName:t.leadBox,agent:e?.agent,userTextLen:r.length})}function Sy(t,{actions:e}={}){ju(t).items.push({id:cs(),text:Uw(e),ts:Date.now(),kind:"ship-notify"}),ky(t)}async function oe(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"||!Mr(e))&&!o.length)throw new Error("text (string) or an attachment required");let s=et(await W(),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`);Fu(t,"say");let a=await Z(s.leadBox);if(!a&&await Wu(s)&&(await z(),a=await Z(s.leadBox)),!a)throw new Error(`lead box "${s.leadBox}" is not running`);let i=s.boxes.find(d=>d.name===s.leadBox),c=Mr(e),l=ju(t),u=await ft(s.leadBox,{lines:xt}).catch(()=>""),f=l.items.length>0||zs(i?.agent,u);if(!f&&Cr(i?.agent,u)){let d=await Ze(s.leadBox).catch(()=>u);f=!!Vn(i,d)}if(f){let d={id:cs(),text:c,ts:Date.now(),...n?{kind:n}:{},...o.length?{attachments:o}:{}};return l.items.push(d),ky(t),delete s.draft,await z(),{ok:!0,leadBox:s.leadBox,queued:!0,id:d.id,position:l.items.length}}return await xy(s,i,a,c,n,o),await z(),{ok:!0,leadBox:s.leadBox}}var cy="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 vy(t,{hint:e}={}){let n=et(await W(),t);if(!n)throw new Error(`track "${t}" not found`);if(n.archivedAt)throw new Error(`track "${t}" is archived \u2014 restore it first`);let r=Cc(n.id);if(r&&!r.finishedAt)throw new Error("a view is already being spun up for this Track");let o=n.boxes.find(i=>i.name===n.leadBox),s=String(e||"").trim()?String(e).trim().slice(0,40):"preview";om(n.id,{name:s,box:n.leadBox});let a=String(e||"").trim()?`${cy}
|
|
170
170
|
|
|
171
|
-
|
|
171
|
+
What to preview: ${String(e).trim()}`:cy;try{await oe(t,a,{kind:"spin-up-view"})}catch(i){throw ua(n.id,{error:"drive-failed"}),i}return{ok:!0,running:!0}}async function Lu(t){let e=et(await W(),t);if(!e)throw new Error(`track "${t}" not found`);if(e.archivedAt)throw new Error(`track "${t}" is archived`);ti.set(t,(ti.get(t)||0)+1);let n=e.boxes.find(g=>g.name===e.leadBox),r=ai(t),o=os.get(t);if(o&&!o.canceled){o.clicks++;let g=r.filter(b=>b.text&&!b.kind).map(b=>b.text),y=null;return g.length&&(y=[e.draft?.text,...g].filter(Boolean).join(`
|
|
172
|
+
|
|
173
|
+
`),e.draft={text:y,updatedAt:Date.now()},await z()),{ok:!0,interrupted:!0,stopped:!1,stopping:!0,dropped:r.length,restored:y}}let s=Date.now(),a=await Z(e.leadBox),i=a?await zI(e.leadBox,n?.agent):{stopped:!1,sends:0},c=a&&i.stopped?await YI(e,n):null,l=!c&&a&&i.stopped?await Ay(e,n):null,u=a?await JI(e).catch(()=>null):null,f=r.filter(g=>g.text&&!g.kind).map(g=>g.text),d=c||l;f.length&&(d=[d,...f].filter(Boolean).join(`
|
|
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?KI(e,n,{startedAt:s,syncSends:i.sends,outcome:p}):await Ty(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 HI=1e3,WI=12,os=new Map,ti=new Map;function qI(t){let e=os.get(t);return!!e&&!e.canceled}function Fu(t,e){let n=os.get(t);n&&!n.canceled&&(n.canceled=!0,n.cancelReason=e||null)}function KI(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<WI&&!s.canceled&&(await Zn(HI),!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 Ay(l,u))}catch{}let c=s.canceled?"canceled":a?"stopped-late":"gave-up";await Ty(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 Ty(t,e,n){await R(t,{type:"turn.interrupted",actor:"user",source:"orchestrator",boxId:t.leadBox,provider:e?.agent||null,payload:n}).catch(()=>{})}var GI=[300,350,400],VI=3;async function zI(t,e){let n=0;for(let r=0;r<VI;r++){await bt(t,["Escape"],{kind:"interrupt"}),n++;for(let o of GI){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 JI(t){let e=ku(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 XI=[250,400,500,600];async function YI(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 XI){await Zn(r);let o=await ft(t.leadBox,{lines:xt}).catch(()=>""),s=qo(e.agent,o);if(s&&Bl(n.text,s))return await Ey(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 ZI=3e4,QI=[350,650,900];async function Ay(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>ZI||n.some(s=>s.type==="unsaid"&&s.saidTs===r.at)||!qt(r.text))return null;for(let s of QI){await Zn(s);let a;try{a=await Il(e,{})}catch{return null}if(sw(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 tR=[250,450,700],$u=2;async function eR(t,e,n){if(e?.agent!=="claude")return!0;for(let r=0;r<=$u;r++){let o=!1;for(let s of tR){await Zn(s);let a=await ft(t.leadBox,{lines:xt}).catch(()=>""),i=aw(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<$u&&(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:$u,textHead:n.slice(0,120)}).catch(()=>{}),!1}async function Ey(t,e,n){for(let r=0;r<3;r++){await Ws(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 Iy(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=Tw(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 Aw(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 Ry(t){for(let e of t){if(!e||!await Ma(e))continue;let n=await fw(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 Wl(n.dir,{ignorePlumbing:!0}).catch(()=>!1),o=await ql(n.dir,n.baseRef).catch(()=>0);(r||o>0)&&e.push({...n,dirty:r,ahead:o})}return e}async function $y(t){let e=t;typeof e=="string"&&!F.isAbsolute(e)&&/^[^/\s]+\/[^/\s]+$/.test(e)&&(e=await ao(e)??await Hi(e));let n=await Ry([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 nR(t,e,n,r){let o=`track/${Gu(e)}-${t}`,s=[];try{await L.mkdir(n,{recursive:!0});let a=new Set([".bostrat"]);for(let i of r){let c=await $y(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 Na(c);await Nr(c,f,o,d),s.push({name:u,dir:f,base:c,baseRef:d,repo:l})}return await ii(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 Oy=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 Cy(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 $y(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 Na(s);await pw(s,o.branch)?await mw(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 ii(o.dir,o.repos,o.branch),ct(r,"retagged",{action:"add",repo:a??s,name:c,workspace:Oy(o)}),await z(),f}async function My(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 Wl(s.dir,{ignorePlumbing:!0}).catch(()=>!1),i=await ql(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 ii(o.dir,o.repos,o.branch),ct(r,"retagged",{action:"remove",repo:s.repo??s.base,name:s.name,workspace:Oy(o)}),await z(),{removed:s.name}}async function rR(t,e,{repo:n,repos:r,cwd:o}={}){await L.mkdir(Mu,{recursive:!0});let s=F.join(Mu,t),a=Array.isArray(r)?r.map(c=>typeof c=="string"?c.trim():"").filter(Boolean):[];if(a.length>=2)return nR(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 Hi(n));let i=await Ry([n,o,vI]);if(i){let c=`track/${Gu(e)}-${t}`;try{let l=await Na(i);await Nr(i,s,c,l),await Nu(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 hw(s),await Nu(s),{dir:s,base:null,branch:null,kind:"repo"}}var ss="# bostrat workspace",Ny=`${ss}
|
|
172
176
|
|
|
173
177
|
TRACK MEMORY: if \`.bostrat/memory/MEMORY.md\` exists in your workspace, read it
|
|
174
178
|
before substantive work \u2014 it is the shared cross-provider memory for this repo
|
|
@@ -335,9 +339,9 @@ which steps to run (and which to skip). An offer buried mid-message, phrased as
|
|
|
335
339
|
statement, or omitted entirely renders no controls \u2014 so close EVERY completed
|
|
336
340
|
implementation turn this way, every time. When the operator has already asked you
|
|
337
341
|
to commit / push / open the PR, just do it; do not re-offer.
|
|
338
|
-
`,
|
|
339
|
-
`))
|
|
340
|
-
`);return`${
|
|
342
|
+
`,oR=Ny,es=Ny+uw;async function Nu(t){try{await L.writeFile(F.join(t,"AGENTS.md"),es,{flag:"wx"})}catch{return}await iR(t,["/AGENTS.md"])}var Ou=t=>typeof t=="string"&&(t===ss||t.startsWith(`${ss}
|
|
343
|
+
`)),sR="# bostrat multi-repo workspace";function aR(t,e){let n=t.map(r=>`- \`${r.name}/\`${r.repo?` \u2014 github.com/${r.repo}`:""}${r.baseRef?` (forked from ${r.baseRef})`:""}`).join(`
|
|
344
|
+
`);return`${sR}
|
|
341
345
|
|
|
342
346
|
This directory is NOT a git repo. It is a bostrat Track workspace holding one
|
|
343
347
|
git worktree per tagged repo, ALL on the same branch \`${e}\`:
|
|
@@ -349,25 +353,25 @@ push / open PRs) in each repo separately, always on that shared branch. Work
|
|
|
349
353
|
only needs to land in the repos it actually touches \u2014 an untouched repo's
|
|
350
354
|
branch simply stays at its fork point and never gets pushed. When asked to run
|
|
351
355
|
or preview an app, run it from that app's own repo directory.
|
|
352
|
-
`}async function
|
|
356
|
+
`}async function ii(t,e,n){let r=aR(e,n);for(let o of["CLAUDE.md","AGENTS.md"])await L.writeFile(F.join(t,o),r).catch(()=>{})}var Cu=es.replace(`${ss}
|
|
353
357
|
`,`${ss}
|
|
354
358
|
|
|
355
359
|
(Global copy managed by bostrat \u2014 rewritten on service start; edits will be lost.)
|
|
356
360
|
SCOPE: these instructions apply ONLY when your working directory is under
|
|
357
361
|
~/.bostrat/workspaces/ or ~/.bostrat/worktrees/ \u2014 a bostrat-orchestrated box.
|
|
358
362
|
When working anywhere else, ignore everything below.
|
|
359
|
-
`);async function
|
|
360
|
-
`).includes(s));o.length&&await
|
|
363
|
+
`);async function Py(t){let{stdout:e}=await xI("git",["-C",t,"rev-parse","--git-path","info/exclude"]);return F.resolve(t,e.trim())}async function iR(t,e){try{let n=await Py(t);await L.mkdir(F.dirname(n),{recursive:!0});let r=await L.readFile(n,"utf8").catch(()=>""),o=e.filter(s=>!r.split(`
|
|
364
|
+
`).includes(s));o.length&&await L.appendFile(n,(r.endsWith(`
|
|
361
365
|
`)||!r?"":`
|
|
362
366
|
`)+o.join(`
|
|
363
367
|
`)+`
|
|
364
|
-
`)}catch{}}async function
|
|
368
|
+
`)}catch{}}async function cR(t,e){try{let n=await Py(t),r=await L.readFile(n,"utf8"),o=r.split(`
|
|
365
369
|
`).filter(s=>s!==e);o.length!==r.split(`
|
|
366
|
-
`).length&&await
|
|
367
|
-
`))}catch{}}async function
|
|
370
|
+
`).length&&await L.writeFile(n,o.join(`
|
|
371
|
+
`))}catch{}}async function By(){let t={refreshed:0,removed:0,codexGlobal:null};try{await L.mkdir(We,{recursive:!0}),await L.writeFile(F.join(We,"CLAUDE.md"),oR).catch(()=>{}),await L.writeFile(F.join(We,"AGENTS.md"),es).catch(()=>{});let e=process.env.CODEX_HOME||F.join(process.env.HOME||"/tmp",".codex");if((await L.stat(e).catch(()=>null))?.isDirectory()){let r=F.join(e,"AGENTS.md"),o=await L.readFile(r,"utf8").catch(()=>null);o===null?(await L.writeFile(r,Cu,{flag:"wx"}).catch(()=>{}),t.codexGlobal="created"):Ou(o)&&o!==Cu&&(await L.writeFile(r,Cu).catch(()=>{}),t.codexGlobal="refreshed")}for(let r of[Mu,Kr]){let o=await L.readdir(r,{withFileTypes:!0}).catch(()=>[]);for(let s of o){if(!s.isDirectory())continue;let a=F.join(r,s.name),i=F.join(a,"AGENTS.md"),c=await L.readFile(i,"utf8").catch(()=>null);c!==null&&Ou(c)&&c!==es&&(await L.writeFile(i,es).catch(()=>{}),t.refreshed++);let l=F.join(a,"CLAUDE.md"),u=await L.readFile(l,"utf8").catch(()=>null);u!==null&&Ou(u)&&(await L.rm(l,{force:!0}).catch(()=>{}),await cR(a,"/CLAUDE.md"),t.removed++)}}}catch{}return t}async function lR(t){let e=t.workspace;if(!(!e||!e.dir))try{if(e.kind==="multi"){for(let n of Dt(e))n.base&&n.dir&&await Xt(n.base,n.dir).catch(()=>{});await L.rm(e.dir,{recursive:!0,force:!0})}else e.kind==="worktree"&&e.base?await Xt(e.base,e.dir):await L.rm(e.dir,{recursive:!0,force:!0})}catch{}}async function Uu({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 oi(o,s),i=await W(),c=cs().slice(0,8),l=await rR(c,t,{repo:n,repos:r,cwd:e}),u=new Set(i.tracks.flatMap(m=>(m.boxes||[]).map(g=>g.name))),f=`${t.trim()} lead`;u.has(ac(f))&&(f=`${t.trim().slice(0,50).trim()} ${c} lead`);let d=await $n(f,l.dir,{agentCmd:a,agent:o});Xs({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:[Gy(d,"lead","live")],events:[],createdAt:Date.now()};return ct(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:g,baseRef:y,repo:b})=>({name:m,base:g,baseRef:y,repo:b}))}:{}}}),i.tracks.push(p),await z(),bn(p)}var uR=new Set(["claude","codex","cursor"]);async function Dy({provider:t="claude",fresh:e=!1}={}){if(!uR.has(t))throw new Error(`unknown setup provider "${t}" (claude | codex | cursor)`);let r=(await W()).tracks.find(u=>u.kind==="setup"&&u.setup?.provider===t&&!u.archivedAt);if(r){if(!e){let u=await bn(r);if(u.boxes.find(d=>d.name===u.leadBox)?.state==="live")return u}await Vr(r.id).catch(()=>{})}let o=(await oi(t,!1)).replace(/\s+--(?:dangerously-skip-permissions|dangerously-bypass-approvals-and-sandbox)\b/g,""),s=cs().slice(0,8),a=F.join(We,"setup",s);await L.mkdir(a,{recursive:!0});let i=await $n(`Connect ${t} ${s}`,a,{agentCmd:o,agent:t,firstRun:!0});t==="codex"&&Xs({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:[Gy(i,"lead","live")],events:[],createdAt:Date.now()};return ct(c,"opened",{agent:t,box:i.name,skipPermissions:!1,trackKind:"setup",name:c.name,cwd:c.cwd,workspace:{base:null,branch:null,kind:"scratch"}}),(await W()).tracks.push(c),await z(),bn(c)}async function jy(t,e,n){await dm(t).catch(()=>{});let r=as.get(t);r&&!r.finishedAt&&(r.error=n,r.finishedAt=Date.now()),as.delete(t),e.handoff&&!e.handoff.finishedAt&&(e.handoff.step="error",e.handoff.error=n,e.handoff.finishedAt=Date.now());for(let a of zr(t))a.finishedAt||(a.canceled=!0,a.finishedAt=Date.now()),await zu(a).catch(()=>{}),re.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 W(),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 ce(a.name).catch(()=>{})}async function Ly(t,{cascadeMocks:e=!0,cascadeRunbooks:n=!1}={}){let r=await W(),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 Gp(s),await jy(t,s,"track deleted"),n?await vm(t).catch(()=>{}):await Sm(t).catch(()=>{}),await _w(),await lR(s),e&&await Vp(t).catch(()=>{}),e?await Yg(t).catch(()=>{}):await Xg(s).catch(()=>{}),ct(s,"track-deleted",{name:s.name,boxes:s.boxes.length,ageMs:s.createdAt?Date.now()-s.createdAt:null}),r.tracks.splice(o,1),await z(),{ok:!0,id:t,name:s.name,boxes:s.boxes.length}}async function Vr(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 already archived`);return n.archivedAt=Date.now(),ct(n,"archived",{boxes:n.boxes.length}),await z(),n.kind==="setup"?await Fy(t):fR(t).catch(()=>{}),bn(n)}var dR=9e4;async function fR(t){try{await pR(t)}catch{}await Fy(t).catch(()=>{})}async function Fy(t){let e=await W(),n=et(e,t);if(!(!n||!n.archivedAt)){await jy(t,n,"track archived");for(let r of n.boxes)Yn.has(r.state)||(r.state="stopped");await z()}}async function pR(t){let e=await W(),n=et(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 Z(n.leadBox).catch(()=>null):null;if(i?.cwd&&a)try{({text:r,items:o}=await yp(n.leadBox,i.cwd,{timeoutMs:dR})),s="lead-cli"}catch{}if(r||(r=cl(n,await D(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 z()}async function Uy(){let t=await W(),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=cl(o,await D(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 z(),{recorded:e,skipped:n,empty:r}}async function hn(t,e){let n=await $t(t,e).catch(()=>null);if(!n?.archiveOnMerge||!n.trackId)return!1;let r=await Hy(n);if(r.length){for(let o of r)await Tr(o.repo,o.number,!0).catch(()=>{});return!1}try{return await Vr(n.trackId),!0}catch(o){if(/already archived/i.test(String(o?.message||o)))return!1;throw o}}async function Hy(t){return(await ye().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 wn(t,e){return Hu(t,e,"merged")}async function Hu(t,e,n="merged"){let r=await $t(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 Hy(r);if(s.length){let i=[...r.notifyPending||[],o];for(let c of s)await lh(c.repo,c.number,i).catch(()=>{});return!1}let a=[...r.notifyPending||[],o];try{return await oe(r.trackId,mR(a),{kind:"merge-notify"}),!0}catch{return!1}}var ly={merged:"merged",closed:"closed on GitHub WITHOUT merging",lost:"no longer found on GitHub (deleted, or its repo left the grant)"};function mR(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 ${ly[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 ${ly[i.outcome]??i.outcome}`).join(`
|
|
368
372
|
`),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}
|
|
369
373
|
${s}
|
|
370
|
-
${a}`}async function Pu(t){if(t.archivedAt)return!1;let e=t.boxes.find(r=>r.name===t.leadBox);if(!e||Jn.has(e.state)||await Y(e.name))return!1;let n=Zg(e.agent,e.sessionId);if(!await js(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),zs({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 _y(t){let e=await q(),n=nt(e,t);if(!n)throw new Error(`track "${t}" not found`);if(!n.archivedAt)throw new Error(`track "${t}" is not archived`);return delete n.archivedAt,ct(n,"unarchived",{}),await Pu(n),await V(),gn(n)}async function Iy(){let t=await q(),e=new Set((await En()).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),!Jn.has(a.state)&&a.state!=="stopped"&&(a.state="stopped",o=!0);return o&&await V(),r}async function Ry(t){let e=await q(),n=nt(e,t);if(!n)throw new Error(`track "${t}" not found`);if(n.archivedAt)throw new Error(`track "${t}" is archived \u2014 restore it before restarting the lead`);let r=n.boxes.find(a=>a.name===n.leadBox);if(!r)throw new Error(`track "${t}" has no lead box`);if(Jn.has(r.state))throw new Error("lead box is terminal \u2014 nothing to restart");if(await Y(n.leadBox)){let a=await gt(n.leadBox,{lines:At}).catch(()=>"");if(Ks(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 Pu(n);if(await V(),!s)throw new Error("rehydrate failed \u2014 see the track's rehydrate-failed event");return gn(n)}var $y=(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 Oy(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)||(Se?nt(Se,t)?.handoff:null)||null;async function ti(t,e){let n=await q(),r=nt(n,t);r&&(r.handoff={...r.handoff||{},...e},await V())}function FI(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 My(){let t=await q(),e=!1;for(let n of t.tracks||[]){let r=n.handoff,o=FI(n);if(!o)continue;e=!0;let s=!!r.toBox&&r.toBox===r.fromBox;if(s&&o.step==="error"){let c=await Y(r.fromBox).catch(()=>null),l=n.boxes.find(u=>u.name===r.fromBox);if(c&&l&&c.agent===r.toAgent&&r.toAgent!==l.agent){let u=Date.now(),f=Oy(l),d=f[f.length-1],m=d.agent,h=d.sessionId??null;d.endedAt=u,r.reason&&(d.endReason=r.reason);let w=c.agent==="claude"&&!!c.sessionId;f.push({agent:r.toAgent,sessionId:c.sessionId||null,pinned:w,startedAt:u,endedAt:null}),l.agent=r.toAgent,l.sessionId=c.sessionId||null,l.pinned=w,l.state="live",n.pendingHandoffHydrate={artifact:oi,at:u},ct(n,"lead-handoff",{from:r.fromBox,to:r.fromBox,fromAgent:m,toAgent:r.toAgent,fromSessionId:h,toSessionId:c.sessionId||null,epoch:f.length-1,singleBox:!0,skipPermissions:r.skipPermissions,reason:r.reason??null,handoffStartedAt:r.startedAt,recovered:!0}),n.handoff={...r,step:"done",finishedAt:u,recovered:"swap adopted from live tmux tags after restart"},await G({flow:"lead-handoff",phase:"reconciled",trackId:n.id,outcome:"done",recovered:n.handoff.recovered,fromBox:r.fromBox,toBox:r.toBox}).catch(()=>{});continue}}let a=!s&&o.recovered==="rolled-back"&&r.toBox&&r.toBox!==n.leadBox?r.toBox:null,i=!s&&o.step==="done"?r.fromBox:null;n.handoff=o,await G({flow:"lead-handoff",phase:"reconciled",trackId:n.id,outcome:o.step,recovered:o.recovered,fromBox:o.fromBox,toBox:o.toBox}).catch(()=>{}),i&&await ce(i).catch(()=>{}),a&&await ce(a).catch(()=>{})}return e&&await V(),e}async function Bu(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=nt(await q(),t);if(!s)throw new Error(`track "${t}" not found`);let a=await ri(e,n),i=await Y(s.leadBox);if(!i)throw new Error(`lead box "${s.leadBox}" is not running`);let c={trackId:t,step:"checkpoint",fromBox:s.leadBox,toAgent:e,skipPermissions:n,reason:Qg(r),toBox:s.leadBox,startedAt:Date.now(),finishedAt:null,error:null};return as.set(t,c),await ti(t,{step:c.step,fromBox:c.fromBox,toAgent:e,toBox:c.toBox,skipPermissions:n,startedAt:c.startedAt,finishedAt:null,error:null}),UI(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 ti(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 UI(t,e,n,r,o){let s=async(A,C={})=>{t.step=A,console.log(`[lead-handoff ${t.trackId}] step \u2192 ${A}`),await ti(t.trackId,{step:A,toBox:t.toBox}),await G({flow:"lead-handoff",phase:A,trackId:t.trackId,fromBox:t.fromBox,toBox:t.toBox,toAgent:o,sessionName:C.sessionName,...C})};console.log(`[lead-handoff ${t.trackId}] ${t.fromBox}: ${n.agent} \u2192 ${o} IN PLACE`);let a=await q(),i=nt(a,t.trackId)?.boxes.find(A=>A.name===t.fromBox)||null,c=i?.agent||n.agent,l=i?.sessionId??n.sessionId??null,u=i?.pinned===!0;await s("checkpoint",{sessionName:t.fromBox}),await rp(t.fromBox),await s("swap",{cwd:n.cwd,agentCmd:r});let f=await rl(t.fromBox,{agentCmd:r,agent:o});await s("hydrate",{sessionName:t.fromBox}),await Qe({cwd:n.cwd,sessionId:f.sessionId,sessionName:t.fromBox,agent:o,agentCmd:ls(o)});let d=await gt(t.fromBox,{lines:At}).catch(()=>"");if(!Gs(o,d)){await s("rollback",{sessionName:t.fromBox,fromAgent:c});let A=Zg(c,l,{skipPermissions:t.skipPermissions});await rl(t.fromBox,{agentCmd:A,agent:c,resumeSessionId:l,pinned:u});let C=await q(),z=nt(C,t.trackId);if(z){let O=z.boxes.find(F=>F.name===t.fromBox);O&&(O.state="live"),ct(z,"lead-handoff-failed",{box:t.fromBox,fromAgent:c,toAgent:o,reason:t.reason,error:`incoming ${o} CLI never drew its composer`,rolledBack:!0,handoffStartedAt:t.startedAt}),await V()}throw new Error(`incoming ${o} CLI never drew its composer \u2014 rolled back to ${c} (--resume, context intact)`)}await s("commit");let m=await q(),h=nt(m,t.trackId);if(!h)throw new Error(`track "${t.trackId}" not found during handoff commit \u2014 was it deleted or archived mid-flight?`);let w=h.boxes.find(A=>A.name===t.fromBox);if(!w)throw new Error(`lead box row "${t.fromBox}" missing during handoff commit`);let g=Date.now(),b=Oy(w),S=b[b.length-1];S.endedAt=g,t.reason&&(S.endReason=t.reason),b.push({agent:o,sessionId:f.sessionId,pinned:f.pinned,startedAt:g,endedAt:null}),w.agent=o,w.sessionId=f.sessionId,w.pinned=f.pinned,w.state="live",w.role="lead",h.pendingHandoffHydrate={artifact:oi,at:g},ct(h,"lead-handoff",{from:t.fromBox,to:t.fromBox,fromAgent:c,toAgent:o,fromSessionId:l,toSessionId:f.sessionId,epoch:b.length-1,singleBox:!0,forced:f.forced||void 0,skipPermissions:t.skipPermissions,reason:t.reason,handoffStartedAt:t.startedAt}),await V(),await s("done",{sessionName:t.fromBox}),await ti(t.trackId,{finishedAt:Date.now(),error:null}),t.finishedAt=Date.now()}var HI=Number(process.env.BOSTRAT_SUBTASK_TIMEOUT_MS)||18e5,WI=Number(process.env.BOSTRAT_SUBTASK_POLL_MS)||3e3,Hr=H.join(We,"worktrees"),Fr=[rs,Qa],re=new Map;var Kr=t=>{let e=new Map([...re.values()].filter(r=>r.trackId===t).map(r=>[r.id,r])),n=Se?nt(Se,t):null;if(n?.subtasks)for(let[r,o]of Object.entries(n.subtasks))e.has(r)||e.set(r,o);return[...e.values()].sort((r,o)=>r.startedAt-o.startedAt)},Du=t=>({id:t.id,trackId:t.trackId,trackName:t.trackName,agent:t.agent,task:t.task,skipPermissions:t.skipPermissions,reason:t.reason||null,step:t.step,failedStep:t.failedStep||null,box:t.box,branch:t.branch,base:t.base,repos:t.repos||null,dir:t.dir,stat:t.stat,files:t.files||null,conflicts:t.conflicts||[],applied:t.applied,error:t.error,canceled:t.canceled,startedAt:t.startedAt,finishedAt:t.finishedAt});async function qI(t,e,n){let r=await q(),o=nt(r,t);o&&(o.subtasks=o.subtasks||{},o.subtasks[e]={...o.subtasks[e]||{},...n},await V())}var Wr=t=>qI(t.trackId,t.id,Du(t));async function Ur(t,e){t.step=e,await Wr(t)}function KI(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 GI(t){return!!t&&!t.finishedAt&&!t.canceled&&t.step==="working"&&!!t.box&&!!t.dir&&!!t.base&&!!t.branch}async function zI(t,e,n){if(!GI(n)||re.has(e)||!await Y(n.box).catch(()=>null))return!1;try{await U.access(n.dir)}catch{return!1}let o=await Y(t.leadBox).catch(()=>null);if(!o?.cwd)return!1;let s={...n,id:e,leadBox:t.leadBox,step:"working",canceled:!1,forceMerge:!1};return re.set(e,s),Dy(s,o).catch(a=>Py(s,a)),!0}async function Cy(){let t=await q(),e=!1;for(let n of t.tracks||[]){for(let[r,o]of Object.entries(n.subtasks||{})){if(re.has(r))continue;if(await zI(n,r,o)){e=!0,ct(n,"subtask-resumed",{box:o.box,agent:o.agent,task:o.task,subtaskId:r,resumedStep:o.step}),await G({flow:"subtask",phase:"resumed",trackId:n.id,subtaskId:r,box:o.box,branch:o.branch}).catch(()=>{});continue}let s=o.step,a=KI(o);if(!a)continue;e=!0,n.subtasks[r]=a;let i=n.boxes.find(c=>c.subtaskId===r);i&&!Jn.has(i.state)&&(i.state="canceled"),ct(n,"subtask-failed",{from:o.box,fromAgent:o.agent,task:o.task,subtaskId:r,failedStep:s,error:a.error,recovered:a.recovered,durationMs:o.startedAt?a.finishedAt-o.startedAt:null}),await G({flow:"subtask",phase:"reconciled",trackId:n.id,subtaskId:r,outcome:a.step,recovered:a.recovered,box:o.box,branch:o.branch}).catch(()=>{}),await Xg(n,o.box,o.dir)}for(let r of n.boxes){if(r.role!=="subtask"||Jn.has(r.state)||re.has(r.subtaskId)||n.subtasks?.[r.subtaskId])continue;e=!0,r.state="canceled";let o=r.subtaskId?H.join(Hr,`${n.id}-${r.subtaskId}`):null;await G({flow:"subtask",phase:"reaped-legacy-orphan",trackId:n.id,subtaskId:r.subtaskId,box:r.name}).catch(()=>{}),await Xg(n,r.name,o)}}return e=await VI(t)||e,e&&await V(),e}async function Xg(t,e,n){if(e&&await ce(e).catch(()=>{}),!n)return;if(t.workspace?.kind==="multi"){for(let o of Bt(t.workspace)){let s=H.join(n,o.name);await Vt(o.base,s).then(()=>!0,()=>!1)||await Vt(o.dir,s).catch(()=>{})}await U.rm(n,{recursive:!0,force:!0}).catch(()=>{});return}let r=await Y(t.leadBox).catch(()=>null);r?.cwd&&await Vt(r.cwd,n).catch(()=>{})}async function VI(t){let e;try{e=await U.readdir(Hr)}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(H.basename(o.dir));let r=!1;for(let o of e){if(n.has(o))continue;let s=H.join(Hr,o),a=(t.tracks||[]).find(i=>o.startsWith(`${i.id}-`));if(a?.workspace?.kind==="multi"){for(let i of Bt(a.workspace))await Vt(i.base,H.join(s,i.name)).catch(()=>{});await U.rm(s,{recursive:!0,force:!0}).catch(()=>{})}else{let i=a?await Y(a.leadBox).catch(()=>null):null;i?.cwd?await Vt(i.cwd,s).catch(()=>{}):await U.rm(s,{recursive:!0,force:!0}).catch(()=>{})}await G({flow:"subtask",phase:"swept-orphan-worktree",dir:s}).catch(()=>{}),r=!0}return r}var ju=t=>t.trim().replace(/\s+/g,"-").replace(/[^A-Za-z0-9._-]/g,"").toLowerCase()||"track",is=class extends Error{};async function Ny(t,{agent:e,task:n,skipPermissions:r=!0,reason:o=null}={}){if(typeof n!="string"||!$r(n))throw new Error("task (string) required");let s=await ri(e,r),a=nt(await q(),t);if(!a)throw new Error(`track "${t}" not found`);let i=await Y(a.leadBox);if(!i)throw new Error(`lead box "${a.leadBox}" is not running`);if(!i.cwd)throw new Error("can't resolve the lead's working directory");let c=a.workspace?.kind==="multi",l=null;if(c){let d=Bt(a.workspace);if(!d.length)throw new Error("this multi-repo Track has no tagged repos to delegate into");for(let m of d)if(!m?.dir||!await Ca(m.dir))throw new Error(`tagged checkout "${m?.name??"?"}" is missing from the lead workspace (${m?.dir??"no dir"})`);l=d.map(m=>({name:m.name,leadDir:m.dir,base:null}))}else if(!await Ca(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:$r(n),skipPermissions:r,reason:Qg(o),step:"snapshot",leadBox:a.leadBox,box:null,branch:`bostrat/${ju(a.name)}-${u}`,base:null,repos:l,dir:H.join(Hr,`${t}-${u}`),stat:null,conflicts:[],applied:null,startedAt:Date.now(),finishedAt:null,error:null,canceled:!1,forceMerge:!1};return re.set(u,f),await Wr(f),JI(f,i,s).catch(d=>Py(f,d)),f}async function Py(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 Fu(t).catch(()=>{}),await Wr(t).catch(()=>{}),await QI(t).catch(()=>{})}function Lu(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 By(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 JI(t,e,n){if(await Ur(t,"snapshot"),t.repos){for(let s of t.repos)s.base=await zo(s.leadDir,`bostrat: snapshot for subtask ${t.id}`);await Wr(t)}else t.base=await zo(e.cwd,`bostrat: snapshot for subtask ${t.id}`);if(await Ur(t,"spawn"),await U.mkdir(Hr,{recursive:!0}),t.repos){await U.mkdir(t.dir,{recursive:!0});for(let a of t.repos)await Or(a.leadDir,H.join(t.dir,a.name),t.branch,a.base);let s=Bt((nt(await q(),t.trackId)||{}).workspace);await ai(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 Or(e.cwd,t.dir,t.branch,t.base),await _u(t.dir);await XI(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 YI(t,o),await ty(t.trackId,o.name,eI(t.task,!!t.repos),{kind:"subtask"}),await Dy(t,e)}async function Dy(t,e){await Ur(t,"working");let n=H.join(t.dir,Qa);if(await eR(n,t),t.canceled)throw new is;if(await Ur(t,"merge"),t.repos){let o={files:0,insertions:0,deletions:0},s=[],a=[],i=!0;for(let c of t.repos){let l=H.join(t.dir,c.name);await Pl(l,`bostrat: subtask ${t.id} result (auto-committed)`),c.stat=await Ll(c.leadDir,c.base,t.branch,Fr),c.files=await Fl(c.leadDir,c.base,t.branch,Fr);let u=await Ul(c.leadDir,c.base,t.branch,Fr);c.applied=u.applied,c.conflicts=u.conflicts??[],o.files+=c.stat.files,o.insertions+=c.stat.insertions,o.deletions+=c.stat.deletions,s.push(...c.files.map(f=>H.join(c.name,f))),a.push(...c.conflicts.map(f=>f.startsWith("patch did not apply cleanly")?`${c.name}: ${f}`:H.join(c.name,f))),u.applied||(i=!1)}t.stat=o,t.files=s,t.conflicts=a,t.applied=i}else{await Pl(t.dir,`bostrat: subtask ${t.id} result (auto-committed)`),t.stat=await Ll(e.cwd,t.base,t.branch,Fr),t.files=await Fl(e.cwd,t.base,t.branch,Fr);let o=await Ul(e.cwd,t.base,t.branch,Fr);t.applied=o.applied,t.conflicts=o.conflicts}await Wr(t);let r=await tR(t,e,n);await Ur(t,"resume"),await ty(t.trackId,t.leadBox,nI(t.task,t.stat,t.conflicts,r),{kind:"subtask-resume"}),await Ur(t,"reap"),await Fu(t),t.step="done",t.finishedAt=Date.now(),await ZI(t),await Wr(t)}async function XI(t,e){let n=H.join(t.dir,H.dirname(rs));await U.mkdir(n,{recursive:!0});let r="";try{r=await ff(e.cwd,e.sessionId)}catch{}if(!r)try{r=await U.readFile(H.join(e.cwd,".bostrat/handoff/latest.md"),"utf8")}catch{r="_(no prior conversation digest available \u2014 rely on the working tree.)_"}let o=`# Lead context for subtask ${t.id}
|
|
374
|
+
${a}`}async function Wu(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=fy(e.agent,e.sessionId);if(!await Us(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),Xs({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 Wy(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 Wu(n),await z(),bn(n)}async function qy(){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 Ky(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(zs(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 Wu(n);if(await z(),!s)throw new Error("rehydrate failed \u2014 see the track's rehydrate-failed event");return bn(n)}var Gy=(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 Vy(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 ei(t,e){let n=await W(),r=et(n,t);r&&(r.handoff={...r.handoff||{},...e},await z())}function hR(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 zy(){let t=await W(),e=!1;for(let n of t.tracks||[]){let r=n.handoff,o=hR(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=Vy(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:si,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 qu(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`);Fu(t,"handoff");let a=await oi(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:py(r),toBox:s.leadBox,startedAt:Date.now(),finishedAt:null,error:null};return as.set(t,c),await ei(t,{step:c.step,fromBox:c.fromBox,toAgent:e,toBox:c.toBox,skipPermissions:n,startedAt:c.startedAt,finishedAt:null,error:null}),wR(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 ei(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 wR(t,e,n,r,o){let s=async(E,M={})=>{t.step=E,console.log(`[lead-handoff ${t.trackId}] step \u2192 ${E}`),await ei(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 Ap(t.fromBox),await s("swap",{cwd:n.cwd,agentCmd:r});let f=await ul(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(!Js(o,d)){await s("rollback",{sessionName:t.fromBox,fromAgent:c});let E=fy(c,l,{skipPermissions:t.skipPermissions});await ul(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=Vy(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:si,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 ei(t.trackId,{finishedAt:Date.now(),error:null}),t.finishedAt=Date.now()}var gR=Number(process.env.BOSTRAT_SUBTASK_TIMEOUT_MS)||18e5,yR=Number(process.env.BOSTRAT_SUBTASK_POLL_MS)||3e3,Kr=F.join(We,"worktrees"),Wr=[ns,Qa],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)},Ku=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 bR(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=>bR(t.trackId,t.id,Ku(t));async function qr(t,e){t.step=e,await Gr(t)}function kR(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 xR(t){return!!t&&!t.finishedAt&&!t.canceled&&t.step==="working"&&!!t.box&&!!t.dir&&!!t.base&&!!t.branch}async function SR(t,e,n){if(!xR(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),Qy(s,o).catch(a=>Yy(s,a)),!0}async function Jy(){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 SR(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=kR(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 uy(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 uy(n,r.name,o)}}return e=await vR(t)||e,e&&await z(),e}async function uy(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 vR(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 Gu=t=>t.trim().replace(/\s+/g,"-").replace(/[^A-Za-z0-9._-]/g,"").toLowerCase()||"track",is=class extends Error{};async function Xy(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 oi(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 Ma(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 Ma(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:py(o),step:"snapshot",leadBox:a.leadBox,box:null,branch:`bostrat/${Gu(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),TR(f,i,s).catch(d=>Yy(f,d)),f}async function Yy(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 zu(t).catch(()=>{}),await Gr(t).catch(()=>{}),await IR(t).catch(()=>{})}function Vu(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 Zy(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 TR(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 ii(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 Nu(t.dir);await AR(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 ER(t,o),await my(t.trackId,o.name,_I(t.task,!!t.repos),{kind:"subtask"}),await Qy(t,e)}async function Qy(t,e){await qr(t,"working");let n=F.join(t.dir,Qa);if(await $R(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 Hl(l,`bostrat: subtask ${t.id} result (auto-committed)`),c.stat=await Gl(c.leadDir,c.base,t.branch,Wr),c.files=await Vl(c.leadDir,c.base,t.branch,Wr);let u=await zl(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 Hl(t.dir,`bostrat: subtask ${t.id} result (auto-committed)`),t.stat=await Gl(e.cwd,t.base,t.branch,Wr),t.files=await Vl(e.cwd,t.base,t.branch,Wr);let o=await zl(e.cwd,t.base,t.branch,Wr);t.applied=o.applied,t.conflicts=o.conflicts}await Gr(t);let r=await RR(t,e,n);await qr(t,"resume"),await my(t.trackId,t.leadBox,II(t.task,t.stat,t.conflicts,r),{kind:"subtask-resume"}),await qr(t,"reap"),await zu(t),t.step="done",t.finishedAt=Date.now(),await _R(t),await Gr(t)}async function AR(t,e){let n=F.join(t.dir,F.dirname(ns));await L.mkdir(n,{recursive:!0});let r="";try{r=await Nf(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}
|
|
371
375
|
|
|
372
376
|
**Delegated task:** ${t.task}
|
|
373
377
|
|
|
@@ -378,49 +382,50 @@ ${a}`}async function Pu(t){if(t.archivedAt)return!1;let e=t.boxes.find(r=>r.name
|
|
|
378
382
|
## Conversation so far (lead \u2192 you)
|
|
379
383
|
|
|
380
384
|
${r}
|
|
381
|
-
`;await
|
|
382
|
-
${n}`}},{type:"section",text:{type:"mrkdwn",text:r}},{type:"context",elements:[{type:"mrkdwn",text:`<${
|
|
385
|
+
`;await L.writeFile(F.join(t.dir,ns),o)}async function ER(t,e){let n=await W(),r=et(n,t.trackId);r&&(r.boxes.push({name:e.name,agent:e.agent,role:"subtask",state:"live",cwd:e.cwd,sessionId:e.sessionId,hookTokenHash:oo(e.hookToken),createdAt:Date.now(),subtaskId:t.id,branch:t.branch,task:t.task}),ct(r,"subtask-delegated",{to:e.name,toAgent:t.agent,task:t.task,subtaskId:t.id,skipPermissions:t.skipPermissions,reason:t.reason}),r.subtasks=r.subtasks||{},r.subtasks[t.id]={...r.subtasks[t.id]||{},...Ku(t)},await z())}async function _R(t){let e=await W(),n=et(e,t.trackId);if(!n)return;let r=n.boxes.find(o=>o.subtaskId===t.id);r&&(r.state=t.conflicts.length?"conflict":"merged",r.stat=t.stat),ct(n,"subtask-merged",{from:t.box,fromAgent:t.agent,task:t.task,subtaskId:t.id,stat:t.stat,conflicts:t.conflicts.length,files:t.files,durationMs:t.finishedAt?t.finishedAt-t.startedAt:null}),await z()}async function IR(t){let e=await W(),n=et(e,t.trackId);if(!n)return;let r=t.canceled||t.error==="canceled",o=n.boxes.find(s=>s.subtaskId===t.id);o&&(o.state=r?"canceled":"failed"),ct(n,r?"subtask-canceled":"subtask-failed",{from:t.box,fromAgent:t.agent,task:t.task,subtaskId:t.id,failedStep:t.failedStep||t.step,error:t.error,durationMs:t.finishedAt?t.finishedAt-t.startedAt:null}),n.subtasks=n.subtasks||{},n.subtasks[t.id]={...n.subtasks[t.id]||{},...Ku(t)},await z()}async function RR(t,e,n){let r=`.bostrat/handoff/subtask-${t.id}.md`;try{let o=F.join(e.cwd,r);await L.mkdir(F.dirname(o),{recursive:!0}),await L.copyFile(n,o)}catch{}return r}async function zu(t){if(t.box&&await ce(t.box).catch(()=>{}),t.repos&&t.dir){for(let e of t.repos)await Xt(e.leadDir,F.join(t.dir,e.name)).catch(()=>{});await L.rm(t.dir,{recursive:!0,force:!0}).catch(()=>{})}else if(t.dir){let n=(await Z(t.leadBox).catch(()=>null))?.cwd;n&&await Xt(n,t.dir).catch(()=>{})}}async function $R(t,e){let n=(e.startedAt||Date.now())+gR;for(;;){if(e.canceled||e.forceMerge)return;try{await L.access(t);return}catch{}if(Date.now()>n)throw new Error("subtask timed out without signaling completion (no result digest written)");await Zn(yR)}}function Zn(t){return new Promise(e=>setTimeout(e,t))}import ds from"node:fs/promises";import ui from"node:path";import PR from"node:os";import OR from"ws";var tb=()=>process.env.BOSTRAT_SLACK_API_BASE||"https://slack.com/api";async function ci(t,e,n={}){let r=await fetch(`${tb()}/${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 Ju=(t,e)=>ci("chat.postMessage",t,e),Jr=(t,e)=>ci("chat.update",t,e);async function jt(t,e){await fetch(t,{method:"POST",headers:{"content-type":"application/json; charset=utf-8"},body:JSON.stringify(e)}).catch(()=>{})}async function CR(t,e,n={}){let r=await fetch(`${tb()}/${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 eb(t,{filename:e,buffer:n,title:r,channelId:o,threadTs:s,initialComment:a}){let{upload_url:i,file_id:c}=await CR("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 ci("files.completeUploadExternal",t,{files:[{id:c,title:r}],channel_id:o,thread_ts:s,...a?{initial_comment:a}:{}})}var MR=2200;function nb({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 ci("apps.connections.open",u))}catch(u){return n(`connections.open failed: ${u?.message||u}`),c()}o=new OR(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=g=>{try{o.send(JSON.stringify(g?{envelope_id:f.envelope_id,payload:g}:{envelope_id:f.envelope_id}))}catch{}},p=!1,m=setTimeout(()=>{p=!0,d()},MR);Promise.resolve().then(()=>e(f)).then(g=>{clearTimeout(m),!p&&(p=!0,d(f.accepts_response_payload?g:void 0))},g=>{clearTimeout(m),p||(p=!0,d()),n(`envelope handler failed: ${g?.message||g}`)})}),o.on("close",c),o.on("error",u=>{n(`socket error: ${u?.message||u}`);try{o.close()}catch{}})}function c(){if(r||s)return;let l=a;a=Math.min(a*2,6e4),s=setTimeout(()=>{s=null,i()},l)}return i(),()=>{r=!0,s&&clearTimeout(s);try{o?.close()}catch{}}}var sb=()=>process.env.BOSTRAT_STATE_DIR||ui.join(process.env.HOME||PR.tmpdir(),".bostrat"),td=()=>ui.join(sb(),"slack.json"),li=null,Xu=null,Yu=Promise.resolve();async function nd(){return li||(Xu??=ds.readFile(td(),"utf8").then(t=>JSON.parse(t)?.threads??{}).catch(()=>({})).then(t=>li=t).finally(()=>Xu=null),Xu)}async function rb(){let t=JSON.stringify({threads:li},null,2),e=`${td()}.${process.pid}.tmp`;await ds.mkdir(sb(),{recursive:!0}),await ds.writeFile(e,t),await ds.rename(e,td())}async function qe(){Yu=Yu.then(rb,rb),await Yu.catch(()=>{})}async function rd(t){return(await nd())[t]??null}async function BR(t,e){let n=await nd();for(let[r,o]of Object.entries(n))if(o.channel===t&&o.ts===e)return{trackId:r,rec:o};return null}var Y=t=>String(t??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");function fs(t,e){let n=String(t??"").trim();return n.length<=e?n:`${n.slice(0,e-1).trimEnd()}\u2026`}function DR(t,e=500){let n=String(t??"").trim().split(/\n\s*\n/)[0]??"";return fs(n.replace(/\n+/g," "),e)}function ab(t){let e=`*${Y(t.name)}*${t.archivedAt?" \xB7 _archived_":""}`,n=`${Y(t.repoLabel)} \xB7 \`${Y(t.branch||"?")}\` \xB7 lead *${Y(t.lead)}*`,r=t.kickoff?`> ${Y(fs(t.kickoff,300))}`:"_no kickoff yet \u2014 reply in this thread to steer the lead_";return[{type:"section",text:{type:"mrkdwn",text:`${e}
|
|
386
|
+
${n}`}},{type:"section",text:{type:"mrkdwn",text:r}},{type:"context",elements:[{type:"mrkdwn",text:`<${ka(t.trackId)}|open the Track> \xB7 reachable on the operator's private network only`}]}]}async function ed(t,e){await Jr(t.botToken,{channel:e.channel,ts:e.ts,text:`Track: ${e.name}`,blocks:ab(e)})}async function tr(t,e,n,r=void 0){return Ju(t.botToken,{channel:e.channel,thread_ts:e.ts,text:n,...r?{blocks:r}:{},unfurl_links:!1})}var ob=new Set;function Qn(t){ob.add(t),t.catch(()=>{}).finally(()=>ob.delete(t))}var ut=t=>({response_type:"ephemeral",text:t}),jR="*/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._",LR=["claude","cursor","codex"];function Zu(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 Qu=(t,e)=>ut(`No live Track matches \u201C${Y(e)}\u201D. Live: ${t.map(n=>`\`${Y(n.name)}\``).join(" \xB7 ")||"none"}`);async function ib(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 jt(o,{...ut(`At capacity \u2014 ${a.length} live Tracks (limit ${e.maxConcurrent}). Archive or stop something first.`),replace_original:!0});return}if(!e.channel){await jt(o,{...ut("No designated channel configured \u2014 set `slack.channel` in integrations.json."),replace_original:!0});return}let i=await s.createTrack({name:n,...r?{repo:r}:{}}),c=i.workspace||{},l={trackId:i.id,channel:e.channel,name:i.name,branch:c.branch||null,repoLabel:c.repo||(c.base?ui.basename(c.base):"fresh workspace"),lead:"claude"},u=await Ju(e.botToken,{channel:e.channel,text:`Track started: ${l.name}`,blocks:ab(l),unfurl_links:!1});l.ts=u.ts,(await nd())[i.id]=l,await qe(),await jt(o,{...ut(`\u2713 *${Y(l.name)}* started \u2014 root message posted.`),replace_original:!0})}catch(a){await jt(o,{...ut(`Start failed: ${a?.message||a}`),replace_original:!0})}}async function FR(t,e,{track:n,responseUrl:r}){let o=t.deps;try{await o.archiveTrack(n.id);let s=await rd(n.id);s&&(s.archivedAt=Date.now(),await qe(),await ed(e,s).catch(()=>{})),await jt(r,{...ut(`\u2713 *${Y(n.name)}* archived.`),replace_original:!0})}catch(s){await jt(r,{...ut(`Archive failed: ${s?.message||s}`),replace_original:!0})}}async function UR(t,e,{track:n,agent:r,responseUrl:o}){try{await t.deps.startLeadHandoff(n.id,r),await jt(o,{...ut(`\u2713 Handing *${Y(n.name)}* to ${Y(r)} \u2014 the root message updates when the baton lands.`),replace_original:!0})}catch(s){await jt(o,{...ut(`Handoff failed: ${s?.message||s}`),replace_original:!0})}}async function HR(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 jt(r,{...ut(`\u2713 Stopped *${Y(n.name)}*${s.length?` (+ ${s.length} subtask${s.length>1?"s":""} canceled)`:""} \u2014 Track parked, resume from the Track page.`),replace_original:!0})}catch(s){await jt(r,{...ut(`Stop failed: ${s?.message||s}`),replace_original:!0})}}function WR(t,e){let n=[...e.slice(0,99).map(r=>({text:{type:"plain_text",text:fs(`${r.name} (${r.branch})`,75)},value:r.path})),{text:{type:"plain_text",text:"fresh workspace"},value:"__fresh__"}];return[{type:"section",block_id:JSON.stringify({name:t}),text:{type:"mrkdwn",text:`Repo for *${Y(t)}*:`},accessory:{type:"static_select",action_id:"bostrat-start-repo",placeholder:{type:"plain_text",text:"Choose a repo"},options:n}}]}async function qR(t){let e=await t.listTracks(),n=e.filter(s=>!s.archivedAt);if(n.sort((s,a)=>(a.lastResponseAt??a.createdAt??0)-(s.lastResponseAt??s.createdAt??0)),!n.length)return"No live Tracks.";let r=n.map(s=>{let a=s.workspace||{},i=a.repo||(a.base?ui.basename(a.base):"fresh"),c=[...s.boxes||[]].reverse().find(u=>u.role==="lead")?.agent||"?",l=s.prompted?"\u26A0 needs an answer":s.working?"working":"idle";return`\u2022 *${Y(s.name)}* \u2014 ${Y(i)}${a.branch?` \`${Y(a.branch)}\``:""} \xB7 lead ${Y(c)} \xB7 ${l}`}),o=e.length-n.length;return r.join(`
|
|
383
387
|
`)+(o?`
|
|
384
|
-
_${o} archived_`:"")}async function
|
|
385
|
-
\u2713 *${
|
|
386
|
-
\u2713 _answered_`}}]})}async function
|
|
387
|
-
_runs on the Track's branch; reachable on the operator's private network only_`}}]);return}case"mock.state.changed":return
|
|
388
|
-
`)){let r=n.trim();if(!r||r[0]!=="{")continue;let o;try{o=JSON.parse(r)}catch{continue}let s=o?.message?.usage;if(!s)continue;let a={input:s.input_tokens||0,output:s.output_tokens||0,cacheWrite:s.cache_creation_input_tokens||0,cacheRead:s.cache_read_input_tokens||0};if(a.input+a.output+a.cacheWrite+a.cacheRead===0)continue;let i=o.message?.id||o.uuid||null;i&&e.set(i,{messageId:i,tokens:a,model:o.message?.model||o.model||null})}return[...e.values()]}function CR(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 NR(t,e){return t?.transcript_path?t.transcript_path:e?.cwd&&e?.sessionId?Ee(e.cwd,e.sessionId):e?.cwd?nc(e.cwd):null}async function zy(t,e){let n=await e(t?.session_id,t?.cwd);if(!n?.track)return[];let{track:r,box:o}=n,s={boxId:o?.name||null,provider:"claude",source:"hook"},a=[],i=async c=>{a.push(await $(r,{...s,...c}))};switch(t?.hook_event_name){case"SessionStart":await i({type:"session.ready",actor:"system",payload:{sessionId:t.session_id}});break;case"UserPromptSubmit":await i({type:"turn.user",actor:"user",payload:{promptHead:String(t.prompt??t.user_prompt??"").slice(0,160)||null}});break;case"PostToolUse":await i({type:"tool.called",actor:"agent",payload:{tool:t.tool_name??null}});break;case"Stop":await PR(r,o,t,i);break;default:break}return a}async function PR(t,e,n,r){let o=[],s=NR(n,e);if(s)try{o=MR(await OR.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=CR(await L(t)),c=o.filter(u=>!i.has(u.messageId));if(c.length){let u=new Map;for(let f of c){let d=f.model||"unknown",m=u.get(d)||{tokens:{input:0,output:0,cacheWrite:0,cacheRead:0},messageIds:[]};for(let h of Object.keys(m.tokens))m.tokens[h]+=f.tokens[h];m.messageIds.push(f.messageId),u.set(d,m)}for(let[f,d]of u)await r({type:"cost.recorded",actor:"system",source:"transcript",payload:{model:f,usd: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 jR from"node:fs";import Xu from"node:fs/promises";import LR from"node:http";import Vr from"node:path";import di from"node:os";import{createRequire as FR}from"node:module";import Yu from"ws";import BR from"node:path";import DR from"node:os";var Vy=process.env.BOSTRAT_STATE_DIR||BR.join(process.env.HOME||DR.tmpdir(),".bostrat");function Jy(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 Xy(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 ui=(()=>{try{return FR(import.meta.url)("../package.json").version||null}catch{return null}})(),UR=process.env.BOSTRAT_STATE_DIR||Vr.join(process.env.HOME||di.tmpdir(),".bostrat");function HR(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 WR({home:t=process.env.HOME||di.homedir(),env:e=process.env}={}){let n=r=>{try{return jR.statSync(r),!0}catch{return!1}};return{claude:n(Vr.join(t,".claude",".credentials.json")),codex:n(Vr.join(e.CODEX_HOME||Vr.join(t,".codex"),"auth.json"))}}var It=1,Yy=1e3,qR=3e4,KR=6e4,GR=1500,zR=500,Zy=100,VR=2e3,JR=25e3;function Qy({url:t,token:e,nodeId:n=di.hostname(),name:r=di.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=Xy(t);let c=a.replace(/^http/,"ws"),l=new Map,u=new Map,f=Vr.join(UR,"plane-link","cursor.json"),d={},m=new Map,h=null,w=!1,g=!1,b=Yy,S=null,A=null,C=null,z=null,O=!1;async function F(){try{d=JSON.parse(await Xu.readFile(f,"utf8"))}catch{d={}}}function T(){O=!0,!C&&(C=setTimeout(async()=>{if(C=null,!!O){O=!1;try{await Xu.mkdir(Vr.dirname(f),{recursive:!0}),await Xu.writeFile(f,JSON.stringify(d))}catch(I){i.error("[plane-link] cursor flush failed:",I?.message||I)}}},VR),C.unref?.())}function M(I){return!h||h.readyState!==Yu.OPEN?!1:(h.send(JSON.stringify(I)),!0)}function W(I){for(let{trackId:R,event:_}of I)m.set(_.id,{trackId:R,seq:_.seq});return M({v:It,ch:"events",type:"append",events:I})}async function st(){let I;try{I=await o()}catch(R){i.error("[plane-link] backfill listTracks failed:",R?.message||R);return}M({v:It,ch:"ctl",type:"tracks",ids:I.map(R=>R.id).filter(R=>typeof R=="string")});for(let R of I){if(!w)return;let _;try{_=await L(R)}catch{continue}let P=d[R.id]??0,et=_.filter(wt=>wt.seq>P&&typeof wt.id=="string");for(let wt=0;wt<et.length;wt+=Zy){let bt=et.slice(wt,wt+Zy).map($t=>({trackId:R.id,event:$t}));if(!W(bt))return}}}function y(I){!w||!I?.trackId||typeof I.id!="string"||W([{trackId:I.trackId,event:I}])}let p=new Map;async function v(I){let{reqId:R,method:_,path:P,headers:et={},body:wt}=I,bt=Object.fromEntries(Object.entries(et).filter(([,Kt])=>Kt!=null)),$t=new AbortController;p.set(R,$t);try{if(I.host){await E(I,bt,$t);return}let Kt=await fetch(a+P,{method:_,headers:bt,body:wt?Buffer.from(wt,"base64"):void 0,signal:$t.signal}),Xt=Object.fromEntries(Kt.headers);for(let k of["connection","keep-alive","transfer-encoding","upgrade","content-encoding","content-length"])delete Xt[k];if(Xt["content-type"]||(Xt["content-type"]="application/octet-stream"),M({v:It,ch:"control",type:"http_head",reqId:R,status:Kt.status,headers:Xt}),Kt.body){for await(let k of Kt.body)if(!M({v:It,ch:"control",type:"http_data",reqId:R,data:Buffer.from(k).toString("base64")}))break}M({v:It,ch:"control",type:"http_end",reqId:R,error:null})}finally{p.delete(R)}}function E(I,R,_){let{reqId:P,method:et,path:wt,body:bt}=I,$t=new URL(a);return new Promise(Kt=>{let Xt=LR.request({host:$t.hostname,port:$t.port,method:et,path:wt,headers:{...R,host:I.host},signal:_.signal},k=>{let{connection:K,"keep-alive":Zn,"transfer-encoding":As,upgrade:Qn,...Ri}=k.headers;M({v:It,ch:"control",type:"http_head",reqId:P,status:k.statusCode||502,headers:Ri}),k.on("data",Dk=>{M({v:It,ch:"control",type:"http_data",reqId:P,data:Buffer.from(Dk).toString("base64")})||k.destroy()}),k.on("end",()=>{M({v:It,ch:"control",type:"http_end",reqId:P,error:null}),Kt()}),k.on("error",()=>{M({v:It,ch:"control",type:"http_end",reqId:P,error:"view upstream error"}),Kt()})});Xt.on("error",k=>{M({v:It,ch:"control",type:"http_end",reqId:P,error:k?.message||"view request failed"}),Kt()}),bt&&Xt.write(Buffer.from(bt,"base64")),Xt.end()})}function rt(){if(g)return;h=new Yu(t,{headers:{authorization:`Bearer ${e}`}}),h.on("open",async()=>{let R=null;try{R=await s?.()||null}catch{R=null}M({v:It,ch:"ctl",type:"hello",nodeId:n,name:r,platform:process.platform,version:ui,providers:WR(),...R?{agents:R}:{}})}),h.on("message",R=>{let _;try{_=JSON.parse(R)}catch{return}if(_.ch==="ctl"&&_.type==="hello_ok"){w=!0,b=Yy,S=null,i.log(`[plane-link] connected to plane as ${n}`),_.minNodeVersion&&ui&&HR(ui,_.minNodeVersion)&&i.error(`[plane-link] this node is bostrat-node ${ui}; the plane asks for \u2265${_.minNodeVersion} \u2014 update with: npm install -g bostrat-node`),z&&clearInterval(z),z=setInterval(()=>M({v:It,ch:"ctl",type:"ping"}),JR),z.unref?.(),st().catch(P=>i.error("[plane-link] backfill failed:",P?.message||P));return}if(_.ch==="events"&&_.type==="ack"&&Array.isArray(_.ids)){for(let P of _.ids){let et=m.get(P);et&&(m.delete(P),(d[et.trackId]??0)<et.seq&&(d[et.trackId]=et.seq))}T();return}if(_.ch==="control"&&_.type==="http_open"){v(_).catch(P=>M({v:It,ch:"control",type:"http_end",reqId:_.reqId,error:P?.message||"tunnel error"}));return}if(_.ch==="control"&&_.type==="http_cancel"){p.get(_.reqId)?.abort();return}if(_.ch==="attach"&&_.type==="open"){let P=new Yu(c+_.path,Array.isArray(_.protocols)?_.protocols:[],_.host?{headers:{host:_.host}}:void 0);l.set(_.streamId,P),u.set(_.streamId,[]),P.on("open",()=>{let wt=u.get(_.streamId)||[];u.delete(_.streamId);for(let{buf:bt,bin:$t}of wt)P.send(bt,{binary:$t})}),P.on("message",(wt,bt)=>{let $t=bt?wt:Buffer.from(String(wt));M({v:It,ch:"attach",type:"data",streamId:_.streamId,data:$t.toString("base64"),...bt?{bin:!0}:{}})});let et=()=>{u.delete(_.streamId),l.delete(_.streamId)&&M({v:It,ch:"attach",type:"close",streamId:_.streamId})};P.on("close",et),P.on("error",et);return}if(_.ch==="attach"&&_.type==="data"){let P=l.get(_.streamId);if(!P)return;let et=Buffer.from(_.data,"base64"),wt=_.bin===!0,bt=u.get(_.streamId);bt?bt.push({buf:et,bin:wt}):P.send(et,{binary:wt});return}if(_.ch==="attach"&&_.type==="close"){let P=l.get(_.streamId);l.delete(_.streamId),u.delete(_.streamId),P?.close();return}});let I=()=>{w&&i.log("[plane-link] disconnected from plane"),w=!1,z&&(clearInterval(z),z=null),m.clear();for(let P of l.values())P.close();l.clear(),u.clear();for(let P of p.values())P.abort();if(p.clear(),g||A)return;S==null&&(S=Date.now());let R=Date.now()-S<KR,_=R?GR+Math.random()*zR:b;A=setTimeout(()=>{A=null,rt()},_),A.unref?.(),R||(b=Math.min(b*2,qR))};h.on("close",I),h.on("error",I)}return zt.on("event",y),F().then(rt),{stop:()=>{g=!0,zt.off("event",y),A&&clearTimeout(A),C&&clearTimeout(C),z&&clearInterval(z),M({v:It,ch:"ctl",type:"bye",reason:"shutdown"}),h?.close()},status:()=>({connected:w,nodeId:n,cursor:{...d},inflight:m.size})}}function Qu(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 tb=()=>({input:0,output:0,cacheRead:0,cacheCreation:0}),eb=(t,e)=>{for(let n of Object.keys(t))t[n]+=e?.[n]||0},Zu=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=tb(),u=0,f=[];for(let d of t||[]){let m=await L(d),h=ue(m,{boxLabels:Qu(d)});if(h.turns===0){d.archivedAt?u++:f.push(d.name||d.id);continue}o+=h.totalUsd,s+=h.turns,a+=h.repricedTurns||0,i+=h.unpricedTurns||0;for(let w of h.unpricedModels||[])c.add(w);eb(l,h.totalTokens);for(let[w,g]of Object.entries(h.byModel))n[w]||={usd:0,events:0,tokens:tb()},n[w].usd=Zu(n[w].usd+g.usd),n[w].events+=g.events,eb(n[w].tokens,g.tokens);for(let[w,g]of Object.entries(h.byProvider))r[w]||={usd:0,events:0},r[w].usd=Zu(r[w].usd+g.usd),r[w].events+=g.events;e.push({id:d.id,name:d.name,usd:h.totalUsd,rollup:h})}return e.sort((d,m)=>m.usd-d.usd),{generatedAt:new Date().toISOString(),pricingBasis:"api-equivalent (subscription usage priced at public API rates)",fleet:{totalUsd:Zu(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)}`,Jr=(t,e)=>String(t).padEnd(e);function rb(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(` ${Jr(a,26)} ${Jr(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(` ${Jr(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(` ${Jr(c.label,40)} ${Jr(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))}${Jr(ps(u.usd),10)} ${u.models.join(",")||"?"}`)}return r.join(`
|
|
389
|
-
`)}import t$ from"node:fs/promises";import e$ from"node:path";var fi=t=>t.payload?.ts??t.ts,XR=12e4,yn=t=>Math.round(t*1e4)/1e4,YR=()=>({usd:0,activeMs:0,turns:0,toolCalls:0,models:{},providers:{},byTrack:new Map});function ZR(t,{since:e}){let{track:n,events:r,brief:o}=t,s=S=>fi(S)>=e,a=lr([bo(n?.name),bo(n?.workspace?.branch),o].filter(Boolean).join(" ")),i=new Map;for(let S of r)if(S.type==="subtask.delegated"&&S.boxId&&S.payload?.task){let A=lr(S.payload.task);A&&i.set(S.boxId,A)}let c=new Map,l=S=>{let A=S||"unattributed";return c.has(A)||c.set(A,{segments:[u(null)],lastTs:null,provider:null}),c.get(A)},u=S=>({textCat:S,tools:{},usd:0,tokens:{input:0,output:0,cacheRead:0,cacheWrite:0},models:{},provider:null,activeMs:0,turns:0,toolCalls:0}),f=[...r].sort((S,A)=>fi(S)-fi(A)),d=0,m=0,h=0,w={turns:0};for(let S of f){let A=l(S.boxId),C=A.segments[A.segments.length-1],z=fi(S);switch(A.lastTs!=null&&z>A.lastTs&&s(S)&&(C.activeMs+=Math.min(z-A.lastTs,XR)),A.lastTs=z,S.provider&&(A.provider=S.provider),S.type){case"turn.user":{let O=S.payload?.text;if(S.source==="hook"||!String(O||"").trim())break;let F=u(lr(O));A.segments.push(F),s(S)&&(F.turns+=1);break}case"tool.called":{if(S.source==="transcript")break;let O=S.payload?.tool;O&&(C.tools[O]=(C.tools[O]||0)+1),s(S)&&(C.toolCalls+=1);break}case"cost.recorded":{if(!s(S))break;let O=S.payload||{},F={input:O.tokens?.input||0,output:O.tokens?.output||0,cacheRead:O.tokens?.cacheRead||0,cacheWrite:O.tokens?.cacheWrite??O.tokens?.cacheCreation??0},T=Number(O.usd)||0;!T&&O.tokens&&(T=le(O.model,F)),!T&&O.tokens&&!ea(O.model)&&(w.turns+=1),C.usd+=T,C.tokens.input+=F.input,C.tokens.output+=F.output,C.tokens.cacheRead+=F.cacheRead,C.tokens.cacheWrite+=F.cacheWrite;let M=O.model||"unknown";C.models[M]=(C.models[M]||0)+T,S.provider&&(C.provider=S.provider);break}case"handoff.done":s(S)&&(d+=1);break;case"subtask.merged":s(S)&&(m+=1);break;case"error":s(S)&&S.payload?.kind==="subtask-failed"&&(h+=1);break;default:break}}let g=new Map,b=!1;for(let[S,A]of c)for(let C of A.segments){if(!(C.usd>0||C.activeMs>0||C.turns>0||C.toolCalls>0))continue;let O=C.textCat?.category??i.get(S)?.category??If(C.tools)?.category??a?.category??"uncategorized";g.has(O)||g.set(O,{usd:0,activeMs:0,turns:0,toolCalls:0,models:{},providers:{}});let F=g.get(O);F.usd+=C.usd,F.activeMs+=C.activeMs,F.turns+=C.turns,F.toolCalls+=C.toolCalls;for(let[M,W]of Object.entries(C.models))F.models[M]=(F.models[M]||0)+W;let T=C.provider||A.provider;T&&(F.providers[T]=(F.providers[T]||0)+C.usd),T==="cursor"&&C.usd===0&&(b=!0)}return{perCategory:g,handoffs:d,merged:m,failed:h,sawCursorActivity:b,unpricedTurns:w.turns}}function sb(t,{windowMs:e=null,now:n=Date.now()}={}){let r=e==null?0:n-e,o=new Map,s=[],a=0,i=!1,c=0;for(let w of t||[]){let{track:g,events:b}=w;if(!b?.length){a+=1;continue}let{perCategory:S,handoffs:A,merged:C,failed:z,sawCursorActivity:O,unpricedTurns:F}=ZR(w,{since:r});O&&(i=!0),c+=F;let T={trackId:g?.id,name:g?.name||g?.id,archived:!!g?.archivedAt,usd:0,activeMs:0,turns:0,categories:{},models:{},providers:{},handoffs:A,subtasksMerged:C,subtasksFailed:z};for(let[M,W]of S){T.usd+=W.usd,T.activeMs+=W.activeMs,T.turns+=W.turns,T.categories[M]={usd:yn(W.usd),activeMs:W.activeMs};for(let[p,v]of Object.entries(W.models))T.models[p]=yn((T.models[p]||0)+v);for(let[p,v]of Object.entries(W.providers))T.providers[p]=yn((T.providers[p]||0)+v);o.has(M)||o.set(M,YR());let st=o.get(M);st.usd+=W.usd,st.activeMs+=W.activeMs,st.turns+=W.turns,st.toolCalls+=W.toolCalls;for(let[p,v]of Object.entries(W.models))st.models[p]=(st.models[p]||0)+v;for(let[p,v]of Object.entries(W.providers))st.providers[p]=(st.providers[p]||0)+v;let y=st.byTrack.get(g?.id)||{usd:0,activeMs:0};y.usd+=W.usd,y.activeMs+=W.activeMs,st.byTrack.set(g?.id,y)}(T.usd>0||T.activeMs>0||T.turns>0)&&(T.usd=yn(T.usd),T.dominantCategory=QR(T.categories),s.push(T))}let l=[...o.values()].reduce((w,g)=>w+g.usd,0),u=[...o.values()].reduce((w,g)=>w+g.activeMs,0),f=l>0?"usd":"time",d=f==="usd"?l:u,m=new Map((t||[]).map(w=>[w.track?.id,w.track?.name||w.track?.id])),h=[...o.entries()].map(([w,g])=>({category:w,sharePct:d>0?Math.round((f==="usd"?g.usd:g.activeMs)/d*100):0,shareBasis:f,usd:yn(g.usd),activeMs:g.activeMs,turns:g.turns,toolCalls:g.toolCalls,models:ob(g.models),providers:ob(g.providers),topWorkstreams:[...g.byTrack.entries()].sort((b,S)=>S[1].usd-b[1].usd||S[1].activeMs-b[1].activeMs).slice(0,2).map(([b,S])=>({trackId:b,name:m.get(b),usd:yn(S.usd),activeMs:S.activeMs}))})).sort((w,g)=>g.usd-w.usd||g.activeMs-w.activeMs);return s.sort((w,g)=>g.usd-w.usd||g.activeMs-w.activeMs),{since:r,capturedAt:n,totalUsd:yn(l),activeMs:u,turns:[...o.values()].reduce((w,g)=>w+g.turns,0),bullets:h,workstreams:s,gaps:{tracksWithoutEvents:a,cursorCostUnknown:i,unpricedTurns:c}}}var QR=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},ob=t=>Object.fromEntries(Object.entries(t).map(([e,n])=>[e,yn(n)]));var Xr={"24h":24*36e5,"7d":168*36e5,"30d":720*36e5,all:null};function n$(t){let e=String(t||"7d");return e in Xr?{key:e,ms:Xr[e]}:{key:"7d",ms:Xr["7d"]}}function r$(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=Da(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 o$(t){if(!t?.cwd)return null;try{let n=(await t$.readFile(e$.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}:n$(e),f=[];for(let d of t||[]){let m=await L(d),h=r$(d,m);i&&c<o&&(h=h.filter(w=>(w.ts??0)<=c)),f.push({track:d,events:h,brief:await o$(d)})}return{window:l,...sb(f,{windowMs:u,now:c})}}var bn=36e5,ub=24*bn,s$=400,a$=12,Yr=t=>Math.round(t*1e4)/1e4;function i$({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 Xr?String(t||"7d"):"7d",i=Xr[a];return{key:a,since:i==null?null:r-i,until:r}}var c$=(t,e)=>t!=null&&e-t<=48*bn?bn:ub;function ib(t,e){if(e===bn)return Math.floor(t/bn)*bn;let n=new Date(t);return n.setHours(0,0,0,0),n.getTime()}function l$(t,e){if(e===bn)return t+bn;let n=new Date(t);return n.setDate(n.getDate()+1),n.setHours(0,0,0,0),n.getTime()}var cb=()=>({input:0,output:0,cacheRead:0,cacheCreation:0}),lb=(t,e)=>{for(let n of Object.keys(t))t[n]+=e?.[n]||0},u$=t=>Object.fromEntries(Object.entries(t).map(([e,n])=>[e,Yr(n)]));async function db(t,e={}){let n=e.now??Date.now(),r=i$({...e,now:n}),o=r.since==null?ub:c$(r.since,r.until),s=T=>(r.since==null||T>=r.since)&&T<r.until,a=r.since!=null?r.until-r.since:null,i=T=>a!=null&&T>=r.since-a&&T<r.since,c={},l={},u=cb(),f=0,d=0,m=0,h=0,w=0,g=new Set,b=new Map,S=[],A=[],C=[],z=null;for(let T of t||[]){let W=(await L(T)).filter(p=>p.type==="cost.recorded");if(!W.length){T.archivedAt||C.push(T.name||T.id);continue}let st=[];for(let p of W){let v=p.ts??0;s(v)?st.push(p):i(v)&&(w+=ra(p).usd)}if(!st.length)continue;let y=ue(st,{boxLabels:Qu(T)});f+=y.totalUsd,d+=y.turns,m+=y.repricedTurns||0,h+=y.unpricedTurns||0;for(let p of y.unpricedModels||[])g.add(p);lb(u,y.totalTokens);for(let[p,v]of Object.entries(y.byModel))c[p]||={usd:0,events:0,tokens:cb()},c[p].usd=Yr(c[p].usd+v.usd),c[p].events+=v.events,lb(c[p].tokens,v.tokens);for(let[p,v]of Object.entries(y.byProvider))l[p]||={usd:0,events:0},l[p].usd=Yr(l[p].usd+v.usd),l[p].events+=v.events;for(let p of st){let{usd:v}=ra(p);if(!v)continue;let E=ib(p.ts??0,o),rt=b.get(E)??{t:E,total:0,byProvider:{}};rt.total+=v;let I=p.provider||"unknown";rt.byProvider[I]=(rt.byProvider[I]||0)+v,b.set(E,rt),(z==null||E<z)&&(z=E)}S.push({id:T.id,name:T.name||T.id,archived:!!T.archivedAt,usd:y.totalUsd,turns:y.turns,providers:Object.keys(y.byProvider)});for(let[p,v]of Object.entries(y.byBox))A.push({trackId:T.id,trackName:T.name||T.id,boxId:p,...v})}let O=r.since!=null?ib(r.since,o):z,F=[];for(let T=O,M=0;T!=null&&T<r.until&&M<s$;T=l$(T,o),M++){let W=b.get(T);F.push(W?{t:T,total:Yr(W.total),byProvider:u$(W.byProvider)}:{t:T,total:0,byProvider:{}})}return S.sort((T,M)=>M.usd-T.usd),A.sort((T,M)=>M.usd-T.usd),{generatedAt:new Date().toISOString(),pricingBasis:"api-equivalent (subscription usage priced at public API rates)",window:r.key,since:r.since,until:r.until,bucketMs:o,series:F,fleet:{totalUsd:Yr(f),turns:d,repricedTurns:m,unpricedTurns:h,unpricedModels:[...g],totalTokens:u,byModel:c,byProvider:l,tracksWithCost:S.length,tracksSilentLive:C},prior:a!=null?{totalUsd:Yr(w),since:r.since-a,until:r.since}:null,tracks:S.slice(0,50),boxes:A.slice(0,a$)}}import to from"node:fs/promises";import d$ from"node:fs";import dt from"node:path";import f$ from"node:os";import{execFile as p$}from"node:child_process";import{promisify as m$}from"node:util";var h$=m$(p$),fb=process.env.BOSTRAT_STATE_DIR||dt.join(process.env.HOME||f$.tmpdir(),".bostrat"),td=!1;async function Zr(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(dt.dirname(e),{recursive:!0}),await to.copyFile(t,e),!0)}var w$=new Set([".claude",".codex",".credentials.json","auth.json",".claude.json","integrations.json"]);async function Qr(t,e){let n=0,r=await to.readdir(t,{withFileTypes:!0}).catch(()=>[]);for(let o of r){if(w$.has(o.name))continue;let s=dt.join(t,o.name);o.isDirectory()?n+=await Qr(s,dt.join(e,o.name)):o.isFile()&&(n+=await Zr(s,dt.join(e,o.name))?1:0)}return n}async function pb({stateDir:t=fb,dest:e=process.env.BOSTRAT_SYNC_DIR||`${fb}-sync`,remote:n=process.env.BOSTRAT_STATE_REMOTE||null,tracks:r=null}={}){try{await to.mkdir(e,{recursive:!0});let o=0;o+=await Zr(dt.join(t,"tracks.json"),dt.join(e,"tracks.json"))?1:0,o+=await Zr(dt.join(t,"mocks.json"),dt.join(e,"mocks.json"))?1:0,o+=await Zr(dt.join(t,"flows.json"),dt.join(e,"flows.json"))?1:0,o+=await Zr(dt.join(t,"runbooks.json"),dt.join(e,"runbooks.json"))?1:0,o+=await Qr(dt.join(t,"runbooks"),dt.join(e,"runbooks")),o+=await Qr(dt.join(t,"mock-archive"),dt.join(e,"mock-archive")),o+=await Zr(dt.join(t,"events.jsonl"),dt.join(e,"events.jsonl"))?1:0,o+=await Qr(dt.join(t,"events"),dt.join(e,"events")),o+=await Qr(dt.join(t,"memory"),dt.join(e,"memory"));for(let l of r||[])!l?.cwd||!l?.id||(o+=await Qr(dt.join(l.cwd,".bostrat"),dt.join(e,"workspaces",l.id,".bostrat")));let s=(...l)=>h$("git",["-C",e,...l]);d$.existsSync(dt.join(e,".git"))||(await s("init","-q"),await s("config","user.email","agent@bostrat.local"),await s("config","user.name","bostrat state sync")),n&&/^https:\/\/github\.com\//i.test(n)&&(await s("config","--get","credential.useHttpPath").then(()=>!0).catch(()=>!1)||await Di(e).catch(()=>{}));let a=!1,{stdout:i}=await s("status","--porcelain");i.trim()&&(await s("add","-A"),await s("commit","-q","-m",`state sync ${new Date().toISOString()}`),a=!0);let c=!1;if(n&&(a||td))try{await s("push","-q",n,"HEAD:refs/heads/main"),c=!0,td=!1}catch{td=!0}return{staged:o,committed:a,pushed:c}}catch(o){return{staged:0,committed:!1,pushed:!1,error:o?.message||String(o)}}}import{execFile as g$}from"node:child_process";import{promisify as y$}from"node:util";var b$=y$(g$),k$=12e4,x$=6e4;function S$(t){return`refs/bostrat/wip/${t}`}function wb(t=process.env){let e=String(t.BOSTRAT_WIP_SYNC_MS??"").trim().toLowerCase();return e!=="0"&&e!=="off"&&e!=="false"}function v$(t=process.env){let e=Number(t.BOSTRAT_WIP_SYNC_MS);return Number.isFinite(e)&&e>0?e:k$}var pi=(t,e,n=15e3)=>b$("git",["-C",t,...e],{timeout:n,maxBuffer:4*1024*1024}),mb=new Map,hb=new Set;async function T$(t,{log:e=console}={}){let{id:n,cwd:r}=t??{};if(!n||!r)return{pushed:!1,reason:"no-workspace"};try{await pi(r,["rev-parse","--git-dir"])}catch{return{pushed:!1,reason:"not-a-repo"}}try{await pi(r,["remote","get-url","origin"])}catch{return hb.has(r)||(hb.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 pi(r,["rev-parse",`${o}^{tree}`]),a=s.trim();if(mb.get(r)===a)return{pushed:!1,reason:"unchanged"};let i=S$(n);return await pi(r,["push","--force","origin",`${o}:${i}`],x$),mb.set(r,a),await $({id:n,cwd:r},{type:"code.pushed",actor:"orchestrator",payload:{ref:i,sha:o,wip:!0,ts:Date.now()}}).catch(()=>{}),e.log?.(`[wip-sync] ${n}: pushed ${o.slice(0,7)} \u2192 ${i}`),{pushed:!0,sha:o,ref:i}}catch(o){return e.log?.(`[wip-sync] ${n}: push failed (will retry): ${String(o?.message||o).split(`
|
|
390
|
-
`)
|
|
391
|
-
`)
|
|
392
|
-
`).map(s=>s.replace(/\s+$/,"")),n=
|
|
393
|
-
`)
|
|
394
|
-
|
|
388
|
+
_${o} archived_`:"")}async function KR(t,e){let n=await jn();if(!n)return ut("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 ut("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?(Qn(ib(t,n,{name:u,repo:l.path,responseUrl:e.response_url})),ut(`Starting *${Y(u)}* on ${Y(l.name)}\u2026`)):{response_type:"ephemeral",text:`Pick a repo for \u201C${u}\u201D`,blocks:WR(u,i)}}case"archive":{if(!a.length)return ut("Usage: `/bostrat archive <track>`");let{match:i,live:c}=Zu(await r.listTracks(),a.join(" "));return i?(Qn(FR(t,n,{track:i,responseUrl:e.response_url})),ut(`Archiving *${Y(i.name)}*\u2026`)):Qu(c,a.join(" "))}case"handoff":{let i=r.knownAgents??LR,c=a.at(-1)?.toLowerCase();if(a.length<2||!i.includes(c))return ut(`Usage: \`/bostrat handoff <track> <${i.join("|")}>\``);let l=a.slice(0,-1).join(" "),{match:u,live:f}=Zu(await r.listTracks(),l);return u?(Qn(UR(t,n,{track:u,agent:c,responseUrl:e.response_url})),ut(`Handing *${Y(u.name)}* to ${Y(c)}\u2026`)):Qu(f,l)}case"stop":{if(!a.length)return ut("Usage: `/bostrat stop <track>`");let{match:i,live:c}=Zu(await r.listTracks(),a.join(" "));return i?(Qn(HR(t,n,{track:i,responseUrl:e.response_url})),ut(`Stopping *${Y(i.name)}*\u2026`)):Qu(c,a.join(" "))}case"list":return ut(await qR(r));default:return ut(jR)}}async function GR(t,e,n,r){let{trackId:o,promptId:s,label:a}=r,i=await rd(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 qe(),await Jr(e.botToken,{channel:i.channel,ts:c,text:`\u2713 ${a}`,blocks:[{type:"section",text:{type:"mrkdwn",text:`~${Y(l||"question")}~
|
|
389
|
+
\u2713 *${Y(a)}*`}}]}).catch(()=>{})}}catch(c){await jt(n.response_url,ut(`Couldn't answer: ${c?.message||c}`))}}async function VR(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,ja(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 jt(n.response_url,ut(`Couldn't act on the offer: ${c?.message||c}`))}}async function zR(t,e){let n=e.actions?.[0];if(!n?.action_id)return;let r=await jn();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 jt(e.response_url,{...ut(`Starting *${Y(s)}*\u2026`),replace_original:!0}),Qn(ib(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"?Qn(GR(t,r,e,o)):n.action_id==="bostrat-ship"&&Qn(VR(t,r,e,o))}async function JR(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 BR(e.channel,e.thread_ts);if(!n)return;let r=await jn();if(r)try{await t.deps.sayToLead(n.trackId,e.text.trim())}catch(o){await tr(r,n.rec,`Couldn't deliver that to the lead: ${o?.message||o}`).catch(()=>{})}}function XR(t){return async e=>{if(e.type==="slash_commands")return KR(t,e.payload);if(e.type==="interactive"){await zR(t,e.payload);return}if(e.type==="events_api"){await JR(t,e.payload?.event);return}}}var YR=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 ZR(t,e){let n=[{type:"section",text:{type:"mrkdwn",text:`\u2753 ${Y(e.question||"The lead needs an answer")}`}}],r=(e.options||[]).slice(0,12);return r.length?n.push({type:"actions",elements:r.map(o=>({type:"button",action_id:"bostrat-prompt",text:{type:"plain_text",text:fs(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 QR(t,e,n){let r=n.payload||{};if(!r.promptId||e.prompt?.promptId===r.promptId)return;e.prompt&&await Jr(t.botToken,{channel:e.channel,ts:e.prompt.ts,text:"superseded",blocks:[{type:"section",text:{type:"mrkdwn",text:`~${Y(e.prompt.question||"question")}~ _superseded_`}}]}).catch(()=>{});let o=await tr(t,e,r.question||"The lead needs an answer",ZR(n.trackId,r));e.prompt={promptId:r.promptId,ts:o.ts,question:r.question||null},await qe()}async function t$(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 qe(),await Jr(t.botToken,{channel:e.channel,ts:o,text:"answered",blocks:[{type:"section",text:{type:"mrkdwn",text:`~${Y(s||"question")}~
|
|
390
|
+
\u2713 _answered_`}}]})}async function e$(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 tr(t,e,r.excerpt||"Ship it?",[{type:"section",text:{type:"mrkdwn",text:`\u{1F6A2} ${Y(fs(r.excerpt||"The lead offered to ship.",300))}`}},{type:"actions",elements:s}]);e.offers=e.offers||{},e.offers[r.offerId]=a.ts,await qe()}async function n$(t,e,n){let r=n.payload||{},o=e.offers?.[r.offerId];if(!o)return;delete e.offers[r.offerId],await qe();let s=r.action==="dismiss"?"dismissed":r.action==="pr"?"\u2713 opening a PR":"\u2713 pushing";await Jr(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$)(o.mockId,o.version??null);if(!s)return;let a=(o.version??1)>1?` (v${o.version})`:"";await eb(t.botToken,{filename:s.filename,buffer:s.buffer,title:`${o.name}${a}`,channelId:e.channel,threadTs:e.ts,initialComment:`\u{1F3A8} mock *${Y(o.name)}*${a}${o.group?` (group \`${Y(o.group)}\`)`:""}`})}else if(o.change==="picked"){let s=(o.siblings??[]).map(a=>`*${Y(a.name)}*`).join(", ");await tr(t,e,`Design decision: ${o.name} picked`,[{type:"section",text:{type:"mrkdwn",text:`\u{1F3A8} design decision: *${Y(o.name)}* picked${s?` over ${s}`:""}`}}])}}async function o$(t,e){let n=await nn(t);if(!n)return null;let r=Ie(n,e);if(!r)return null;let o=await ds.readFile(Ht(n,r.v).png).catch(()=>null);return o?{filename:`${n.name.replace(/[^\w.-]+/g,"-")}.v${r.v}.png`,buffer:o}:null}async function s$(t,e){if(!YR.has(e.type))return;let n=await rd(e.trackId);if(!n)return;let r=await jn();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 qe(),await ed(r,n);return;case"turn.agent":{if(!o.text)return;let s=DR(o.text);if(!s)return;await tr(r,n,s,[{type:"section",text:{type:"mrkdwn",text:Y(s)}},{type:"context",elements:[{type:"mrkdwn",text:`<${ka(e.trackId)}|full turn \u2192>`}]}]);return}case"prompt.raised":return QR(r,n,e);case"prompt.answered":return t$(r,n,e);case"pr.offered":return e$(r,n,e);case"pr.offer.answered":return n$(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 tr(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 qe(),await ed(r,n);return;case"view.state.changed":{if(o.change!=="created"||o.restarted||!o.slug)return;await tr(r,n,`Live preview ${o.name}: ${Sa(o.slug)}`,[{type:"section",text:{type:"mrkdwn",text:`\u{1F50D} live preview *${Y(o.name)}*: ${Sa(o.slug)}
|
|
391
|
+
_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 cb(t){let e={deps:t};Jt.on("event",s=>{s$(e,s).catch(()=>{})});let n=null,r=null,o=async()=>{n||!await jn()||(r&&clearInterval(r),r=null,n=nb({getToken:async()=>(await jn())?.appToken,onEnvelope:XR(e),log:s=>console.log(`[slack] ${s}`)}))};return r=setInterval(()=>o().catch(()=>{}),3e4),r.unref?.(),o().catch(()=>{}),()=>{r&&clearInterval(r),n?.()}}import a$ from"node:fs/promises";function i$(t){let e=new Map;for(let n of String(t||"").split(`
|
|
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 c$(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 l$(t,e){return t?.transcript_path?t.transcript_path:e?.cwd&&e?.sessionId?Ae(e.cwd,e.sessionId):e?.cwd?uc(e.cwd):null}async function lb(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 u$(r,o,t,i);break;default:break}return a}async function u$(t,e,n,r){let o=[],s=l$(n,e);if(s)try{o=i$(await a$.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=c$(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 p$ from"node:fs";import od from"node:fs/promises";import m$ from"node:http";import Xr from"node:path";import fi from"node:os";import{createRequire as h$}from"node:module";import sd from"ws";import d$ from"node:path";import f$ from"node:os";var ub=process.env.BOSTRAT_STATE_DIR||d$.join(process.env.HOME||f$.tmpdir(),".bostrat");function db(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 fb(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 di=(()=>{try{return h$(import.meta.url)("../package.json").version||null}catch{return null}})(),w$=process.env.BOSTRAT_STATE_DIR||Xr.join(process.env.HOME||fi.tmpdir(),".bostrat");function g$(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 y$({home:t=process.env.HOME||fi.homedir(),env:e=process.env}={}){let n=r=>{try{return p$.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,pb=1e3,b$=3e4,k$=6e4,x$=1500,S$=500,mb=100,v$=2e3,T$=25e3;function hb({url:t,token:e,nodeId:n=fi.hostname(),name:r=fi.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=fb(t);let c=a.replace(/^http/,"ws"),l=new Map,u=new Map,f=Xr.join(w$,"plane-link","cursor.json"),d={},p=new Map,m=null,g=!1,y=!1,b=pb,S=null,E=null,M=null,J=null,$=!1;async function j(){try{d=JSON.parse(await od.readFile(f,"utf8"))}catch{d={}}}function v(){$=!0,!M&&(M=setTimeout(async()=>{if(M=null,!!$){$=!1;try{await od.mkdir(Xr.dirname(f),{recursive:!0}),await od.writeFile(f,JSON.stringify(d))}catch(O){i.error("[plane-link] cursor flush failed:",O?.message||O)}}},v$),M.unref?.())}function C(O){return!m||m.readyState!==sd.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+=mb){let yt=K.slice(dt,dt+mb).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=m$.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,...Mi}=k.headers;C({v:_t,ch:"control",type:"http_head",reqId:G,status:k.statusCode||502,headers:Mi}),k.on("data",Qk=>{C({v:_t,ch:"control",type:"http_data",reqId:G,data:Buffer.from(Qk).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 sd(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:di,providers:y$(),...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=pb,S=null,i.log(`[plane-link] connected to plane as ${n}`),A.minNodeVersion&&di&&g$(di,A.minNodeVersion)&&i.error(`[plane-link] this node is bostrat-node ${di}; 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"}),T$),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 sd(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<k$,A=I?x$+Math.random()*S$:b;E=setTimeout(()=>{E=null,_()},A),E.unref?.(),I||(b=Math.min(b*2,b$))};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 id(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 wb=()=>({input:0,output:0,cacheRead:0,cacheCreation:0}),gb=(t,e)=>{for(let n of Object.keys(t))t[n]+=e?.[n]||0},ad=t=>Math.round(t*1e4)/1e4;async function yb(t){let e=[],n={},r={},o=0,s=0,a=0,i=0,c=new Set,l=wb(),u=0,f=[];for(let d of t||[]){let p=await D(d),m=ue(p,{boxLabels:id(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);gb(l,m.totalTokens);for(let[g,y]of Object.entries(m.byModel))n[g]||={usd:0,events:0,tokens:wb()},n[g].usd=ad(n[g].usd+y.usd),n[g].events+=y.events,gb(n[g].tokens,y.tokens);for(let[g,y]of Object.entries(m.byProvider))r[g]||={usd:0,events:0},r[g].usd=ad(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:ad(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 bb(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 R$ from"node:fs/promises";import $$ from"node:path";var pi=t=>t.payload?.ts??t.ts,A$=12e4,kn=t=>Math.round(t*1e4)/1e4,E$=()=>({usd:0,activeMs:0,turns:0,toolCalls:0,models:{},providers:{},byTrack:new Map});function _$(t,{since:e}){let{track:n,events:r,brief:o}=t,s=S=>pi(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)=>pi(S)-pi(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=pi(S);switch(E.lastTs!=null&&J>E.lastTs&&s(S)&&(M.activeMs+=Math.min(J-E.lastTs,A$)),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&&!oa($.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??Xf(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 xb(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}=_$(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,E$());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=I$(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:kb(y.models),providers:kb(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 I$=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},kb=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 O$(t){let e=String(t||"7d");return e in Zr?{key:e,ms:Zr[e]}:{key:"7d",ms:Zr["7d"]}}function C$(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=Da(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 M$(t){if(!t?.cwd)return null;try{let n=(await R$.readFile($$.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 Sb(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}:O$(e),f=[];for(let d of t||[]){let p=await D(d),m=C$(d,p);i&&c<o&&(m=m.filter(g=>(g.ts??0)<=c)),f.push({track:d,events:m,brief:await M$(d)})}return{window:l,...xb(f,{windowMs:u,now:c})}}var xn=36e5,Eb=24*xn,N$=400,P$=12,Qr=t=>Math.round(t*1e4)/1e4;function B$({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 D$=(t,e)=>t!=null&&e-t<=48*xn?xn:Eb;function vb(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 j$(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 Tb=()=>({input:0,output:0,cacheRead:0,cacheCreation:0}),Ab=(t,e)=>{for(let n of Object.keys(t))t[n]+=e?.[n]||0},L$=t=>Object.fromEntries(Object.entries(t).map(([e,n])=>[e,Qr(n)]));async function _b(t,e={}){let n=e.now??Date.now(),r=B$({...e,now:n}),o=r.since==null?Eb:D$(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=Tb(),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+=aa(w).usd)}if(!rt.length)continue;let st=ue(rt,{boxLabels:id(v)});f+=st.totalUsd,d+=st.turns,p+=st.repricedTurns||0,m+=st.unpricedTurns||0;for(let w of st.unpricedModels||[])y.add(w);Ab(u,st.totalTokens);for(let[w,h]of Object.entries(st.byModel))c[w]||={usd:0,events:0,tokens:Tb()},c[w].usd=Qr(c[w].usd+h.usd),c[w].events+=h.events,Ab(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}=aa(w);if(!h)continue;let T=vb(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?vb(r.since,o):J,j=[];for(let v=$,C=0;v!=null&&v<r.until&&C<N$;v=j$(v,o),C++){let q=b.get(v);j.push(q?{t:v,total:Qr(q.total),byProvider:L$(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,P$)}}import to from"node:fs/promises";import F$ from"node:fs";import It from"node:path";import U$ from"node:os";import{execFile as H$}from"node:child_process";import{promisify as W$}from"node:util";var q$=W$(H$),Ib=process.env.BOSTRAT_STATE_DIR||It.join(process.env.HOME||U$.tmpdir(),".bostrat"),mi=!1,cd=null;async function ld(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 K$=new Set([".claude",".codex",".credentials.json","auth.json",".claude.json","integrations.json"]);async function hi(t,e,{skip:n=null}={}){let r=0,o=await to.readdir(t,{withFileTypes:!0}).catch(()=>[]);for(let s of o){if(K$.has(s.name)||n?.has(s.name))continue;let a=It.join(t,s.name);s.isDirectory()?r+=await hi(a,It.join(e,s.name)):s.isFile()&&(r+=await ld(a,It.join(e,s.name))?1:0)}return r}async function Rb({stateDir:t=Ib,dest:e=process.env.BOSTRAT_SYNC_DIR||`${Ib}-sync`,remote:n=process.env.BOSTRAT_STATE_REMOTE||null,tracks:r=null}={}){try{await to.mkdir(e,{recursive:!0});let o=0;o+=await ld(It.join(t,"tracks.json"),It.join(e,"tracks.json"))?1:0,o+=await ld(It.join(t,"events.jsonl"),It.join(e,"events.jsonl"))?1:0,o+=await hi(It.join(t,"events"),It.join(e,"events")),o+=await hi(It.join(t,"memory"),It.join(e,"memory"));let s=new Set(["mocks"]);for(let u of r||[])!u?.cwd||!u?.id||(o+=await hi(It.join(u.cwd,".bostrat"),It.join(e,"workspaces",u.id,".bostrat"),{skip:s}));let a=(...u)=>q$("git",["-C",e,...u]);F$.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 Ui(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||mi))try{await a("push","-q",n,"HEAD:refs/heads/main"),l=!0,mi&&console.log("[state-sync] push recovered \u2014 off-box copy is current again"),mi=!1,cd=null}catch(u){mi=!0;let f=(u?.stderr||u?.message||String(u)).trim().split(`
|
|
394
|
+
`).slice(-3).join(" | ").slice(0,300);f!==cd&&(cd=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 G$}from"node:child_process";import{promisify as V$}from"node:util";var z$=V$(G$),J$=12e4,X$=6e4;function Y$(t){return`refs/bostrat/wip/${t}`}function Cb(t=process.env){let e=String(t.BOSTRAT_WIP_SYNC_MS??"").trim().toLowerCase();return e!=="0"&&e!=="off"&&e!=="false"}function Z$(t=process.env){let e=Number(t.BOSTRAT_WIP_SYNC_MS);return Number.isFinite(e)&&e>0?e:J$}var wi=(t,e,n=15e3)=>z$("git",["-C",t,...e],{timeout:n,maxBuffer:4*1024*1024}),$b=new Map,Ob=new Set;async function Q$(t,{log:e=console}={}){let{id:n,cwd:r}=t??{};if(!n||!r)return{pushed:!1,reason:"no-workspace"};try{await wi(r,["rev-parse","--git-dir"])}catch{return{pushed:!1,reason:"not-a-repo"}}try{await wi(r,["remote","get-url","origin"])}catch{return Ob.has(r)||(Ob.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 Go(r,"bostrat: wip snapshot"),{stdout:s}=await wi(r,["rev-parse",`${o}^{tree}`]),a=s.trim();if($b.get(r)===a)return{pushed:!1,reason:"unchanged"};let i=Y$(n);return await wi(r,["push","--force","origin",`${o}:${i}`],X$),$b.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(`
|
|
395
|
+
`)[0]}`),{pushed:!1,reason:"push-failed"}}}function Mb({listTracks:t,sweepMs:e=Z$(),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 Q$(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 tO}from"node:child_process";import{promisify as eO}from"node:util";import{randomUUID as nO}from"node:crypto";import rO from"node:fs";import oO from"node:os";import sO from"node:path";var vn=eO(tO),gi=t=>new Promise(e=>setTimeout(e,t)),Tn=nc,ud=process.env.BOSTRAT_USAGE_PROBE_CWD||sO.join(oO.homedir(),".bostrat","usage-probe"),aO=Number(process.env.BOSTRAT_USAGE_IDLE_REAP_MS)||5*6e4,iO=Number(process.env.BOSTRAT_USAGE_PANEL_TIMEOUT_MS)||15e3,cO=Number(process.env.BOSTRAT_USAGE_READY_TIMEOUT_MS)||45e3,Nb=Number(process.env.BOSTRAT_USAGE_CACHE_MS)||2e4,Sn=null,ms=null,hs=null;async function lO(){try{return await vn(N,P("has-session","-t",Tn)),!0}catch{return!1}}function uO(){Sn&&clearTimeout(Sn),Sn=setTimeout(()=>dO().catch(()=>{}),aO),Sn.unref&&Sn.unref()}async function dO(){Sn&&(clearTimeout(Sn),Sn=null);try{await vn(N,P("kill-session","-t",Tn))}catch{}}async function fO(){let t=Date.now()+cO;for(;;){let e=await dd().catch(()=>"");if(/for agents|for shortcuts|bypass permissions on/i.test(e)){await gi(500);return}if(Date.now()>t)throw new Error("usage probe didn't reach the prompt (trust/onboarding may be blocking)");await gi(500)}}async function pO(){if(await lO())return;if(!await Ye())throw new Error("host tmux server not running \u2014 start globo-tmux.service");rO.mkdirSync(ud,{recursive:!0}),await Ge(ud);let t=nO();await vn(N,P("new-session","-d","-s",Tn,"-c",ud,"-x","200","-y","60"));let e=`${ec} --session-id ${t}`;await vn(N,P("send-keys","-t",Tn,e,"Enter")),await fO()}async function dd(){let{stdout:t}=await vn(N,P("capture-pane","-t",Tn,"-p"));return t}async function mO(){await pO(),await vn(N,P("send-keys","-t",Tn,"-l","/usage")),await vn(N,P("send-keys","-t",Tn,"Enter"));let t=Date.now()+iO,e="";for(;await gi(400),e=await dd(),!(/Current\s+(session|week)/i.test(e)||/\d+%\s*used/i.test(e)||Date.now()>t););return await gi(900),e=await dd(),await vn(N,P("send-keys","-t",Tn,"Escape")).catch(()=>{}),uO(),e}function hO(t){let e=String(t||"").split(`
|
|
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 wO(t){let e=String(t||"").split(`
|
|
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 Pb({fresh:t=!1}={}){return!t&&ms&&Date.now()-ms.at<Nb?ms.data:hs||(hs=(async()=>{try{let e=await mO(),n=hO(e),r={...n,contributors:wO(e),subscription:!!(n.session||n.week||n.weekModels),capturedAt:Date.now()};return ms={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 ms={at:Date.now()-(Nb-3e3),data:n},n}finally{hs=null}})(),hs)}var gO=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 Bb(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 bO(t){let e=String(t||"").split(`
|
|
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:Bb(i.primary),week:Bb(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 fd(t){let e={provider:"codex",supported:!0,contributors:null};try{let n=await Hn(t?.cwd,t?.sessionId);if(!n)return{...e,session:null,week:null,subscription:!1,error:"no codex rollout found for this box yet",capturedAt:Date.now()};let r=await Nt(n.file,gO),o=bO(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 Db(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 yi({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 fd(i);if(c.subscription){a=c;break}(!a||a.error&&!c.error)&&(a=c)}r.push(a||await fd(null))}return n.has("cursor")&&r.push(await yi({agent:"cursor"})),{providers:r,capturedAt:Date.now()}}async function yi(t,{fresh:e=!1}={}){let n=t?.agent||"claude";return n==="codex"?fd(t):n==="cursor"?{provider:"cursor",supported:!1,subscription:!1,session:null,week:null,contributors:null,capturedAt:Date.now()}:{provider:"claude",supported:!0,...await Pb({fresh:e})}}function kO(t=process.env){return!!(zt(t)&&t.BOSTRAT_PLANE_TOKEN)}var xO=t=>({authorization:`Bearer ${t.BOSTRAT_PLANE_TOKEN||""}`}),bi=(t,e)=>Object.assign(new Error(t),{status:e});async function jb(t,e){if(!kO(e))throw bi("node is not plane-linked \u2014 Gmail unavailable",503);let n;try{n=await fetch(`${zt(e)}${t}`,{headers:xO(e)})}catch(o){throw bi(`plane unreachable \u2014 Gmail unavailable (${o?.message||o})`,503)}let r=await n.json().catch(()=>({}));if(!n.ok)throw bi(r?.error||`plane replied ${n.status}`,n.status);return r}async function Lb(t,{max:e=10,env:n=process.env}={}){let r=new URLSearchParams({q:t||""});return e&&r.set("max",String(e)),(await jb(`/v1/gmail/messages?${r.toString()}`,n)).messages||[]}async function Fb(t,{env:e=process.env}={}){if(!t)throw bi("message id required",400);return(await jb(`/v1/gmail/messages/${encodeURIComponent(t)}`,e)).message}function pd(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 SO(t){let[e,n]=t.split("/"),r=pd(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 Ub(t){return(t||"").split(",").map(e=>e.trim()).filter(Boolean).map(SO).filter(Boolean)}function vO(t){let e=t.headers["fly-client-ip"];return typeof e=="string"&&e.trim()?e.trim():(t.socket&&t.socket.remoteAddress||"").replace(/^::ffff:/,"")}function Hb(t){let e=Ub(t),n=String(t||"").trim().toLowerCase()==="open";return{open:n,clientIp:vO,allowed(r){if(n)return!0;let o=pd((r||"").replace(/^::ffff:/,""));return o==null?!1:e.some(s=>s(o))}}}function Wb(t,e){let n=Ub(t);return{active:n.length>0,allows(r){if(!n.length)return!1;let o=(r.socket&&r.socket.remoteAddress||"").replace(/^::ffff:/,""),s=pd(o);if(s==null||!n.some(i=>i(s)))return!1;let a=e(r);return!!(a&&a.view)}}}import qb from"node:crypto";import ki from"node:fs";import TO from"node:path";function Kb(t=process.env,{stateDir:e=ub,log:n=console}={}){let r=String(t.BOSTRAT_NODE_API_TOKEN||"").trim();if(r)return r;let o=TO.join(e,"api-token");try{let a=ki.readFileSync(o,"utf8").trim();if(a)return a}catch{}let s=qb.randomBytes(32).toString("base64url");try{ki.mkdirSync(e,{recursive:!0}),ki.writeFileSync(o,s+`
|
|
399
|
+
`,{mode:384}),ki.chmodSync(o,384)}catch(a){n.error?.(`[bostrat-agent] could not persist the node API token: ${a?.message||a}`)}return s}function ws(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 Gb(t,e){if(!t||!e)return!1;let n=Buffer.from(String(t)),r=Buffer.from(String(e));return n.length!==r.length?!1:qb.timingSafeEqual(n,r)}function Vb(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 AO=[/^\/api\/github\/token\b/],EO=t=>AO.some(e=>e.test(t));function zb({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=Vb(o);return Gb(c,t)?a():EO(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||ws(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 Jb(t,e,{enforce:n=!0}={}){return!n||Gb(Vb(t),e)?!0:ws(t)}import _O from"node:http";import IO from"node:net";function Xb(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 xi(t){let e=Xb(t.headers.host);return e?{slug:e,view:Pc(e)}:null}var RO=t=>`<!doctype html><meta charset="utf-8">
|
|
395
400
|
<meta http-equiv="refresh" content="3">
|
|
396
401
|
<title>${t.name} \u2014 starting</title>
|
|
397
402
|
<body style="font-family:system-ui;background:#0b0e14;color:#dce3f0;display:grid;place-items:center;height:100vh;margin:0">
|
|
398
403
|
<div style="text-align:center"><p style="font-size:2rem;margin:0">\u23F3</p>
|
|
399
|
-
<p><b>${t.name}</b> is waking up\u2026</p><p style="opacity:.6">auto-refreshing</p></div>`;function
|
|
404
|
+
<p><b>${t.name}</b> is waking up\u2026</p><p style="opacity:.6">auto-refreshing</p></div>`;function Yb(t,e,n){let r=xi(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(RO(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=_O.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 Zb(t,e,n){let r=xi(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
|
|
400
405
|
\r
|
|
401
406
|
bostrat: no live view "${s}"\r
|
|
402
|
-
`),e.destroy(),!0;let a=
|
|
407
|
+
`),e.destroy(),!0;let a=IO.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
|
|
403
408
|
`)+`\r
|
|
404
409
|
\r
|
|
405
|
-
`),n?.length&&a.write(n),e.pipe(a),a.pipe(e)}),i=()=>{a.destroy(),e.destroy()};return a.on("error",i),e.on("error",i),!0}function
|
|
406
|
-
`)){let s=o.trim();if(!s)continue;if(s.startsWith("#")){n==null&&(n=s.replace(/^#+\s*/,"").trim().slice(0,rO)||null);continue}let a=/^[-*]\s+(.*)$/.exec(s);if(!a)continue;let i=a[1].trim(),c=null,l=new RegExp(`^\\[([^\\]]{1,${oO}})\\]\\s+(.*)$`).exec(i);if(l&&(c=l[1].trim(),i=l[2].trim()),!!i){if(i.length>Lb)throw new Error(`bullet ${r.length+1} is ${i.length} chars \u2014 over the ${Lb}-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>jb)throw new Error(`${r.length} bullets \u2014 over the ${jb}-bullet cap; group related changes into one line`);return{title:n||null,bullets:r}}async function Ub(t,{repos:e=od()}={}){let n=new Set(e);return(await be()).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 Hb(t,{md:e,day:n=null}){let r=iO(e),o=n==null||n===""?aO():String(n);if(!sO.test(o)||Number.isNaN(Date.parse(o)))throw new Error(`invalid day "${n}" \u2014 expected YYYY-MM-DD`);let s=await $({id:t.id,cwd:t.cwd},{type:"changelog.published",actor:"agent",source:"orchestrator",boxId:t.leadBox||null,payload:{ts:Date.now(),day:o,title:r.title,bullets:r.bullets}});return{day:o,title:r.title,bullets:r.bullets,eventId:s.id}}import{execFile as cO}from"node:child_process";import{promisify as lO}from"node:util";var ad=lO(cO),Wb=3e4,uO=30*6e4,dO=Wb,fO=10*6e4,pO=10080*6e4,mO=60*6e4,hO=8,wO=1440*6e4,gO=3,qb="number,url,title,headRefName,headRefOid,state,mergedAt,closedAt",sd=60*6e4,yO=15*6e4,yi=new Map,bi=new Map,bO=/could not resolve|not found|no pull requests?/i,kO=t=>bO.test(String(t?.message||t||""));function xO(t,e){return[t.ciFix?.delegatedAt,t.reconcile?.delegatedAt,t.ciFixAwait?.since,t.reconcileAwait?.since].some(n=>n&&e-n<sd)}var Kb={fast:dO,slow:fO,decay:mO};function Gb(t,e){return t.armedAt&&e-t.armedAt<uO||xO(t,e)?"fast":e-(t.firstSeen||0)>pO?"decay":"slow"}function SO(t,e){return Kb[Gb(t,e)]}function ks(t,e,n,r){let o=t.lanes?.get(ft(e.repo,e.number))||{};return Va({change:n,repo:e.repo,number:Number(e.number),githubAt:r||null,lane:o.lane||null,intervalMs:o.intervalMs||null,source:"watch"})}async function vO(t,e){Le("watch");try{let{stdout:n}=await ad("gh",["pr","view",String(e),"--repo",t,"--json",qb],{timeout:3e4,maxBuffer:4194304,env:{...process.env,...await ae({slug:t})}});return JSON.parse(n||"null")}catch(n){throw un(n)&&dn(n),n}}async function TO(t){Le("watch");try{let{stdout:e}=await ad("gh",["pr","list","--repo",t,"--state","all","--limit","100","--json",qb],{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 un(e)&&dn(e),e}}async function AO(){let t=new Map;for(let e of await ot().catch(()=>[]))t.set(e.id,e);return t}async function EO(t,e){await $({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"user",source:"orchestrator",boxId:null,payload:{ts:Date.now(),change:"merged",...e}})}async function _O(t,e){let n=await zn(t);return n?Br(n,e):null}async function IO(t,e,n){let r=await zn(t);if(!r)throw new Error(`no local checkout for ${t}`);return Dr(r,e,{method:n})}async function RO(t,e){let n=await zn(t);if(!n)throw new Error(`no local checkout for ${t}`);return ts(n,e)}async function $O(t,e){await $({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"user",source:"orchestrator",boxId:null,payload:{ts:Date.now(),change:"auto-canceled",...e}})}async function 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 OO=10*6e4;async function MO(t,e,n){let r=t.autoMerge?.armedAt||0;if(r&&(n.now??Date.now())-r>wO){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<OO)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 zb(t,{...o,state:"MERGED"},e,n,{change:"auto-merged",githubAt:o.checks.settledAt||null})}}async function gs(t,e,n){await $({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"user",source:"orchestrator",boxId:null,payload:{ts:Date.now(),change:e,...n}})}async function CO(t,e){try{return await ad("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 L(t).catch(()=>[])).some(s=>s.type==="pr.state.changed"&&s.payload?.change===e&&s.payload?.number===Number(n)&&r(s))}var bs=(t,e)=>({number:Number(t.number),url:e?.url||t.url,title:e?.title||t.title,branch:e?.headRefName||t.headRef,repo:t.repo});async function NO(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 Ta(t.repo,t.number,{headSha:s}).catch(()=>{}),t.reconcile?.headSha&&s!==t.reconcile.headSha&&await Ea(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 Vm(t.repo,t.number,s).catch(()=>{}),t.baseFoldAt&&await zm(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)>sd)await Aa(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 Aa(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 Aa(t.repo,t.number).catch(()=>{})}}}if(t.reconcileAwait)if(o-(t.reconcileAwait.since||0)>sd)await _a(t.repo,t.number).catch(()=>{});else{let i=await r.detail(t.repo,t.number).catch(()=>null);if(i){if(i.mergeable==="CONFLICTING")await _a(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 _a(t.repo,t.number).catch(()=>{})}}}}async function PO(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 BO(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 zb(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 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 DO(t={}){let{now:e=Date.now(),auth:n=iu,view:r=vO,list:o=TO,links:s=Xm,trackMap:a=AO,emitMerged:i=EO,mark:c=sn,archiveIfArmed:l=pn,notifyIfArmed:u=mn,notifyIfResolved:f=Nu,batchThreshold:d=gO,detail:m=_O,merge:h=IO,updateBranch:w=RO,lookup:g=Rt,disarm:b=Lo,emitDisarmed:S=$O,localHasCommit:A=CO}=t;if(!(await n().catch(()=>null))?.authenticated||Ua()>0)return 0;let O=(await s().catch(()=>[])).filter(p=>e-(yi.get(ft(p.repo,p.number))||0)>=SO(p,e));if(!O.length)return 0;let F=Date.now(),T=new Map,M=new Map;for(let p of O){let v=Gb(p,e);T.set(ft(p.repo,p.number),{lane:v,intervalMs:Kb[v]}),yi.set(ft(p.repo,p.number),e),M.has(p.repo)||M.set(p.repo,[]),M.get(p.repo).push(p)}let W=await a().catch(()=>new Map),st={now:e,lanes:T,emitMerged:i,mark:c,archiveIfArmed:l,notifyIfArmed:u,notifyIfResolved:f,detail:m,merge:h,updateBranch:w,lookup:g,disarm:b,emitDisarmed:S,localHasCommit:A};for(let[p,v]of M){let E,rt=new Set;if(v.length>d)E=await o(p).catch(()=>null);else{E=new Map;for(let I of v)try{let R=await r(p,I.number);R?.number!=null&&E.set(Number(R.number),R)}catch(R){kO(R)&&rt.add(ft(I.repo,I.number))}}if(E)for(let I of v){let R=ft(I.repo,I.number),_=E.get(Number(I.number));if(!_){if(rt.has(R)){let P=(bi.get(R)||0)+1;bi.set(R,P),P>=hO&&(await BO(I,W,st),bi.delete(R),yi.delete(R))}continue}bi.delete(R),_.state==="MERGED"||_.state==="CLOSED"?(_.state==="MERGED"?await zb(I,_,W,st):await PO(I,_,W,st),yi.delete(R)):(await NO(I,_,W,st).catch(()=>{}),I.autoMerge&&await MO(I,W,st).catch(()=>{}))}}let y={};for(let{lane:p}of T.values())y[p]=(y[p]||0)+1;return console.log(`[pr-watch] probed ${O.length} link(s) (${Object.entries(y).map(([p,v])=>`${p} ${v}`).join(", ")}) in ${Date.now()-F}ms`),O.length}function Vb(){let t=!1;setInterval(async()=>{if(!t){t=!0;try{await DO()}catch{}finally{t=!1}}},Wb)}import Ke from"node:fs/promises";import no from"node:path";import ek from"node:os";import Jb from"node:fs/promises";import ki from"node:path";import{execFile as jO}from"node:child_process";import{promisify as LO}from"node:util";import{fileURLToPath as FO}from"node:url";var UO=LO(jO),Yb=ki.dirname(FO(import.meta.url)),HO=ki.resolve(Yb,"..","..","client","dist"),WO="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",qO=new Map([...WO].map((t,e)=>[t,e]));function KO(t){let e=[],n=0,r=0;for(let o of t){let s=qO.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 GO(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=KO(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 zO(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 Xb=new Map;async function VO(t){let e;try{e=await Jb.stat(t)}catch{return null}let n=Xb.get(t);if(n&&n.mtimeMs===e.mtimeMs)return n;try{let r=JSON.parse(await Jb.readFile(t,"utf8")),o={mtimeMs:e.mtimeMs,sources:r.sources||[],lines:GO(r.mappings||"")};return Xb.set(t,o),o}catch{return null}}var id=null;function JO(){return id||(id=process.env.BOSTRAT_RELEASE?Promise.resolve(process.env.BOSTRAT_RELEASE):UO("git",["rev-parse","--short","HEAD"],{cwd:ki.resolve(Yb,"..","..")}).then(t=>t.stdout.trim()).catch(()=>null)),id}var XO=/(?:https?:\/\/[^\s()]*)?\/assets\/([^\s()/]+\.js):(\d+):(\d+)/g;async function YO(t,{distDir:e=HO}={}){let n=[],r=0,o=!1;for(let s of t.matchAll(XO)){let[a,i,c,l]=s,u=await VO(ki.join(e,"assets",`${i}.map`)),f=u?zO(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 Zb(t,e={}){try{if(!t?.error?.stack||!t.release||t.release==="unknown")return t;let n=e.release??await JO();if(!n||t.release!==n)return t;let{stack:r,symbolicated:o}=await YO(t.error.stack,e);o&&(t.error.stack=r,t.error.symbolicated=!0)}catch{}return t}var ZO=()=>process.env.BOSTRAT_STATE_DIR||no.join(process.env.HOME||ek.tmpdir(),".bostrat"),vn=()=>no.join(ZO(),"telemetry"),nk=()=>no.join(vn(),".relay-offsets.json"),QO=50,tM=14,eM=3e4,Qb=500,qt=(t,e)=>typeof t=="string"?t.slice(0,e):"";function nM(t,e){if(!t||typeof t!="object")return null;let n=qt(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:qt(t.anonId,64),sessionId:qt(t.sessionId,64),userId:qt(t.userId,64),release:qt(t.release,64),appVersion:qt(t.appVersion,32),route:qt(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:qt(t.error.kind,256),message:qt(t.error.message,8192),stack:qt(t.error.stack,32768),fingerprint:qt(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:qt(s?.n,64),r:qt(s?.r,128)}))),o}var rk=t=>new Date(t).toISOString().slice(0,10),tk=0;async function ok(t,e=Date.now()){let r=(Array.isArray(t?.events)?t.events.slice(0,QO):[]).map(s=>nM(s,e)).filter(Boolean);if(!r.length)return 0;for(let s of r)s.error?.stack&&await Zb(s);await Ke.mkdir(vn(),{recursive:!0});let o=no.join(vn(),`${rk(e)}.ndjson`);return await Ke.appendFile(o,r.map(s=>JSON.stringify(s)+`
|
|
407
|
-
`).join(""),"utf8"),e-
|
|
408
|
-
`).filter(Boolean),f=[];for(let d of u)try{f.push(JSON.parse(d))}catch{}for(let d=0;d<f.length;d+=Qb){let m=f.slice(d,d+Qb).map(w=>oM(w)),h=await t(`${e.url.replace(/\/$/,"")}/v1/logs`,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${e.ingestKey}`},body:JSON.stringify({events:m})}).catch(()=>null);if(!h||!h.ok)return{sent:o,disabled:!1};o+=m.length}r[s]=i+l+1,await ik(r)}return{sent:o,disabled:!1}}var cd=null,ld=!1;function ck(){cd||(cd=setInterval(async()=>{if(!ld){ld=!0;try{await sM()}catch(t){console.error("[telemetry] relay failed:",t?.message||t)}finally{ld=!1}}},eM),cd.unref?.())}import vi from"node:fs/promises";import pk from"node:path";import aM from"node:os";var mk=()=>process.env.BOSTRAT_STATE_DIR||pk.join(process.env.HOME||aM.tmpdir(),".bostrat"),hk=()=>pk.join(mk(),"dictation-glossary.json"),iM=100,cM=500,lM=["bostrat","globopi","codex","tmux","worktree","Deepgram","Linear","Firecracker","Tigris","WireGuard","misous","postyx","cedar","gerbil","Anthropic","Opus","Haiku","Sonnet","keyterm","monorepo","dogfood"],Ti=null,ud=null,dd=Promise.resolve();function lk(){let t={},e=Date.now();for(let n of lM)t[n.toLowerCase()]={term:n,count:1,lastUsed:e,source:"seed"};return t}async function wk(){return Ti||(ud??=vi.readFile(hk(),"utf8").then(t=>JSON.parse(t)?.terms??lk()).catch(()=>lk()).then(t=>Ti=t).finally(()=>ud=null),ud)}async function uk(){let t=JSON.stringify({terms:Ti},null,2),e=hk(),n=`${e}.${process.pid}.tmp`;await vi.mkdir(mk(),{recursive:!0}),await vi.writeFile(n,t),await vi.rename(n,e)}function uM(){return dd=dd.then(uk,uk),dd.catch(()=>{})}async function fd(){return Object.values(await wk()).sort((e,n)=>n.count-e.count||n.lastUsed-e.lastUsed)}var dM=t=>Math.max(1,Math.ceil(t.length/4));async function fM(){let t=[],e=0;for(let{term:n}of await fd()){let r=dM(n);if(!(e+r>cM)&&(t.push(n),e+=r,t.length>=iM))break}return t}async function pM(t,e){let n=await wk(),r=t.toLowerCase(),o=n[r];n[r]={term:t,count:(o?.count??0)+1,lastUsed:Date.now(),source:o?.source??e}}var dk=t=>(t||"").split(/\s+/).map(e=>e.replace(/^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu,"")).filter(Boolean),Si=t=>t.toLowerCase();function fk(t){return!new RegExp("\\p{L}","u").test(t)||t.length<3?!1:/^[a-z]+$/.test(t)?t.length>=4:!0}async function gk(t,e){let n=dk(t),r=dk(e);if(!n.length||!r.length)return[];let o=n.length,s=r.length,a=Array.from({length:o+1},()=>new Array(s+1).fill(0));for(let u=o-1;u>=0;u--)for(let f=s-1;f>=0;f--)a[u][f]=Si(n[u])===Si(r[f])?a[u+1][f+1]+1:Math.max(a[u+1][f],a[u][f+1]);let i=new Set,c=0,l=0;for(;c<o&&l<s;)Si(n[c])===Si(r[l])?(c++,l++):a[c+1][l]>=a[c][l+1]?c++:(fk(r[l])&&i.add(r[l]),l++);for(;l<s;l++)fk(r[l])&&i.add(r[l]);if(!i.size)return[];for(let u of i)await pM(u,"learned");return await uM(),[...i]}var xs=null;function mM(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 yk(){if(!xs)return null;let t=await Mo();if(!t||t.apiKey.slice(-6)!==xs.keyTail)return null;let{keyTail:e,...n}=xs;return n}async function bk(t,e){let n=await Mo();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 fM())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=mM(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 pd={"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 hM(t){if(!t)return null;let e=String(t).trim();if(pd[e])return e;let n=null;for(let r of Object.keys(pd))e.startsWith(r)&&(!n||r.length>n.length)&&(n=r);return n}function wM(t,e={}){let n=pd[hM(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 gM(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 yM(){if(!Ss)return null;let t=await ya();if(!t||t.apiKey.slice(-6)!==Ss.keyTail)return null;let{keyTail:e,...n}=Ss;return n}async function bM(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=gM(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 kM={openai:bM};async function kk(){let t=await ya();return t?{configured:!0,provider:t.provider,model:t.model,voice:t.voice,problem:await yM()}:{configured:!1}}async function xk(){let t=await ya();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 kM[t.provider](t);return{provider:t.provider,model:t.model,voice:t.voice,...e}}async function Sk(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=wM(e,s),i=await $(t,{type:"cost.recorded",actor:"user",provider:"openai-realtime",payload:{model:e||"unknown",usd:a,tokens:{input:s.textIn+s.audioIn,output:s.textOut+s.audioOut,cacheRead:s.textInCached+s.audioInCached,cacheWrite:0},voice:s,seconds:Math.max(0,Math.round(Number(r)||0)),source:"voice-chat"}});return{usd:a,seq:i.seq}}var Ik=lt.dirname(SM(import.meta.url)),vk=Number(process.env.PORT)||3362,Ai=Jy(process.env),Rk=Number(process.env.BOSTRAT_AUTO_TRIGGER_PCT)||80,ro=Eb(process.env.BOSTRAT_ALLOW_IPS),$k=Ib(),Ok=process.env.BOSTRAT_ALLOW_LAN_TOKENLESS!=="1",x=ve();x.disable("x-powered-by");x.use((t,e,n)=>{if(ws(t)||ro.allowed(ro.clientIp(t)))return n();e.status(403).type("text/plain").send("bostrat: access restricted to the allowlist")});x.use(Pb);x.use(Ob({token:$k,enforce:Ok}));x.use("/api/mocks",ve.json({limit:"1mb"}));x.use("/api/mocks",ve.raw({type:"image/png",limit:"24mb"}));x.use("/api/runbooks",ve.json({limit:"1mb"}));x.use("/api/telemetry",ve.json({limit:"256kb"}));x.use("/api/hook",ve.json({limit:"2mb"}));x.use("/api/dictation",ve.raw({type:["audio/*","video/mp4","application/octet-stream"],limit:"32mb"}));x.use("/api/tracks/:id/upload",ve.raw({type:()=>!0,limit:"25mb"}));x.use(ve.json({limit:"64kb"}));x.get("/api/health",async(t,e)=>{e.json({ok:!0,tmuxSocket:Ft,hostTmuxAlive:await Je(),autoTriggerPct:Rk})});x.get("/api/sessions",async(t,e)=>{e.json({sessions:await En()})});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(!yt(n))return e.status(400).json({error:"invalid session name"});if(!await Y(n))return e.status(404).json({error:"session not found"});try{await 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(!yt(n))return e.status(400).json({error:"invalid session name"});let r=await Y(n);if(!r)return e.status(404).json({error:"session not found"});let o=await go(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 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=ta(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 Tk=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>Tk){let s=new Set([...r].sort((a,i)=>(i.archivedAt||0)-(a.archivedAt||0)).slice(0,Tk).map(a=>a.id));o=n.filter(a=>!a.archivedAt||s.has(a.id))}e.json({tracks:o,archivedTotal:r.length,agents:ni,agentAvailability:await Iu(),tickets:await ka()})});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 nb(n);if(r.cursorUsage=ru(),t.query.format==="text")return e.type("text/plain").send(rb(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 db(await ot(),{window:t.query.window,from:t.query.from,to:t.query.to});n.cursorUsage=ru(),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 xb(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 eh(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 nh(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 Ay()})}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 Sd(t.get(_s),{byToken:ry,legacy:()=>ny(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 zy(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(!sk(ro.clientIp(t)))return e.json({ok:!0,dropped:!0});let n=await ok(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 Te(),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 Ms(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:Bw(),latency:cg()})});x.post("/api/github/refresh",(t,e)=>{Md(),e.json({ok:!0})});x.get("/api/gmail/search",async(t,e)=>{try{let n=await vb(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 Tb(String(t.params.id))})}catch(n){e.status(n?.status||502).json({error:n?.message||String(n)})}});function yd(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 Ei({repo:t=null,number:e=null,branch:n=null}){let r=t?await tt(t):null;if(r&&e!=null){let s=await Rt(r,e),a=s&&await Q(s.trackId);if(a)return a}return n?(await ot()).find(s=>s.workspace?.branch===n&&yd(s,t,r))??null:null}async function Mk({repo:t=null,number:e=null,branch:n=null}){let r=await Ei({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&&yd(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 Ei({repo:s,number:e,branch:o}).catch(()=>null);if(!c)return;let l=i.repo??(s?await tt(s).catch(()=>null):null);await $({id:c.id,cwd:c.cwd},{type:"pr.state.changed",actor:"user",source:"orchestrator",payload:{ts:Date.now(),change:t,number:e,url:n,title:r,branch:o??c.workspace?.branch??null,...l?{repo:l}:{},...i}}).catch(()=>{})}async function EM(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 Rt(r,o.number))continue;let s=e.find(a=>a.workspace?.branch===o.headRefName&&yd(a,n.path,r));s&&await Ht({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 hg();await EM(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(!Qo.has(w))throw new Error(`invalid merge method: ${w}`);let g=Number(r);if(!Number.isInteger(g)||g<=0)throw new Error("invalid PR number");let b=await tt((await Fe(n)).path);if(!b)throw new Error("repo has no GitHub remote");let S=await Ei({repo:n,number:g,branch:l}).catch(()=>null);if(!S)throw new Error("auto-merge needs a Track to watch this PR \u2014 none owns it");if(a&&await ts(n,g),await Ht({repo:b,number:g,trackId:S.id,url:f,headRef:l,title:u,source:"matched"}).catch(()=>{}),!await jo(b,g,{merge:{method:w}}))throw new Error("couldn't record the auto-merge arm");return await Sr(b,g,i===!0),await Ia(b,g,c===!0&&i!==!0),xe(),await se("auto-armed",{number:g,url:f,title:u,branch:l,repo:n,track:S,...a?{extra:{updated:!0}}:{}}),e.json({ok:!0,number:g,auto:!0})}let d=await Dr(n,r,{method:o||"squash"});await se("merged",{number:d.number,url:f,title:u,branch:l,repo:n});let m=await tt(n).catch(()=>null),h=!1;if(m&&(i===!0&&await Sr(m,d.number,!0).catch(()=>{}),c===!0&&i!==!0&&await Ia(m,d.number,!0).catch(()=>{}),await mn(m,d.number).catch(()=>{}),h=await pn(m,d.number).catch(()=>!1),await sn(m,d.number,"merged").catch(()=>{})),i===!0&&!h){let w=await Ei({repo:n,number:d.number,branch:l}).catch(()=>null);w&&!w.archivedAt&&await qr(w.id).catch(()=>{})}e.json(d)}catch(d){let m=d?.message||String(d),h=/invalid|unknown repo|required|needs a Track|no GitHub remote/i.test(m)?400:502;e.status(h).json({error:m})}});x.post("/api/git/prs/disarm-auto",async(t,e)=>{let{repo:n,number:r,headRefName:o,title:s,url:a}=t.body||{};try{let i=Number(r);if(!Number.isInteger(i)||i<=0)throw new Error("invalid PR number");let c=await tt((await Fe(n)).path).catch(()=>null),l=c?await Lo(c,i).catch(()=>null):null;try{await uu(n,i)}catch(u){if(!l)throw u}xe(),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&&mn(o,a.number).catch(()=>{}).then(()=>pn(o,a.number)).catch(()=>{}).then(()=>sn(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:yg()})});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(!Qo.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 be()).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 Sr(d.repo,d.number,o===!0).catch(()=>{}),await Ia(d.repo,d.number,s===!0&&o!==!0).catch(()=>{});let u=[],f=[];for(let d of l){let m={repo:d.repo,number:d.number,ok:!0,action:"armed"};u.push(m);try{let h=await zn(d.repo);if(!h)throw new Error(`no local checkout for ${d.repo}`);let w=await Br(h,d.number);f.push({r:m,l:d,repoPath:h,pr:w,plan:wg(w,{update:r!==!1})})}catch(h){m.ok=!1,m.action="failed",m.reason=h?.message||String(h)}}for(let{r:d,l:m,repoPath:h,pr:w,plan:g}of gg(f))try{if(g.action==="skip")d.action="skipped",d.reason=g.reason;else if(g.action==="merge")await Dr(h,m.number,{method:i}),await se("merged",{number:m.number,url:w.url,title:w.title,branch:w.headRefName,repo:h,track:c}),await mn(m.repo,m.number).catch(()=>{}),await pn(m.repo,m.number).catch(()=>{}),await sn(m.repo,m.number,"merged").catch(()=>{}),d.action="merged";else{if(g.action==="update-arm"&&(await ts(h,m.number).catch(()=>{}),d.action="updated-armed"),!await jo(m.repo,m.number,{merge:{method:i}}))throw new Error("couldn't record the auto-merge arm");await se("auto-armed",{number:m.number,url:w.url,title:w.title,branch:w.headRefName,repo:h,track:c,...d.action==="updated-armed"?{extra:{updated:!0}}:{}})}}catch(b){d.ok=!1,d.action="failed",d.reason=b?.message||String(b)}if(xe(),!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 be()).filter(a=>a.trackId===r.id&&a.state!=="merged"&&a.state!=="closed"),s=[];for(let a of o){let i=await Lo(a.repo,a.number).catch(()=>null),c=await zn(a.repo).catch(()=>null);c&&await uu(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}))}xe(),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=Bt(i.workspace),f,d=Array.isArray(a)?a.filter(Boolean):[];if(d.length)f=d.map(w=>{let g=u.find(b=>b.name===w||b.repo===w||b.base===w||b.dir===w);if(!g)throw new Error(`invalid repo "${w}" \u2014 not tagged on this Track`);return g});else if(f=await Jo(i),!f.length)return e.status(409).json({error:"no tagged repo has work to ship"});let m=[];for(let w of f)try{let g=await fu(w.dir,{title:r,body:o,base:s}),b=w.repo||await tt(w.dir).catch(()=>null);b&&g.number&&await Ht({repo:b,number:g.number,trackId:i.id,url:g.url,headRef:i.workspace?.branch??null,title:r,source:"api"}),await se("opened",{number:g.number,url:g.url,title:r,track:i,branch:i.workspace?.branch,extra:{repo:b??w.name}}),m.push({repo:w.name,slug:b??null,ok:!0,url:g.url,number:g.number})}catch(g){m.push({repo:w.name,slug:w.repo??null,ok:!1,error:g?.message||String(g)})}let h=m.filter(w=>w.ok);return h.length?e.json({ok:!0,multi:!0,results:m,url:h[0].url,number:h[0].number}):e.status(502).json({error:m.map(w=>`${w.repo}: ${w.error}`).join("; "),results:m})}let c=await fu(i.cwd,{title:r,body:o,base:s}),l=await tt(i.cwd).catch(()=>null);l&&c.number&&await Ht({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 Jo(n),o=new Map(r.map(a=>[a.base,a])),s=Bt(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 wy(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 gy(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 Br(n,r),s=await tt(n).catch(()=>null),a=await Mk({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=Bt(a.workspace).find(b=>b.base===n||s&&b.repo===s)??null,c=a.workspace?.kind==="multi",l=await kg(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 jt.promises.mkdir(f,{recursive:!0}),await jt.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
|
|
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 Qb(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}.${$O(n.host)}/`}function $O(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 Xb(e)&&Pc(n[0])?n.slice(1).join("."):e}var tk=10,ek=200,OO=120,CO=24,MO=/^\d{4}-\d{2}-\d{2}$/;function md(t=process.env){return(t.CHANGELOG_REPOS||"globocodes/bostrat").split(",").map(e=>e.trim()).filter(Boolean)}var NO=(t=Date.now())=>new Date(t).toISOString().slice(0,10);function nk(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 PO(t){if(typeof t!="string"||!t.trim())throw new Error("md (markdown string) required");let e=Ro(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(`
|
|
411
|
+
`)){let s=o.trim();if(!s)continue;if(s.startsWith("#")){n==null&&(n=s.replace(/^#+\s*/,"").trim().slice(0,OO)||null);continue}let a=/^[-*]\s+(.*)$/.exec(s);if(!a)continue;let i=a[1].trim(),c=null,l=new RegExp(`^\\[([^\\]]{1,${CO}})\\]\\s+(.*)$`).exec(i);if(l&&(c=l[1].trim(),i=l[2].trim()),!!i){if(i.length>ek)throw new Error(`bullet ${r.length+1} is ${i.length} chars \u2014 over the ${ek}-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>tk)throw new Error(`${r.length} bullets \u2014 over the ${tk}-bullet cap; group related changes into one line`);return{title:n||null,bullets:r}}async function rk(t,{repos:e=md()}={}){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 ok(t,{md:e,day:n=null}){let r=PO(e),o=n==null||n===""?NO():String(n);if(!MO.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 BO}from"node:child_process";import{promisify as DO}from"node:util";var wd=DO(BO),sk=3e4,jO=30*6e4,LO=sk,FO=10*6e4,UO=10080*6e4,HO=60*6e4,WO=8,qO=1440*6e4,KO=3,ak="number,url,title,headRefName,headRefOid,state,mergedAt,closedAt",hd=60*6e4,GO=15*6e4,Si=new Map,vi=new Map,VO=/could not resolve|not found|no pull requests?/i,zO=t=>VO.test(String(t?.message||t||""));function JO(t,e){return[t.ciFix?.delegatedAt,t.reconcile?.delegatedAt,t.ciFixAwait?.since,t.reconcileAwait?.since].some(n=>n&&e-n<hd)}var ik={fast:LO,slow:FO,decay:HO};function ck(t,e){return t.armedAt&&e-t.armedAt<jO||JO(t,e)?"fast":e-(t.firstSeen||0)>UO?"decay":"slow"}function XO(t,e){return ik[ck(t,e)]}function ks(t,e,n,r){let o=t.lanes?.get(H(e.repo,e.number))||{};return za({change:n,repo:e.repo,number:Number(e.number),githubAt:r||null,lane:o.lane||null,intervalMs:o.intervalMs||null,source:"watch"})}async function YO(t,e){Le("watch");try{let{stdout:n}=await wd("gh",["pr","view",String(e),"--repo",t,"--json",ak],{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 ZO(t){Le("watch");try{let{stdout:e}=await wd("gh",["pr","list","--repo",t,"--state","all","--limit","100","--json",ak],{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 QO(){let t=new Map;for(let e of await ot().catch(()=>[]))t.set(e.id,e);return t}async function tC(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 eC(t,e){let n=await Jn(t);return n?Lr(n,e):null}async function nC(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 rC(t,e){let n=await Jn(t);if(!n)throw new Error(`no local checkout for ${t}`);return Qo(n,e)}async function oC(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 sC=10*6e4;async function aC(t,e,n){let r=t.autoMerge?.armedAt||0;if(r&&(n.now??Date.now())-r>qO){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<sC)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 lk(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 iC(t,e){try{return await wd("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 cC(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 Ta(t.repo,t.number,{headSha:s}).catch(()=>{}),t.reconcile?.headSha&&s!==t.reconcile.headSha&&await Ea(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<GO;await ch(t.repo,t.number,s).catch(()=>{}),t.baseFoldAt&&await ih(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)>hd)await Aa(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 Aa(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 Aa(t.repo,t.number).catch(()=>{})}}}if(t.reconcileAwait)if(o-(t.reconcileAwait.since||0)>hd)await _a(t.repo,t.number).catch(()=>{});else{let i=await r.detail(t.repo,t.number).catch(()=>null);if(i){if(i.mergeable==="CONFLICTING")await _a(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 _a(t.repo,t.number).catch(()=>{})}}}}async function lC(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 uC(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 lk(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 dC(t={}){let{now:e=Date.now(),auth:n=mu,view:r=YO,list:o=ZO,links:s=uh,trackMap:a=QO,emitMerged:i=tC,mark:c=cn,archiveIfArmed:l=hn,notifyIfArmed:u=wn,notifyIfResolved:f=Hu,batchThreshold:d=KO,detail:p=eC,merge:m=nC,updateBranch:g=rC,lookup:y=$t,disarm:b=jo,emitDisarmed:S=oC,localHasCommit:E=iC}=t;if(!(await n().catch(()=>null))?.authenticated||Ua()>0)return 0;let $=(await s().catch(()=>[])).filter(w=>e-(Si.get(H(w.repo,w.number))||0)>=XO(w,e));if(!$.length)return 0;let j=Date.now(),v=new Map,C=new Map;for(let w of $){let h=ck(w,e);v.set(H(w.repo,w.number),{lane:h,intervalMs:ik[h]}),Si.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){zO(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=(vi.get(I)||0)+1;vi.set(I,G),G>=WO&&(await uC(O,q,rt),vi.delete(I),Si.delete(I))}continue}vi.delete(I),A.state==="MERGED"||A.state==="CLOSED"?(A.state==="MERGED"?await lk(O,A,q,rt):await lC(O,A,q,rt),Si.delete(I)):(await cC(O,A,q,rt).catch(()=>{}),O.autoMerge&&await aC(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 uk(){let t=!1;setInterval(async()=>{if(!t){t=!0;try{await dC()}catch{}finally{t=!1}}},sk)}import Ke from"node:fs/promises";import no from"node:path";import gk from"node:os";import dk from"node:fs/promises";import Ti from"node:path";import{execFile as fC}from"node:child_process";import{promisify as pC}from"node:util";import{fileURLToPath as mC}from"node:url";var hC=pC(fC),pk=Ti.dirname(mC(import.meta.url)),wC=Ti.resolve(pk,"..","..","client","dist"),gC="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",yC=new Map([...gC].map((t,e)=>[t,e]));function bC(t){let e=[],n=0,r=0;for(let o of t){let s=yC.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 kC(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=bC(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 xC(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 fk=new Map;async function SC(t){let e;try{e=await dk.stat(t)}catch{return null}let n=fk.get(t);if(n&&n.mtimeMs===e.mtimeMs)return n;try{let r=JSON.parse(await dk.readFile(t,"utf8")),o={mtimeMs:e.mtimeMs,sources:r.sources||[],lines:kC(r.mappings||"")};return fk.set(t,o),o}catch{return null}}var gd=null;function vC(){return gd||(gd=process.env.BOSTRAT_RELEASE?Promise.resolve(process.env.BOSTRAT_RELEASE):hC("git",["rev-parse","--short","HEAD"],{cwd:Ti.resolve(pk,"..","..")}).then(t=>t.stdout.trim()).catch(()=>null)),gd}var TC=/(?:https?:\/\/[^\s()]*)?\/assets\/([^\s()/]+\.js):(\d+):(\d+)/g;async function AC(t,{distDir:e=wC}={}){let n=[],r=0,o=!1;for(let s of t.matchAll(TC)){let[a,i,c,l]=s,u=await SC(Ti.join(e,"assets",`${i}.map`)),f=u?xC(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 mk(t,e={}){try{if(!t?.error?.stack||!t.release||t.release==="unknown")return t;let n=e.release??await vC();if(!n||t.release!==n)return t;let{stack:r,symbolicated:o}=await AC(t.error.stack,e);o&&(t.error.stack=r,t.error.symbolicated=!0)}catch{}return t}var EC=()=>process.env.BOSTRAT_STATE_DIR||no.join(process.env.HOME||gk.tmpdir(),".bostrat"),An=()=>no.join(EC(),"telemetry"),yk=()=>no.join(An(),".relay-offsets.json"),_C=50,IC=14,RC=3e4,hk=500,Gt=(t,e)=>typeof t=="string"?t.slice(0,e):"";function $C(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 bk=t=>new Date(t).toISOString().slice(0,10),wk=0;async function kk(t,e=Date.now()){let r=(Array.isArray(t?.events)?t.events.slice(0,_C):[]).map(s=>$C(s,e)).filter(Boolean);if(!r.length)return 0;for(let s of r)s.error?.stack&&await mk(s);await Ke.mkdir(An(),{recursive:!0});let o=no.join(An(),`${bk(e)}.ndjson`);return await Ke.appendFile(o,r.map(s=>JSON.stringify(s)+`
|
|
412
|
+
`).join(""),"utf8"),e-wk>6*3600*1e3&&(wk=e,OC(e).catch(()=>{})),r.length}async function OC(t=Date.now()){let e=bk(t-IC*24*3600*1e3),n=[];try{n=await Ke.readdir(An())}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 Ke.rm(no.join(An(),s),{force:!0});let o=await Sk();for(let s of r)delete o[s];await vk(o)}var Ai=new Map;function xk(t,e=Date.now()){let n=t||"unknown",r=Ai.get(n);return r||(Ai.size>512&&Ai.clear(),r={tokens:40,at:e},Ai.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 Sk(){try{return JSON.parse(await Ke.readFile(yk(),"utf8"))||{}}catch{return{}}}async function vk(t){await Ke.mkdir(An(),{recursive:!0}),await Ke.writeFile(yk(),JSON.stringify(t),"utf8")}function CC(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||gk.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 MC({fetchImpl:t=fetch}={}){let e=await Rm();if(!e)return{sent:0,disabled:!0};let n;try{n=(await Ke.readdir(An())).filter(s=>s.endsWith(".ndjson")).sort()}catch{return{sent:0,disabled:!1}}let r=await Sk(),o=0;for(let s of n){let a=no.join(An(),s),i=r[s]||0,c;try{let d=await Ke.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(`
|
|
413
|
+
`).filter(Boolean),f=[];for(let d of u)try{f.push(JSON.parse(d))}catch{}for(let d=0;d<f.length;d+=hk){let p=f.slice(d,d+hk).map(g=>CC(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 vk(r)}return{sent:o,disabled:!1}}var yd=null,bd=!1;function Tk(){yd||(yd=setInterval(async()=>{if(!bd){bd=!0;try{await MC()}catch(t){console.error("[telemetry] relay failed:",t?.message||t)}finally{bd=!1}}},RC),yd.unref?.())}import _i from"node:fs/promises";import Rk from"node:path";import NC from"node:os";var $k=()=>process.env.BOSTRAT_STATE_DIR||Rk.join(process.env.HOME||NC.tmpdir(),".bostrat"),Ok=()=>Rk.join($k(),"dictation-glossary.json"),PC=100,BC=500,DC=["bostrat","globopi","codex","tmux","worktree","Deepgram","Linear","Firecracker","Tigris","WireGuard","misous","postyx","cedar","gerbil","Anthropic","Opus","Haiku","Sonnet","keyterm","monorepo","dogfood"],Ii=null,kd=null,xd=Promise.resolve();function Ak(){let t={},e=Date.now();for(let n of DC)t[n.toLowerCase()]={term:n,count:1,lastUsed:e,source:"seed"};return t}async function Ck(){return Ii||(kd??=_i.readFile(Ok(),"utf8").then(t=>JSON.parse(t)?.terms??Ak()).catch(()=>Ak()).then(t=>Ii=t).finally(()=>kd=null),kd)}async function Ek(){let t=JSON.stringify({terms:Ii},null,2),e=Ok(),n=`${e}.${process.pid}.tmp`;await _i.mkdir($k(),{recursive:!0}),await _i.writeFile(n,t),await _i.rename(n,e)}function jC(){return xd=xd.then(Ek,Ek),xd.catch(()=>{})}async function Sd(){return Object.values(await Ck()).sort((e,n)=>n.count-e.count||n.lastUsed-e.lastUsed)}var LC=t=>Math.max(1,Math.ceil(t.length/4));async function FC(){let t=[],e=0;for(let{term:n}of await Sd()){let r=LC(n);if(!(e+r>BC)&&(t.push(n),e+=r,t.length>=PC))break}return t}async function UC(t,e){let n=await Ck(),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),Ei=t=>t.toLowerCase();function Ik(t){return!new RegExp("\\p{L}","u").test(t)||t.length<3?!1:/^[a-z]+$/.test(t)?t.length>=4:!0}async function Mk(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]=Ei(n[u])===Ei(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;)Ei(n[c])===Ei(r[l])?(c++,l++):a[c+1][l]>=a[c][l+1]?c++:(Ik(r[l])&&i.add(r[l]),l++);for(;l<s;l++)Ik(r[l])&&i.add(r[l]);if(!i.size)return[];for(let u of i)await UC(u,"learned");return await jC(),[...i]}var xs=null;function HC(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 Nk(){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 Pk(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 FC())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=HC(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 vd={"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 WC(t){if(!t)return null;let e=String(t).trim();if(vd[e])return e;let n=null;for(let r of Object.keys(vd))e.startsWith(r)&&(!n||r.length>n.length)&&(n=r);return n}function qC(t,e={}){let n=vd[WC(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 KC(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 GC(){if(!Ss)return null;let t=await ba();if(!t||t.apiKey.slice(-6)!==Ss.keyTail)return null;let{keyTail:e,...n}=Ss;return n}async function VC(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=KC(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 zC={openai:VC};async function Bk(){let t=await ba();return t?{configured:!0,provider:t.provider,model:t.model,voice:t.voice,problem:await GC()}:{configured:!1}}async function Dk(){let t=await ba();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 zC[t.provider](t);return{provider:t.provider,model:t.model,voice:t.voice,...e}}async function jk(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=qC(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 qk=lt.dirname(XC(import.meta.url)),Lk=Number(process.env.PORT)||3362,Ri=db(process.env),Kk=Number(process.env.BOSTRAT_AUTO_TRIGGER_PCT)||80,ro=Hb(process.env.BOSTRAT_ALLOW_IPS),Id=Wb(process.env.BOSTRAT_VIEW_FRONT_CIDRS,xi),Gk=Kb(),Vk=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))||Id.allows(t))return n();e.status(403).type("text/plain").send("bostrat: access restricted to the allowlist")});x.use(Yb);x.use(zb({token:Gk,enforce:Vk}));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:Kk})});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 vp(n);e.json({ok:!0,step:r.step,file:r.file,startedAt:r.startedAt})}catch(r){e.status(409).json({error:r?.message||String(r)})}});x.get("/api/handoff/:name",(t,e)=>{let n=decodeURIComponent(t.params.name),r=ra(n);if(!r)return e.json({running:!1});e.json({running:!r.finishedAt,step:r.step,error:r.error,file:r.file,startedAt:r.startedAt,finishedAt:r.finishedAt})});var Fk=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>Fk){let s=new Set([...r].sort((a,i)=>(i.archivedAt||0)-(a.archivedAt||0)).slice(0,Fk).map(a=>a.id));o=n.filter(a=>!a.archivedAt||s.has(a.id))}e.json({tracks:o,archivedTotal:r.length,agents:ri,agentAvailability:await Pu(),tickets:await xa()})});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 yb(n);if(r.cursorUsage=uu(),t.query.format==="text")return e.type("text/plain").send(bb(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 Sb(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=uu(),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 Db(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 wh(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 gh(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 Uy()})}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 Md(t.get(_s),{byToken:gy,legacy:()=>wy(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 lb(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(!xk(ro.clientIp(t)))return e.json({ok:!0,dropped:!0});let n=await kk(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:Xw(),latency:Sg()})});x.post("/api/github/refresh",(t,e)=>{Wd(),e.json({ok:!0})});x.get("/api/gmail/search",async(t,e)=>{try{let n=await Lb(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 Fb(String(t.params.id))})}catch(n){e.status(n?.status||502).json({error:n?.message||String(n)})}});function Rd(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 $i({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&&Rd(s,t,r))??null:null}async function zk({repo:t=null,number:e=null,branch:n=null}){let r=await $i({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&&Rd(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 $i({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 tM(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&&Rd(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 Rg();await tM(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 $i({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 Ia(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 Ia(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 $i({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 gu(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 Mg(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:Cg()})});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 Ia(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:$g(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 Og(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 gu(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 bu(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 bu(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 Cy(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 My(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 zk({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 Ng(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
|
|
409
414
|
${b.log??"(no fetchable log \u2014 see link)"}
|
|
410
415
|
`).join(`
|
|
411
|
-
`));let m=l.map(b=>b.name).join(", "),h=c&&i?` in the ${i.name}/ checkout`:"",w=await oe(a.id,`CI is red on PR #${o.number} "${o.title}" (${o.url}) \u2014 branch ${o.headRefName}${h}. Failing check${l.length===1?"":"s"}: ${m}. The failure log is at ${d} in this ${c?"workspace":"worktree"} \u2014 read it, fix the root cause${c&&i?` in ${i.name}/`:""}, run the relevant tests locally, then commit and push to update the PR.`,{kind:"ci-fix"}),g=s;g&&(await Ht({repo:g,number:o.number,trackId:a.id,url:o.url,headRef:o.headRefName,title:o.title,source:"matched"}),await qm(g,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: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 Br(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 Mk({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=Bt(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 Ht({repo:u,number:o.number,trackId:a.id,url:o.url,headRef:o.headRefName,title:o.title,source:"matched"}),await Km(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=vw.filter(f=>t.body.actions.includes(f)),!o.length)return e.status(400).json({error:"actions must include commit, rebase, push and/or pr"})}else if(r==="pr")o=["commit","push","pr"];else if(r==="push")o=["commit","push"];else if(r!=="dismiss")return e.status(400).json({error:'offerId and actions ["commit"|"rebase"|"push"|"pr"] (or action "dismiss") required'});if(!n)return e.status(400).json({error:"offerId required"});let s=o!=null&&t.body?.notify===!0,a=await Q(t.params.id);if(!a)return e.status(404).json({error:"track not found"});let i=await L(a),c=i.find(f=>f.type==="pr.offered"&&f.payload?.offerId===n);if(!c)return e.status(404).json({error:"unknown offer"});if(i.some(f=>f.type==="pr.offer.answered"&&f.payload?.offerId===n))return e.status(409).json({error:"offer already answered"});let l=null;o&&(l=await oe(a.id,ja(o,{branch:c.payload.branch,base:c.payload.base,toPr:c.payload.toPr||null,repos:c.payload.repos||null,offeredRebase:Array.isArray(c.payload.offered)&&c.payload.offered.includes("rebase")}),{kind:o.includes("pr")?"pr-go":"push-go"}));let u=o?o.includes("pr")?"pr":o.includes("push")?"push":o.includes("commit")?"commit":"rebase":"dismiss";await $({id:a.id,cwd:a.cwd},{type:"pr.offer.answered",actor:"user",source:"orchestrator",boxId:c.boxId,payload:{ts:Date.now(),offerId:n,action:u,actions:o,...s?{notify:!0}:{}}}),s&&cy(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:Db(e,t.headers)});x.get("/api/views",async(t,e)=>{try{let n=await da({trackId:t.query.track||void 0});e.json({views:n.map(vs(t)),maxLive:nn})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/views/:id",async(t,e)=>{let n=await Vp(t.params.id);if(!n)return e.status(404).json({error:"view not found"});e.json({view:vs(t)(n),session:gr(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 Oc({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 Jp(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 Xp(t.params.id))})}catch(n){let r=n?.message||String(n),o=/not found/i.test(r)?404:/cap reached|no known command/i.test(r)?409:500;e.status(o).json({error:r})}});x.delete("/api/views/:id",async(t,e)=>{try{e.json(await Yp(t.params.id))}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});var _M=[["linear",Nn,jc],["jira",ga,Bc]],md=new Map;x.get("/api/integrations/ticketing",async(t,e)=>{try{let n=await Pc(),r={};for(let[o,s,a]of _M){let i=await s();if(!i){md.delete(o),r[o]={configured:!1};continue}let c=md.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},md.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 Pc()})}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 hm(o),i=n?await Q(n):null;!i&&r&&(i=(await ot()).find(l=>l.cwd&&(r===l.cwd||r.startsWith(l.cwd+lt.sep)))||null),i&&await $({id:i.id,cwd:i.cwd},{type:"ticket.flow.changed",actor:s==="user"?"user":"agent",source:"orchestrator",payload:{ts:Date.now(),set:{...o},flow:a}}).catch(()=>{}),e.json({ok:!0,flow:a})}catch(a){let i=a?.message||String(a);e.status(/unknown|nothing to set/i.test(i)?400:500).json({error:i})}});x.post("/api/tickets/link",async(t,e)=>{let{trackId:n,cwd:r,ref:o}=t.body||{};try{let s=n?await Q(n):null;if(!s&&r&&(s=(await ot()).find(c=>c.cwd&&(r===c.cwd||r.startsWith(c.cwd+lt.sep)))||null),!s)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});if(!o)return e.status(400).json({error:"ref (issue URL or key) required"});let a=await xr(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 $m(),ticket:await ye(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 ye(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 xr(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 Xc(t.params.id)})});var Ii=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 Nm(t.params.id))}catch(n){let r=n?.message||String(n);e.status(Ii(r)).json({error:r})}});x.post("/api/tracks/:id/ticket/state",async(t,e)=>{try{e.json({ok:!0,...await Yc(t.params.id,(t.body||{}).stateId)})}catch(n){let r=n?.message||String(n);e.status(Ii(r)).json({error:r})}});x.post("/api/tracks/:id/ticket/comment",async(t,e)=>{try{e.json({ok:!0,...await Pm(t.params.id,(t.body||{}).body)})}catch(n){let r=n?.message||String(n);e.status(Ii(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 Bm(o,n,r===!0)})}catch(o){let s=o?.message||String(o);e.status(Ii(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 jm(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 Dm()})}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 Lm(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 On({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 en(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 en(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(Ut(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 en(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(Ut(n,r.v).html,o=>{o&&!e.headersSent&&e.status(404).json({error:"source artifact missing"})})});x.post("/api/mocks",async(t,e)=>{let n=Buffer.isBuffer(t.body)?t.body:null,r=n?t.query:t.body||{},{trackId:o,name:s,cwd:a,width:i,group:c}=r;try{let l=o?await Q(o):null;if(!l&&a&&(l=(await ot()).find(m=>m.cwd&&(a===m.cwd||a.startsWith(m.cwd+lt.sep)))||null),!l)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});let u=(l.boxes||[]).find(d=>d.name===l.leadBox),f=await Ip({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 Rp(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 Op(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 Pg(t.params.id)).map(r=>({id:r.id,name:r.name}));e.json({...await Mp(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 $g({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 Og(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 Mg({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 Cg(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 Ng(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 ma({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 Cc(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 Cc(t.params.id);if(!n)return e.status(404).json({error:"runbook not found"});let r=nm(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(Ce(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 sm({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 am(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 im(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 cm(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=Fb(t.query.since||null);e.json({since:n,repos:od(),prs:await Ub(n)})}catch(n){let r=n?.message||String(n);e.status(/unparseable/i.test(r)?400:500).json({error:r})}});x.post("/api/changelog/publish",async(t,e)=>{let{md:n,day:r,trackId:o,cwd:s}=t.body||{};try{let a=o?await Q(o):null;if(!a&&s&&(a=(await ot()).find(c=>c.cwd&&(s===c.cwd||String(s).startsWith(c.cwd+lt.sep)))||null),!a)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});e.json({ok:!0,entry:await Hb(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,hd=400,_i=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>hd)return e.status(400).json({error:`message is ${i.length} chars \u2014 over the ${hd} cap; a notification is a nudge, put the detail in chat`});let c=$o(`${n||""}
|
|
412
|
-
${i}`);if(c.length)return e.status(400).json({error:`notification appears to contain a secret VALUE (${c.join(", ")}) \u2014 notifications ship off-box; never put a credential in one`});let l=a?await Q(a):null;if(!l&&s&&(l=(await ot()).find(
|
|
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 oh(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 zk({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 sh(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=Dw.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,ja(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&&Sy(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:Qb(e,t.headers)});x.get("/api/views",async(t,e)=>{try{let n=await fa({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 im(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 Bc({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 cm(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 lm(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 um(t.params.id))}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});var eM=[["linear",Dn,Wc],["jira",ya,Uc]],Td=new Map;x.get("/api/integrations/ticketing",async(t,e)=>{try{let n=await Fc(),r={};for(let[o,s,a]of eM){let i=await s();if(!i){Td.delete(o),r[o]={configured:!1};continue}let c=Td.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},Td.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 Fc()})}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 Im(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 Wm(),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 el(t.params.id)})});var Ci=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 Vm(t.params.id))}catch(n){let r=n?.message||String(n);e.status(Ci(r)).json({error:r})}});x.post("/api/tracks/:id/ticket/state",async(t,e)=>{try{e.json({ok:!0,...await nl(t.params.id,(t.body||{}).stateId)})}catch(n){let r=n?.message||String(n);e.status(Ci(r)).json({error:r})}});x.post("/api/tracks/:id/ticket/comment",async(t,e)=>{try{e.json({ok:!0,...await zm(t.params.id,(t.body||{}).body)})}catch(n){let r=n?.message||String(n);e.status(Ci(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 Jm(o,n,r===!0)})}catch(o){let s=o?.message||String(o);e.status(Ci(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 Ym(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 Xm()})}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 Zm(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 Up({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 Hp(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 qp(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 Jg(t.params.id)).map(r=>({id:r.id,name:r.name}));e.json({...await Kp(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 qg({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 Kg(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 Gg({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 Vg(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 zg(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 ha({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=hm(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 ym({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 bm(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 km(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 xm(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=nk(t.query.since||null);e.json({since:n,repos:md(),prs:await rk(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 ok(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 nM=120,Ad=400,Oi=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>Ad)return e.status(400).json({error:`message is ${i.length} chars \u2014 over the ${Ad} 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 Q(a):null;if(!l&&s&&(l=(await ot()).find(m=>m.cwd&&(s===m.cwd||s.startsWith(m.cwd+lt.sep)))||null),!l)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});let u=(l.boxes||[]).find(p=>p.name===l.leadBox),f=(p,m)=>String(p).replace(/\s+/g," ").trim().slice(0,m),d={title:n?f(n,nM):l.name,body:f(i,Ad),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:Oi?Oi.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 Pk(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 Oo(),terms:await Sd(),problem:await Nk()})});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 Mk(n,r)})});x.get("/api/tracks/:id/voice-chat",async(t,e)=>{if(!await Q(t.params.id))return e.status(404).json({error:"track not found"});e.json(await Bk())});x.post("/api/tracks/:id/voice-chat/session",async(t,e)=>{try{let n=await Q(t.params.id);if(!n)return e.status(404).json({error:"track not found"});if(n.archivedAt)return e.status(409).json({error:"track is archived"});e.json(await Dk())}catch(n){e.status(n?.status||500).json({error:n?.message||String(n),code:n?.code})}});x.post("/api/tracks/:id/voice-chat/usage",async(t,e)=>{try{let n=await Q(t.params.id);if(!n)return e.status(404).json({error:"track not found"});let{model:r,usage:o,seconds:s}=t.body||{};e.json({ok:!0,...await jk(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 Uu({name:n,cwd:r,repo:o,repos:s,agent:a,skipPermissions:i!==!1}),u=null;if(c)try{await vr(l,c,{source:"create"})}catch(f){u=f?.message||String(f)}e.json({ok:!0,track:l,...u?{ticketWarning:u}:{}})}catch(l){let u=l?.message||String(l);e.status(/required|unknown agent|invalid|must be/i.test(u)?400:500).json({error:u})}});x.get("/api/tracks/:id",async(t,e)=>{let n=await Q(t.params.id);if(!n)return e.status(404).json({error:"track not found"});e.json({track:n,handoff:Jk(us(t.params.id)),subtasks:zr(t.params.id).map(Ts)})});x.delete("/api/tracks/:id",async(t,e)=>{try{let n=await Ly(t.params.id,{cascadeMocks:t.query.mocks!=="0",cascadeRunbooks:t.query.runbooks==="1"});await el(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 Vr(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 Wy(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 Bu(t.params.id);if(!n)return e.status(404).json({error:"track not found"});e.json(n)});x.get("/api/tracks/:id/events",async(t,e)=>{let n=await Q(t.params.id);if(!n)return e.status(404).json({error:"track not found"});e.set({"content-type":"text/event-stream","cache-control":"no-cache",connection:"keep-alive","x-accel-buffering":"no"}),e.flushHeaders();let r=i=>e.write(`id: ${i.seq}
|
|
413
418
|
data: ${JSON.stringify(i)}
|
|
414
419
|
|
|
415
|
-
`),o=Number(t.query.since??t.headers["last-event-id"])||0;for(let i of await
|
|
420
|
+
`),o=Number(t.query.since??t.headers["last-event-id"])||0;for(let i of await D(n))i.seq>o&&r(i);let s=sp(n.id,r),a=setInterval(()=>e.write(`: hb
|
|
416
421
|
|
|
417
|
-
`),25e3);t.on("close",()=>{clearInterval(a),s()})});x.get("/api/tracks/:id/search",async(t,e)=>{let n=await Q(t.params.id);if(!n)return e.status(404).json({error:"track not found"});let r=String(t.query.q||"").trim();if(!r)return e.status(400).json({error:"q (query) is required"});let o=Math.min(Math.max(Number(t.query.k)||8,1),50),s=
|
|
418
|
-
`);x.post("/api/tracks/:id/memory/consolidate",async(t,e)=>{try{e.json({ok:!0,...await oe(t.params.id,
|
|
422
|
+
`),25e3);t.on("close",()=>{clearInterval(a),s()})});x.get("/api/tracks/:id/search",async(t,e)=>{let n=await Q(t.params.id);if(!n)return e.status(404).json({error:"track not found"});let r=String(t.query.q||"").trim();if(!r)return e.status(400).json({error:"q (query) is required"});let o=Math.min(Math.max(Number(t.query.k)||8,1),50),s=So(await D(n)),a=Ys(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 rf()})});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 of(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 rM=["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(`
|
|
423
|
+
`);x.post("/api/tracks/:id/memory/consolidate",async(t,e)=>{try{e.json({ok:!0,...await oe(t.params.id,rM)})}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 Ky(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 oe(t.params.id,n,{attachments:r}))}catch(o){let s=o?.message||String(o);e.status(/not found/i.test(s)?404:/archived/i.test(s)?409:/required|not running/i.test(s)?400:500).json({error:s})}});x.post("/api/tracks/:id/upload",async(t,e)=>{try{let n=await Q(t.params.id);if(!n)return e.status(404).json({error:`track "${t.params.id}" not found`});if(n.archivedAt)return e.status(409).json({error:"track is archived"});if(!n.cwd)return e.status(409).json({error:"track has no workspace yet"});let r=Buffer.isBuffer(t.body)?t.body:null,o=await ey(n.cwd,{filename:t.query.filename,buffer:r});e.json({ok:!0,attachment:o})}catch(n){let r=n?.message||String(n);e.status(/too large|unsupported|empty|required/i.test(r)?400:500).json({error:r})}});x.get("/api/tracks/:id/uploads/:file",async(t,e)=>{try{let n=await Q(t.params.id);if(!n)return e.status(404).json({error:`track "${t.params.id}" not found`});if(!n.cwd)return e.status(404).json({error:"track has no workspace"});let r=lt.basename(String(t.params.file||""));if(!r||!Au(lt.extname(r).slice(1)))return e.status(400).json({error:"not an upload path"});e.set("cache-control","private, max-age=31536000, immutable"),e.set("content-security-policy","sandbox"),e.sendFile(lt.join(Tu(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 oM=/^[0-9a-fA-F-]{8,64}$/;x.get("/api/tracks/:id/chat-images/:box/:uuid/:idx",async(t,e)=>{try{let n=await Q(t.params.id);if(!n)return e.status(404).json({error:`track "${t.params.id}" not found`});let{box:r,uuid:o}=t.params,s=Number(t.params.idx);if(!oM.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=Ae(a.cwd,a.sessionId);if(!i)return e.status(404).json({error:"transcript not found"});let c=`"${o}"`,l=null,u=JC.createInterface({input:Lt.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?dc(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 Lu(t.params.id))}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:/archived/i.test(r)?409:500).json({error:r})}});x.delete("/api/tracks/:id/queue",(t,e)=>{e.json({ok:!0,removed:ai(t.params.id).filter(n=>n.kind!=="ship-notify")})});x.post("/api/tracks/:id/answer-prompt",async(t,e)=>{let{promptId:n,answers:r,text:o}=t.body||{};try{e.json(await Du(t.params.id,{promptId:n,answers:r,text:o}))}catch(s){let a=s?.message||String(s),i=/not found/i.test(a)?404:/no pending prompt|changed since|not running|not supported|needs a code|look like a sign-in code|no answer selected/i.test(a)?409:500;e.status(i).json({error:a})}});x.post("/api/setup/:provider/start",async(t,e)=>{try{let n=await Dy({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 by(t.params.id,{promptId:n,questionIndex:r}))}catch(o){let s=o?.message||String(o),a=/not found/i.test(s)?404:/no pending prompt|changed since|not running|not supported|out of range|review screen/i.test(s)?409:500;e.status(a).json({error:s})}});x.put("/api/tracks/:id/draft",async(t,e)=>{let{text:n}=t.body||{};try{e.json(await _y(t.params.id,typeof n=="string"?n:""))}catch(r){let o=r?.message||String(r),s=/not found/i.test(o)?404:/required/i.test(o)?400:500;e.status(s).json({error:o})}});x.get("/api/tracks/:id/usage",async(t,e)=>{let n=await Q(t.params.id);if(!n)return e.status(404).json({error:"track not found"});let r=n.boxes?.find(s=>s.name===n.leadBox)||null,o=t.query.fresh==="1"||t.query.fresh==="true";e.json(await yi(r,{fresh:o}))});x.post("/api/tracks/:id/model",async(t,e)=>{let{model:n}=t.body||{};try{e.json(await Iy(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 Xy(t.params.id,{agent:n,task:r,skipPermissions:o!==!1,reason:s});e.json({ok:!0,subtask:Ts(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:zr(t.params.id).map(Ts)})});x.get("/api/tracks/:id/subtask/:sid/messages",async(t,e)=>{let n=await yy(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:Ts(Vu(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:Ts(Zy(t.params.sid))})}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:409).json({error:r})}});function Ts(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 qu(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 vy(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(Jk(us(t.params.id))??{running:!1})});function Jk(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:Ls(),paths:xf,trace:await kf({limit:n})})});var Ed=lt.resolve(qk,"..","..","client","dist");Lt.existsSync(Ed)&&(x.use(Se.static(Ed)),x.get(/^(?!\/api\/).*/,(t,e)=>e.sendFile(lt.join(Ed,"index.html"))));var Xk=YC.createServer(x),sM=new QC({noServer:!0}),Yk=(t,e,n)=>{if(!(ws(t)||ro.allowed(ro.clientIp(t))||Id.allows(t))){e.write(`HTTP/1.1 403 Forbidden\r
|
|
419
424
|
\r
|
|
420
|
-
`),e.destroy();return}if(
|
|
425
|
+
`),e.destroy();return}if(Zb(t,e,n))return;if(!Jb(t,Gk,{enforce:Vk})){e.write(`HTTP/1.1 401 Unauthorized\r
|
|
421
426
|
\r
|
|
422
|
-
`),e.destroy();return}let r
|
|
427
|
+
`),e.destroy();return}let r=$f(t.url);if(!r.ok){e.write(`HTTP/1.1 ${r.status} ${r.status===400?"Bad Request":"Not Found"}\r
|
|
423
428
|
\r
|
|
424
|
-
`),e.destroy();return}let o=r.sessionName;if(!
|
|
429
|
+
`),e.destroy();return}let o=r.sessionName;if(!wt(o)){e.write(`HTTP/1.1 400 Bad Request\r
|
|
425
430
|
\r
|
|
426
|
-
`),e.destroy();return}let{cols:s,rows:a}=r;
|
|
431
|
+
`),e.destroy();return}let{cols:s,rows:a}=r;sM.handleUpgrade(t,e,n,i=>_f(i,o,{cols:s,rows:a}))};Xk.on("upgrade",Yk);var Zk=process.env.BOSTRAT_CERT_DIR||lt.join(process.env.HOME||_d.tmpdir(),".bostrat","certs"),Uk=Number(process.env.BOSTRAT_TLS_PORT)||3443,Hk=lt.join(Zk,"bostrat.globopi.key"),Wk=lt.join(Zk,"bostrat.globopi.crt");if(Lt.existsSync(Hk)&&Lt.existsSync(Wk)){let t=ZC.createServer({key:Lt.readFileSync(Hk),cert:Lt.readFileSync(Wk)},x);t.on("upgrade",Yk),t.on("error",e=>{console.error(`[bostrat-agent] https listener failed (${e?.code||e?.message}) \u2014 continuing HTTP-only`)}),t.listen(Uk,Ri,()=>{console.log(`[bostrat-agent] https listening on ${Ri||"0.0.0.0"}:${Uk}`)})}Xk.listen(Lk,Ri,()=>{console.log(`[bostrat-agent] listening on ${Ri||"0.0.0.0"}:${Lk}`),console.log(`[bostrat-agent] tmux socket: ${Ut}`),console.log(`[bostrat-agent] auto-trigger default: ${Kk}%`),console.log(`[bostrat-agent] ip allowlist: ${ro.open?"OPEN":process.env.BOSTRAT_ALLOW_IPS||"CLOSED (BOSTRAT_ALLOW_IPS unset)"}`+(Id.active?` \xB7 view front-door: ${process.env.BOSTRAT_VIEW_FRONT_CIDRS} (registered views only)`:"")),zy().then(t=>t&&console.log("[bostrat-agent] reconciled interrupted handoff(s)")).catch(t=>console.error("[bostrat-agent] handoff reconcile failed:",t?.message||t)),Jy().then(t=>t&&console.log("[bostrat-agent] reconciled interrupted subtask(s)")).catch(t=>console.error("[bostrat-agent] subtask reconcile failed:",t?.message||t)),qy().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)),By().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)),lM(),cM(),Cb()&&Mb({listTracks:ot}),uk(),Qm({resolveTrack:Q}),cb({listTracks:ot,createTrack:Uu,archiveTrack:Vr,listRepos:ve,resolveTrack:Q,sayToLead:oe,answerPrompt:Du,startLeadHandoff:qu,interruptLead:Lu,listSubtaskJobs:zr,cancelSubtask:Vu,readEvents:D,appendEvent:R,knownAgents:ri}),aM(),go().then(t=>{t||console.warn(`[boot] no host tmux server on ${Ut} and auto-start failed \u2014 boxes cannot spawn until one runs`)}),Tk(),process.env.BOSTRAT_PLANE_URL&&process.env.BOSTRAT_PLANE_TOKEN&&(Oi=hb({url:process.env.BOSTRAT_PLANE_URL,token:process.env.BOSTRAT_PLANE_TOKEN,nodeId:process.env.BOSTRAT_NODE_ID||_d.hostname(),name:process.env.BOSTRAT_NODE_NAME||_d.hostname(),listTracks:ot,detectAgents:Pu}),console.log(`[bostrat-agent] plane link \u2192 ${process.env.BOSTRAT_PLANE_URL}`),ir().catch(()=>{}),Tm().then(t=>t.pushed&&console.log(`[content-plane] seeded ${t.pushed} runbooks to the plane`),()=>{}),th().then(t=>t.pushed&&console.log(`[content-plane] seeded ${t.pushed} ticket links to the plane`),()=>{}),zp().then(t=>t.pushed&&console.log(`[content-plane] seeded ${t.pushed} mocks to the plane`),()=>{}),Zg().then(t=>t.pushed&&console.log(`[content-plane] seeded ${t.pushed} flows to the plane`),()=>{}),dh().then(t=>t.pushed&&console.log(`[content-plane] seeded ${t.pushed} PR links to the plane`),()=>{}))});process.on("SIGTERM",()=>{try{Oi?.stop()}catch{}setTimeout(()=>process.exit(0),150)});function aM(){iM(),da().catch(()=>{}),setInterval(()=>da().catch(()=>{}),1e4)}function iM(){for(let t of["bostrat-view","bostrat-mock","bostrat-flow","bostrat-ticket","bostrat-runbook","bostrat-notify","bostrat-gmail","bostrat-changelog","gh"])try{let e=lt.resolve(qk,"..","bin",t),n=lt.join(process.env.HOME||"",".local","bin");if(!process.env.HOME||!Lt.existsSync(e))continue;Lt.mkdirSync(n,{recursive:!0});let r=lt.join(n,t);(Lt.existsSync(r)?Lt.readlinkSync(r):null)!==e&&(Lt.rmSync(r,{force:!0}),Lt.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 cM(){let t=!1,e=async()=>{if(!t){t=!0;try{let n=await ot(),r=await Rb({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 lM(){let t=!1;setInterval(async()=>{if(!t){t=!0;try{let e=await ot();for(let n of e)n.archivedAt||!n.boxes?.some(r=>r.state==="live")||(await Bu(n.id).catch(()=>{}),await Pg(n).catch(()=>{}))}catch{}finally{t=!1}}},4e3)}
|