bostrat-node 0.2.16 → 0.2.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +146 -148
- 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 ax=Object.defineProperty;var Nd=(t,e)=>{for(var n in e)ax(t,n,{get:e[n],enumerable:!0})};import ut from"node:path";import Ft from"node:fs";import sM from"node:readline";import{fileURLToPath as aM}from"node:url";import Se from"express";import iM from"node:http";import cM from"node:https";import Od from"node:os";import{WebSocketServer as lM}from"ws";import{execFile as A0}from"node:child_process";import{promisify as E0}from"node:util";import Ks from"node:fs";import{randomUUID as _0}from"node:crypto";import Ut from"node:fs/promises";import jd from"node:os";import Mt from"node:path";import{execFile as dx}from"node:child_process";import{promisify as fx}from"node:util";import{createHash as ix,randomBytes as cx,timingSafeEqual as lx}from"node:crypto";var $s="BOSTRAT_HOOK_TOKEN",Os="x-bostrat-hook";function Pd(){return cx(32).toString("hex")}function ao(t){let e=typeof t=="string"?t.trim():"";return e?ix("sha256").update(e).digest("hex"):null}function Bd(t,e){return typeof t!="string"||typeof e!="string"||t.length!==e.length||!t?!1:lx(Buffer.from(t),Buffer.from(e))}var ji=new Map,Li=new Map,ux=6e4;function Cs(t,e={},{now:n=Date.now(),log:r=console.warn}={}){if(ji.set(t,(ji.get(t)||0)+1),Li.has(t)&&n-Li.get(t)<ux)return!1;Li.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=${ji.get(t)}${o?` ${o}`:""}`),!0}async function Dd(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 Ms(){return/^(1|true|yes|on)$/i.test(process.env.BOSTRAT_CLAUDE_USE_API_KEY||"")&&process.env.ANTHROPIC_API_KEY?.trim()||null}function px(){return process.env.CLAUDE_CONFIG_DIR?Mt.join(process.env.CLAUDE_CONFIG_DIR,".claude.json"):Mt.join(jd.homedir(),".claude.json")}function mx(){return process.env.CLAUDE_CONFIG_DIR?Mt.join(process.env.CLAUDE_CONFIG_DIR,"settings.json"):Mt.join(jd.homedir(),".claude","settings.json")}async function Ge(t){if(!t||typeof t!="string")return;let e=Mt.resolve(t),n=px(),r=mx(),o={};try{o=JSON.parse(await Ut.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 Ut.mkdir(Mt.dirname(n),{recursive:!0}),await Ut.writeFile(n,JSON.stringify(o,null,2));let i={};try{i=JSON.parse(await Ut.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 "${Os}: $${$s}" --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 Ut.mkdir(Mt.dirname(r),{recursive:!0}),await Ut.writeFile(r,JSON.stringify(i,null,2)),await hx(e)}async function hx(t){try{let e=Mt.resolve(t),n=Mt.join(e,".bostrat","memory","claude"),r=Mt.join(e,".claude","settings.local.json"),o={};try{o=JSON.parse(await Ut.readFile(r,"utf8"))}catch{}let s=o.autoMemoryDirectory,a=typeof s=="string"&&s.includes(Mt.join(".bostrat","memory"));if(s&&!a||s===n)return;o.autoMemoryDirectory=n,await Ut.mkdir(n,{recursive:!0}),await Ut.mkdir(Mt.dirname(r),{recursive:!0}),await Ut.writeFile(r,JSON.stringify(o,null,2)),await wx(e,["/.claude/settings.local.json"])}catch{}}async function wx(t,e){try{let{stdout:n}=await fx(dx)("git",["-C",t,"rev-parse","--git-path","info/exclude"]),r=Mt.resolve(t,n.trim()),o=await Ut.readFile(r,"utf8").catch(()=>""),s=e.filter(a=>!o.split(`
|
|
2
|
+
`).includes(a));if(!s.length)return;await Ut.mkdir(Mt.dirname(r),{recursive:!0}),await Ut.appendFile(r,(o&&!o.endsWith(`
|
|
3
3
|
`)?`
|
|
4
4
|
`:"")+s.join(`
|
|
5
5
|
`)+`
|
|
6
|
-
`)}catch{}}import
|
|
6
|
+
`)}catch{}}import Fi from"node:fs/promises";import gx from"node:os";import Ns from"node:path";function yx(){let t=process.env.CODEX_HOME||Ns.join(gx.homedir(),".codex");return Ns.join(t,"config.toml")}function Ld(t){return`[projects."${t}"]`}function bx(t,e){let n=Ld(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 sr(t){if(!t||typeof t!="string")return;let e=Ns.resolve(t),n=yx(),r="";try{r=await Fi.readFile(n,"utf8")}catch{}if(bx(r,e))return;let o=Ld(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 Fi.mkdir(Ns.dirname(n),{recursive:!0}),await Fi.writeFile(n,r)}catch(s){console.warn(`[codex-config] could not seed folder trust for ${e}: ${s?.message||s}`)}}import Fd from"node:fs/promises";import kx from"node:os";import io from"node:path";function xx(t){let n=io.resolve(t).slice(1).replace(/\//g,"-").replace(/\./g,""),r=process.env.CURSOR_CONFIG_DIR||io.join(kx.homedir(),".cursor");return io.join(r,"projects",n)}async function ar(t){if(!t||typeof t!="string")return;let e=io.resolve(t),n=xx(e),r=io.join(n,".workspace-trusted");await Fd.mkdir(n,{recursive:!0}),await Fd.writeFile(r,`${JSON.stringify({trustedAt:new Date().toISOString(),workspacePath:e},null,2)}
|
|
14
|
+
`,"utf8")}import j from"node:fs/promises";import zx from"node:os";import J from"node:path";import{execFile as Jx}from"node:child_process";import{promisify as Xx}from"node:util";import Cx from"node:fs";import Hd from"node:fs/promises";import lo from"node:path";import Mx from"node:os";import{fileURLToPath as Nx}from"node:url";import{execFile as Px}from"node:child_process";import{promisify as Bx}from"node:util";import Hi from"node:fs/promises";import Ve from"node:path";import{execFile as Sx}from"node:child_process";import{promisify as vx}from"node:util";var Tx=vx(Sx),Ud=process.env.HOME||"/home/globo",Ax=Ve.join(process.env.BOSTRAT_STATE_DIR||Ve.join(Ud,".bostrat"),"repos"),Ex=[...(process.env.BOSTRAT_REPOS_ROOT||Ve.join(Ud,"Documents")).split(","),Ax].map(t=>t.trim()).filter(Boolean),_x=Number(process.env.BOSTRAT_REPOS_DEPTH)||3,Ix=new Set(["node_modules",".git",".cache","dist","build",".next","vendor","__pycache__","venv",".venv","target"]);async function Rx(t){try{return(await Hi.stat(Ve.join(t,".git"))).isDirectory()?"repo":"worktree"}catch{return null}}async function $x(t){let e=i=>Hi.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 Rx(o);if(a==="worktree")return;if(a==="repo"){t.set(o,{path:o,name:Ve.basename(o),branch:await $x(o)});return}if(s>=_x)return;let i;try{i=await Hi.readdir(o,{withFileTypes:!0})}catch{return}for(let c of i)!c.isDirectory()||c.name.startsWith(".")||Ix.has(c.name)||await e(Ve.join(o,c.name),s+1)}for(let o of Ex)await e(Ve.resolve(o),0);let n=new Set,r=[];for(let o of t.values()){let s=(await et(o.path))?.toLowerCase();if(s){if(n.has(s))continue;n.add(s)}r.push(o)}return r.sort((o,s)=>o.name.localeCompare(s.name))}function Ox(t){let e=String(t||"").trim().match(/github\.com[:/]+([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/i);return e?`${e[1]}/${e[2]}`:null}var Ui=new Map;async function et(t){if(!t)return null;if(Ui.has(t))return Ui.get(t);let e=null;try{let{stdout:n}=await Tx("git",["-C",t,"config","--get","remote.origin.url"],{timeout:5e3});e=Ox(n)}catch{e=null}return Ui.set(t,e),e}async function co(t){let e=String(t||"").toLowerCase();if(!e)return null;for(let n of await ve())if((await et(n.path))?.toLowerCase()===e)return n.path;return null}var Wd=Bx(Px),Dx=process.env.BOSTRAT_STATE_DIR||lo.join(process.env.HOME||Mx.tmpdir(),".bostrat"),jx=lo.join(Dx,"repos"),qd=/^[^/\s]+\/[^/\s]+$/,Lx=5*6e4,Fx=6e4,Ux=10*6e4,Kd=fetch,Wi=new Map,_n=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 Ps(t=process.env){return!!(zt(t)&&t.BOSTRAT_PLANE_TOKEN)}var Gd=t=>({authorization:`Bearer ${t.BOSTRAT_PLANE_TOKEN||""}`}),ir=(t,e)=>Object.assign(new Error(t),{status:e});async function Bs(t,{env:e=process.env}={}){let n=String(t||"").toLowerCase();if(!qd.test(n))throw ir("repo must be owner/name",400);let r=Wi.get(n);if(r&&r.expiresAt-Date.now()>Lx)return r;if(!Ps(e))throw ir("node is not plane-linked \u2014 GitHub auth unavailable",503);let o;try{o=await Kd(`${zt(e)}/v1/github/token?repo=${encodeURIComponent(t)}`,{headers:Gd(e)})}catch(i){if(r&&r.expiresAt>Date.now())return r;throw ir(`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 ir(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 Wi.set(n,a),a}async function uo({env:t=process.env,force:e=!1}={}){if(!Ps(t))return{configured:!1,repos:[],username:null};if(!e&&_n&&Date.now()-_n.at<Fx)return _n.data;try{let n=await Kd(`${zt(t)}/v1/github/repos`,{headers:Gd(t)});if(!n.ok)throw new Error(`plane replied ${n.status}`);let r=await n.json();return _n={at:Date.now(),data:r},r}catch(n){return _n?_n.data:{configured:!0,repos:[],username:null,error:n?.message||String(n)}}}function Vd(){Wi.clear(),_n=null}async function ae({cwd:t=null,slug:e=null,env:n=process.env}={}){try{let r=e||(t?await et(t):null);if(!r)return{};let{token:o}=await Bs(r,{env:n});return o?{GH_TOKEN:o}:{}}catch{return{}}}async function qi({env:t=process.env}={}){return(await uo({env:t})).username??null}var Hx=()=>Nx(new URL("../bin/bostrat-git-credential",import.meta.url)),Wx=(t=process.env)=>`http://127.0.0.1:${Number(t.PORT)||3362}`;function qx(t){let[e,n]=String(t).split("/");return lo.join(jx,e,n)}async function Ki(t,{env:e=process.env}={}){let n=(...r)=>Wd("git",["-C",t,...r],{timeout:1e4});await n("config","--replace-all","credential.helper",""),await n("config","--add","credential.helper",`!"${Hx()}" ${Wx(e)}`),await n("config","credential.useHttpPath","true")}async function Gi(t,{env:e=process.env,baseUrl:n="https://github.com"}={}){if(!qd.test(String(t||"")))throw ir(`bad repo slug "${t}"`,400);let r=qx(t);if(Cx.existsSync(lo.join(r,".git")))return r;let{token:o}=await Bs(t,{env:e});await Hd.mkdir(lo.dirname(r),{recursive:!0});let s="!f() { echo username=x-access-token; echo password=$BOSTRAT_GIT_TOKEN; }; f";try{await Wd("git",["-c","credential.helper=","-c",`credential.helper=${s}`,"clone","--no-tags","-q",`${n}/${t}.git`,r],{timeout:Ux,env:{...e,BOSTRAT_GIT_TOKEN:o,GIT_TERMINAL_PROMPT:"0"}})}catch(a){throw await Hd.rm(r,{recursive:!0,force:!0}).catch(()=>{}),ir(`clone of ${t} failed: ${a?.stderr?.trim?.()||a?.message||a}`,502)}return await Ki(r,{env:e}),r}var Kx=6e4,Gx=5e3,Vi=new Map,Vx=(...t)=>fetch(...t);function zd(t=process.env){return!!(zt(t)&&t.BOSTRAT_PLANE_TOKEN)}var Jd=t=>({authorization:`Bearer ${t.BOSTRAT_PLANE_TOKEN||""}`});async function Xd(t,e,n){let r=zt(n),o=new AbortController,s=setTimeout(()=>o.abort(),Gx);try{return await Vx(`${r}${t}`,{...e,signal:o.signal})}finally{clearTimeout(s)}}async function Yd(t,{env:e=process.env,force:n=!1}={}){if(!zd(e))return{facts:[],source:"unconfigured"};let r=Vi.get(t);if(!n&&r&&Date.now()-r.at<Kx)return{facts:r.facts,source:"cache"};try{let o=await Xd(`/v1/memory/${encodeURIComponent(t)}`,{headers:Jd(e)},e);if(!o.ok)throw new Error(`plane ${o.status}`);let s=await o.json(),a=Array.isArray(s?.facts)?s.facts:[];return Vi.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 Ds(t,e,{env:n=process.env}={}){if(!zd(n))return{pushed:0,source:"unconfigured"};if(!e?.length)return{pushed:0,source:"empty"};try{let r=await Xd(`/v1/memory/${encodeURIComponent(t)}`,{method:"POST",headers:{...Jd(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 Vi.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 Yx=Xx(Jx),zi=process.env.BOSTRAT_STATE_DIR||J.join(process.env.HOME||zx.tmpdir(),".bostrat"),Zd=200,Zx=25*1024;function js(){return J.join(zi,"memory","repos")}var Te="operator";function In(t){return t===Te?J.join(zi,"memory",Te):J.join(js(),t)}var nf=".tombstones.json";async function fo(t){try{return JSON.parse(await j.readFile(J.join(t,nf),"utf8"))}catch{return{}}}async function Ji(t,e){let n=await fo(t);for(let[r,o]of e)n[r]=Math.max(Number(n[r])||0,o);await j.mkdir(t,{recursive:!0}),await j.writeFile(J.join(t,nf),JSON.stringify(n))}function Qd(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 tf(t){try{let{stdout:e}=await Yx("git",["-C",t,"config","--get","remote.origin.url"]);return e.trim()||null}catch{return null}}async function Xi(t){if(!t)return[];let e=Qd(await tf(t));if(e)return[e];let n=[],r=[];try{r=await j.readdir(t,{withFileTypes:!0})}catch{return[]}for(let o of r){if(!o.isDirectory()||o.name.startsWith("."))continue;let s=Qd(await tf(J.join(t,o.name)));s&&!n.includes(s)&&n.push(s)}return n}function Ls(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 j.readFile(t,"utf8").catch(()=>""),{fm:r}=Ls(n),o=ze(r,"modified"),s=o?Date.parse(o):NaN;if(Number.isFinite(s))return s;try{return(await j.stat(t)).mtimeMs}catch{return 0}}function rf(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 cr=t=>/\]\(([^)]+\.md)\)/.exec(t)?.[1]||null;function Yi(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=cr(c);l&&c.trim().startsWith("-")&&s.set(l,c)}let a=new Set,i=o.map(c=>{let l=cr(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 of(t,e){let n=ze(e,"name")||t.replace(/\.md$/,""),r=ze(e,"description")||"";return`- [${n}](${t})${r?` \u2014 ${r}`:""}`}async function po(t){let e=[];try{e=await j.readdir(t,{withFileTypes:!0})}catch{return[]}return e.filter(n=>n.isFile()&&n.name.endsWith(".md")&&n.name!=="MEMORY.md").map(n=>n.name)}async function sf(t,e){let n=J.join(t,".bostrat","memory"),r=await po(n);if(!r.length)return{harvested:0,changedByKey:new Map};let o=new Map,s=new Map,a=0,i=new Map;for(let l of r){let u=J.join(n,l),f;try{f=await j.readFile(u,"utf8")}catch{continue}let{fm:d}=Ls(f),p=rf(d,e);if(!p)continue;let m=In(p),h=J.join(m,l),y=await ie(u,f);if(i.has(m)||i.set(m,await fo(m)),(i.get(m)[l]||0)>=y)continue;let b=await ie(h).catch(()=>0);if(await j.access(h).then(()=>!0).catch(()=>!1)&&b>=y)continue;await j.mkdir(m,{recursive:!0}),await j.writeFile(h,f),a++;let I=o.get(m);I||o.set(m,I={won:new Set,generated:new Map}),I.won.add(l),I.generated.set(l,of(l,d)),s.has(p)||s.set(p,new Set),s.get(p).add(l)}let c=await j.readFile(J.join(n,"MEMORY.md"),"utf8").catch(()=>"");for(let[l,u]of o){let f=J.join(l,"MEMORY.md"),d=await j.readFile(f,"utf8").catch(()=>"");await j.writeFile(f,Yi(d,c,u.won,u.generated))}return{harvested:a,changedByKey:s}}async function Qx(){let t=J.join(zi,"workspaces"),e=[];try{e=await j.readdir(t,{withFileTypes:!0})}catch{return{harvested:0}}let n=0,r=new Map;for(let o of e){if(!o.isDirectory())continue;let s=J.join(t,o.name);if(!await j.access(J.join(s,".bostrat","memory")).then(()=>!0).catch(()=>!1))continue;let[i]=await Xi(s),c=await sf(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 t0=/^[A-Za-z0-9][A-Za-z0-9._-]*\.md$/,e0=t=>{let e=new Map;for(let n of(t||"").split(`
|
|
20
|
+
`)){let r=cr(n);r&&n.trim().startsWith("-")&&e.set(r,n)}return e};async function n0(t,e){let n=In(t),r=await fo(n),o=new Set,s=new Map,a=[],i=0;for(let c of e||[]){let l=String(c?.file||"");if(!t0.test(l)||l==="MEMORY.md")continue;let u=J.join(n,l),f=await j.access(u).then(()=>!0).catch(()=>!1),d=f?await ie(u):0,p=Number(c.modified)||0;if(c.deleted){p>d&&(f&&await j.rm(u,{force:!0}),await Ji(n,[[l,p]]),f&&(a.push(l),i++));continue}f&&d>=p||(r[l]||0)>=p||(await j.mkdir(n,{recursive:!0}),await j.writeFile(u,String(c.content??"")),o.add(l),typeof c.indexLine=="string"&&c.indexLine.trim()&&s.set(l,c.indexLine),i++)}if(o.size||a.length){let c=J.join(n,"MEMORY.md"),l=await j.readFile(c,"utf8").catch(()=>""),u=Yi(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(cr(d))).join(`
|
|
23
|
+
`)}await j.mkdir(n,{recursive:!0}),await j.writeFile(c,u)}return{changed:i,tombstoned:a.length}}async function Zi(t,e=null){let n=In(t),r=e?.length?e:await po(n);if(!r.length)return{pushed:0,source:"empty"};let o=e0(await j.readFile(J.join(n,"MEMORY.md"),"utf8").catch(()=>"")),s=[];for(let a of r){let i=J.join(n,a),c;try{c=await j.readFile(i,"utf8")}catch{continue}s.push({file:a,content:c,modified:await ie(i,c),indexLine:o.get(a)??null})}return Ds(t,s)}async function r0(t){let{facts:e,source:n}=await Yd(t);e.length?await n0(t,e):n==="plane"&&await Zi(t)}function o0(t){let e=t.split(`
|
|
24
|
+
`);e.length>Zd&&(e=e.slice(0,Zd));let n=e.join(`
|
|
25
|
+
`);for(;Buffer.byteLength(n,"utf8")>Zx&&e.length;)e.pop(),n=e.join(`
|
|
26
|
+
`);return n}var s0=`# 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 a0(t,e){if(!t||!e?.length)return{materialized:!1,reason:"no-repo-key"};let n=J.join(t,".bostrat","memory"),r=0,o=0,s=[];for(let u of e){let f=In(u),d=await fo(f);for(let[y,b]of Object.entries(d)){let S=J.join(n,y);await j.access(S).then(()=>!0).catch(()=>!1)&&await ie(S)<=b&&(await j.rm(S,{force:!0}),o++)}let p=await po(f);if(!p.length)continue;await j.mkdir(n,{recursive:!0});for(let y of p){let b=J.join(f,y),S=J.join(n,y),I=await ie(b),M=await ie(S).catch(()=>0);await j.access(S).then(()=>!0).catch(()=>!1)&&M>=I||(await j.copyFile(b,S),r++)}let m=await j.readFile(J.join(f,"MEMORY.md"),"utf8").catch(()=>""),h=u===Te?"## operator memory":`## repo: ${u}`;m.trim()&&s.push(e.length>1?`${h}
|
|
32
32
|
|
|
33
33
|
${m.trim()}`:m.trim())}if(!s.length)return o?{materialized:!1,reason:"empty-store",cleaned:o}:{materialized:!1,reason:"empty-store"};let i="_Shared Track memory (point-in-time facts). Verify file/line citations against the current code before acting on them; correct a wrong fact in place (bump `modified`, use `supersedes`) rather than working around it._\n\n"+s.join(`
|
|
34
34
|
|
|
35
35
|
`)+`
|
|
36
|
-
`,c=
|
|
37
|
-
`).filter(p=>!s.has(
|
|
38
|
-
`);await
|
|
39
|
-
`).filter(l=>
|
|
40
|
-
`)).catch(()=>{});let c=await
|
|
41
|
-
`,n!=null?{mode:n}:void 0),await
|
|
42
|
-
`,"utf8");let n=(await
|
|
43
|
-
`).filter(Boolean);n.length>
|
|
36
|
+
`,c=o0(i);await j.mkdir(n,{recursive:!0}),await j.writeFile(J.join(n,"MEMORY.md"),c);let l=J.join(n,"claude");return await j.mkdir(l,{recursive:!0}),await j.writeFile(J.join(l,"MEMORY.md"),s0,{flag:"wx"}).catch(()=>{}),{materialized:!0,facts:r,capped:c!==i}}var ef=0,i0=3e4;async function af(t){try{let e=await Xi(t);if(!e.length)return{materialized:!1,reason:"no-repo-key"};let n=[...e,Te];if(Date.now()-ef>i0){ef=Date.now(),await c0();let{changedByKey:r}=await Qx();for(let[o,s]of r)await Zi(o,[...s])}for(let r of n)await r0(r);return await a0(t,n)}catch(e){return{materialized:!1,reason:"error",error:e?.message||String(e)}}}async function c0(){let t=0,e=[];try{e=await j.readdir(js(),{withFileTypes:!0})}catch{return{moved:t}}for(let n of e){if(!n.isDirectory())continue;let r=J.join(js(),n.name),o=In(Te),s=new Set,a=new Map;for(let p of await po(r)){let m=J.join(r,p),h;try{h=await j.readFile(m,"utf8")}catch{continue}let{fm:y}=Ls(h);if(rf(y,n.name)!==Te)continue;let b=J.join(o,p),S=await ie(b).catch(()=>0),I=await ie(m,h);await j.mkdir(o,{recursive:!0}),I>S&&await j.writeFile(b,h),await j.rm(m,{force:!0}),s.add(p),a.set(p,of(p,y)),t++}if(!s.size)continue;let i=Date.now();await Ji(r,[...s].map(p=>[p,i])),await Ds(n.name,[...s].map(p=>({file:p,deleted:!0,modified:i})));let c=J.join(r,"MEMORY.md"),l=await j.readFile(c,"utf8").catch(()=>""),u=l.split(`
|
|
37
|
+
`).filter(p=>!s.has(cr(p))).join(`
|
|
38
|
+
`);await j.writeFile(c,u).catch(()=>{});let f=J.join(o,"MEMORY.md"),d=await j.readFile(f,"utf8").catch(()=>"");await j.writeFile(f,Yi(d,l,s,a))}return{moved:t}}async function cf(){let t=[],e=[Te];try{for(let n of await j.readdir(js(),{withFileTypes:!0}))n.isDirectory()&&e.push(n.name)}catch{}for(let n of e){let r=In(n),o=await po(r),s=await fo(r);if(!o.length&&!Object.keys(s).length)continue;let a=[];for(let i of o){let c=await j.readFile(J.join(r,i),"utf8").catch(()=>""),{fm:l}=Ls(c);a.push({file:i,name:ze(l,"name")||i.replace(/\.md$/,""),description:ze(l,"description")||"",scope:ze(l,"scope")||null,modified:await ie(J.join(r,i),c)})}t.push({key:n,facts:a,tombstones:Object.keys(s).length})}return t}async function lf(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=In(t),r=J.join(n,e);if(!await j.access(r).then(()=>!0).catch(()=>!1))throw new Error(`fact ${e} not found in ${t}`);let s=Date.now();await j.rm(r,{force:!0}),await Ji(n,[[e,s]]);let a=J.join(n,"MEMORY.md"),i=await j.readFile(a,"utf8").catch(()=>"");await j.writeFile(a,i.split(`
|
|
39
|
+
`).filter(l=>cr(l)!==e).join(`
|
|
40
|
+
`)).catch(()=>{});let c=await Ds(t,[{file:e,deleted:!0,modified:s}]);return{deleted:e,key:t,plane:c.source}}async function uf(t){try{let e=await Xi(t),{harvested:n,changedByKey:r}=await sf(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 nc from"node:fs/promises";import bf from"node:path";import pf from"node:fs/promises";import mf from"node:os";import hf from"node:path";import Qi from"node:fs/promises";import l0 from"node:path";var u0=0;async function Nt(t,e,{mode:n}={}){await Qi.mkdir(l0.dirname(t),{recursive:!0});let r=`${t}.${process.pid}.${++u0}.tmp`;await Qi.writeFile(r,JSON.stringify(e,null,2)+`
|
|
41
|
+
`,n!=null?{mode:n}:void 0),await Qi.rename(r,t)}var d0=1e4,f0=3e4,df=2e3,p0=()=>process.env.BOSTRAT_STATE_DIR||hf.join(process.env.HOME||mf.tmpdir(),".bostrat"),wf=()=>hf.join(p0(),"content-outbox.json"),m0=()=>process.env.BOSTRAT_NODE_ID||mf.hostname(),h0=(...t)=>fetch(...t),w0=f0;function pt(t=process.env){return!!(zt(t)&&t.BOSTRAT_PLANE_TOKEN)}var ho=(t=process.env)=>({authorization:`Bearer ${t.BOSTRAT_PLANE_TOKEN||""}`});async function wo(t,e={},n=process.env){let r=new AbortController,o=setTimeout(()=>r.abort(),d0);try{return await h0(`${zt(n)}${t}`,{...e,signal:r.signal})}finally{clearTimeout(o)}}var Us=null,tc=null,Fs=null,mo=null;async function gf(){return Us||(tc||=pf.readFile(wf(),"utf8").then(t=>JSON.parse(t)).catch(()=>({ops:[]})).then(t=>Us={ops:Array.isArray(t?.ops)?t.ops:[]}).finally(()=>tc=null),tc)}async function yf(){await Nt(wf(),Us).catch(()=>{})}function ff(t){return t.op==="blob"?`blob ${t.family} ${t.id} ${t.v} ${t.kind}`:`${t.op} ${t.family} ${t.id}`}async function ec(t){let e=await gf(),n=ff(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=>ff(o)===n);r>=0?e.ops[r]=t:e.ops.push(t)}e.ops.length>df&&(e.ops=e.ops.slice(-df)),await yf()}function g0(){Fs||(Fs=setTimeout(()=>{Fs=null,lr().catch(()=>{})},w0),Fs.unref?.())}async function y0(t,e=process.env){if(t.op==="record"){let n=await wo(`/v1/content/${encodeURIComponent(t.family)}`,{method:"POST",headers:{...ho(e),"content-type":"application/json"},body:JSON.stringify({records:[{id:t.id,trackId:t.trackId??null,data:t.data,updatedAt:t.updatedAt,originNode:m0()}]})},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 pf.readFile(t.path).catch(()=>null);if(!n)return{delivered:!0,dropped:"source file gone"};let r=await wo(`/v1/content/${encodeURIComponent(t.family)}/${encodeURIComponent(t.id)}/blob?v=${t.v}&kind=${encodeURIComponent(t.kind)}`,{method:"POST",headers:{...ho(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 wo(`/v1/content/${encodeURIComponent(t.family)}/${encodeURIComponent(t.id)}?deletedAt=${t.deletedAt}`,{method:"DELETE",headers:ho(e)},e);if(!n.ok)throw new Error(`plane ${n.status}`);return{delivered:!0}}return{delivered:!0,dropped:"unknown op"}}async function lr({env:t=process.env,log:e=console}={}){return pt(t)?mo||(mo=(async()=>{let n=await gf(),r=0;for(;n.ops.length;){let o=n.ops[0],s;try{s=await y0(o,t)}catch(a){return g0(),{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 yf()}return{flushed:r,pending:0}})().finally(()=>{mo=null}),mo):{flushed:0,source:"unconfigured"}}async function $t(t,e,{trackId:n=null,env:r=process.env}={}){return pt(r)?(await ec({op:"record",family:t,id:e.id,trackId:n??e.trackId??null,data:e,updatedAt:Number(e.updatedAt)||Date.now()}),lr({env:r})):{queued:!1,source:"unconfigured"}}async function Je(t,e,n,r,o,{env:s=process.env}={}){return pt(s)?(await ec({op:"blob",family:t,id:e,v:Number(n),kind:r,path:o}),lr({env:s})):{queued:!1,source:"unconfigured"}}async function Qt(t,e,{deletedAt:n=Date.now(),env:r=process.env}={}){return pt(r)?(await ec({op:"delete",family:t,id:e,deletedAt:n}),lr({env:r})):{queued:!1,source:"unconfigured"}}async function At(t,{env:e=process.env}={}){if(!pt(e))return{records:[],source:"unconfigured"};try{let n=await wo(`/v1/content/${encodeURIComponent(t)}?tombstones=1`,{headers:ho(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 Hs(t,e,n,r,{env:o=process.env}={}){if(!pt(o))return null;try{let s=await wo(`/v1/content/${encodeURIComponent(t)}/${encodeURIComponent(e)}/blob?v=${Number(n)}&kind=${encodeURIComponent(r)}`,{headers:ho(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 b0=".bostrat/handoff",k0="latest.md",x0=t=>/workspaces[/\\]([0-9a-f]{8})$/.exec(t||"")?.[1]??null;async function kf(t,e,{box:n=null,agent:r=null}={}){if(!t||!pt())return{pushed:!1};try{let o=Date.now();return await $t("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 xf(t){try{let e=x0(t);if(!e||!pt())return{restored:!1};let n=bf.join(t,b0,k0);if(await nc.stat(n).then(()=>!0,()=>!1))return{restored:!1};let{records:r,source:o}=await At("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 Hs("checkpoints",e,s.data.v,"md");return typeof a!="string"||!a?{restored:!1}:(await nc.mkdir(bf.dirname(n),{recursive:!0}),await nc.writeFile(n,a),{restored:!0,v:s.data.v})}catch{return{restored:!1}}}import go from"node:fs/promises";import S0 from"node:os";import vf from"node:path";var v0=[/[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 yo(t){let e=String(t??"");for(let n of v0)e=e.replace(n,"\u2039redacted\u203A");return e}var rc=process.env.BOSTRAT_DEBUG_DIR||vf.join(S0.homedir(),".bostrat","debug"),ur=vf.join(rc,"handoff.jsonl"),Sf=Number(process.env.BOSTRAT_HANDOFF_DEBUG_MAX)||500;function Ws(){let t=process.env.BOSTRAT_HANDOFF_DEBUG;return t==="1"||t==="true"}async function T0(t){try{await go.mkdir(rc,{recursive:!0}),await go.appendFile(ur,`${JSON.stringify(t)}
|
|
42
|
+
`,"utf8");let n=(await go.readFile(ur,"utf8")).split(`
|
|
43
|
+
`).filter(Boolean);n.length>Sf&&await go.writeFile(ur,`${n.slice(-Sf).join(`
|
|
44
44
|
`)}
|
|
45
|
-
`,"utf8")}catch(e){console.warn(`[handoff-debug] failed to write ${
|
|
46
|
-
`).filter(Boolean).slice(-t).map(r=>{try{let o=JSON.parse(r);return typeof o?.paneTail=="string"&&(o.paneTail=
|
|
47
|
-
`).filter(Boolean).map(r=>{let[o,s,a,i,c,l,u,f,d]=r.split("|"),p=Number(i);return{name:o,title:u||null,windows:Number(s),attached:Number(a),lastActivityMs:Number.isFinite(p)?n-p*1e3:null,cwd:c||null,isBostrat:l==="1",sessionId:f||null,agent:d||(l==="1"?"claude":null)}}).filter(r=>!
|
|
48
|
-
`).map(n=>n.trim()).includes(t)}catch{return!1}}var
|
|
49
|
-
`).filter(Boolean).length}catch{return 0}}async function
|
|
50
|
-
`);for(let n=e.length-1;n>=0;n--){let r=e[n].trim();if(!r||r[0]!=="{")continue;let o;try{o=JSON.parse(r)}catch{continue}let s=o?.message?.usage;if(!s)continue;let a=(s.input_tokens||0)+(s.cache_creation_input_tokens||0)+(s.cache_read_input_tokens||0);if(a>0)return{occupancy:a,model:o.message.model||o.model||null}}return null}async function
|
|
51
|
-
`).trim():""}function
|
|
52
|
-
`)){let i=a.trim();if(!i||i[0]!=="{")continue;let c;try{c=JSON.parse(i)}catch{continue}if(c.type==="attachment"){let m=c.attachment;m?.type==="queued_command"&&m.commandMode==="task-notification"&&typeof m.prompt=="string"&&m.prompt.trim()&&o.push({role:"user",text:m.prompt.trim(),ts:c.timestamp?Date.parse(c.timestamp):null,model:null});continue}let l=c.message;if(!l)continue;let u=l.role||c.type;if(u!=="user"&&u!=="assistant"||u==="assistant"&&l.model==="<synthetic>"&&!c.isApiErrorMessage)continue;if(u==="assistant"&&Array.isArray(l.content))for(let m of l.content){let
|
|
53
|
-
|
|
54
|
-
`):""}import
|
|
55
|
-
`);if(!
|
|
56
|
-
`);for(let r=n.length-1;r>=0;r--)if(
|
|
57
|
-
`);return e}function
|
|
58
|
-
`),o=[],s=-1;for(let l=0;l<r.length;l++){let u=r[l].match(
|
|
59
|
-
`),o=r.findIndex(i=>
|
|
60
|
-
`).map(a=>a.trim()),o=r.findIndex(a=>/Press any key to log in/.test(a)),s=r.slice(0,o<0?r.length:o).filter(a=>a&&!/^Cursor Agent$/i.test(a)&&!/^v?\d{4}\.\d{2}\.\d{2}/.test(a)).join(" ");return{provider:"cursor",box:e,multiSelect:!1,cursorIndex:0,answerMode:"keys",promptId:"cursor:signin-start",questions:[{question:(s?`${s} `:"")+"Cursor isn't signed in on this box \u2014 start the sign-in to get a login link you can open from any device.",header:"Sign in",multiSelect:!1,options:[{label:"Start sign-in",description:"Shows a login link (and a QR option) \u2014 your login goes to cursor.com, never through bostrat"}]}]}}var
|
|
61
|
-
`),o="",s=r.findIndex(a=>/https:\/\//.test(a));if(s>=0){o=r[s].slice(r[s].indexOf("https://")).trim();for(let a=s+1;a<r.length;a++){let i=r[a].trim();if(!i||!/^[A-Za-z0-9~%&=?/_.-]+$/.test(i))break;o+=i}}return{provider:"cursor",box:e,multiSelect:!1,cursorIndex:0,promptId:`cursor:signin-link:${(o.match(/[?&]challenge=([\w-]+)/)||[])[1]?.slice(0,16)||o.slice(-24)||"pending"}`,questions:[{question:o?`Cursor is waiting for you to finish signing in: open ${o} on any device and log in. Your login goes straight to cursor.com \u2014 bostrat never sees it. This card clears once sign-in completes.`:"Cursor is waiting for you to finish signing in \u2014 open the login link shown in the Terminal tab. This card clears once sign-in completes.",header:"Sign in",multiSelect:!1,options:[]}]}}function
|
|
62
|
-
`))
|
|
63
|
-
`).slice(-
|
|
64
|
-
`);return
|
|
65
|
-
`))if(a.trim())try{r.push(JSON.parse(a))}catch{}}catch{}return
|
|
66
|
-
${o}`.trim()||e.slice(0,2e3)}function
|
|
67
|
-
`):""}async function
|
|
45
|
+
`,"utf8")}catch(e){console.warn(`[handoff-debug] failed to write ${ur}: ${e?.message||e}`)}}async function q(t={}){let e={at:new Date().toISOString(),ts:Date.now(),...t},n=[e.flow||"handoff",e.phase||e.step||"?",e.trackId?`track=${e.trackId}`:"",e.sessionName?`box=${e.sessionName}`:"",e.error?`ERR=${e.error}`:""].filter(Boolean).join(" \xB7 ");if(console.log(`[handoff-debug] ${n}`),e.sessionName&&Ws())try{e.paneTail=yo((await ft(e.sessionName,{lines:35})).slice(-4e3))}catch{e.paneTail=null}(Ws()||e.error||/^(checkpoint|spawn|hydrate|commit|done|summarize|clear|resume|ready|send|failed|launch)/.test(String(e.phase||e.step||"")))&&await T0(e)}async function Tf({limit:t=80}={}){try{return(await go.readFile(ur,"utf8")).split(`
|
|
46
|
+
`).filter(Boolean).slice(-t).map(r=>{try{let o=JSON.parse(r);return typeof o?.paneTail=="string"&&(o.paneTail=yo(o.paneTail)),o}catch{return{raw:yo(r)}}})}catch{return[]}}var Af={DEBUG_DIR:rc,LOG_FILE:ur};var rt=E0(A0);function dr(t){return new Promise(e=>setTimeout(e,t))}function qs(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 P=process.env.BOSTRAT_TMUX_BIN||"/usr/bin/tmux";function _f(t){return`/tmp/tmux-${t}/default`}function I0({explicit:t,legacyExists:e,runtimeDir:n,uid:r}){if(t)return t;let o=_f(r);return e?o:n?`${n}/bostrat/tmux-default`:o}var Ef=typeof process.getuid=="function"?process.getuid():0,Ht=I0({explicit:process.env.BOSTRAT_TMUX_SOCKET,legacyExists:(()=>{try{return Ks.existsSync(_f(Ef))}catch{return!1}})(),runtimeDir:process.env.XDG_RUNTIME_DIR,uid:Ef}),R0=process.env.BOSTRAT_DEFAULT_CWD||process.env.HOME||"/",oc=process.env.BOSTRAT_AGENT_CMD||"claude --dangerously-skip-permissions",$0="CLAUDE_CODE_DISABLE_ALTERNATE_SCREEN";function sc(){return process.env.BOSTRAT_CLAUDE_ALT_SCREEN==="1"?[]:["-e",`${$0}=1`]}var ac="bostrat-usage-probe",O0=new Set([ac]);function ic(t,e){let n=Rn(t),r=/(^|\s)(--session-id|--resume|--continue|-r|-c)(\s|=|$)/.test(t);return!n||r?t:`${t} --session-id ${e}`}function Rn(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 C0(t){let e=String(t||"").trim();return!e||/fake-|\.sh(\s|$)/.test(e)?!1:Rn(e)||/(^|\/|\s)codex(\s|$)/.test(e)||/cursor-agent/.test(e)||/(^|\/|\s)gemini(\s|$)/.test(e)}async function cc(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 rt(r,[o,'command -v "$1" >/dev/null 2>&1',"_",t],{timeout:5e3}),!0}catch(s){return s?.code===1?!1:n}}async function Gs(t){return cc(String(t||"").trim().split(/\s+/)[0])}var lc={claude:"npm install -g @anthropic-ai/claude-code",codex:"npm install -g @openai/codex","cursor-agent":"curl https://cursor.com/install -fsS | bash",gh:"apt install gh (Debian: enable cli.github.com/packages) / brew install gh \u2014 see https://github.com/cli/cli#installation"};function B(...t){return["-S",Ht,...t]}async function Ye(){if(!Ks.existsSync(Ht))return!1;try{return await rt(P,B("list-sessions")),!0}catch(t){let e=t&&t.stderr?String(t.stderr):"";return!/no server running/i.test(e)}}async function bo(){if(await Ye())return!0;try{Ks.mkdirSync(Ht.replace(/\/[^/]+$/,""),{recursive:!0}),await rt(P,B("new-session","-d","-s","keeper","sleep 2147483647"))}catch{}return await Ye()}var M0=/^[A-Za-z0-9_.-]{1,64}$/;function gt(t){return typeof t=="string"&&M0.test(t)}async function $n(){if(!Ks.existsSync(Ht))return[];try{let t="#{session_name}|#{session_windows}|#{session_attached}|#{session_activity}|#{pane_current_path}|#{@bostrat-session}|#{@bostrat-title}|#{@bostrat-sid}|#{@bostrat-agent}",{stdout:e}=await rt(P,B("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=>!O0.has(r.name)&&!r.name.startsWith("bostrat-view-"))}catch{return[]}}async function Q(t){return(await $n()).find(n=>n.name===t)||null}var N0=/^[A-Za-z0-9 ._↻-]+$/,P0=64;function B0(t){if(typeof t!="string")return!1;let e=t.trim();return e.length>=1&&e.length<=P0&&N0.test(e)}function uc(t){return t.trim().replace(/\s+/g,"-").toLowerCase().replace(/[^A-Za-z0-9_.-]/g,"").replace(/^-+|-+$/g,"")}async function If(t){try{let{stdout:e}=await rt(P,B("list-sessions","-F","#{session_name}"));return e.split(`
|
|
48
|
+
`).map(n=>n.trim()).includes(t)}catch{return!1}}var D0=()=>Number(process.env.BOSTRAT_LAUNCH_VERIFY_MS)||12e3,j0=1e3,L0=/not found|Unknown command/i;function F0(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 U0(t,e){let n=Date.now()+D0(),r=F0(e),o=!1;for(;;){if(await dr(j0),!await If(t)||!await fr(t))return;let s=await ft(t,{lines:15});if(!o&&L0.test(s)&&r?.test(s)){o=!0,await q({flow:"spawn",phase:"launch-retry",sessionName:t,agent:Xe(e),promptHead:e.slice(0,120)}).catch(()=>{}),await rt(P,B("send-keys","-t",t,"C-u")),await rt(P,B("send-keys","-t",t,"-l",e)),await rt(P,B("send-keys","-t",t,"Enter"));continue}if(Date.now()>n){await q({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 On(t,e=R0,n={}){if(!B0(t))throw new Error("displayName must be 1\u201364 chars (letters, digits, space . _ -)");let r=t.trim(),o=uc(r);if(!gt(o))throw new Error("resulting tmux session name is invalid");if(!await bo())throw new Error(`host tmux server not running on ${Ht} and auto-start failed \u2014 start one: ${P} -S ${Ht} new-session -d -s keeper "sleep 2147483647"`);if(await If(o))throw new Error(`tmux session "${o}" already exists`);let s=n.agentCmd||oc,a=n.agent||Xe(s),i=!!n.resumeSessionId,c=n.resumeSessionId||_0(),l=i?s:ic(s,c),u=i?n.pinned===!0:/(^|\s)--session-id(\s|=)/.test(l),f=a==="claude"&&Rn(s),d=n.firstRun===!0;d||(f&&await Ge(e),a==="codex"&&await sr(e),a==="cursor"&&await ar(e),await af(e),await xf(e));let p=f&&!d?Ms():null,m=Pd(),h=["-e",`${$s}=${m}`];p&&h.push("-e",`ANTHROPIC_API_KEY=${p}`),f&&h.push(...sc());try{await rt(P,B("new-session","-d","-s",o,"-c",e,...h))}catch(y){throw qs(p?qs(y,p):y,m)}return await Cn(o),await rt(P,B("set-option","-t",o,"@bostrat-session","1")),await rt(P,B("set-option","-t",o,"@bostrat-title",r)),await rt(P,B("set-option","-t",o,"@bostrat-sid",c)),await rt(P,B("set-option","-t",o,"@bostrat-agent",a)),await rt(P,B("send-keys","-t",o,l,"Enter")),C0(l)&&U0(o,l).catch(()=>{}),{name:o,title:r,cwd:e,sessionId:c,agent:a,pinned:u,hookToken:m}}async function ce(t){if(!gt(t))throw new Error("invalid session name");await rt(P,B("kill-session","-t",t))}async function Rf(t,e){if(gt(t))try{let n=e?B("set-option","-t",t,"@bostrat-sid",e):B("set-option","-u","-t",t,"@bostrat-sid");await rt(P,n)}catch{}}async function ft(t,{lines:e=40}={}){if(!gt(t))return"";try{let n=B("capture-pane","-t",t,"-p");Number.isFinite(e)&&e>0&&n.push("-S",`-${Math.floor(e)}`);let{stdout:r}=await rt(P,n);return String(r||"")}catch{return""}}var H0=Number(process.env.BOSTRAT_PROMPT_CAPTURE_COLS)||100,W0=Number(process.env.BOSTRAT_PROMPT_CAPTURE_ROWS)||40,xt=Number(process.env.BOSTRAT_PROMPT_CAPTURE_LINES)||80,q0=Number(process.env.BOSTRAT_BOX_COLS)||120,K0=Number(process.env.BOSTRAT_BOX_ROWS)||40;async function $f(t){if(!gt(t))return 0;try{let{stdout:e}=await rt(P,B("list-clients","-t",t,"-F","1"));return String(e||"").trim().split(`
|
|
49
|
+
`).filter(Boolean).length}catch{return 0}}async function Cn(t,{cols:e=q0,rows:n=K0}={}){if(gt(t)&&!await $f(t))try{let r=await Of(t);if(r.cols===e&&r.rows===n)return;await rt(P,B("resize-window","-t",t,"-x",String(e),"-y",String(n))),await rt(P,B("set-window-option","-t",t,"window-size","latest")),await dr(80)}catch{}}async function G0(t){if(!gt(t))return 0;try{let{stdout:e}=await rt(P,B("display-message","-t",t,"-p","#{pane_pid}"));return Number(String(e||"").trim())||0}catch{return 0}}async function fr(t){let e=await G0(t);if(!e)return!1;try{return await rt("pgrep",["-P",String(e)]),!1}catch(n){return n?.code===1}}async function Of(t){if(!gt(t))return{cols:0,rows:0};try{let{stdout:e}=await rt(P,B("display-message","-t",t,"-p","#{window_width} #{window_height}")),[n,r]=String(e||"").trim().split(/\s+/).map(o=>Number(o)||0);return{cols:n,rows:r}}catch{return{cols:0,rows:0}}}async function Vs(t,{cols:e=H0,rows:n=W0}={}){if(gt(t)&&!await $f(t))try{let r=await Of(t),o=Math.max(r.cols,e),s=Math.max(r.rows,n);(o>r.cols||s>r.rows)&&(await rt(P,B("resize-window","-t",t,"-x",String(o),"-y",String(s))),await rt(P,B("set-window-option","-t",t,"window-size","latest")),await dr(80))}catch{}}async function Ze(t){return await Vs(t),ft(t,{lines:xt})}var V0=new Set(["Up","Down","Left","Right","Space","Enter","Tab","BTab","Escape","BSpace"]);async function bt(t,e,{settleMs:n=40,kind:r}={}){if(!gt(t))throw new Error("invalid session name");let o=(Array.isArray(e)?e:[e]).filter(s=>V0.has(s));for(let s=0;s<o.length;s++)await rt(P,B("send-keys","-t",t,o[s])),n>0&&s<o.length-1&&await dr(n);return q({flow:"tmux",phase:"send-keys",sessionName:t,kind:r||"keys",keys:o.join(" ")}).catch(()=>{}),o.length}async function zs(t,e,{kind:n}={}){if(!gt(t))throw new Error("invalid session name");let r=Math.max(0,Math.floor(Number(e)||0));return r>0&&await rt(P,B("send-keys","-t",t,"-N",String(r),"BSpace")),q({flow:"tmux",phase:"send-keys",sessionName:t,kind:n||"wipe",keys:`BSpace x${r}`}).catch(()=>{}),r}async function Js(t,e,{perCharDelayMs:n=120,kind:r}={}){if(!gt(t))throw new Error("invalid session name");let o=[...String(e)];for(let s of o)await rt(P,B("send-keys","-t",t,"-l",s)),n>0&&await dr(n);q({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(!gt(t))throw new Error("invalid session name");if(typeof e=="string"&&e.length>0&&await rt(P,B("send-keys","-t",t,"-l",e)),n){let s=Number(r);Number.isFinite(s)&&s>0&&await dr(s),await rt(P,B("send-keys","-t",t,"Enter"))}q({flow:"tmux",phase:"send",sessionName:t,kind:o||"text",textLen:typeof e=="string"?e.length:0,textHead:typeof e=="string"?e.slice(0,120):"",enter:n,submitDelayMs:r}).catch(()=>{})}import z0 from"node-pty";async function Mf(t,e,n={}){if(!gt(e)){Xs(t,`invalid session name: ${e}`),t.close(1008,"invalid session name");return}if(!await Ye()){Xs(t,`host tmux server not running on ${Ht}`),t.close(1011,"tmux server unavailable");return}if(!await Q(e)){Xs(t,`session "${e}" not found`),t.close(1008,"session not found");return}let r=Cf(n.cols,120,Nf),o=Cf(n.rows,32,Pf),s;try{s=z0.spawn(P,["-S",Ht,"attach-session","-d","-t",e],{name:"xterm-256color",cols:r,rows:o,cwd:process.env.HOME||"/",env:{...process.env,TERM:"xterm-256color"}})}catch(a){Xs(t,`failed to spawn tmux attach: ${a.message}`),t.close(1011,"spawn failed");return}s.onData(a=>{t.readyState===t.OPEN&&dc(t,{type:"data",data:a})}),s.onExit(({exitCode:a,signal:i})=>{t.readyState===t.OPEN&&(dc(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"&&J0(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{}Cn(e).catch(()=>{})})}var Nf=20,Pf=6;function J0(t,e){return Number.isFinite(Number(t))&&Number.isFinite(Number(e))&&Number(t)>=Nf&&Number(e)>=Pf}function Cf(t,e,n){let r=Number(t);return!Number.isFinite(r)||r<n?e:Math.min(500,r|0)}function dc(t,e){try{t.send(JSON.stringify(e))}catch{}}function Xs(t,e){dc(t,{type:"error",message:e})}function Bf(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 ko from"node:fs";import X0 from"node:fs/promises";import pr from"node:path";import Y0 from"node:os";var Df={"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},Z0=2e5,fc=process.env.BOSTRAT_CLAUDE_PROJECTS||pr.join(Y0.homedir(),".claude","projects");function pc(t){return t.replace(/[^A-Za-z0-9]/g,"-")}function mc(t){let e=pr.join(fc,pc(t)),n;try{n=ko.readdirSync(e).filter(o=>o.endsWith(".jsonl"))}catch{return null}let r=null;for(let o of n){let s=pr.join(e,o);try{let{mtimeMs:a}=ko.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=pr.join(fc,pc(t),`${e}.jsonl`);return ko.existsSync(n)?n:null}function jf(t){if(!t)return[];let e=pr.join(fc,pc(t)),n;try{n=ko.readdirSync(e).filter(o=>o.endsWith(".jsonl"))}catch{return[]}let r=[];for(let o of n)try{let s=ko.statSync(pr.join(e,o));r.push({id:o.slice(0,-6),birthMs:s.birthtimeMs||s.ctimeMs||s.mtimeMs})}catch{}return r}async function Pt(t,e=131072){let n=await X0.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 Q0(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 xo(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=mc(t),o=!1,!r)return null}else return null;let s;try{s=Q0(await Pt(r))}catch{return null}if(!s)return null;let a=Object.prototype.hasOwnProperty.call(Df,s.model),i=Df[s.model]||Z0;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 tS(t,e){return!t||!e?null:Ae(t,e)}function eS(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 nS(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 rS(t){return Array.isArray(t)&&t.length>0&&t.every(e=>e&&e.type==="tool_result")}function hc(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 Lf(t,e){if(!t.uuid)return null;let n=hc(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 oS=/\.(png|jpe?g|gif|webp|bmp|svg|tiff?|avif|heic|heif|ico)$/i;async function wc(t,{maxBytes:e=524288,limit:n=80}={}){let r;try{r=await Pt(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 h=m?.type==="tool_use"?m.input?.file_path||m.input?.path:null;typeof h=="string"&&oS.test(h)&&s.set(m.id,h.split("/").pop())}if(rS(l.content)){let m=Lf(c,s);m&&o.push({role:"assistant",text:"",images:m,ts:c.timestamp?Date.parse(c.timestamp):null,model:null});continue}let f=eS(l.content),d=u==="assistant"?nS(l.content):null,p=u==="user"?Lf(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 sS=(t,e)=>t.length>e?`${t.slice(0,e).trimEnd()}\u2026`:t;async function Ff(t,e,{turns:n=16}={}){let r=tS(t,e);if(!r)return"";let o=await wc(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}:** ${sS(s.text||c,700)} ${i}`.trim()}).join(`
|
|
53
|
+
|
|
54
|
+
`):""}import ev from"node:fs";import gr from"node:fs/promises";import yr from"node:path";import aS from"node:fs/promises";import{execFile as iS}from"node:child_process";import{promisify as cS}from"node:util";var gc=cS(iS),lS=Number(process.env.BOSTRAT_CODEX_UPDATE_TIMEOUT_MS)||5*6e4,Uf=new Set,Hf=Promise.resolve(),yc=null;function qf(t){yc=typeof t=="function"?t:null}function uS(t){let e=String(t||""),n=e.indexOf("/lib/node_modules/");return n>0?e.slice(0,n):null}function Kf({sessionName:t,from:e,to:n}={}){let r=String(n||"").trim();!r||Uf.has(r)||(Uf.add(r),Hf=Hf.then(()=>dS({sessionName:t,from:e,to:r})).catch(()=>{}))}async function dS({sessionName:t,from:e,to:n}){await q({flow:"codex-update",phase:"start",sessionName:t,from:e,to:n}).catch(()=>{});try{let r=(await gc("/bin/sh",["-c","command -v codex"])).stdout.trim();if(!r)throw new Error("codex not on PATH");let o=await aS.realpath(r),s=uS(o),a=s?{...process.env,PATH:`${s}/bin:${process.env.PATH||""}`}:process.env;await gc("npm",["install","-g","@openai/codex"],{env:a,timeout:lS});let i=(await gc(r,["--version"])).stdout.trim().replace(/^codex-cli\s*/,"")||n;await q({flow:"codex-update",phase:"done",sessionName:t,from:e,to:n,version:i}).catch(()=>{}),await Wf({sessionName:t,from:e,to:n,version:i,status:"updated"})}catch(r){let o=String(r?.message||r).slice(0,300);await q({flow:"codex-update",phase:"failed",sessionName:t,from:e,to:n,error:o}).catch(()=>{}),await Wf({sessionName:t,from:e,to:n,status:"failed",error:o})}}async function Wf(t){if(yc)try{await yc(t)}catch{}}var fS=/^\s*[›>*\-❯]?\s*(\d+)[.)]\s+(.+?)\s*$/,Gf=/\b(allow|approve|apply (this )?patch|run (this )?command|do you want to run|grant)\b/i;function pS(t,{provider:e,box:n}){let r=String(t).replace(/\r/g,"").split(`
|
|
55
|
+
`);if(!Gf.test(t))return null;let o=[],s=!1;for(let l=r.length-1;l>=0;l--){let u=r[l].match(fS);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"&&Gf.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 Ys=/(^|\n)\s*›/,Jf=/^\s*Welcome to Codex\b/;function mr(t){let e=String(t??"").replace(/\r/g,""),n=e.split(`
|
|
56
|
+
`);for(let r=n.length-1;r>=0;r--)if(Jf.test(n[r]))return n.slice(r).join(`
|
|
57
|
+
`);return e}function mS(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 hS(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 Vf=/^\s*(>)?\s*(\d+)\.\s+(.+?)\s*$/;function wS(t,e=""){let n=mr(t);if(Ys.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(Vf);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(Vf.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&&!Jf.test(r[l])&&mS(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=hS(l.label);return{label:l.label,description:[l.desc,u].filter(Boolean).join(" \xB7 ")}})}]}}var zf=/Enter this one-time code/,gS=/^\s*([A-Z0-9]{2,10}-[A-Z0-9]{2,10})\s*$/;function yS(t,e=""){let n=mr(t);if(Ys.test(n)||!zf.test(n))return null;let r=n.split(`
|
|
59
|
+
`),o=r.findIndex(i=>zf.test(i)),s="";for(let i=o+1;i<r.length;i++){let c=r[i].match(gS);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 bS(t,e=""){let n=mr(t);return Ys.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 kS(t,e=""){let n=mr(t);return Ys.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 bc=/Run Everything|Add a follow-up|Plan, search, build anything/;function xS(t,e=""){let n=String(t);if(!/Press any key to log in/.test(n)||bc.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 SS=/click this link to log in|Signing in with the browser/;function vS(t,e=""){let n=String(t);if(!SS.test(n)||bc.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 TS(t,e=""){let n=String(t);return!/Scan this QR code to log in/.test(n)||bc.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 Xf(t,e,n=""){if(t!=="cursor"&&t!=="codex")return null;if(t==="codex"){let r=yS(e,n)||bS(e,n)||kS(e,n)||wS(e,n);if(r)return r}if(t==="cursor"){let r=vS(e,n)||TS(e,n)||xS(e,n);if(r)return r}return pS(e,{provider:t,box:n})}function Yf(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 AS=()=>Number(process.env.BOSTRAT_AGENT_READY_TIMEOUT_MS)||9e4,kc=Number(process.env.BOSTRAT_AGENT_READY_POLL_MS)||500,So=Number(process.env.BOSTRAT_AGENT_READY_SETTLE_MS)||800,Et={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/}},ES=/Trust this workspace|\[a\]\s*Trust/,_S=/Update available!\s*([\d][\w.-]*)\s*->\s*([\d][\w.-]*)/;function IS(t){let e=String(t||"");if(!Et.codex.block.test(e))return null;let n=mr(e);if(!Et.codex.block.test(n))return null;let r=n.match(_S);return{from:r?.[1]||null,to:r?.[2]||null}}function Qe(t){return new Promise(e=>setTimeout(e,t))}var RS=()=>Number(process.env.BOSTRAT_GATE_RESEND_MS)||3e3,Zs=new Map;function Zf(t,e){let n=Date.now(),r=RS();for(let[s,a]of Zs)n-a>=r&&Zs.delete(s);let o=`${t}::${e}`;return Zs.has(o)?!1:(Zs.set(o,n),!0)}var $S=68,OS=/^\s*[─━]{10,}\s*$/;function CS(t){let e=0;for(let n of String(t).split(`
|
|
62
|
+
`))OS.test(n)&&(e=Math.max(e,n.trimEnd().length));return e}function xc(t,e){let n=Et[t];if(!n?.busy)return"idle";if(Sc(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=CS(e);return t==="claude"&&r>0&&r<$S?"unknown":"idle"}var MS=/\bWaiting for \d+\b[^\n]*\bto finish\b|\/workflows to (?:stop|monitor)\b/,NS=15;function tp(t,e){if(t!=="claude")return!1;let n=String(e||"").split(`
|
|
63
|
+
`).slice(-NS).join(`
|
|
64
|
+
`);return MS.test(n)}function Ee(t,e){return xc(t,e)==="busy"}function Qs(t,e){return xc(t,e)!=="idle"}function ta(t,e){let n=Et[t];return n?.up?n.up.test(e):!0}function PS(t,e){let n=Et[t];return n?Sc(t,e)?!1:n.up.test(e)&&xc(t,e)==="idle":!0}function Sc(t,e){let n=Et[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 Qf(t,e,n){let r=Date.now()+n,o=0;for(await Cn(t).catch(()=>{});;){let s=await ft(t);if(PS(e,s)){await Qe(So);return}if(e==="cursor"&&ES.test(s)&&!Et.cursor.up.test(s)&&o<3){if(!Zf(t,"cursor-trust")){await Qe(kc);continue}o+=1,await q({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(So);continue}if(e==="codex"&&o<3){let a=IS(s);if(a){if(!Zf(t,`codex-update:${a.from}->${a.to}`)){await Qe(kc);continue}o+=1,await q({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"}),Kf({sessionName:t,from:a.from,to:a.to}),await Qe(So);continue}}if(Date.now()>r){if(Sc(e,s)){let a=`${e} is blocked on an interactive prompt`;throw e==="codex"?a=Et.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"&&Et.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"&&Et.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"&&Et.claude.block.test(s)&&(a='claude is waiting on its "Press Enter to continue" screen \u2014 answer the Continue card in chat (or press Enter in the Terminal tab), then send again'),q({flow:"agent-ready",phase:"blocked",sessionName:t,agent:e,timeoutMs:n,gateAccepts:o,error:a}).catch(()=>{}),new Error(a)}if(await fr(t)){let a=`${e} isn't running in this box \u2014 its pane is sitting at a bare shell (the launch command was likely swallowed at shell startup, or the CLI exited). Relaunch it from the Terminal tab, or stop and reopen the box, then send again.`;throw q({flow:"agent-ready",phase:"blocked",reason:"cli-not-running",sessionName:t,agent:e,timeoutMs:n,gateAccepts:o,error:a}).catch(()=>{}),new Error(a)}console.warn(`[agent-ready] ${e} readiness unconfirmed in ${t} after ${n}ms \u2014 driving the box anyway.`),q({flow:"agent-ready",phase:"timeout",sessionName:t,agent:e,timeoutMs:n,gateAccepts:o,paneBlocked:!!Et[e]?.block?.test(s),paneUp:!!Et[e]?.up?.test(s),paneBusy:!!Et[e]?.busy?.test(s)}).catch(()=>{}),await Qe(So);return}await Qe(kc)}}function ep({agent:t,agentCmd:e}={}){let n=e?Xe(e):Xe(t||"");return{label:n,realClaude:e?Rn(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 ea({cwd:t,sessionName:e,agent:n,agentCmd:r}={}){let{realCodex:o,realCursor:s}=ep({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=AS()}={}){let{label:a,realClaude:i,realCodex:c,realCursor:l}=ep({agent:r,agentCmd:o});if(i&&t&&await Ge(t),c&&t&&await sr(t),l&&t&&await ar(t),(i||c||l)&&n){await Qf(n,a,s);return}let u=Et[r]?r:Et[a]?a:null;if(process.env.BOSTRAT_FAKE_POLL_READY==="1"&&n&&u){await Qf(n,u,s);return}await Qe(So)}var BS=(()=>{let t=Number(process.env.BOSTRAT_CODEX_SUBMIT_SETTLE_MS??120);return Number.isFinite(t)&&t>=0?t:120})(),DS=(()=>{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:BS}:t==="claude"?{enter:!0,submitDelayMs:DS}:{enter:!0}}import ra from"node:fs/promises";import ip from"node:path";var jS=["bug-fix","orchestration","design","ops-infra","docs-writing","research","feature-build"],LS={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 hr(t){let e=String(t||"").toLowerCase();if(!e.trim())return null;let n=null;for(let r of jS){let o=0;for(let s of LS[r])s.test(e)&&o++;o>0&&(!n||o>n.score)&&(n={category:r,score:o})}return n}var FS=new Set(["Read","Grep","Glob","LS","WebSearch","WebFetch","NotebookRead"]),US=new Set(["Edit","Write","MultiEdit","NotebookEdit"]);function np(t){let e=0,n=0;for(let[r,o]of Object.entries(t||{}))FS.has(r)&&(e+=o||0),US.has(r)&&(n+=o||0);return e>=5&&n===0?{category:"research",score:1}:n>=3?{category:"feature-build",score:1}:null}function vo(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 rp=1.5,op=.75,HS=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 ap(t){let e=vo(String(t||"")).toLowerCase(),n=[];for(let r of e.split(/[^a-z0-9]+/))r.length<2||HS.has(r)||n.push(r);return n}function WS(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(sp(n,"user",o))}else if(n.type==="turn.agent"){if(!o)continue;e.push(sp(n,"assistant",o))}}return e}function sp(t,e,n){let r=hr(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 To(t){let e=WS(t),n=new Map,r=new Array(e.length).fill(0),o=0;e.forEach((a,i)=>{let c=ap(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 na(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(ap(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,h]of f){let y=t.lengths[m],b=h+rp*(1-op+op*(y/(t.avgdl||1))),S=p*(h*(rp+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 cp="Relevant prior discussion (verbatim, retrieved";async function qS(t){let e=ip.join(t,".bostrat","events"),n;try{n=await ra.readdir(e)}catch{return To([])}let r=[];for(let o of n)if(o.endsWith(".ndjson"))try{let s=await ra.readFile(ip.join(e,o),"utf8");for(let a of s.split(`
|
|
65
|
+
`))if(a.trim())try{r.push(JSON.parse(a))}catch{}}catch{}return To(r)}function KS(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 GS(t,e,{k:n=6,maxChars:r=4e3,exclude:o=null}={}){let s=na(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?["",`## ${cp}, 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 lp(t,e,n={}){let{k:r=6,maxChars:o=4e3,exclude:s=null}=n;try{let a=await ra.readFile(e,"utf8");if(a.includes(cp))return{appended:!1,reason:"already-enriched"};let i=await qS(t);if(!i.N)return{appended:!1,reason:"empty-index"};let c=GS(i,KS(a),{k:r,maxChars:o,exclude:s});return c?(await ra.appendFile(e,`
|
|
68
68
|
`+c),{appended:!0,chars:c.length,chunks:c.split(`
|
|
69
|
-
- `).length-1,indexed:i.N}):{appended:!1,reason:"no-hits",indexed:i.N}}catch(a){return{appended:!1,reason:"error",error:a?.message||String(a)}}}import
|
|
70
|
-
`),Jt.emit("event",i),Jt.emit(`track:${n}`,i),i}async function
|
|
71
|
-
`))if(a.trim())try{s.push(JSON.parse(a))}catch{}return s}function
|
|
69
|
+
- `).length-1,indexed:i.N}):{appended:!1,reason:"no-hits",indexed:i.N}}catch(a){return{appended:!1,reason:"error",error:a?.message||String(a)}}}import vc from"node:fs/promises";import wr from"node:path";import VS from"node:os";import{EventEmitter as zS}from"node:events";import{randomUUID as JS}from"node:crypto";var XS=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"]),YS=new Set(["user","agent","orchestrator","system"]),ZS=new Set(["hook","otel","statusline","transcript","pane","orchestrator","vendor"]),Jt=new zS;Jt.setMaxListeners(0);var Tc=process.env.BOSTRAT_STATE_DIR||wr.join(process.env.HOME||VS.tmpdir(),".bostrat");function sa(t){return t?.cwd?wr.join(t.cwd,".bostrat","events"):wr.join(Tc,"events")}var up=(t,e)=>wr.join(t,`${e}.ndjson`),oa=new Map;async function QS(t,e){if(!oa.has(e)){let o=(await L(t,e)).reduce((s,a)=>a.seq>s?a.seq:s,0);oa.set(e,o)}let n=oa.get(e)+1;return oa.set(e,n),n}function tv(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||!XS.has(e.type))throw new Error(`appendEvent: unknown event type "${e?.type}"`);if(e.actor&&!YS.has(e.actor))throw new Error(`appendEvent: bad actor "${e.actor}"`);if(e.source&&!ZS.has(e.source))throw new Error(`appendEvent: bad source "${e.source}"`);let r=typeof t=="string"?wr.join(Tc,"events"):sa(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=tv(c,Number(e.payload?.ts??e.ts)||Date.now())}let s=await QS(r,n),a=e.userId??process.env.BOSTRAT_OWNER??null,i={id:e.id||JS(),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 vc.mkdir(r,{recursive:!0}),await vc.appendFile(up(r,n),JSON.stringify(i)+`
|
|
70
|
+
`),Jt.emit("event",i),Jt.emit(`track:${n}`,i),i}async function L(t,e){let n,r;typeof t=="string"&&e?(n=t,r=e):(r=typeof t=="string"?t:t?.id,n=typeof t=="string"?wr.join(Tc,"events"):sa(t));let o;try{o=await vc.readFile(up(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 dp(t,e){let n=`track:${t}`;return Jt.on(n,e),()=>Jt.off(n,e)}var hp=".bostrat/handoff",wp="latest.md",fp=32*1024;function nv(t){let e=yo(String(t??"")),n=Buffer.byteLength(e,"utf8");return n<=fp?{content:e,bytes:n,truncated:!1}:{content:`${Buffer.from(e,"utf8").subarray(0,fp).toString("utf8").replace(/�+$/,"")}
|
|
72
72
|
|
|
73
|
-
[\u2026 truncated by bostrat at 32 KiB \u2014 full artifact in the workspace]`,bytes:n,truncated:!0}}var mp="Checkpoint: write a work-in-progress handoff",hp="HANDOFF WRITTEN",wp="Resume from .bostrat/handoff/latest.md",tv="/clear",ev=t=>`${mp} to the EXACT absolute path ${t} (write it there specifically \u2014 NOT into any sub-project or other directory; create the directory if needed, overwrite the file) so a FRESH Claude context with none of this conversation can resume seamlessly. Use these sections: Objective; Done (with file paths); In progress (exact state); Next steps (ordered); Key files & commands; Open decisions/blockers; Resume instruction (one line). Be concrete and self-contained \u2014 assume the reader has none of this chat. When the file is written, reply only with ${hp} and take no further action.`,nv=`${wp}: read it, restate the Objective and the single next step in one line, then continue the work from 'Next steps'.`,gp="Beat: write a",yp="BEAT WRITTEN",rv=".bostrat/arc",ov="beat.txt",up=200,bp=4,kp=5,sv=36,av=t=>`${gp} short summary of what this workstream accomplished, to the EXACT absolute path ${t} (write it there specifically \u2014 NOT into any sub-project or other directory; create the directory if needed, overwrite the file). Format the file as: FIRST LINE = one sentence of at most 18 words, plain past tense, saying the outcome \u2014 no heading, no bullet, no trailing list. THEN, only if this workstream covered several separable pieces of work, up to ${bp} further lines, each starting with "- " and naming ONE piece in at most ${kp} words \u2014 a label, not a sentence (e.g. "- stale working flag on poll"). Write nothing else in the file. When the file is written, reply only with ${yp} and take no further action.`,dp=/^(?:[-–—*•·]|\d+[.)])\s+/,iv=(t,e,n)=>{let r=t.split(/\s+/).filter(Boolean).slice(0,e).join(" ");return r.length>n?`${r.slice(0,n-1).trimEnd()}\u2026`:r};function cv(t){let e=String(t||"").split(/\r?\n/).map(s=>s.trim()).filter(Boolean),n=[],r=[];for(let s of e)if(dp.test(s)){if(r.length>=bp)continue;let a=iv(s.replace(dp,"").replace(/[.;,:]+$/,""),kp,sv);a&&r.push(a)}else r.length||n.push(s);let o=n.join(" ").replace(/\s+/g," ").replace(/[:\-–—]+$/,"").trim();if(!o)throw new Error("beat file has no summary line");return o.length>up&&(o=`${o.slice(0,up-1).trimEnd()}\u2026`),{text:o,items:r.length>1?r:[]}}async function xp(t,e,{timeoutMs:n=9e4}={}){let r=wr.join(e,rv),o=wr.join(r,ov);await hr.mkdir(r,{recursive:!0});let s=await na(o),a=await Z(t);if(!a)throw new Error(`session "${t}" not found`);return await ra(t,a,av(o)),await Rp(o,s,n),cv(await hr.readFile(o,"utf8"))}function Sp(t,e){let n=(e||"").trim();return n?t==="assistant"?n===hp||n===yp:n.startsWith(mp)||n===tv||n.startsWith(wp)||n.startsWith(gp):!1}var lv=6e5;function vp(t){let e=[];for(let r of t?.events||[]){if(r.type!=="lead-handoff"||!r.from)continue;let o=r.at??Date.now(),s=r.handoffStartedAt??o-lv;e.push({box:r.from,from:s,to:o}),r.to&&r.to!==r.from&&e.push({box:r.to,from:s,to:o})}let n=t?.handoff;return n?.fromBox&&n.startedAt&&n.finishedAt&&e.push({box:n.fromBox,from:n.startedAt,to:n.finishedAt}),n?.toBox&&n.startedAt&&n.finishedAt&&e.push({box:n.toBox,from:n.startedAt,to:n.finishedAt}),e}function Tp(t,e){let n=[];for(let r of t?.boxes||[]){let o=e?.(r.name);o?.startedAt&&n.push({box:r.name,from:o.startedAt,to:o.finishedAt||Date.now()+1e12})}return n}function Ap(t,{windows:e=[]}={}){if(Sp(t.role,t.text))return!0;if(t.role!=="assistant"||!t.ts)return!1;for(let n of e)if(t.box===n.box&&t.ts>=n.from&&t.ts<=n.to)return!0;return!1}var uv=3500,dv=3e5,Sc=new Map;function oa(t){return Sc.get(t)||null}async function Ep(t){let e=Sc.get(t);if(e&&!e.finishedAt)throw new Error("a handoff is already running for this session");let n=await Z(t);if(!n)throw new Error(`session "${t}" not found`);if(!n.cwd)throw new Error(`can't resolve cwd for "${t}"`);let r={name:t,step:"summarize",startedAt:Date.now(),finishedAt:null,error:null,file:wr.join(n.cwd,fp,pp)};return Sc.set(t,r),fv(r,n.cwd).catch(o=>{r.error=o?.message||String(o),r.finishedAt=Date.now()}),r}async function fv(t,e){t.step="summarize",await _p(t.name,e),t.step="clear";let n=await Z(t.name);await ra(t.name,n,"/clear"),await $p(uv),t.step="resume",await ra(t.name,n,nv),t.step="done",t.finishedAt=Date.now()}async function _p(t,e){let n=wr.join(e,fp),r=wr.join(n,pp);await hr.mkdir(n,{recursive:!0});let o=await na(r),s=await Z(t);await U({flow:"checkpoint",phase:"summarize-start",sessionName:t,cwd:e,file:r,beforeMtime:o}),await ra(t,s,ev(r)),await Rp(r,o,dv),await U({flow:"checkpoint",phase:"summarize-done",sessionName:t,file:r,afterMtime:await na(r)});let a=await ap(e,r,{exclude:l=>Sp(l.role,l.text)});await U({flow:"checkpoint",phase:"seed-enriched",sessionName:t,file:r,...a});let i=/workspaces[/\\]([0-9a-f]{8})$/.exec(e||"")?.[1];if(i){await R({id:i,cwd:e},{type:"memory.seed",actor:"orchestrator",payload:{...a,tokensEst:a.chars?Math.round(a.chars/4):0,ts:Date.now()}}).catch(()=>{});try{let l=await hr.readFile(r,"utf8");await R({id:i,cwd:e},{type:"handoff.artifact",actor:"orchestrator",payload:{...QS(l),ts:Date.now()}})}catch{}}await af(e);let c=await pv(n,r);return i&&await gf(i,c||r,{box:t,agent:s?.agent??null}),r}async function Ip(t){let e=await Z(t);if(!e)throw new Error(`session "${t}" not found`);if(!e.cwd)throw new Error(`can't resolve cwd for "${t}"`);return _p(t,e.cwd)}async function na(t){try{return(await hr.stat(t)).mtimeMs}catch{return 0}}async function Rp(t,e,n){let r=Date.now()+n;for(;;){if(await na(t)>e)return;if(Date.now()>r)throw new Error(`timed out waiting for the handoff file to be written (${t})`);await $p(2e3)}}async function pv(t,e){try{if(!ZS.existsSync(e))return null;let n=new Date().toISOString().replace(/[:.]/g,"-"),r=wr.join(t,`${n}.md`);return await hr.copyFile(e,r),r}catch{return null}}function $p(t){return new Promise(e=>setTimeout(e,t))}async function ra(t,e,n){if(e){let o=e.agent==="claude"?process.env.BOSTRAT_AGENT_CMD||"claude":e.agent==="cursor"?process.env.BOSTRAT_CURSOR_CMD||"cursor-agent":e.agent==="codex"?process.env.BOSTRAT_CODEX_CMD||"codex":"";await tn({cwd:e.cwd,sessionId:e.sessionId,sessionName:t,agent:e.agent,agentCmd:o})}let r=await Z(t);if(!r)throw new Error(`session "${t}" disappeared before the orchestration prompt could be sent`);await St(t,n,{kind:"orchestration-drive",..._e(r.agent)})}import L from"node:fs/promises";import F from"node:path";import{randomUUID as cs}from"node:crypto";import{execFile as TI}from"node:child_process";import{promisify as AI}from"node:util";import hh from"node:path";var vc={"claude-opus-5":{input:5,output:25,cacheWrite:6.25,cacheRead:.5},"claude-opus-4-8":{input:5,output:25,cacheWrite:6.25,cacheRead:.5},"claude-sonnet-5":{input:3,output:15,cacheWrite:3.75,cacheRead:.3},"claude-sonnet-4-6":{input:3,output:15,cacheWrite:3.75,cacheRead:.3},"claude-haiku-4-5":{input:1,output:5,cacheWrite:1.25,cacheRead:.1},"claude-fable-5":{input:10,output:50,cacheWrite:12.5,cacheRead:1},"gpt-5.5":{input:5,output:30,cacheWrite:5,cacheRead:.5},"gpt-5.6-sol":{input:5,output:30,cacheWrite:6.25,cacheRead:.5},"gpt-5.6-terra":{input:2.5,output:15,cacheWrite:3.125,cacheRead:.25},"gpt-5.6-luna":{input:1,output:6,cacheWrite:1.25,cacheRead:.1}},Op={opus:"claude-opus-4-8",opusplan:"claude-opus-4-8",default:"claude-opus-4-8",sonnet:"claude-sonnet-4-6",haiku:"claude-haiku-4-5",fable:"claude-fable-5","gpt-5.6":"gpt-5.6-sol"};function Cp(t){if(!t)return null;let e=String(t).trim();return vc[e]?e:Op[e]?Op[e]:Object.keys(vc).find(r=>e.startsWith(r))||null}var sa=t=>Cp(t)!==null;function le(t,e={}){let n=vc[Cp(t)];return n?((e.input||0)*n.input+(e.output||0)*n.output+(e.cacheWrite||0)*n.cacheWrite+(e.cacheRead||0)*n.cacheRead)/1e6:0}var Mp=()=>({input:0,output:0,cacheRead:0,cacheCreation:0});function mv(t){return t?{input:t.input||0,output:t.output||0,cacheRead:t.cacheRead||0,cacheWrite:t.cacheWrite??t.cacheCreation??0}:null}function Np(t,e){e&&(t.input+=e.input,t.output+=e.output,t.cacheRead+=e.cacheRead,t.cacheCreation+=e.cacheWrite)}function ia(t){let e=t.payload?.model||"unknown",n=mv(t.payload?.tokens),r=Number(t.payload?.usd)||0,o=!1;!r&&n&&(r=le(e,n),r>0&&(o=!0));let s=n?n.input+n.output+n.cacheRead+n.cacheWrite:0,a=!r&&s>0&&!sa(e);return{usd:r,model:e,tokens:n,repriced:o,unpriced:a}}function ue(t,{boxLabels:e={}}={}){let n=(t||[]).filter(f=>f.type==="cost.recorded"),r={},o={},s={},a=0,i=0,c=0,l=new Set,u=Mp();for(let f of n){let{usd:d,model:p,tokens:m,repriced:g,unpriced:y}=ia(f),b=f.provider||"unknown",S=f.boxId||"unattributed";if(g&&i++,y&&(c++,l.add(p)),a+=d,Np(u,m),r[p]||={usd:0,tokens:Mp(),events:0},r[p].usd+=d,r[p].events+=1,Np(r[p].tokens,m),o[b]||={usd:0,events:0},o[b].usd+=d,o[b].events+=1,s[S]||={usd:0,events:0,provider:b,models:new Set,label:e[S]||S},s[S].usd+=d,s[S].events+=1,p!=="unknown"&&s[S].models.add(p),f.epoch!=null){let E=(s[S].epochs||={})[f.epoch]||={usd:0,events:0,provider:b,models:new Set};E.usd+=d,E.events+=1,E.provider=b,p!=="unknown"&&E.models.add(p)}}return{totalUsd:vo(a),totalTokens:u,turns:n.length,repricedTurns:i,unpricedTurns:c,unpricedModels:[...l],byModel:aa(r,f=>({...f,usd:vo(f.usd)})),byProvider:aa(o,f=>({...f,usd:vo(f.usd)})),byBox:aa(s,f=>({usd:vo(f.usd),events:f.events,provider:f.provider,label:f.label,models:[...f.models],...f.epochs?{epochs:aa(f.epochs,d=>({usd:vo(d.usd),events:d.events,provider:d.provider,models:[...d.models]}))}:{}}))}}var vo=t=>Math.round(t*1e4)/1e4,aa=(t,e)=>Object.fromEntries(Object.entries(t).map(([n,r])=>[n,e(r)]));import kt from"node:fs/promises";import Pt from"node:path";import To from"node:os";import{randomUUID as hv}from"node:crypto";import{execFile as wv}from"node:child_process";import{promisify as gv}from"node:util";import{chromium as yv}from"playwright-core";var Bp=gv(wv),Dp=process.env.BOSTRAT_STATE_DIR||Pt.join(process.env.HOME||To.tmpdir(),".bostrat"),jp=Pt.join(Dp,"mocks.json"),bv=process.env.BOSTRAT_MOCK_ENGINE||"chromium",kv=process.env.BOSTRAT_CHROMIUM_BIN||"/usr/bin/chromium",xv=process.env.BOSTRAT_FIREFOX_BIN||"firefox",Sv=1300,gr=2,vv=32,Lp=1500,yr=Number(process.env.BOSTRAT_MOCK_TIMEOUT_MS)||75e3,Tv=700*1024*1024;async function Av(){if(process.platform!=="darwin")return To.freemem();try{let{stdout:t}=await Bp("vm_stat",[],{timeout:3e3}),e=Number(/page size of (\d+) bytes/.exec(t)?.[1])||4096,n=o=>Number(new RegExp(`Pages ${o}:\\s+(\\d+)\\.`).exec(t)?.[1])||0,r=(n("free")+n("inactive")+n("speculative"))*e;return r>0?r:To.freemem()}catch{return To.freemem()}}var te=class extends Error{constructor(e,n=""){super(n?`${e}
|
|
73
|
+
[\u2026 truncated by bostrat at 32 KiB \u2014 full artifact in the workspace]`,bytes:n,truncated:!0}}var gp="Checkpoint: write a work-in-progress handoff",yp="HANDOFF WRITTEN",bp="Resume from .bostrat/handoff/latest.md",rv="/clear",ov=t=>`${gp} 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 ${yp} and take no further action.`,sv=`${bp}: read it, restate the Objective and the single next step in one line, then continue the work from 'Next steps'.`,kp="Beat: write a",xp="BEAT WRITTEN",av=".bostrat/arc",iv="beat.txt",pp=200,Sp=4,vp=5,cv=36,lv=t=>`${kp} 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 ${Sp} further lines, each starting with "- " and naming ONE piece in at most ${vp} 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 ${xp} and take no further action.`,mp=/^(?:[-–—*•·]|\d+[.)])\s+/,uv=(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 dv(t){let e=String(t||"").split(/\r?\n/).map(s=>s.trim()).filter(Boolean),n=[],r=[];for(let s of e)if(mp.test(s)){if(r.length>=Sp)continue;let a=uv(s.replace(mp,"").replace(/[.;,:]+$/,""),vp,cv);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>pp&&(o=`${o.slice(0,pp-1).trimEnd()}\u2026`),{text:o,items:r.length>1?r:[]}}async function Tp(t,e,{timeoutMs:n=9e4}={}){let r=yr.join(e,av),o=yr.join(r,iv);await gr.mkdir(r,{recursive:!0});let s=await aa(o),a=await Q(t);if(!a)throw new Error(`session "${t}" not found`);return await ia(t,a,lv(o)),await Cp(o,s,n),dv(await gr.readFile(o,"utf8"))}function Ap(t,e){let n=(e||"").trim();return n?t==="assistant"?n===yp||n===xp:n.startsWith(gp)||n===rv||n.startsWith(bp)||n.startsWith(kp):!1}var fv=6e5;function Ep(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-fv;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 _p(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 Ip(t,{windows:e=[]}={}){if(Ap(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 pv=3500,mv=3e5,Ac=new Map;function ca(t){return Ac.get(t)||null}async function Rp(t){let e=Ac.get(t);if(e&&!e.finishedAt)throw new Error("a handoff is already running for this session");let n=await Q(t);if(!n)throw new Error(`session "${t}" not found`);if(!n.cwd)throw new Error(`can't resolve cwd for "${t}"`);let r={name:t,step:"summarize",startedAt:Date.now(),finishedAt:null,error:null,file:yr.join(n.cwd,hp,wp)};return Ac.set(t,r),hv(r,n.cwd).catch(o=>{r.error=o?.message||String(o),r.finishedAt=Date.now()}),r}async function hv(t,e){t.step="summarize",await $p(t.name,e),t.step="clear";let n=await Q(t.name);await ia(t.name,n,"/clear"),await Mp(pv),t.step="resume",await ia(t.name,n,sv),t.step="done",t.finishedAt=Date.now()}async function $p(t,e){let n=yr.join(e,hp),r=yr.join(n,wp);await gr.mkdir(n,{recursive:!0});let o=await aa(r),s=await Q(t);await q({flow:"checkpoint",phase:"summarize-start",sessionName:t,cwd:e,file:r,beforeMtime:o}),await ia(t,s,ov(r)),await Cp(r,o,mv),await q({flow:"checkpoint",phase:"summarize-done",sessionName:t,file:r,afterMtime:await aa(r)});let a=await lp(e,r,{exclude:l=>Ap(l.role,l.text)});await q({flow:"checkpoint",phase:"seed-enriched",sessionName:t,file:r,...a});let i=/workspaces[/\\]([0-9a-f]{8})$/.exec(e||"")?.[1];if(i){await R({id:i,cwd:e},{type:"memory.seed",actor:"orchestrator",payload:{...a,tokensEst:a.chars?Math.round(a.chars/4):0,ts:Date.now()}}).catch(()=>{});try{let l=await gr.readFile(r,"utf8");await R({id:i,cwd:e},{type:"handoff.artifact",actor:"orchestrator",payload:{...nv(l),ts:Date.now()}})}catch{}}await uf(e);let c=await wv(n,r);return i&&await kf(i,c||r,{box:t,agent:s?.agent??null}),r}async function Op(t){let e=await Q(t);if(!e)throw new Error(`session "${t}" not found`);if(!e.cwd)throw new Error(`can't resolve cwd for "${t}"`);return $p(t,e.cwd)}async function aa(t){try{return(await gr.stat(t)).mtimeMs}catch{return 0}}async function Cp(t,e,n){let r=Date.now()+n;for(;;){if(await aa(t)>e)return;if(Date.now()>r)throw new Error(`timed out waiting for the handoff file to be written (${t})`);await Mp(2e3)}}async function wv(t,e){try{if(!ev.existsSync(e))return null;let n=new Date().toISOString().replace(/[:.]/g,"-"),r=yr.join(t,`${n}.md`);return await gr.copyFile(e,r),r}catch{return null}}function Mp(t){return new Promise(e=>setTimeout(e,t))}async function ia(t,e,n){if(e){let o=e.agent==="claude"?process.env.BOSTRAT_AGENT_CMD||"claude":e.agent==="cursor"?process.env.BOSTRAT_CURSOR_CMD||"cursor-agent":e.agent==="codex"?process.env.BOSTRAT_CODEX_CMD||"codex":"";await tn({cwd:e.cwd,sessionId:e.sessionId,sessionName:t,agent:e.agent,agentCmd:o})}let r=await Q(t);if(!r)throw new Error(`session "${t}" disappeared before the orchestration prompt could be sent`);await St(t,n,{kind:"orchestration-drive",..._e(r.agent)})}import U from"node:fs/promises";import H from"node:path";import{randomUUID as fs}from"node:crypto";import{execFile as _I}from"node:child_process";import{promisify as II}from"node:util";import yh from"node:path";var Ec={"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}},Np={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 Pp(t){if(!t)return null;let e=String(t).trim();return Ec[e]?e:Np[e]?Np[e]:Object.keys(Ec).find(r=>e.startsWith(r))||null}var la=t=>Pp(t)!==null;function le(t,e={}){let n=Ec[Pp(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 Bp=()=>({input:0,output:0,cacheRead:0,cacheCreation:0});function gv(t){return t?{input:t.input||0,output:t.output||0,cacheRead:t.cacheRead||0,cacheWrite:t.cacheWrite??t.cacheCreation??0}:null}function Dp(t,e){e&&(t.input+=e.input,t.output+=e.output,t.cacheRead+=e.cacheRead,t.cacheCreation+=e.cacheWrite)}function da(t){let e=t.payload?.model||"unknown",n=gv(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&&!la(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=Bp();for(let f of n){let{usd:d,model:p,tokens:m,repriced:h,unpriced:y}=da(f),b=f.provider||"unknown",S=f.boxId||"unattributed";if(h&&i++,y&&(c++,l.add(p)),a+=d,Dp(u,m),r[p]||={usd:0,tokens:Bp(),events:0},r[p].usd+=d,r[p].events+=1,Dp(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 I=(s[S].epochs||={})[f.epoch]||={usd:0,events:0,provider:b,models:new Set};I.usd+=d,I.events+=1,I.provider=b,p!=="unknown"&&I.models.add(p)}}return{totalUsd:Ao(a),totalTokens:u,turns:n.length,repricedTurns:i,unpricedTurns:c,unpricedModels:[...l],byModel:ua(r,f=>({...f,usd:Ao(f.usd)})),byProvider:ua(o,f=>({...f,usd:Ao(f.usd)})),byBox:ua(s,f=>({usd:Ao(f.usd),events:f.events,provider:f.provider,label:f.label,models:[...f.models],...f.epochs?{epochs:ua(f.epochs,d=>({usd:Ao(d.usd),events:d.events,provider:d.provider,models:[...d.models]}))}:{}}))}}var Ao=t=>Math.round(t*1e4)/1e4,ua=(t,e)=>Object.fromEntries(Object.entries(t).map(([n,r])=>[n,e(r)]));import kt from"node:fs/promises";import Bt from"node:path";import Eo from"node:os";import{randomUUID as yv}from"node:crypto";import{execFile as bv}from"node:child_process";import{promisify as kv}from"node:util";import{chromium as xv}from"playwright-core";var Lp=kv(bv),Fp=process.env.BOSTRAT_STATE_DIR||Bt.join(process.env.HOME||Eo.tmpdir(),".bostrat"),Up=Bt.join(Fp,"mocks.json"),Sv=process.env.BOSTRAT_MOCK_ENGINE||"chromium",vv=process.env.BOSTRAT_CHROMIUM_BIN||"/usr/bin/chromium",Tv=process.env.BOSTRAT_FIREFOX_BIN||"firefox",Av=1300,br=2,Ev=32,Hp=1500,kr=Number(process.env.BOSTRAT_MOCK_TIMEOUT_MS)||75e3,_v=700*1024*1024;async function Iv(){if(process.platform!=="darwin")return Eo.freemem();try{let{stdout:t}=await Lp("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:Eo.freemem()}catch{return Eo.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"}},Cn=null,Tc=null;async function fe(){return Cn||(Tc||=(async()=>{try{Cn=JSON.parse(await kt.readFile(jp,"utf8"))}catch{Cn={mocks:[]}}finally{Tc=null}return Cn.mocks||=[],Cn})(),Tc)}async function de(){await Mt(jp,Cn)}var en=t=>Rt("mocks",{...t},{trackId:t.trackId}).catch(()=>{}),Mn=(t,e,n)=>Je("mocks",t.id,e,n,Ht(t,e)[n]).catch(()=>{});function ee(t,e,n={}){return R({id:t.trackId,cwd:t.cwd},{type:"mock.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box||null,provider:t.agent||null,payload:{ts:Date.now(),change:e,mockId:t.id,name:t.name,...t.group?{group:t.group}:{},...n}}).catch(()=>{})}var ca=t=>Pt.join(t,".bostrat","mocks");function Ht(t,e){let n=ca(t.cwd);return{html:Pt.join(n,`${t.id}.v${e}.html`),png:Pt.join(n,`${t.id}.v${e}.png`)}}function Ie(t,e=null){let n=t.versions||[];return e!=null?n.find(r=>r.v===Number(e))||null:n.length?n[n.length-1]:null}function Ec(t){return t.length<24||t.readUInt32BE(12)!==1229472850?null:{width:t.readUInt32BE(16),height:t.readUInt32BE(20)}}var Ev=Buffer.from([137,80,78,71,13,10,26,10]);function Fp(t){return Buffer.isBuffer(t)&&t.length>24&&t.subarray(0,8).equals(Ev)}async function _v(t){let e=Buffer.alloc(24),n=await kt.open(t,"r");try{await n.read(e,0,24,0)}finally{await n.close()}return e}function Iv({imgW:t,imgH:e,bbox:n,pad:r,align:o=1}){if(!n||!(t>0)||!(e>0))return null;let s=n.right-n.left+1,a=n.bottom-n.top+1;if(s<=0||a<=0||s>=t*.9&&a>=e*.9)return null;let i=s+2*r,c=a+2*r;return i+=(o-i%o)%o,c+=(o-c%o)%o,{sx:n.left,sy:n.top,sw:s,sh:a,outW:i,outH:c,dx:Math.floor((i-s)/2),dy:Math.floor((c-a)/2)}}async function Rv(t,e){return t.evaluate(async n=>{let r=new Image;await new Promise((m,g)=>{r.onload=m,r.onerror=()=>g(new Error("decode failed")),r.src=`data:image/png;base64,${n}`});let o=r.naturalWidth,s=r.naturalHeight;if(!o||!s||o*s>24e6)return null;let a=document.createElement("canvas");a.width=o,a.height=s;let i=a.getContext("2d",{willReadFrequently:!0});i.drawImage(r,0,0);let c=i.getImageData(0,0,o,s).data,l=[c[0],c[1],c[2]],u=o,f=s,d=-1,p=-1;for(let m=0;m<s;m++)for(let g=m*o*4,y=0;y<o;y++,g+=4)Math.abs(c[g]-l[0])+Math.abs(c[g+1]-l[1])+Math.abs(c[g+2]-l[2])>24&&(y<u&&(u=y),y>d&&(d=y),m<f&&(f=m),p=m);return d<0?null:{imgW:o,imgH:s,bg:l,bbox:{left:u,top:f,right:d,bottom:p}}},e.toString("base64"))}async function $v(t,e,{bg:n,crop:r}){let o=await t.evaluate(async({b64:s,bg:a,crop:i})=>{let c=new Image;await new Promise((f,d)=>{c.onload=f,c.onerror=()=>d(new Error("decode failed")),c.src=`data:image/png;base64,${s}`});let l=document.createElement("canvas");l.width=i.outW,l.height=i.outH;let u=l.getContext("2d");return u.fillStyle=`rgb(${a[0]},${a[1]},${a[2]})`,u.fillRect(0,0,i.outW,i.outH),u.drawImage(c,i.sx,i.sy,i.sw,i.sh,i.dx,i.dy,i.sw,i.sh),l.toDataURL("image/png").split(",")[1]},{b64:e.toString("base64"),bg:n,crop:r});return Buffer.from(o,"base64")}async function Ov({htmlPath:t,outPath:e,width:n}){let r=Date.now(),o;try{o=await yv.launch({executablePath:kv,args:["--disable-gpu"],timeout:3e4})}catch(s){throw new te("chromium failed to launch",s?.message||"")}try{let a=await(await o.newContext({viewport:{width:n,height:800},deviceScaleFactor:gr})).newPage();a.setDefaultTimeout(yr),await a.goto(`file://${t}`,{waitUntil:"load",timeout:yr}),await a.waitForTimeout(250);let i;for(let l=1;;l++)try{i=await a.screenshot({fullPage:!0,timeout:yr});break}catch(u){if(l>=3||!/captureScreenshot|Unable to capture/i.test(u?.message||""))throw u;await a.waitForTimeout(500)}try{let l=await Rv(a,i),u=l&&Iv({...l,pad:vv*gr,align:gr});u&&(i=await $v(a,i,{bg:l.bg,crop:u}))}catch{}if(i.length<Lp)throw new te(`capture produced a ${i.length}-byte image`);let c=Ec(i);if(!c)throw new te("output is not a PNG");return await kt.writeFile(e,i),{width:Math.round(c.width/gr),height:Math.round(c.height/gr),dpr:gr,renderMs:Date.now()-r}}catch(s){throw s instanceof te?s:new te(/timeout/i.test(s?.message||"")?`render timed out after ${yr}ms`:"chromium render failed",s?.message||"")}finally{await o.close().catch(()=>{})}}async function Pp({htmlPath:t,outPath:e,width:n}){let r=await kt.mkdtemp(Pt.join(To.tmpdir(),"bostrat-mock-profile-")),o=Date.now(),s="";try{try{s=(await Bp(xv,["--headless","--no-remote","--profile",r,`--window-size=${n}`,"--screenshot",e,`file://${t}`],{env:{...process.env,MOZ_HEADLESS:"1"},timeout:yr,killSignal:"SIGKILL",maxBuffer:4194304})).stderr||""}catch(c){throw s=c?.stderr||"",new te(c?.killed?`render timed out after ${yr}ms`:"firefox exited non-zero",s)}let a=await kt.stat(e).catch(()=>null);if(!a||a.size<Lp)throw new te(`render produced ${a?`a ${a.size}-byte file`:"no file"} (silent no-op \u2014 another firefox holding the profile?)`,s);let i=Ec(await _v(e));if(!i)throw new te("output is not a PNG",s);return{width:i.width,height:i.height,dpr:1,renderMs:Date.now()-o}}finally{await kt.rm(r,{recursive:!0,force:!0}).catch(()=>{})}}async function Cv(t){if(bv==="firefox")return Pp(t);try{return await Ov(t)}catch(e){if(/failed to launch/i.test(e?.message||""))return Pp(t);throw e}}var Mv=Cv;var Ac=Promise.resolve();function Up(t){return Ac=Ac.then(t,t),Ac}async function Hp(t,e){let n=await fe(),r=n.mocks.find(o=>o.id===t);if(r){r.state="rendering",await de(),await ee(r,"rendering",{version:e});try{let o=await Av();if(o<Tv)throw new te(`box is low on memory (${Math.round(o/1e6)} MB available) \u2014 not spawning a browser`);let s=Ht(r,e),a=await Mv({htmlPath:s.html,outPath:s.png,width:r.width});if(!n.mocks.some(i=>i.id===t)){await kt.rm(s.png,{force:!0}).catch(()=>{});return}Wp(r,{v:e,source:"html",htmlRel:Pt.relative(r.cwd,s.html),pngRel:Pt.relative(r.cwd,s.png),width:a.width,height:a.height,dpr:a.dpr,renderMs:a.renderMs,createdAt:Date.now()}),await de(),await ee(r,"ready",{version:e,width:a.width,height:a.height,dpr:a.dpr}),en(r),Mn(r,e,"png")}catch(o){if(!n.mocks.some(s=>s.id===t))return;r.state="error",r.error=o?.message||String(o),await de(),await ee(r,"error",{version:e,error:r.error}),en(r)}}}function Wp(t,e){t.versions=(t.versions||[]).filter(r=>r.v!==e.v).concat(e),t.versions.sort((r,o)=>r.v-o.v);let n=t.versions[t.versions.length-1];t.readyVersion=n.v,t.height=n.height,t.dpr=n.dpr,t.renderMs=n.renderMs??null,n.v===t.version&&(t.state="ready",t.error=null)}function Nv({trackId:t,name:e,cwd:n,width:r}){if(!t)throw new Error("trackId required");if(typeof e!="string"||!e.trim())throw new Error("name (string) required");if(!n)throw new Error("cwd required");let o=r==null?Sv:Number(r);if(!Number.isInteger(o)||o<320||o>4e3)throw new Error("invalid width (320-4000)");return o}async function Nn({trackId:t,group:e}={}){let n=await fe(),r=t?n.mocks.filter(o=>o.trackId===t):n.mocks;return e&&(r=r.filter(o=>o.group===e)),r.map(o=>({...o}))}async function nn(t){let n=(await fe()).mocks.find(r=>r.id===t);return n?{...n}:null}async function qp({trackId:t,name:e,html:n,image:r,cwd:o,width:s,group:a,box:i,agent:c,prompt:l}){let u=Nv({trackId:t,name:e,cwd:o,width:s}),f=typeof n=="string"&&n.trim();if(!f&&!r)throw new Error("html (string) or image (png) required");if(r&&!Fp(r))throw new Error("image must be a PNG");let d=await fe(),p={id:hv().slice(0,8),trackId:t,box:i||null,agent:c||null,name:e.trim(),prompt:l?String(l).slice(0,500):null,group:a?String(a).trim():null,cwd:o,width:u,height:null,dpr:null,state:"queued",source:f?"html":"image",version:1,readyVersion:null,versions:[],error:null,renderMs:null,createdAt:Date.now(),updatedAt:Date.now()};return await kt.mkdir(ca(o),{recursive:!0}),d.mocks.push(p),f?(await kt.writeFile(Ht(p,1).html,n),await de(),await ee(p,"created"),en(p),Mn(p,1,"html"),Up(()=>Hp(p.id,1))):(await Gp(p,1,r),await de(),await ee(p,"created"),await ee(p,"ready",{version:1,width:p.width,height:p.height,dpr:p.dpr}),en(p),Mn(p,1,"png")),{...p}}async function Kp(t,{html:e,image:n,width:r}={}){let s=(await fe()).mocks.find(c=>c.id===t);if(!s)throw new Error("mock not found");let a=typeof e=="string"&&e.trim();if(!a&&!n)throw new Error("html (string) or image (png) required");if(n&&!Fp(n))throw new Error("image must be a PNG");if(r!=null){let c=Number(r);if(!Number.isInteger(c)||c<320||c>4e3)throw new Error("invalid width (320-4000)");s.width=c}let i=s.version+1;return s.version=i,s.updatedAt=Date.now(),s.source=a?"html":"image",await kt.mkdir(ca(s.cwd),{recursive:!0}),a?(s.state="queued",await kt.writeFile(Ht(s,i).html,e),await de(),await ee(s,"updated",{version:i}),en(s),Mn(s,i,"html"),Up(()=>Hp(s.id,i))):(await Gp(s,i,n),await de(),await ee(s,"updated",{version:i}),await ee(s,"ready",{version:i,width:s.width,height:s.height,dpr:s.dpr}),en(s),Mn(s,i,"png")),{...s}}async function Gp(t,e,n){let r=Ec(n);if(!r)throw new Error("image must be a PNG");let o=Ht(t,e);await kt.writeFile(o.png,n),Wp(t,{v:e,source:"image",htmlRel:null,pngRel:Pt.relative(t.cwd,o.png),width:r.width,height:r.height,dpr:1,renderMs:null,createdAt:Date.now()}),t.width=r.width}async function Vp(t){let e=await fe(),n=e.mocks.find(o=>o.id===t);if(!n)throw new Error("mock not found");if(n.readyVersion==null)throw new Error(`mock is ${n.state} \u2014 nothing to pick yet`);let r=n.group?e.mocks.filter(o=>o.trackId===n.trackId&&o.group===n.group&&o.id!==t&&o.readyVersion!=null).map(o=>({mockId:o.id,name:o.name})):[];return await ee(n,"picked",{version:n.readyVersion,siblings:r}),{mock:{...n},siblings:r}}async function zp(t){let e=await fe(),n=e.mocks.findIndex(o=>o.id===t);if(n===-1)throw new Error("mock not found");let r=e.mocks[n];e.mocks.splice(n,1);for(let o=1;o<=r.version;o++){let s=Ht(r,o);await kt.rm(s.html,{force:!0}).catch(()=>{}),await kt.rm(s.png,{force:!0}).catch(()=>{})}return await de(),await ee(r,"deleted"),Qt("mocks",r.id).catch(()=>{}),{ok:!0,id:t,name:r.name}}async function Jp(t){let n=(await fe()).mocks.filter(i=>i.trackId===t.id);if(!n.length)return 0;let r=Pt.join(Dp,"mock-archive",t.id),o=ca(t.cwd),s=Pt.join(r,".bostrat","mocks");await kt.mkdir(s,{recursive:!0});let a=await kt.readdir(o).catch(()=>[]);for(let i of a)await kt.copyFile(Pt.join(o,i),Pt.join(s,i));for(let i of n){for(let c of i.versions||[])for(let l of[c.htmlRel,c.pngRel]){if(!l)continue;if(!(await kt.stat(Pt.join(r,l)).catch(()=>null))?.isFile())throw new Error(`mock evacuation incomplete: ${l} missing from the archive`)}i.cwd=r,i.trackDeletedAt=Date.now(),i.trackName=t.name||t.id,i.updatedAt=Date.now(),en(i)}return await de(),n.length}async function Xp(t){let e=await fe(),n=e.mocks.filter(r=>r.trackId===t);if(n.length){e.mocks=e.mocks.filter(r=>r.trackId!==t),await de();for(let r of n)Qt("mocks",r.id).catch(()=>{})}return n.length}async function Yp(){if(!pt())return{pushed:0,source:"unconfigured"};let{records:t,source:e}=await Tt("mocks");if(e!=="plane"||t.length)return{pushed:0,source:e};let n=await fe();for(let r of n.mocks){await en(r);for(let o of r.versions||[])o.htmlRel&&await Mn(r,o.v,"html"),o.pngRel&&await Mn(r,o.v,"png")}return{pushed:n.mocks.length,source:"plane"}}import Eo from"node:fs/promises";import Bt from"node:path";import Pv from"node:os";import Bv from"node:net";import{randomUUID as Dv}from"node:crypto";import{execFile as jv}from"node:child_process";import{promisify as Lv}from"node:util";var me=Lv(jv),Fv=process.env.BOSTRAT_STATE_DIR||Bt.join(process.env.HOME||Pv.tmpdir(),".bostrat"),nm=Bt.join(Fv,"views.json"),[Zp,Qp]=(process.env.BOSTRAT_VIEW_PORTS||"4300-4349").split("-").map(t=>Number(t)||0),rn=Number(process.env.BOSTRAT_VIEW_MAX)||3,Uv="bostrat-view-",br=t=>`${Uv}${t}`,tm=new Set(["bostrat","www","api","views","app"]),la=t=>Object.assign(new Error(t),{status:400}),Hv=new Set(["npm","pnpm","yarn","bun"]),Rc="package.json",Wv=/[|;&`]|\$\(/,qv=new Set(["sh","bash","zsh","dash","env","exec","nohup","time"]),Kv=/(^|\s)(-C|--cwd|--manifest-path|--dir)(=|\s)/,rm=t=>String(t).trim().split(/\s+/).filter(Boolean),Gv=/^[A-Za-z_][A-Za-z0-9_]*=/;function Vv(t){for(let e of rm(t))if(!Gv.test(e))return e;return null}function om(t){let e=rm(t),n=e.indexOf("--");return n===-1?e:e.slice(0,n)}function zv(t,e){let n=om(e);for(let r=0;r<n.length;r++){let o=/^--prefix=(.+)$/.exec(n[r]);if(o)return Bt.resolve(t,o[1]);if(n[r]==="--prefix"&&n[r+1])return Bt.resolve(t,n[r+1])}return t}var Jv=t=>Eo.stat(t).then(e=>e.isDirectory(),()=>!1),sm=t=>Eo.stat(t).then(e=>e.isFile(),()=>!1),ua=t=>Eo.realpath(t).catch(()=>Bt.resolve(t));async function Xv(t,e){if(Bt.resolve(t)===Bt.resolve(e))return!0;let[n,r]=await Promise.all([ua(t),ua(e)]);return n===r}async function Yv(t){let n=(await Eo.readdir(t,{withFileTypes:!0}).catch(()=>[])).filter(o=>o.isDirectory()&&!o.name.startsWith(".")&&o.name!=="node_modules");return(await Promise.all(n.map(async o=>await sm(Bt.join(t,o.name,Rc))?`./${o.name}`:null))).filter(Boolean)}async function Zv(t,e){let[n,r]=await Promise.all([ua(t),ua(e)]),o=Bt.relative(n,r);if(o&&(o.startsWith("..")||Bt.isAbsolute(o)))throw la(`serve directory escapes the Track workspace: ${e}`)}async function Qv(t,e){if(!await Jv(t))throw la(`serve directory does not exist: ${t}`);let n=Vv(e);if(!n)return;if(!n.includes("/")&&!await sc(n,{loginShell:!0,failOpen:!0}))throw la(`\`${n}\` is not on the box's PATH \u2014 install it in the box first`);if(Wv.test(e)||qv.has(n)||!Hv.has(n)||!om(e).includes("run")||Kv.test(e))return;let r=zv(t,e);if(await sm(Bt.join(r,Rc)))return;let o=await Yv(r),s=o.length?` \u2014 found one in ${o.join(", ")}; run bostrat-view from there`:"";throw la(`no ${Rc} in ${r}${s}`)}var pe=null,_c=null;async function Oe(){return pe||(_c||=(async()=>{try{pe=JSON.parse(await Eo.readFile(nm,"utf8"))}catch{pe={views:[],adoptWatermarks:{}}}finally{_c=null}return pe.views||=[],pe.adoptWatermarks||={},pe})(),_c)}async function he(){await Mt(nm,pe)}function $e(t,e,n={}){return R({id:t.trackId,cwd:t.cwd},{type:"view.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box||null,payload:{ts:Date.now(),change:e,viewId:t.id,name:t.name,slug:t.slug,port:t.port,...n}}).catch(()=>{})}var Ao=new Map,Cc=Number(process.env.BOSTRAT_VIEW_JOB_TIMEOUT)||3e5,am=12e3;function Mc(t){return Ao.get(t)||null}function im(t,{name:e,box:n}={}){let r=Ao.get(t);if(r&&!r.finishedAt)throw new Error("a view is already being spun up for this Track");let o={trackId:t,name:e||"preview",box:n||null,step:"spinning-up",startedAt:Date.now(),finishedAt:null,viewId:null,error:null};return Ao.set(t,o),o}function $c(t,e){let n=Ao.get(t);n&&!n.finishedAt&&!n.viewId&&(n.viewId=e,n.step="booting")}function da(t,{error:e=null}={}){let n=Ao.get(t);return!n||n.finishedAt?null:(n.finishedAt=Date.now(),e&&(n.error=e),n)}function cm(t){let e=[];for(let n of t?.events||[])n.type!=="view-spinup"||!n.box||!n.startedAt||!n.endedAt||e.push({box:n.box,from:n.startedAt,to:n.endedAt});return e}function Oc(t){return String(t||"").trim().toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,40).replace(/-+$/g,"")||"view"}function tT(t,e){let n=Oc(e),r=new Set(t.views.map(o=>o.slug));if(!r.has(n)&&!tm.has(n))return n;for(let o=2;;o++){let s=`${n}-${o}`;if(!r.has(s)&&!tm.has(s))return s}}function _o(t,{timeoutMs:e=1200}={}){return new Promise(n=>{let r=Bv.connect({port:t,host:"127.0.0.1"}),o=s=>{r.destroy(),n(s)};r.setTimeout(e,()=>o(!1)),r.once("connect",()=>o(!0)),r.once("error",()=>o(!1))})}async function Nc(t){if(process.platform!=="darwin"){await me("fuser",["-k",`${t}/tcp`]).catch(()=>{});return}let e="";try{({stdout:e}=await me("lsof",["-t","-sTCP:LISTEN","-i",`tcp:${t}`]))}catch{return}for(let n of e.split(/\s+/).filter(Boolean)){let r=Number(n);if(!(!r||r===process.pid))try{process.kill(r,"SIGKILL")}catch{}}}function lm(t=process.env){return new Set([Number(t.PORT)||3362,Number(t.BOSTRAT_TLS_PORT)||3443])}async function eT(t,e){let n=new Set(t.views.map(o=>o.port)),r=lm();if(e){let o=Number(e);if(!Number.isInteger(o)||o<1024||o>65535)throw new Error("invalid port");if(r.has(o))throw new Error(`port ${o} is the node's own API port`);if(n.has(o))throw new Error(`port ${o} is already owned by a view`);return o}for(let o=Zp;o<=Qp;o++)if(!(n.has(o)||r.has(o))&&!await _o(o,{timeoutMs:250}))return o;throw new Error(`no free view port in ${Zp}-${Qp}`)}async function nT(t){try{let{stdout:e}=await me(N,P("list-sessions","-F","#{session_name}"));return e.split(`
|
|
77
|
-
`).some(n=>n.trim()===
|
|
76
|
+
`)}`:e),this.name="MockRenderError"}},Mn=null,_c=null;async function fe(){return Mn||(_c||=(async()=>{try{Mn=JSON.parse(await kt.readFile(Up,"utf8"))}catch{Mn={mocks:[]}}finally{_c=null}return Mn.mocks||=[],Mn})(),_c)}async function de(){await Nt(Up,Mn)}var en=t=>$t("mocks",{...t},{trackId:t.trackId}).catch(()=>{}),Nn=(t,e,n)=>Je("mocks",t.id,e,n,Wt(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 fa=t=>Bt.join(t,".bostrat","mocks");function Wt(t,e){let n=fa(t.cwd);return{html:Bt.join(n,`${t.id}.v${e}.html`),png:Bt.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 Rc(t){return t.length<24||t.readUInt32BE(12)!==1229472850?null:{width:t.readUInt32BE(16),height:t.readUInt32BE(20)}}var Rv=Buffer.from([137,80,78,71,13,10,26,10]);function Wp(t){return Buffer.isBuffer(t)&&t.length>24&&t.subarray(0,8).equals(Rv)}async function $v(t){let e=Buffer.alloc(24),n=await kt.open(t,"r");try{await n.read(e,0,24,0)}finally{await n.close()}return e}function Ov({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 Cv(t,e){return t.evaluate(async n=>{let r=new Image;await new Promise((m,h)=>{r.onload=m,r.onerror=()=>h(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 h=m*o*4,y=0;y<o;y++,h+=4)Math.abs(c[h]-l[0])+Math.abs(c[h+1]-l[1])+Math.abs(c[h+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 Mv(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 Nv({htmlPath:t,outPath:e,width:n}){let r=Date.now(),o;try{o=await xv.launch({executablePath:vv,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:br})).newPage();a.setDefaultTimeout(kr),await a.goto(`file://${t}`,{waitUntil:"load",timeout:kr}),await a.waitForTimeout(250);let i;for(let l=1;;l++)try{i=await a.screenshot({fullPage:!0,timeout:kr});break}catch(u){if(l>=3||!/captureScreenshot|Unable to capture/i.test(u?.message||""))throw u;await a.waitForTimeout(500)}try{let l=await Cv(a,i),u=l&&Ov({...l,pad:Ev*br,align:br});u&&(i=await Mv(a,i,{bg:l.bg,crop:u}))}catch{}if(i.length<Hp)throw new te(`capture produced a ${i.length}-byte image`);let c=Rc(i);if(!c)throw new te("output is not a PNG");return await kt.writeFile(e,i),{width:Math.round(c.width/br),height:Math.round(c.height/br),dpr:br,renderMs:Date.now()-r}}catch(s){throw s instanceof te?s:new te(/timeout/i.test(s?.message||"")?`render timed out after ${kr}ms`:"chromium render failed",s?.message||"")}finally{await o.close().catch(()=>{})}}async function jp({htmlPath:t,outPath:e,width:n}){let r=await kt.mkdtemp(Bt.join(Eo.tmpdir(),"bostrat-mock-profile-")),o=Date.now(),s="";try{try{s=(await Lp(Tv,["--headless","--no-remote","--profile",r,`--window-size=${n}`,"--screenshot",e,`file://${t}`],{env:{...process.env,MOZ_HEADLESS:"1"},timeout:kr,killSignal:"SIGKILL",maxBuffer:4194304})).stderr||""}catch(c){throw s=c?.stderr||"",new te(c?.killed?`render timed out after ${kr}ms`:"firefox exited non-zero",s)}let a=await kt.stat(e).catch(()=>null);if(!a||a.size<Hp)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=Rc(await $v(e));if(!i)throw new te("output is not a PNG",s);return{width:i.width,height:i.height,dpr:1,renderMs:Date.now()-o}}finally{await kt.rm(r,{recursive:!0,force:!0}).catch(()=>{})}}async function Pv(t){if(Sv==="firefox")return jp(t);try{return await Nv(t)}catch(e){if(/failed to launch/i.test(e?.message||""))return jp(t);throw e}}var Bv=Pv;var Ic=Promise.resolve();function qp(t){return Ic=Ic.then(t,t),Ic}async function Kp(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 Iv();if(o<_v)throw new te(`box is low on memory (${Math.round(o/1e6)} MB available) \u2014 not spawning a browser`);let s=Wt(r,e),a=await Bv({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}Gp(r,{v:e,source:"html",htmlRel:Bt.relative(r.cwd,s.html),pngRel:Bt.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),Nn(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 Gp(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 Dv({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?Av:Number(r);if(!Number.isInteger(o)||o<320||o>4e3)throw new Error("invalid width (320-4000)");return o}async function Pn({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 Vp({trackId:t,name:e,html:n,image:r,cwd:o,width:s,group:a,box:i,agent:c,prompt:l}){let u=Dv({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&&!Wp(r))throw new Error("image must be a PNG");let d=await fe(),p={id:yv().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(fa(o),{recursive:!0}),d.mocks.push(p),f?(await kt.writeFile(Wt(p,1).html,n),await de(),await ee(p,"created"),en(p),Nn(p,1,"html"),qp(()=>Kp(p.id,1))):(await Jp(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),Nn(p,1,"png")),{...p}}async function zp(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&&!Wp(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(fa(s.cwd),{recursive:!0}),a?(s.state="queued",await kt.writeFile(Wt(s,i).html,e),await de(),await ee(s,"updated",{version:i}),en(s),Nn(s,i,"html"),qp(()=>Kp(s.id,i))):(await Jp(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),Nn(s,i,"png")),{...s}}async function Jp(t,e,n){let r=Rc(n);if(!r)throw new Error("image must be a PNG");let o=Wt(t,e);await kt.writeFile(o.png,n),Gp(t,{v:e,source:"image",htmlRel:null,pngRel:Bt.relative(t.cwd,o.png),width:r.width,height:r.height,dpr:1,renderMs:null,createdAt:Date.now()}),t.width=r.width}async function Xp(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 Yp(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=Wt(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 Zp(t){let n=(await fe()).mocks.filter(i=>i.trackId===t.id);if(!n.length)return 0;let r=Bt.join(Fp,"mock-archive",t.id),o=fa(t.cwd),s=Bt.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(Bt.join(o,i),Bt.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(Bt.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 Qp(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 tm(){if(!pt())return{pushed:0,source:"unconfigured"};let{records:t,source:e}=await At("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 Nn(r,o.v,"html"),o.pngRel&&await Nn(r,o.v,"png")}return{pushed:n.mocks.length,source:"plane"}}import Io from"node:fs/promises";import Dt from"node:path";import jv from"node:os";import Lv from"node:net";import{randomUUID as Fv}from"node:crypto";import{execFile as Uv}from"node:child_process";import{promisify as Hv}from"node:util";var me=Hv(Uv),Wv=process.env.BOSTRAT_STATE_DIR||Dt.join(process.env.HOME||jv.tmpdir(),".bostrat"),sm=Dt.join(Wv,"views.json"),[em,nm]=(process.env.BOSTRAT_VIEW_PORTS||"4300-4349").split("-").map(t=>Number(t)||0),rn=Number(process.env.BOSTRAT_VIEW_MAX)||3,qv="bostrat-view-",xr=t=>`${qv}${t}`,rm=new Set(["bostrat","www","api","views","app"]),pa=t=>Object.assign(new Error(t),{status:400}),Kv=new Set(["npm","pnpm","yarn","bun"]),Cc="package.json",Gv=/[|;&`]|\$\(/,Vv=new Set(["sh","bash","zsh","dash","env","exec","nohup","time"]),zv=/(^|\s)(-C|--cwd|--manifest-path|--dir)(=|\s)/,am=t=>String(t).trim().split(/\s+/).filter(Boolean),Jv=/^[A-Za-z_][A-Za-z0-9_]*=/;function Xv(t){for(let e of am(t))if(!Jv.test(e))return e;return null}function im(t){let e=am(t),n=e.indexOf("--");return n===-1?e:e.slice(0,n)}function Yv(t,e){let n=im(e);for(let r=0;r<n.length;r++){let o=/^--prefix=(.+)$/.exec(n[r]);if(o)return Dt.resolve(t,o[1]);if(n[r]==="--prefix"&&n[r+1])return Dt.resolve(t,n[r+1])}return t}var Zv=t=>Io.stat(t).then(e=>e.isDirectory(),()=>!1),cm=t=>Io.stat(t).then(e=>e.isFile(),()=>!1),ma=t=>Io.realpath(t).catch(()=>Dt.resolve(t));async function Qv(t,e){if(Dt.resolve(t)===Dt.resolve(e))return!0;let[n,r]=await Promise.all([ma(t),ma(e)]);return n===r}async function tT(t){let n=(await Io.readdir(t,{withFileTypes:!0}).catch(()=>[])).filter(o=>o.isDirectory()&&!o.name.startsWith(".")&&o.name!=="node_modules");return(await Promise.all(n.map(async o=>await cm(Dt.join(t,o.name,Cc))?`./${o.name}`:null))).filter(Boolean)}async function eT(t,e){let[n,r]=await Promise.all([ma(t),ma(e)]),o=Dt.relative(n,r);if(o&&(o.startsWith("..")||Dt.isAbsolute(o)))throw pa(`serve directory escapes the Track workspace: ${e}`)}async function nT(t,e){if(!await Zv(t))throw pa(`serve directory does not exist: ${t}`);let n=Xv(e);if(!n)return;if(!n.includes("/")&&!await cc(n,{loginShell:!0,failOpen:!0}))throw pa(`\`${n}\` is not on the box's PATH \u2014 install it in the box first`);if(Gv.test(e)||Vv.has(n)||!Kv.has(n)||!im(e).includes("run")||zv.test(e))return;let r=Yv(t,e);if(await cm(Dt.join(r,Cc)))return;let o=await tT(r),s=o.length?` \u2014 found one in ${o.join(", ")}; run bostrat-view from there`:"";throw pa(`no ${Cc} in ${r}${s}`)}var pe=null,$c=null;async function Oe(){return pe||($c||=(async()=>{try{pe=JSON.parse(await Io.readFile(sm,"utf8"))}catch{pe={views:[],adoptWatermarks:{}}}finally{$c=null}return pe.views||=[],pe.adoptWatermarks||={},pe})(),$c)}async function he(){await Nt(sm,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 _o=new Map,Pc=Number(process.env.BOSTRAT_VIEW_JOB_TIMEOUT)||3e5,lm=12e3;function Bc(t){return _o.get(t)||null}function um(t,{name:e,box:n}={}){let r=_o.get(t);if(r&&!r.finishedAt)throw new Error("a view is already being spun up for this Track");let o={trackId:t,name:e||"preview",box:n||null,step:"spinning-up",startedAt:Date.now(),finishedAt:null,viewId:null,error:null};return _o.set(t,o),o}function Mc(t,e){let n=_o.get(t);n&&!n.finishedAt&&!n.viewId&&(n.viewId=e,n.step="booting")}function ha(t,{error:e=null}={}){let n=_o.get(t);return!n||n.finishedAt?null:(n.finishedAt=Date.now(),e&&(n.error=e),n)}function dm(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 Nc(t){return String(t||"").trim().toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,40).replace(/-+$/g,"")||"view"}function rT(t,e){let n=Nc(e),r=new Set(t.views.map(o=>o.slug));if(!r.has(n)&&!rm.has(n))return n;for(let o=2;;o++){let s=`${n}-${o}`;if(!r.has(s)&&!rm.has(s))return s}}function Ro(t,{timeoutMs:e=1200}={}){return new Promise(n=>{let r=Lv.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 Dc(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 fm(t=process.env){return new Set([Number(t.PORT)||3362,Number(t.BOSTRAT_TLS_PORT)||3443])}async function oT(t,e){let n=new Set(t.views.map(o=>o.port)),r=fm();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=em;o<=nm;o++)if(!(n.has(o)||r.has(o))&&!await Ro(o,{timeoutMs:250}))return o;throw new Error(`no free view port in ${em}-${nm}`)}async function sT(t){try{let{stdout:e}=await me(P,B("list-sessions","-F","#{session_name}"));return e.split(`
|
|
77
|
+
`).some(n=>n.trim()===xr(t))}catch{return!1}}async function $o(t){try{await me(P,B("kill-session","-t",xr(t)))}catch{}}async function jc(t){if(!await bo())throw new Error("host tmux server not running and auto-start failed \u2014 cannot spawn a view server");let e=xr(t.slug),n=t.serveCwd??t.cwd,r=Dt.dirname(process.execPath);await me(P,B("new-session","-d","-s",e,"-c",n)),await me(P,B("send-keys","-t",e,"-l",`export PORT=${t.port} PATH="${r}:$PATH"`)),await me(P,B("send-keys","-t",e,"Enter")),await me(P,B("send-keys","-t",e,"-l",t.cmd)),await me(P,B("send-keys","-t",e,"Enter"))}async function om(t,e=12){try{let{stdout:n}=await me(P,B("capture-pane","-t",xr(t),"-p"));return String(n||"").trimEnd().split(`
|
|
78
78
|
`).filter(Boolean).slice(-e).join(`
|
|
79
|
-
`)}catch{return""}}var Re=new Map,rT=12e4;async function oT(t){if(t.state==="stopped"||t.state==="error")return!1;let e=t.origin!=="detected",n=e?await nT(t.slug):!0;if(e&&!n){let s=t.state==="starting";return t.state=s?"error":"stopped",s&&(t.error=await em(t.slug)||"server exited before listening"),Re.delete(t.id),await $e(t,t.state,t.error?{error:t.error}:{}),!0}if(await _o(t.port))return Re.delete(t.id),t.lastHealthyAt=Date.now(),t.state!=="live"?(t.state="live",t.error=null,await $e(t,"live"),!0):!1;if(t.state==="starting")return Date.now()-t.createdAt>rT?(t.state="error",t.error=await em(t.slug)||"never started listening",await $e(t,"error",{error:t.error}),!0):!1;let o=(Re.get(t.id)||0)+1;return Re.set(t.id,o),o>=3&&t.state==="live"?(t.state="unhealthy",!0):!1}var Ic=!1;async function fa(){if(!Ic){Ic=!0;try{let t=await Oe(),e=!1;for(let n of t.views)await oT(n)&&(e=!0);e&&await he()}finally{Ic=!1}}}async function pa({trackId:t}={}){await fa();let e=await Oe();return(t?e.views.filter(r=>r.trackId===t):e.views).map(r=>({...r}))}async function um(t){let n=(await Oe()).views.find(r=>r.id===t);return n?{...n}:null}function Bc(t){return pe&&pe.views.find(e=>e.slug===t)||null}async function Dc({trackId:t,name:e,cmd:n,cwd:r,serveCwd:o,port:s,box:a,origin:i="agent",branch:c=null}){if(!t)throw new Error("trackId required");if(typeof e!="string"||!e.trim())throw new Error("name (string) required");if(i!=="detected"&&(typeof n!="string"||!n.trim()))throw new Error("cmd (string) required");if(!r)throw new Error("cwd required");let l=o!=null,u=Bt.resolve(r,o??"."),f=i==="detected"?null:async()=>{await Zv(r,u),await Qv(u,n)},d=await Oe();if(i!=="detected"){let g=Oc(e),y=String(n).trim(),b=d.views.find(S=>S.trackId===t&&(S.slug===g||Oc(S.name)===g||S.cmd===y&&Bt.resolve(S.serveCwd??S.cwd)===u));if(b)return sT(d,b,{cmd:y,box:a,branch:c,serveCwd:l?u:void 0,validate:f})}if(f&&await f(),d.views.filter(g=>g.state==="live"||g.state==="starting").length>=rn)throw new Error(`view cap reached (${rn} live) \u2014 stop one first`);let m={id:Dv().slice(0,8),trackId:t,box:a||null,name:e.trim(),slug:tT(d,e),cmd:n?String(n).trim():null,cwd:r,serveCwd:u,branch:c,port:await eT(d,s),origin:i,state:"starting",error:null,createdAt:Date.now(),lastHealthyAt:null};return i!=="detected"&&await Pc(m),d.views.push(m),await he(),await $e(m,"created",{origin:i}),$c(t,m.id),i==="detected"&&await _o(m.port)&&(m.state="live",m.lastHealthyAt=Date.now(),await he(),await $e(m,"live")),{...m}}async function sT(t,e,{cmd:n,box:r,branch:o,serveCwd:s,validate:a}){r&&(e.box=r),o&&(e.branch=o);let i=e.cmd!==n,c=s!==void 0&&!await Xv(e.serveCwd??e.cwd,s);if((e.state==="live"||e.state==="starting")&&!i&&!c&&await _o(e.port))return await he(),$c(e.trackId,e.id),{...e,reused:!0};if(a&&await a(),t.views.filter(f=>f.id!==e.id&&(f.state==="live"||f.state==="starting")).length>=rn)throw new Error(`view cap reached (${rn} live) \u2014 stop one first`);return e.origin==="detected"?(await Nc(e.port),e.origin="agent"):await Io(e.slug),e.cmd=n,s!==void 0&&(e.serveCwd=s),e.state="starting",e.error=null,e.createdAt=Date.now(),Re.delete(e.id),await Pc(e),await he(),await $e(e,"created",{restarted:!0,reused:!0}),$c(e.trackId,e.id),{...e,reused:!0}}async function dm(t){let n=(await Oe()).views.find(r=>r.id===t);if(!n)throw new Error("view not found");return n.origin==="detected"?await Nc(n.port):await Io(n.slug),n.state!=="stopped"&&(n.state="stopped",Re.delete(n.id),await he(),await $e(n,"stopped")),{...n}}async function fm(t){let e=await Oe(),n=e.views.find(o=>o.id===t);if(!n)throw new Error("view not found");if(!n.cmd)throw new Error("a detected view has no known command \u2014 recreate it as a managed view");if(e.views.filter(o=>o.id!==t&&(o.state==="live"||o.state==="starting")).length>=rn)throw new Error(`view cap reached (${rn} live) \u2014 stop one first`);return await Io(n.slug),n.state="starting",n.error=null,n.createdAt=Date.now(),Re.delete(n.id),await Pc(n),await he(),await $e(n,"created",{restarted:!0}),{...n}}async function pm(t){let e=await Oe(),n=e.views.findIndex(o=>o.id===t);if(n===-1)throw new Error("view not found");let r=e.views[n];return r.origin==="detected"?await Nc(r.port):await Io(r.slug),e.views.splice(n,1),Re.delete(r.id),await he(),await $e(r,"deleted"),{ok:!0,id:t,slug:r.slug}}async function mm(t){let e=await Oe(),n=e.views.filter(r=>r.trackId===t);for(let r of n)r.origin!=="detected"&&await Io(r.slug),Re.delete(r.id);return n.length&&(e.views=e.views.filter(r=>r.trackId!==t),await he()),n.length}var aT=/https?:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0)(?::(\d{2,5}))?(?:\/|\b)/g;async function hm(t,e){if(!t?.id||!Array.isArray(e)||!e.length)return[];let n=await Oe(),r=n.adoptWatermarks[t.id]??null,o=e.filter(c=>c.role==="assistant"&&c.ts!=null&&(r==null?!1:c.ts>r)),s=e.reduce((c,l)=>l.ts>c?l.ts:c,r??0);if(n.adoptWatermarks[t.id]!==s&&(n.adoptWatermarks[t.id]=s,await he()),!o.length)return[];let a=new Set;for(let c of o)for(let l of String(c.text||"").matchAll(aT)){let u=Number(l[1]||80);u>=1024&&u<=65535&&a.add(u)}if(!a.size)return[];let i=[];for(let c of a)if(!lm().has(c)&&!n.views.some(l=>l.port===c)&&await _o(c))try{i.push(await Dc({trackId:t.id,name:`${t.name} preview`,cmd:null,cwd:t.cwd,port:c,origin:"detected",branch:t.workspace?.branch??null}))}catch{}return i}import Ne from"node:fs/promises";import Pe from"node:path";import iT from"node:os";import{randomUUID as cT}from"node:crypto";var Pn=process.env.BOSTRAT_STATE_DIR||Pe.join(process.env.HOME||iT.tmpdir(),".bostrat"),gm=Pe.join(Pn,"runbooks.json"),ma=Pe.join(Pn,"runbooks"),wm=256*1024,lT=[{label:"Anthropic API key",re:/\bsk-ant-[A-Za-z0-9_-]{20,}/},{label:"OpenAI-style API key",re:/\bsk-(?:proj-|svcacct-)?[A-Za-z0-9_-]{32,}\b/},{label:"Stripe secret key",re:/\bsk_(?:live|test)_[A-Za-z0-9]{16,}/},{label:"GitHub token",re:/\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{36,}|\bgithub_pat_[A-Za-z0-9_]{22,}/},{label:"AWS access key id",re:/\bAKIA[0-9A-Z]{16}\b/},{label:"Slack token",re:/\bxox[baprs]-[A-Za-z0-9-]{10,}/},{label:"private key block",re:/-----BEGIN [A-Z ]*PRIVATE KEY-----/},{label:"JWT",re:/\beyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/}];function Ro(t){return lT.filter(({re:e})=>e.test(t)).map(({label:e})=>e)}var Ce=null,jc=null;async function Be(){return Ce||(jc||=(async()=>{let t=!0;try{Ce=JSON.parse(await Ne.readFile(gm,"utf8"))}catch{Ce={runbooks:[]},t=!1}finally{jc=null}return Ce.runbooks||=[],!t&&pt()&&await uT().catch(()=>{}),Ce})(),jc)}async function Bn(){await Mt(gm,Ce)}var $o=t=>Promise.resolve(t).catch(()=>{});function ha(t){return $o(Rt("runbooks",{...t},{trackId:t.trackId}))}async function uT(){let{records:t,source:e}=await Tt("runbooks");if(!(e!=="plane"||!t.length)){await Ne.mkdir(ma,{recursive:!0});for(let n of t){if(n.deletedAt||!n.data?.id)continue;let r=n.data;for(let o of r.versions||[]){let s=await js("runbooks",r.id,o.v,"md");typeof s=="string"&&await Ne.writeFile(Me(r,o.v),s).catch(()=>{})}Ce.runbooks.push(r)}Ce.runbooks.length&&await Bn()}}function kr(t,e,n={}){return R({id:t.trackId,cwd:t.cwd},{type:"runbook.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box||null,provider:t.agent||null,payload:{ts:Date.now(),change:e,runbookId:t.id,name:t.name,status:t.status,...n}}).catch(()=>{})}function Me(t,e){return Pe.join(ma,`${t.id}.v${e}.md`)}function ym(t,e=null){let n=t.versions||[];return e!=null?n.find(r=>r.v===Number(e))||null:n.length?n[n.length-1]:null}function bm(t){if(typeof t!="string"||!t.trim())throw new Error("md (markdown string) required");let e=Buffer.byteLength(t,"utf8");if(e>wm)throw new Error(`runbook is ${Math.round(e/1024)} KB \u2014 over the ${wm/1024} KB cap; a doc that size belongs in the repo, not a runbook`);let n=Ro(t);if(n.length)throw new Error(`runbook appears to contain a secret VALUE (${n.join(", ")}) \u2014 runbooks ship off-box; name the secret and where it goes, never its value. Replace it with a placeholder like <your key> and retry.`);return e}function km(t,e){if(t.trackDeletedAt)throw new Error(`runbook is orphaned (its Track was deleted) \u2014 cannot ${e} it`)}async function wa({trackId:t,status:e}={}){let n=await Be(),r=t?n.runbooks.filter(o=>o.trackId===t):n.runbooks;return e&&(r=r.filter(o=>o.status===e)),r.map(o=>({...o}))}async function Lc(t){let n=(await Be()).runbooks.find(r=>r.id===t);return n?{...n}:null}async function xm({trackId:t,trackName:e,name:n,md:r,cwd:o,box:s,agent:a}){if(!t)throw new Error("trackId required");if(typeof n!="string"||!n.trim())throw new Error("name (string) required");let i=bm(r),c=await Be(),l={id:cT().slice(0,8),trackId:t,trackName:e||t,box:s||null,agent:a||null,name:n.trim(),status:"open",version:1,versions:[],trackDeletedAt:null,cwd:o||null,createdAt:Date.now(),updatedAt:Date.now()};return await Ne.mkdir(ma,{recursive:!0}),await Ne.writeFile(Me(l,1),r),l.versions.push({v:1,mdRel:Pe.relative(Pn,Me(l,1)),bytes:i,note:null,createdAt:Date.now()}),c.runbooks.push(l),await Bn(),await kr(l,"created",{version:1}),ha(l),$o(Je("runbooks",l.id,1,"md",Me(l,1))),{...l}}async function Sm(t,{md:e,note:n}={}){let o=(await Be()).runbooks.find(i=>i.id===t);if(!o)throw new Error("runbook not found");km(o,"update");let s=bm(e),a=o.version+1;return o.version=a,o.updatedAt=Date.now(),await Ne.mkdir(ma,{recursive:!0}),await Ne.writeFile(Me(o,a),e),o.versions.push({v:a,mdRel:Pe.relative(Pn,Me(o,a)),bytes:s,note:n?String(n).slice(0,200):null,createdAt:Date.now()}),await Bn(),await kr(o,"updated",{version:a,...n?{note:String(n).slice(0,200)}:{}}),ha(o),$o(Je("runbooks",o.id,a,"md",Me(o,a))),{...o}}async function vm(t,e){if(e!=="open"&&e!=="done")throw new Error('status must be "open" or "done"');let r=(await Be()).runbooks.find(o=>o.id===t);if(!r)throw new Error("runbook not found");return km(r,e==="done"?"complete":"reopen"),r.status===e?{...r}:(r.status=e,r.updatedAt=Date.now(),await Bn(),await kr(r,e==="done"?"done":"reopened"),ha(r),{...r})}async function Tm(t){let e=await Be(),n=e.runbooks.findIndex(o=>o.id===t);if(n===-1)throw new Error("runbook not found");let r=e.runbooks[n];e.runbooks.splice(n,1);for(let o of r.versions||[])await Ne.rm(Pe.join(Pn,o.mdRel),{force:!0}).catch(()=>{});return await Bn(),r.trackDeletedAt||await kr(r,"deleted"),$o(Qt("runbooks",r.id)),{ok:!0,id:t,name:r.name}}async function Am(t){let n=(await Be()).runbooks.filter(r=>r.trackId===t&&!r.trackDeletedAt);for(let r of n)r.trackDeletedAt=Date.now(),r.updatedAt=Date.now(),await kr(r,"orphaned"),r.cwd=null,ha(r);return n.length&&await Bn(),n.length}async function Em(t){let e=await Be(),n=e.runbooks.filter(r=>r.trackId===t);for(let r of n){await kr(r,"deleted");for(let o of r.versions||[])await Ne.rm(Pe.join(Pn,o.mdRel),{force:!0}).catch(()=>{});$o(Qt("runbooks",r.id))}return n.length&&(e.runbooks=e.runbooks.filter(r=>r.trackId!==t),await Bn()),n.length}async function _m(){if(!pt())return{pushed:0,source:"unconfigured"};let{records:t,source:e}=await Tt("runbooks");if(e!=="plane"||t.length)return{pushed:0,source:e};let n=await Be();for(let r of n.runbooks){await Rt("runbooks",{...r},{trackId:r.trackId}).catch(()=>{});for(let o of r.versions||[])await Je("runbooks",r.id,o.v,"md",Pe.join(Pn,o.mdRel)).catch(()=>{})}return{pushed:n.runbooks.length,source:"plane"}}import Mo from"node:fs/promises";import Um from"node:path";import FT from"node:os";import{randomUUID as Hm}from"node:crypto";import dT from"node:fs/promises";import Im from"node:path";import fT from"node:os";var Rm=()=>Im.join(process.env.BOSTRAT_STATE_DIR||Im.join(process.env.HOME||fT.tmpdir(),".bostrat"),"integrations.json");async function De(){try{return JSON.parse(await dT.readFile(Rm(),"utf8"))||{}}catch{return{}}}var ga=["linked","prOpened","prMerged","create"],Fc=["prompt","auto","off"],pT={create:["prompt","off"]},$m=t=>pT[t]??Fc;function ya(t){let e=t.linear||{},n=e.transitions===!0?"auto":"prompt",r={};for(let o of ga){let s=$m(o),a=s.includes(n)?n:"prompt",i=t.ticketFlow?.[o]??e.ticketFlow?.[o];r[o]=s.includes(i)?i:a}return r}async function Dn(){let t=await De(),e=process.env.BOSTRAT_LINEAR_API_KEY||t.linear?.apiKey,n=process.env.BOSTRAT_LINEAR_DEFAULT_TEAM||t.linear?.defaultTeam||null;return e?{apiKey:e,ticketFlow:ya(t),defaultTeam:n}:null}async function ba(){let t=await De(),e=t.jira||{},n=process.env.BOSTRAT_JIRA_BASE_URL||e.baseUrl,r=process.env.BOSTRAT_JIRA_EMAIL||e.email,o=process.env.BOSTRAT_JIRA_API_TOKEN||e.apiToken;return!n||!r||!o?null:{baseUrl:String(n).replace(/\/$/,""),email:r,apiToken:o,tokenExpiresAt:e.tokenExpiresAt??null,ticketFlow:ya(t)}}async function Uc(){return ya(await De())}async function Om(t){let e=Object.entries(t||{});if(!e.length)throw new Error(`nothing to set \u2014 phases: ${ga.join(", ")}`);for(let[r,o]of e){if(!ga.includes(r))throw new Error(`unknown phase "${r}" \u2014 configurable phases: ${ga.join(", ")}`);let s=$m(r);if(!s.includes(o))throw new Error(s.length<Fc.length?`mode "${o}" not allowed for "${r}" \u2014 ${r} is always confirmed (modes: ${s.join(", ")})`:`unknown mode "${o}" \u2014 modes: ${Fc.join(", ")}`)}let n=await De();return n.ticketFlow={...ya(n),...Object.fromEntries(e)},n.linear&&(delete n.linear.ticketFlow,delete n.linear.transitions),await Mt(Rm(),n,{mode:384}),n.ticketFlow}async function Oo(){let t=(await De()).deepgram||{},e=process.env.DEEPGRAM_API_KEY||t.apiKey;return e?{apiKey:e}:null}async function mT(){let t=(await De()).openai||{},e=process.env.BOSTRAT_OPENAI_API_KEY||t.apiKey;return e?{apiKey:e}:null}var hT={openai:{model:"gpt-realtime-2.1-mini",voice:"marin"}};async function ka(){let e=(await De()).voiceChat||{},n=e.provider||"openai",r=hT[n];if(!r)return null;let o=n==="openai"?await mT():null;return o?{provider:n,model:e.model||r.model,voice:e.voice||r.voice,apiKey:o.apiKey}:null}async function Cm(){let t=(await De()).cedar||{},e=process.env.BOSTRAT_CEDAR_INGEST_KEY||t.ingestKey,n=process.env.BOSTRAT_CEDAR_URL||t.url||"https://cedar-api.fly.dev";return e?{url:n,ingestKey:e}:null}async function jn(){let t=(await De()).slack||{},e=process.env.BOSTRAT_SLACK_BOT_TOKEN||t.botToken,n=process.env.BOSTRAT_SLACK_APP_TOKEN||t.appToken;return!e||!n?null:{botToken:e,appToken:n,channel:process.env.BOSTRAT_SLACK_CHANNEL||t.channel||null,maxConcurrent:Number.isInteger(t.maxConcurrent)&&t.maxConcurrent>0?t.maxConcurrent:2}}var Wc={};Od(Wc,{createComment:()=>TT,findIssueUrl:()=>bT,issueStates:()=>_T,mdToAdf:()=>Pm,moveIssue:()=>IT,parseIssueRef:()=>yT,resolveIssue:()=>kT,uploadImage:()=>AT,upsertCard:()=>xT,whoami:()=>Hc});var xr=t=>String(t.baseUrl||"").replace(/\/$/,""),Nm=t=>`Basic ${Buffer.from(`${t.email}:${t.apiToken}`).toString("base64")}`;async function Ln(t,e,n,r){let o=await fetch(`${xr(t)}${n}`,{method:e,headers:{authorization:Nm(t),accept:"application/json",...r!==void 0?{"content-type":"application/json"}:{}},body:r!==void 0?JSON.stringify(r):void 0}),s=await o.text(),a=null;try{a=s?JSON.parse(s):null}catch{a=null}if(!o.ok){let i=a?.errorMessages?.[0]??Object.values(a?.errors??{})[0]??`HTTP ${o.status}`,c=new Error(`jira: ${i}`);throw c.status=o.status,c}return a}var wT=/\/browse\/([A-Za-z][A-Za-z0-9]{0,9}-\d+)/,gT=/^([A-Z][A-Z0-9]{0,9}-\d+)$/;function yT(t){let e=String(t||"").trim();if(/^https?:\/\//i.test(e)){let r=e.match(wT);return r?r[1].toUpperCase():null}let n=e.match(gT);return n?n[1]:null}function bT(t,e=null){let n=null;try{n=e?.baseUrl?new URL(e.baseUrl).host:null}catch{n=null}let r=/https?:\/\/[^\s)]+\/browse\/[A-Za-z][A-Za-z0-9]{0,9}-\d+[^\s)]*/g;for(let o of String(t||"").matchAll(r))try{let s=new URL(o[0]).host;if(s===n||/\.atlassian\.net$/i.test(s))return o[0]}catch{}return null}async function kT(t,e){let n;try{n=await Ln(t,"GET",`/rest/api/3/issue/${encodeURIComponent(e)}?fields=summary,status`)}catch(r){throw r.status===404?new Error(`jira: issue "${e}" not found`):r}return{issueId:n.id,key:n.key,title:n.fields?.summary??null,url:`${xr(t)}/browse/${n.key}`,state:n.fields?.status?.name??null}}async function xT(t,e,{url:n,title:r,subtitle:o}){await Ln(t,"POST",`/rest/api/3/issue/${encodeURIComponent(e.key)}/remotelink`,{globalId:n,application:{type:"com.bostrat.tracks",name:"Bostrat"},object:{url:n,title:r,summary:o,icon:{title:"Bostrat"}}})}var ST=/(!\[[^\]]*\]\([^)\s]+\)|\*\*[^*\n]+\*\*|`[^`\n]+`|_[^_\n]+_|https?:\/\/[^\s)]+)/g;function vT(t){let e=[],n=0;for(let r of t.matchAll(ST)){r.index>n&&e.push({type:"text",text:t.slice(n,r.index)});let o=r[0];if(o.startsWith("![")){let[,s,a]=o.match(/^!\[([^\]]*)\]\(([^)\s]+)\)$/);e.push({type:"text",text:s||a,marks:[{type:"link",attrs:{href:a}}]})}else o.startsWith("**")?e.push({type:"text",text:o.slice(2,-2),marks:[{type:"strong"}]}):o.startsWith("`")?e.push({type:"text",text:o.slice(1,-1),marks:[{type:"code"}]}):o.startsWith("_")?e.push({type:"text",text:o.slice(1,-1),marks:[{type:"em"}]}):e.push({type:"text",text:o,marks:[{type:"link",attrs:{href:o}}]});n=r.index+o.length}return n<t.length&&e.push({type:"text",text:t.slice(n)}),e}function Pm(t){let e=String(t??"").split(/\n{2,}/).map(n=>n.trim()).filter(Boolean).map(n=>{let r=[];return n.split(`
|
|
80
|
-
`).forEach((o,s)=>{s>0&&r.push({type:"hardBreak"}),r.push(...
|
|
79
|
+
`)}catch{return""}}var Re=new Map,aT=12e4;async function iT(t){if(t.state==="stopped"||t.state==="error")return!1;let e=t.origin!=="detected",n=e?await sT(t.slug):!0;if(e&&!n){let s=t.state==="starting";return t.state=s?"error":"stopped",s&&(t.error=await om(t.slug)||"server exited before listening"),Re.delete(t.id),await $e(t,t.state,t.error?{error:t.error}:{}),!0}if(await Ro(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>aT?(t.state="error",t.error=await om(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 Oc=!1;async function wa(){if(!Oc){Oc=!0;try{let t=await Oe(),e=!1;for(let n of t.views)await iT(n)&&(e=!0);e&&await he()}finally{Oc=!1}}}async function ga({trackId:t}={}){await wa();let e=await Oe();return(t?e.views.filter(r=>r.trackId===t):e.views).map(r=>({...r}))}async function pm(t){let n=(await Oe()).views.find(r=>r.id===t);return n?{...n}:null}function Lc(t){return pe&&pe.views.find(e=>e.slug===t)||null}async function Fc({trackId:t,name:e,cmd:n,cwd:r,serveCwd:o,port:s,box:a,origin:i="agent",branch:c=null}){if(!t)throw new Error("trackId required");if(typeof e!="string"||!e.trim())throw new Error("name (string) required");if(i!=="detected"&&(typeof n!="string"||!n.trim()))throw new Error("cmd (string) required");if(!r)throw new Error("cwd required");let l=o!=null,u=Dt.resolve(r,o??"."),f=i==="detected"?null:async()=>{await eT(r,u),await nT(u,n)},d=await Oe();if(i!=="detected"){let h=Nc(e),y=String(n).trim(),b=d.views.find(S=>S.trackId===t&&(S.slug===h||Nc(S.name)===h||S.cmd===y&&Dt.resolve(S.serveCwd??S.cwd)===u));if(b)return cT(d,b,{cmd:y,box:a,branch:c,serveCwd:l?u:void 0,validate:f})}if(f&&await f(),d.views.filter(h=>h.state==="live"||h.state==="starting").length>=rn)throw new Error(`view cap reached (${rn} live) \u2014 stop one first`);let m={id:Fv().slice(0,8),trackId:t,box:a||null,name:e.trim(),slug:rT(d,e),cmd:n?String(n).trim():null,cwd:r,serveCwd:u,branch:c,port:await oT(d,s),origin:i,state:"starting",error:null,createdAt:Date.now(),lastHealthyAt:null};return i!=="detected"&&await jc(m),d.views.push(m),await he(),await $e(m,"created",{origin:i}),Mc(t,m.id),i==="detected"&&await Ro(m.port)&&(m.state="live",m.lastHealthyAt=Date.now(),await he(),await $e(m,"live")),{...m}}async function cT(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 Qv(e.serveCwd??e.cwd,s);if((e.state==="live"||e.state==="starting")&&!i&&!c&&await Ro(e.port))return await he(),Mc(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 Dc(e.port),e.origin="agent"):await $o(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 jc(e),await he(),await $e(e,"created",{restarted:!0,reused:!0}),Mc(e.trackId,e.id),{...e,reused:!0}}async function mm(t){let n=(await Oe()).views.find(r=>r.id===t);if(!n)throw new Error("view not found");return n.origin==="detected"?await Dc(n.port):await $o(n.slug),n.state!=="stopped"&&(n.state="stopped",Re.delete(n.id),await he(),await $e(n,"stopped")),{...n}}async function hm(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 $o(n.slug),n.state="starting",n.error=null,n.createdAt=Date.now(),Re.delete(n.id),await jc(n),await he(),await $e(n,"created",{restarted:!0}),{...n}}async function wm(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 Dc(r.port):await $o(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 gm(t){let e=await Oe(),n=e.views.filter(r=>r.trackId===t);for(let r of n)r.origin!=="detected"&&await $o(r.slug),Re.delete(r.id);return n.length&&(e.views=e.views.filter(r=>r.trackId!==t),await he()),n.length}var lT=/https?:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0)(?::(\d{2,5}))?(?:\/|\b)/g;async function ym(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(lT)){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(!fm().has(c)&&!n.views.some(l=>l.port===c)&&await Ro(c))try{i.push(await Fc({trackId:t.id,name:`${t.name} preview`,cmd:null,cwd:t.cwd,port:c,origin:"detected",branch:t.workspace?.branch??null}))}catch{}return i}import Ne from"node:fs/promises";import Pe from"node:path";import uT from"node:os";import{randomUUID as dT}from"node:crypto";var Bn=process.env.BOSTRAT_STATE_DIR||Pe.join(process.env.HOME||uT.tmpdir(),".bostrat"),km=Pe.join(Bn,"runbooks.json"),ya=Pe.join(Bn,"runbooks"),bm=256*1024,fT=[{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 Oo(t){return fT.filter(({re:e})=>e.test(t)).map(({label:e})=>e)}var Ce=null,Uc=null;async function Be(){return Ce||(Uc||=(async()=>{let t=!0;try{Ce=JSON.parse(await Ne.readFile(km,"utf8"))}catch{Ce={runbooks:[]},t=!1}finally{Uc=null}return Ce.runbooks||=[],!t&&pt()&&await pT().catch(()=>{}),Ce})(),Uc)}async function Dn(){await Nt(km,Ce)}var Co=t=>Promise.resolve(t).catch(()=>{});function ba(t){return Co($t("runbooks",{...t},{trackId:t.trackId}))}async function pT(){let{records:t,source:e}=await At("runbooks");if(!(e!=="plane"||!t.length)){await Ne.mkdir(ya,{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 Hs("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 Dn()}}function Sr(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(ya,`${t.id}.v${e}.md`)}function xm(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 Sm(t){if(typeof t!="string"||!t.trim())throw new Error("md (markdown string) required");let e=Buffer.byteLength(t,"utf8");if(e>bm)throw new Error(`runbook is ${Math.round(e/1024)} KB \u2014 over the ${bm/1024} KB cap; a doc that size belongs in the repo, not a runbook`);let n=Oo(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 vm(t,e){if(t.trackDeletedAt)throw new Error(`runbook is orphaned (its Track was deleted) \u2014 cannot ${e} it`)}async function ka({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 Hc(t){let n=(await Be()).runbooks.find(r=>r.id===t);return n?{...n}:null}async function Tm({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=Sm(r),c=await Be(),l={id:dT().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(ya,{recursive:!0}),await Ne.writeFile(Me(l,1),r),l.versions.push({v:1,mdRel:Pe.relative(Bn,Me(l,1)),bytes:i,note:null,createdAt:Date.now()}),c.runbooks.push(l),await Dn(),await Sr(l,"created",{version:1}),ba(l),Co(Je("runbooks",l.id,1,"md",Me(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");vm(o,"update");let s=Sm(e),a=o.version+1;return o.version=a,o.updatedAt=Date.now(),await Ne.mkdir(ya,{recursive:!0}),await Ne.writeFile(Me(o,a),e),o.versions.push({v:a,mdRel:Pe.relative(Bn,Me(o,a)),bytes:s,note:n?String(n).slice(0,200):null,createdAt:Date.now()}),await Dn(),await Sr(o,"updated",{version:a,...n?{note:String(n).slice(0,200)}:{}}),ba(o),Co(Je("runbooks",o.id,a,"md",Me(o,a))),{...o}}async function Em(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 vm(r,e==="done"?"complete":"reopen"),r.status===e?{...r}:(r.status=e,r.updatedAt=Date.now(),await Dn(),await Sr(r,e==="done"?"done":"reopened"),ba(r),{...r})}async function _m(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(Bn,o.mdRel),{force:!0}).catch(()=>{});return await Dn(),r.trackDeletedAt||await Sr(r,"deleted"),Co(Qt("runbooks",r.id)),{ok:!0,id:t,name:r.name}}async function Im(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 Sr(r,"orphaned"),r.cwd=null,ba(r);return n.length&&await Dn(),n.length}async function Rm(t){let e=await Be(),n=e.runbooks.filter(r=>r.trackId===t);for(let r of n){await Sr(r,"deleted");for(let o of r.versions||[])await Ne.rm(Pe.join(Bn,o.mdRel),{force:!0}).catch(()=>{});Co(Qt("runbooks",r.id))}return n.length&&(e.runbooks=e.runbooks.filter(r=>r.trackId!==t),await Dn()),n.length}async function $m(){if(!pt())return{pushed:0,source:"unconfigured"};let{records:t,source:e}=await At("runbooks");if(e!=="plane"||t.length)return{pushed:0,source:e};let n=await Be();for(let r of n.runbooks){await $t("runbooks",{...r},{trackId:r.trackId}).catch(()=>{});for(let o of r.versions||[])await Je("runbooks",r.id,o.v,"md",Pe.join(Bn,o.mdRel)).catch(()=>{})}return{pushed:n.runbooks.length,source:"plane"}}import Po from"node:fs/promises";import qm from"node:path";import WT from"node:os";import{randomUUID as Km}from"node:crypto";import mT from"node:fs/promises";import Om from"node:path";import hT from"node:os";var Cm=()=>Om.join(process.env.BOSTRAT_STATE_DIR||Om.join(process.env.HOME||hT.tmpdir(),".bostrat"),"integrations.json");async function De(){try{return JSON.parse(await mT.readFile(Cm(),"utf8"))||{}}catch{return{}}}var xa=["linked","prOpened","prMerged","create"],Wc=["prompt","auto","off"],wT={create:["prompt","off"]},Mm=t=>wT[t]??Wc;function Sa(t){let e=t.linear||{},n=e.transitions===!0?"auto":"prompt",r={};for(let o of xa){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 jn(){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:Sa(t),defaultTeam:n}:null}async function va(){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:Sa(t)}}async function qc(){return Sa(await De())}async function Nm(t){let e=Object.entries(t||{});if(!e.length)throw new Error(`nothing to set \u2014 phases: ${xa.join(", ")}`);for(let[r,o]of e){if(!xa.includes(r))throw new Error(`unknown phase "${r}" \u2014 configurable phases: ${xa.join(", ")}`);let s=Mm(r);if(!s.includes(o))throw new Error(s.length<Wc.length?`mode "${o}" not allowed for "${r}" \u2014 ${r} is always confirmed (modes: ${s.join(", ")})`:`unknown mode "${o}" \u2014 modes: ${Wc.join(", ")}`)}let n=await De();return n.ticketFlow={...Sa(n),...Object.fromEntries(e)},n.linear&&(delete n.linear.ticketFlow,delete n.linear.transitions),await Nt(Cm(),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 gT(){let t=(await De()).openai||{},e=process.env.BOSTRAT_OPENAI_API_KEY||t.apiKey;return e?{apiKey:e}:null}var yT={openai:{model:"gpt-realtime-2.1-mini",voice:"marin"}};async function Ta(){let e=(await De()).voiceChat||{},n=e.provider||"openai",r=yT[n];if(!r)return null;let o=n==="openai"?await gT():null;return o?{provider:n,model:e.model||r.model,voice:e.voice||r.voice,apiKey:o.apiKey}:null}async function Pm(){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 Ln(){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 Gc={};Nd(Gc,{createComment:()=>_T,findIssueUrl:()=>ST,issueStates:()=>$T,mdToAdf:()=>jm,moveIssue:()=>OT,parseIssueRef:()=>xT,resolveIssue:()=>vT,uploadImage:()=>IT,upsertCard:()=>TT,whoami:()=>Kc});var vr=t=>String(t.baseUrl||"").replace(/\/$/,""),Dm=t=>`Basic ${Buffer.from(`${t.email}:${t.apiToken}`).toString("base64")}`;async function Fn(t,e,n,r){let o=await fetch(`${vr(t)}${n}`,{method:e,headers:{authorization:Dm(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 bT=/\/browse\/([A-Za-z][A-Za-z0-9]{0,9}-\d+)/,kT=/^([A-Z][A-Z0-9]{0,9}-\d+)$/;function xT(t){let e=String(t||"").trim();if(/^https?:\/\//i.test(e)){let r=e.match(bT);return r?r[1].toUpperCase():null}let n=e.match(kT);return n?n[1]:null}function ST(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 vT(t,e){let n;try{n=await Fn(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:`${vr(t)}/browse/${n.key}`,state:n.fields?.status?.name??null}}async function TT(t,e,{url:n,title:r,subtitle:o}){await Fn(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 AT=/(!\[[^\]]*\]\([^)\s]+\)|\*\*[^*\n]+\*\*|`[^`\n]+`|_[^_\n]+_|https?:\/\/[^\s)]+)/g;function ET(t){let e=[],n=0;for(let r of t.matchAll(AT)){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 jm(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(...ET(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 _T(t,e,n){let r=await Fn(t,"POST",`/rest/api/3/issue/${encodeURIComponent(e.key)}/comment`,{body:jm(n)});return r?.id?`${vr(t)}/browse/${e.key}?focusedCommentId=${r.id}`:null}async function Kc(t){let e=await Fn(t,"GET","/rest/api/3/myself"),n=null;try{n=new URL(vr(t)).host}catch{n=t.baseUrl??null}return{user:e?.displayName??e?.emailAddress??null,org:n}}async function IT(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(`${vr(t)}/rest/api/3/issue/${encodeURIComponent(e)}/attachments`,{method:"POST",headers:{authorization:Dm(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??`${vr(t)}/rest/api/3/attachment/content/${c.id}`}var RT={new:"unstarted",indeterminate:"started",done:"completed"},Bm=t=>RT[t?.key]??"started";async function $T(t,e){let n=encodeURIComponent(e.key),[r,o]=await Promise.all([Fn(t,"GET",`/rest/api/3/issue/${n}?fields=status`),Fn(t,"GET",`/rest/api/3/issue/${n}/transitions`)]),s=r?.fields?.status??null,a=s?{id:`status:${s.id}`,name:s.name,type:Bm(s.statusCategory)}:null,i=(o?.transitions??[]).map((c,l)=>({id:c.id,name:c.to?.name??c.name,type:Bm(c.to?.statusCategory),position:l}));return{current:a,states:i}}async function OT(t,e,n){await Fn(t,"POST",`/rest/api/3/issue/${encodeURIComponent(e.key)}/transitions`,{transition:{id:String(n)}})}var Xc={};Nd(Xc,{createComment:()=>LT,createIssue:()=>Jc,createProject:()=>zc,findIssueUrl:()=>BT,issueStates:()=>UT,moveIssue:()=>HT,parseIssueRef:()=>PT,resolveIssue:()=>DT,teams:()=>Tr,uploadImage:()=>FT,upsertCard:()=>jT,whoami:()=>Vc});var CT=()=>process.env.BOSTRAT_LINEAR_API_URL||"https://api.linear.app/graphql";async function we(t,e,n={}){let r=await fetch(CT(),{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 MT=/linear\.app\/[^/\s]+\/issue\/([A-Za-z][A-Za-z0-9]{0,9}-\d+)/,NT=/^([A-Z][A-Z0-9]{0,9}-\d+)$/;function PT(t){let e=String(t||"").trim(),n=e.match(MT);if(n)return n[1].toUpperCase();let r=e.match(NT);return r?r[1]:null}function BT(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 DT(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 jT(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 LT(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 Vc(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 FT(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 jT(t,e){let n=await we(t,"query($id: String!) { issue(id: $id) { state { id name type } team { states { nodes { id name type position } } } } }",{id:e.key});if(!n.issue)throw new Error(`linear: issue "${e.key}" not found`);return{current:n.issue.state??null,states:n.issue.team?.states?.nodes??[]}}async function LT(t,e,n){if(!(await we(t,"mutation($id: String!, $input: IssueUpdateInput!) { issueUpdate(id: $id, input: $input) { success } }",{id:e.issueId,input:{stateId:n}})).issueUpdate?.success)throw new Error("linear: issueUpdate did not succeed")}async function Sr(t){return((await we(t,"query { teams(first: 100) { nodes { id key name } } }")).teams?.nodes??[]).map(n=>({id:n.id,key:n.key,name:n.name}))}var Bm=255;async function Kc(t,{teamId:e,name:n,description:r}){let o=r||null,s=!!o&&o.length>Bm,a=s?`${o.slice(0,Bm-1).trimEnd()}\u2026`:o,i=await we(t,"mutation($input: ProjectCreateInput!) { projectCreate(input: $input) { success project { id url name } } }",{input:{teamIds:[e],name:n,...a?{description:a}:{},...s?{content:o}:{}}}),c=i.projectCreate?.project;if(!i.projectCreate?.success||!c)throw new Error("linear: projectCreate did not succeed");return{id:c.id,url:c.url,name:c.name}}async function Gc(t,{teamId:e,title:n,description:r,projectId:o,priority:s}){let a=await we(t,"mutation($input: IssueCreateInput!) { issueCreate(input: $input) { success issue { id identifier url title } } }",{input:{teamId:e,title:n,...r?{description:r}:{},...o?{projectId:o}:{},...Number.isInteger(s)?{priority:s}:{}}}),i=a.issueCreate?.issue;if(!a.issueCreate?.success||!i)throw new Error("linear: issueCreate did not succeed");return{id:i.id,key:i.identifier,url:i.url,title:i.title}}var Wm={linear:{api:Vc,config:Dn},jira:{api:Wc,config:ba}};async function tl(){let t=[];for(let[e,{api:n,config:r}]of Object.entries(Wm)){let o=await r();o&&t.push({name:e,api:n,cfg:o})}return t}async function Fn(t){let e=Wm[t?.provider];if(!e)return null;let n=await e.config();return n?{name:t.provider,api:e.api,cfg:n}:null}function el(t,e){if(!t||t.name)return t;let n=e.find(r=>r.id===t.id);return n?{...t,name:n.name}:t}var qm=()=>process.env.BOSTRAT_STATE_DIR||Um.join(process.env.HOME||FT.tmpdir(),".bostrat"),Yc=()=>Um.join(qm(),"tickets.json"),Km=()=>(process.env.BOSTRAT_PUBLIC_URL||"http://bostrat.globopi").replace(/\/$/,""),xa=t=>`${Km()}/track/${t}`,on=null,zc=null,Jc=Promise.resolve();async function Po(){return on||(zc??=Mo.readFile(Yc(),"utf8").then(t=>({tickets:JSON.parse(t)?.tickets??{},existed:!0})).catch(()=>({tickets:{},existed:!1})).then(async({tickets:t,existed:e})=>{if(on=t,!e&&pt()){let{records:n,source:r}=await Tt("tickets").catch(()=>({records:[],source:"error"}));if(r==="plane"){for(let o of n){if(o.deletedAt||!o.data?.id)continue;let{id:s,updatedAt:a,...i}=o.data;on[s]=i}Object.keys(on).length&&await Zc().catch(()=>{})}}return on}).finally(()=>zc=null),zc)}async function Zc(){let t=JSON.stringify({tickets:on},null,2),e=`${Yc()}.${process.pid}.tmp`;await Mo.mkdir(qm(),{recursive:!0}),await Mo.writeFile(e,t),await Mo.rename(e,Yc())}async function sn(t=null){if(Jc=Jc.then(Zc,Zc),await Jc.catch(()=>{}),!t)return;let e=on?.[t];e?Rt("tickets",{id:t,updatedAt:Date.now(),...e},{trackId:t}).catch(()=>{}):Qt("tickets",t).catch(()=>{})}async function ge(t){return(await Po())[t]??null}async function Sa(){return{...await Po()}}async function Gm(){return(await tl()).length>0}async function vr(t,e,{source:n="api"}={}){let r=await tl();if(!r.length)throw new Error("no ticketing integration configured \u2014 add a linear or jira entry to ~/.bostrat/integrations.json");let o=null,s=null,a=null;for(let u of r){let f=u.api.parseIssueRef(e);if(f)try{s=await u.api.resolveIssue(u.cfg,f),o=u;break}catch(d){a=d}}if(!o)throw a||new Error(`"${e}" is not a Linear or Jira issue URL or key (ABC-123)`);let i=(t.boxes||[]).find(u=>u.name===t.leadBox),c={provider:o.name,key:s.key,issueId:s.issueId,url:s.url,title:s.title,state:s.state,source:n,linkedAt:Date.now(),track:{name:t.name,branch:t.workspace?.branch??null,leadAgent:i?.agent??null,cwd:t.cwd??null,...t.workspace?.kind==="multi"?{repos:(t.workspace.repos??[]).map(u=>u?.name).filter(Boolean)}:{}},pr:null};(await Po())[t.id]=c,await sn(t.id),await R({id:t.id,cwd:t.cwd},{type:"ticket.linked",actor:"user",source:"orchestrator",payload:{ts:Date.now(),provider:c.provider,key:c.key,url:c.url,title:c.title,linkSource:n}}).catch(()=>{}),await No(t.id,c);let l=[];return c.track.branch&&l.push(`\`${c.track.branch}\``),c.track.repos?.length&&l.push(`repos ${c.track.repos.join(" + ")}`),c.track.leadAgent&&l.push(`lead ${c.track.leadAgent}`),await Co(c,`**Bostrat** \u2014 Track **${c.track.name}** linked${l.length?` (${l.join(", ")})`:""}.`),await Jm(t.id,c,"linked"),c}async function nl(t){let e=await Po();return e[t]?(delete e[t],await sn(t),!0):!1}var Qc=t=>`$${t>=100?Math.round(t):t.toFixed(2)}`;async function Vm(t,e){if(!t.track?.cwd)return null;let n=ue(await D({id:e,cwd:t.track.cwd}));return!n.totalUsd||n.totalUsd<.01?null:n}function UT(t){let e=Object.entries(t.byProvider).sort((r,o)=>o[1].usd-r[1].usd).map(([r,o])=>`${r} ${Qc(o.usd)}`).join(" \xB7 "),n=Object.keys(t.byBox).length;return`${Qc(t.totalUsd)}${e?` (${e})`:""}${n>1?` across ${n} boxes`:""}`}function HT(t,e){let n=[];return t.track.leadAgent&&n.push(`lead ${t.track.leadAgent}`),t.track.branch&&n.push(t.track.branch),t.track.repos?.length&&n.push(t.track.repos.join(" + ")),t.pr?.number&&n.push(`PR #${t.pr.number} ${t.pr.change==="merged"?"merged":"open"}`),e&&n.push(Qc(e.totalUsd)),n.join(" \xB7 ")||"linked"}async function No(t,e){let n=await Fn(e);if(!n)return;let r=await Vm(e,t).catch(()=>null);await n.api.upsertCard(n.cfg,e,{url:xa(t),title:`Bostrat Track: ${e.track.name}`,subtitle:HT(e,r)})}function va(t){let e=new URL(Km());return`${e.protocol}//${t}.${e.host}/`}var WT={linked:"linked",opened:"prOpened",merged:"prMerged"};function qT(t,e,n){let r=o=>e.filter(s=>s.type===o).sort((s,a)=>s.position-a.position);if(n==="linked"){if(["triage","backlog","unstarted"].includes(t?.type))return r("started")[0]??null}else if(n==="opened"){if(t?.type!=="completed")return r("started").find(o=>/review/i.test(o.name))??null}else if(n==="merged"&&t?.type!=="completed")return r("completed")[0]??null;return null}async function zm(t,e,{from:n,to:r,mode:o,phase:s=null,actor:a="user"}){e.state=r,await sn(t),await R({id:t,cwd:e.track?.cwd},{type:"ticket.state.changed",actor:a,source:"orchestrator",payload:{ts:Date.now(),key:e.key,url:e.url,from:n??null,to:r,mode:o,...s?{phase:s}:{}}}).catch(()=>{})}async function Jm(t,e,n){let r=await Fn(e);if(!r)return;let o=r.cfg.ticketFlow?.[WT[n]]??"prompt";if(o==="off")return;let{current:s,states:a}=await r.api.issueStates(r.cfg,e),i=el(s,a),c=qT(i,a,n);if(!c||c.id===i?.id)return;let l=i?.name??e.state??null;if(o==="auto"){await r.api.moveIssue(r.cfg,e,c.id),await zm(t,e,{from:l,to:c.name,mode:"auto",phase:n,actor:"system"});return}await R({id:t,cwd:e.track?.cwd},{type:"ticket.move.offered",actor:"system",source:"orchestrator",payload:{ts:Date.now(),offerId:Hm(),provider:e.provider,key:e.key,url:e.url,phase:n,from:l,to:{id:c.id,name:c.name,type:c.type},...e.pr?.number?{prNumber:e.pr.number}:{}}}).catch(()=>{})}async function Xm(t){let e=await ge(t);if(!e)throw new Error("no ticket linked to this Track");let n=await Fn(e);if(!n)throw new Error("no ticketing integration configured");let{current:r,states:o}=await n.api.issueStates(n.cfg,e),s=el(r,o);return s?.name&&e.state!==s.name&&(e.state=s.name,await sn(t)),{key:e.key,url:e.url,current:s?{id:s.id,name:s.name??null,type:s.type??null}:null,states:[...o].sort((a,i)=>a.position-i.position).map(({id:a,name:i,type:c,position:l})=>({id:a,name:i,type:c,position:l}))}}async function rl(t,e,{mode:n="manual"}={}){if(!e)throw new Error("stateId required");let r=await ge(t);if(!r)throw new Error("no ticket linked to this Track");let o=await Fn(r);if(!o)throw new Error("no ticketing integration configured");let{current:s,states:a}=await o.api.issueStates(o.cfg,r),i=el(s,a),c=a.find(u=>u.id===e);if(!c)throw new Error("unknown state for this ticket's team");let l=i?.name??r.state??null;return i?.id===c.id?(r.state!==c.name&&(r.state=c.name,await sn(t)),{key:r.key,from:l,to:c.name,moved:!1}):(await o.api.moveIssue(o.cfg,r,c.id),await zm(t,r,{from:l,to:c.name,mode:n}),{key:r.key,from:l,to:c.name,moved:!0})}async function Ym(t,e){let n=await ge(t);if(!n)throw new Error("no ticket linked to this Track");let r=await Fn(n);if(!r)throw new Error("no ticketing integration configured");let o=String(e||"").trim();if(!o)throw new Error("comment body required");let s=await r.api.createComment(r.cfg,n,o);return await R({id:t,cwd:n.track?.cwd},{type:"ticket.commented",actor:"user",source:"orchestrator",payload:{ts:Date.now(),key:n.key,url:s??n.url,excerpt:o.length>280?`${o.slice(0,279)}\u2026`:o}}).catch(()=>{}),{key:n.key,url:s??n.url}}async function Zm(t,e,n){if(!e)throw new Error("offerId required");let r=await D({id:t.id,cwd:t.cwd}),o=r.find(a=>a.type==="ticket.move.offered"&&a.payload?.offerId===e);if(!o)throw new Error("unknown offer");if(r.some(a=>a.type==="ticket.move.answered"&&a.payload?.offerId===e))throw new Error("offer already answered");let s=null;return n&&(s=await rl(t.id,o.payload.to?.id,{mode:"offer"})),await R({id:t.id,cwd:t.cwd},{type:"ticket.move.answered",actor:"user",source:"orchestrator",payload:{ts:Date.now(),offerId:e,action:n?"moved":"dismiss",...s?{from:s.from,to:s.to}:{}}}),{offerId:e,action:n?"moved":"dismiss",...s??{}}}var Dm=50;async function Qm(){let t=await Dn();if(!t)throw new Error("no ticketing integration configured");return Sr(t)}function KT(t){let e=t&&typeof t=="object"?t:{},n=(Array.isArray(e.issues)?e.issues:[]).map(o=>o&&typeof o=="object"?o:{title:String(o||"")}).map(o=>({title:String(o.title||"").trim(),description:o.description?String(o.description):null,priority:Number.isInteger(o.priority)&&o.priority>=0&&o.priority<=4?o.priority:null})).filter(o=>o.title),r=e.epic&&String(e.epic.name||"").trim()?{name:String(e.epic.name).trim(),description:e.epic.description?String(e.epic.description):null}:null;if(!n.length&&!r)throw new Error("a plan needs an epic or at least one issue");if(n.length>Dm)throw new Error(`too many issues (${n.length}); cap is ${Dm} per proposal`);return{epic:r,issues:n,team:e.team?String(e.team).trim():null,linkCurrentTrack:e.linkCurrentTrack===!0}}async function GT(t,e,n,r){let o=r??await Sr(t),s=i=>o.find(c=>c.id===i||c.key.toLowerCase()===String(i).toLowerCase()||c.name.toLowerCase()===String(i).toLowerCase())??null;if(e.team){let i=s(e.team);if(i)return i}let a=await ge(n.id);if(a?.key){let i=a.key.split("-")[0],c=o.find(l=>l.key===i);if(c)return c}if(t.defaultTeam){let i=s(t.defaultTeam);if(i)return i}return o.length===1?o[0]:null}async function th(t,e){let n=await Dn();if(!n)throw new Error("no ticketing integration configured \u2014 add a linear apiKey to ~/.bostrat/integrations.json");if(n.ticketFlow?.create==="off")throw new Error("ticket creation offers are off (bostrat-ticket flow set create=prompt to re-enable)");let r=KT(e),o=await Sr(n),s=await GT(n,r,t,o),a=s?{id:s.id,key:s.key,name:s.name}:null,i=Hm();return await R({id:t.id,cwd:t.cwd},{type:"ticket.create.offered",actor:"system",source:"orchestrator",payload:{ts:Date.now(),proposalId:i,provider:"linear",epic:r.epic,issues:r.issues,team:a,needsTeam:!s,linkCurrentTrack:r.linkCurrentTrack}}),{proposalId:i,epic:r.epic,issues:r.issues,team:a,needsTeam:!s}}async function eh(t,e,n,{teamId:r=null}={}){if(!e)throw new Error("proposalId required");let o=await D({id:t.id,cwd:t.cwd}),s=o.find(m=>m.type==="ticket.create.offered"&&m.payload?.proposalId===e);if(!s)throw new Error("unknown proposal");if(o.some(m=>m.type==="ticket.create.answered"&&m.payload?.proposalId===e))throw new Error("proposal already answered");if(!n)return await R({id:t.id,cwd:t.cwd},{type:"ticket.create.answered",actor:"user",source:"orchestrator",payload:{ts:Date.now(),proposalId:e,action:"dismiss"}}),{proposalId:e,action:"dismiss"};let a=await Dn();if(!a)throw new Error("no ticketing integration configured");let i=s.payload,c=i.team??null;if(!c&&r){let m=(await Sr(a)).find(g=>g.id===r);if(!m)throw new Error("unknown team");c={id:m.id,key:m.key,name:m.name}}if(!c)throw new Error("a team is required to create \u2014 pick one");let l=[],u=[],f=null;if(i.epic){let m=await Kc(a,{teamId:c.id,name:i.epic.name,description:i.epic.description});f=m.id,l.push({kind:"epic",title:m.name,url:m.url})}for(let m of i.issues||[])try{let g=await Gc(a,{teamId:c.id,title:m.title,description:m.description,projectId:f,priority:m.priority});l.push({kind:"issue",key:g.key,title:g.title,url:g.url})}catch(g){u.push({title:m.title,message:g?.message||String(g)})}await R({id:t.id,cwd:t.cwd},{type:"ticket.created",actor:"user",source:"orchestrator",payload:{ts:Date.now(),proposalId:e,team:{key:c.key,name:c.name},items:l,failed:u}}),await R({id:t.id,cwd:t.cwd},{type:"ticket.create.answered",actor:"user",source:"orchestrator",payload:{ts:Date.now(),proposalId:e,action:"created",createdCount:l.length,failedCount:u.length}});let d=l.filter(m=>m.kind==="issue"),p=null;return i.linkCurrentTrack&&d.length===1&&!await ge(t.id)&&(p=await vr(t,d[0].key,{source:"created"}).catch(()=>null)),{proposalId:e,action:"created",created:l,failed:u,linked:p?p.key:null}}async function Co(t,e){let n=await Fn(t);n&&await n.api.createComment(n.cfg,t,e)}async function VT(t,e,n){let r=await Fn(t);if(!r)return null;let o=await nn(e);if(!o)return null;let s=Ie(o,n);if(!s)return null;let a=await Mo.readFile(Ht(o,s.v).png).catch(()=>null);return a?r.api.uploadImage(r.cfg,{issue:t.key,filename:`${o.name.replace(/[^\w.-]+/g,"-")}.v${s.v}.png`,contentType:"image/png",buffer:a}):null}var jm=()=>Number(process.env.BOSTRAT_TICKET_COST_SYNC_MS??12e4),Xc=new Map;function zT(t,e){if(jm()<=0)return No(t,e);if(Xc.has(t))return;let n=setTimeout(()=>{Xc.delete(t),ge(t).then(r=>r?No(t,r):null).catch(()=>{})},jm());n.unref?.(),Xc.set(t,n)}var Lm=new Map;async function Fm(t,e,n){let r=Lm.get(t)??0;Date.now()-r<600*1e3||(Lm.set(t,Date.now()),await R({id:t,cwd:e},{type:"ticket.sync.failed",actor:"system",source:"orchestrator",payload:{ts:Date.now(),message:n?.message||String(n)}}).catch(()=>{}))}async function JT(t,{resolveTrack:e=null}={}){let n=await ge(t.trackId),r=t.payload||{};if(!n){if(t.type!=="turn.user"||t.actor!=="user")return;let o=null;for(let a of await tl())if(o=a.api.findIssueUrl(r.text,a.cfg),o)break;if(!o)return;let s=e?await e(t.trackId):null;if(!s)return;try{await vr(s,o,{source:"chat"})}catch(a){await Fm(t.trackId,s.cwd,a)}return}if(!n.track.cwd&&e){let o=await e(t.trackId).catch(()=>null);o?.cwd&&(n.track.cwd=o.cwd,await sn(t.trackId))}try{switch(t.type){case"handoff.done":n.track.leadAgent=r.toAgent??n.track.leadAgent,await sn(t.trackId),await No(t.trackId,n);break;case"pr.state.changed":{if(!r.change)break;if(r.number&&(n.pr={number:r.number,url:r.url??n.pr?.url??null,change:r.change}),await sn(t.trackId),await No(t.trackId,n),r.change==="opened"||r.change==="merged"){let o=r.change==="merged"?await Vm(n,t.trackId).catch(()=>null):null;await Co(n,`**Bostrat** \u2014 PR ${r.change} for **${n.track.name}**${r.url?`: ${r.url}`:r.number?` (#${r.number})`:""}`+(o?`
|
|
86
|
-
|
|
87
|
-
Agent cost for this Track: ${
|
|
88
|
-
|
|
89
|
-
`)}else if(r.change==="picked"){let o=(r.siblings??[]).map(s=>`**${s.name}**`).join(", ");await
|
|
90
|
-
_(runs on the Track's branch; reachable on the operator's private network only)_`);break}case"cost.recorded":await zT(t.trackId,n);break;default:break}}catch(o){let s=e?await e(t.trackId).catch(()=>null):null;await Fm(t.trackId,s?.cwd??null,o)}}function nh({resolveTrack:t}){Jt.on("event",e=>{JT(e,{resolveTrack:t}).catch(()=>{})})}async function rh(){if(!pt())return{pushed:0,source:"unconfigured"};let{records:t,source:e}=await Tt("tickets");if(e!=="plane"||t.length)return{pushed:0,source:e};let n=await Po(),r=0;for(let[o,s]of Object.entries(n))await Rt("tickets",{id:o,updatedAt:Date.now(),...s},{trackId:o}).catch(()=>{}),r++;return{pushed:r,source:"plane"}}import XT from"node:crypto";import Ta from"node:fs/promises";import oh from"node:path";import YT from"node:os";var sh=process.env.BOSTRAT_STATE_DIR||oh.join(process.env.HOME||YT.tmpdir(),".bostrat"),al=oh.join(sh,"pr-links.json"),H=(t,e)=>`${t}#${Number(e)}`,ah=t=>{let e=t.replace(/[^A-Za-z0-9._-]+/g,"-").replace(/^[^A-Za-z0-9]+/,"").slice(0,55),n=XT.createHash("sha256").update(t).digest("hex").slice(0,8);return`${e||"pr"}-${n}`},an=null,ol=null,sl=Promise.resolve();async function gt(){return an||(ol??=Ta.readFile(al,"utf8").then(t=>({links:JSON.parse(t)?.links??{},existed:!0})).catch(()=>({links:{},existed:!1})).then(async({links:t,existed:e})=>{if(an=t,!e&&pt()){let{records:n,source:r}=await Tt("prlinks").catch(()=>({records:[],source:"error"}));if(r==="plane"){for(let o of n){if(o.deletedAt||!o.data?.repo||o.data?.number==null)continue;let{id:s,updatedAt:a,...i}=o.data;an[H(i.repo,i.number)]=i}Object.keys(an).length&&await il().catch(()=>{})}}return an}).finally(()=>ol=null),ol)}async function il(){let t=JSON.stringify({links:an},null,2),e=`${al}.${process.pid}.tmp`;await Ta.mkdir(sh,{recursive:!0}),await Ta.writeFile(e,t),await Ta.rename(e,al)}async function vt(t){sl=sl.then(il,il),await sl.catch(()=>{});let e=an?.[t];e&&Rt("prlinks",{id:ah(t),updatedAt:Date.now(),...e},{trackId:e.trackId??null}).catch(()=>{})}var ZT=new Set(["api","observed","reconciled"]);async function Wt({repo:t,number:e,trackId:n,url:r=null,headRef:o=null,title:s=null,source:a="matched"}){if(!t||!Number.isInteger(Number(e))||!n)return{link:null,created:!1};let i=await gt(),c=H(t,e);return i[c]?{link:i[c],created:!1}:(i[c]={repo:t,number:Number(e),trackId:n,url:r,headRef:o,title:s,source:a,announced:ZT.has(a),firstSeen:Date.now()},await vt(H(t,e)),{link:i[c],created:!0})}async function Bo(t,e){if(!t||e==null)return null;let n=(await gt())[H(t,e)];return!n||n.announced?null:(n.announced=!0,await vt(H(t,e)),n)}async function $t(t,e){return!t||e==null?null:(await gt())[H(t,e)]??null}async function cn(t,e,n){if(!t||e==null)return null;let r=(await gt())[H(t,e)];return r?(r.state=n,r.resolvedAt=Date.now(),delete r.armedAt,delete r.autoMerge,delete r.archiveOnMerge,delete r.notifyOnMerge,delete r.notifyPending,await vt(H(t,e)),r):null}async function ih(t,e,{headSha:n=null,trackId:r=null}={}){if(!t||e==null)return null;let o=(await gt())[H(t,e)];return o?(o.ciFix={delegatedAt:Date.now(),headSha:n,trackId:r},await vt(H(t,e)),o):null}async function ch(t,e,{headSha:n=null,trackId:r=null}={}){if(!t||e==null)return null;let o=(await gt())[H(t,e)];return o?(o.reconcile={delegatedAt:Date.now(),headSha:n,trackId:r},await vt(H(t,e)),o):null}async function Aa(t,e,{headSha:n=null}={}){if(!t||e==null)return null;let r=(await gt())[H(t,e)];return!r||!r.ciFix?null:(r.ciFixAwait={since:Date.now(),trackId:r.ciFix.trackId??null,headSha:n},delete r.ciFix,await vt(H(t,e)),r)}async function Ea(t,e){if(!t||e==null)return null;let n=(await gt())[H(t,e)];return!n||!n.ciFixAwait?null:(delete n.ciFixAwait,await vt(H(t,e)),n)}async function _a(t,e,{headSha:n=null}={}){if(!t||e==null)return null;let r=(await gt())[H(t,e)];return!r||!r.reconcile?null:(r.reconcileAwait={since:Date.now(),trackId:r.reconcile.trackId??null,headSha:n},delete r.reconcile,await vt(H(t,e)),r)}async function Ia(t,e){if(!t||e==null)return null;let n=(await gt())[H(t,e)];return!n||!n.reconcileAwait?null:(delete n.reconcileAwait,await vt(H(t,e)),n)}async function lh(t,e,{at:n=Date.now()}={}){if(!t||e==null)return null;let r=(await gt())[H(t,e)];return r?(r.baseFoldAt=n,await vt(H(t,e)),r):null}async function uh(t,e){if(!t||e==null)return null;let n=(await gt())[H(t,e)];return!n||!n.baseFoldAt?null:(delete n.baseFoldAt,await vt(H(t,e)),n)}async function dh(t,e,n){if(!t||e==null||!n)return null;let r=(await gt())[H(t,e)];return r?(r.lastHeadSha=n,await vt(H(t,e)),r):null}async function Do(t,e,{merge:n=null}={}){if(!t||e==null)return null;let r=(await gt())[H(t,e)];return r?(r.armedAt=Date.now(),n&&(r.autoMerge={method:n.method||"merge",armedAt:r.armedAt}),await vt(H(t,e)),r):null}async function Tr(t,e,n=!0){if(!t||e==null)return null;let r=(await gt())[H(t,e)];return r?(n?r.archiveOnMerge=!0:delete r.archiveOnMerge,await vt(H(t,e)),r):null}async function Ra(t,e,n=!0){if(!t||e==null)return null;let r=(await gt())[H(t,e)];return r?(n?r.notifyOnMerge=!0:delete r.notifyOnMerge,await vt(H(t,e)),r):null}async function fh(t,e,n){if(!t||e==null)return null;let r=(await gt())[H(t,e)];if(!r)return null;r.notifyOnMerge=!0;let o=new Map((r.notifyPending||[]).map(s=>[`${s.repo}#${s.number}`,s]));for(let s of n||[])s&&s.number!=null&&o.set(`${s.repo}#${s.number}`,s);return r.notifyPending=[...o.values()],await vt(H(t,e)),r}async function jo(t,e){if(!t||e==null)return null;let n=(await gt())[H(t,e)];return!n||!n.armedAt&&!n.autoMerge?null:(delete n.armedAt,delete n.autoMerge,delete n.archiveOnMerge,delete n.notifyOnMerge,delete n.notifyPending,await vt(H(t,e)),n)}var QT=new Set(["merged","closed","lost"]);async function ph(){return Object.values(await gt()).filter(t=>!QT.has(t.state))}async function ye(){return Object.values(await gt()).sort((t,e)=>e.firstSeen-t.firstSeen)}async function mh(){if(!pt())return{pushed:0,source:"unconfigured"};let{records:t,source:e}=await Tt("prlinks");if(e!=="plane"||t.length)return{pushed:0,source:e};let n=await gt(),r=0;for(let[o,s]of Object.entries(n))await Rt("prlinks",{id:ah(o),updatedAt:Date.now(),...s},{trackId:s.trackId??null}).catch(()=>{}),r++;return{pushed:r,source:"plane"}}var wh=t=>Math.round(t*100)/100;function tA(t){return!t||t.kind==="setup"?[]:Dt(t.workspace).filter(e=>e&&(e.repo||e.base)).map(e=>({key:e.repo||e.base,slug:e.repo??null,base:e.base??null,name:e.repo?e.repo.split("/").pop():hh.basename(e.base)}))}function gh(t){let e=new Map;for(let n of t||[])for(let r of tA(n)){let o=e.get(r.key);o||e.set(r.key,o={...r,tracks:[]}),o.tracks.push(n)}return e}function $a(t){let e=t.events?.length?t.events[t.events.length-1].at:0;return Math.max(t.createdAt||0,t.archivedAt||0,t.lastResponseAt||0,e||0)||null}var cl=(t,e)=>`${t} ${e}${t===1?"":"s"}`;function eA(t){if(!Number.isFinite(t)||t<=0)return null;let e=t/864e5;if(e>=1)return`${Math.round(e)}d`;let n=t/36e5;return n>=1?`${Math.round(n)}h`:`${Math.max(1,Math.round(t/6e4))}m`}function ll(t,e=[]){let n=[],r=t?.events||[],o=new Map,s=0;for(let u of e){if(u.type!=="pr.state.changed")continue;let f=u.payload||{};!f.repo||f.number==null||(f.change==="merged"?o.set(`${f.repo}#${f.number}`,f):f.change==="opened"&&s++)}if(o.size){let u=[...o.keys()].map(f=>{let[d,p]=f.split("#");return`${d.split("/").pop()}#${p}`});n.push(`Merged ${u.slice(0,4).join(", ")}${u.length>4?` +${u.length-4}`:""}`)}else s&&n.push(cl(s,"PR")+" opened");let a=Object.keys(t?.subtasks||{}).length||r.filter(u=>u.type==="subtask-delegated").length;a&&n.push(cl(a,"subtask"));let i=r.filter(u=>u.type==="lead-handoff"&&u.fromAgent&&u.toAgent);if(i.length){let u=i.slice(0,3).map(f=>`${f.fromAgent}\u2192${f.toAgent}`).join(", ");n.push(`${cl(i.length,"handoff")} ${u}${i.length>3?"\u2026":""}`)}let c=ue(e);c.totalUsd>=.01&&n.push(`$${c.totalUsd.toFixed(2)}`);let l=Number.isFinite(t?.archivedAt)&&Number.isFinite(t?.createdAt)?eA(t.archivedAt-t.createdAt):null;return n.length&&l&&n.push(l),n.length?n.join(" \xB7 "):null}function nA(t){let e=[],n=[...t?.boxes||[]].sort((o,s)=>(o.createdAt||0)-(s.createdAt||0));for(let o of n){if(o.role!=="lead"&&o.role!=="handed-off")continue;let s=Array.isArray(o.epochs)&&o.epochs.length?o.epochs.map(a=>a.agent):[o.agent];for(let a of s)a&&e[e.length-1]!==a&&e.push(a)}let r=new Set(n.filter(o=>o.role==="subtask"&&o.agent&&!e.includes(o.agent)).map(o=>o.agent));return{lead:e,subtask:[...r]}}function yh(t,e,n){let r=new Set(t.tracks.map(d=>d.id)),o=0,s=0,a=0,i=null,c=null,l=0;for(let d of t.tracks){d.archivedAt?s++:o++,d.beat?.text&&a++;let p=$a(d);p&&(!i||p>i)&&(i=p),d.createdAt&&(!c||d.createdAt<c)&&(c=d.createdAt),l+=e.get(d.id)||0}let u=0,f=0;for(let d of n)r.has(d.trackId)&&(t.slug&&d.repo!==t.slug||t.slug&&(d.state==="merged"?f++:d.state!=="closed"&&u++));return{key:t.key,slug:t.slug,base:t.base,name:t.name,tracks:{total:t.tracks.length,live:o,archived:s,withBeat:a},prs:{open:u,merged:f},usd:wh(l),firstActivityAt:c,lastActivityAt:i}}async function bh(t){let e=gh(t),n=await ye().catch(()=>[]),r=new Map,o=new Set;for(let a of e.values())for(let i of a.tracks){if(o.has(i.id))continue;o.add(i.id);let c=await D(i).catch(()=>[]);r.set(i.id,ue(c).totalUsd)}let s=[...e.values()].map(a=>yh(a,r,n)).sort((a,i)=>(i.lastActivityAt??0)-(a.lastActivityAt??0));return{generatedAt:Date.now(),arcs:s}}async function kh(t,e){let r=gh(t).get(e);if(!r)return null;let[o,s,a,i,c]=await Promise.all([ye().catch(()=>[]),Nn().catch(()=>[]),pa().catch(()=>[]),wa().catch(()=>[]),Sa().catch(()=>({}))]),l=new Map,u=[],f=(d,p)=>($a(p)||p.createdAt||0)-($a(d)||d.createdAt||0);for(let d of[...r.tracks].sort(f)){let p=await D(d).catch(()=>[]),m=ue(p);l.set(d.id,m.totalUsd);let g=c[d.id]||null;u.push({trackId:d.id,name:d.name,createdAt:d.createdAt??null,archivedAt:d.archivedAt??null,live:!d.archivedAt,beat:d.beat??null,agents:nA(d),handoffs:(d.events||[]).filter(y=>y.type==="lead-handoff").length,subtasks:Object.keys(d.subtasks||{}).length,repos:Dt(d.workspace).map(y=>y.repo||hh.basename(y.base||"")),prs:o.filter(y=>y.trackId===d.id&&(!r.slug||y.repo===r.slug)).map(y=>({repo:y.repo,number:y.number,state:y.state??null,title:y.title??null,url:y.url??null})),mocks:s.filter(y=>y.trackId===d.id).length,views:a.filter(y=>y.trackId===d.id).length,runbooks:i.filter(y=>y.trackId===d.id).map(y=>({name:y.name,status:y.status})),ticket:g?{provider:g.provider,key:g.key,url:g.url??null}:null,usd:wh(m.totalUsd),lastActivityAt:$a(d)})}return{...yh(r,l,o),generatedAt:Date.now(),segments:u}}import{execFile as rA}from"node:child_process";import{promisify as oA}from"node:util";import{randomUUID as sA}from"node:crypto";var Ar=oA(rA),Oa=t=>new Promise(e=>setTimeout(e,t)),aA=Number(process.env.BOSTRAT_SWAP_EXIT_TIMEOUT_MS)||15e3,xh=Number(process.env.BOSTRAT_SWAP_SHELL_TIMEOUT_MS)||8e3,iA=250,cA=400,Sh={claude:{escape:!0,wipe:!0,text:"/exit"},codex:{escape:!0,wipe:!0,text:"/quit"},cursor:{escape:!0,wipe:!0,text:"/quit"},default:{escape:!1,wipe:!1,text:"exit"}};async function ul(t,e){let n=Date.now()+e;for(;;){if(await ur(t))return!0;if(Date.now()>n)return!1;await Oa(iA)}}async function lA(t,e){let n=Sh[e]||Sh.default;n.escape&&(await bt(t,["Escape"],{kind:"swap-exit"}),await Oa(150)),n.wipe&&await qs(t,400,{kind:"swap-exit"}),await Ks(t,n.text,{perCharDelayMs:90,kind:"swap-exit"});let r=_e(e).submitDelayMs||0;await Oa(Math.max(r,250)),await bt(t,["Enter"],{kind:"swap-exit"})}async function uA(t,e){try{let{stdout:n}=await Ar(N,P("show-environment","-t",t,e));return!String(n||"").trimStart().startsWith("-")}catch{return!1}}async function vh(t){await Ar(N,P("respawn-pane","-k","-t",t))}async function dl(t,e={}){if(!wt(t))throw new Error("invalid session name");let n=String(e.agentCmd||"").trim();if(!n)throw new Error("agentCmd is required");let r=await Z(t);if(!r)throw new Error(`session "${t}" not found`);if(!r.cwd)throw new Error(`can't resolve cwd for "${t}"`);let o=r.agent||"agent",s=e.agent||Xe(n),a=Number(e.exitTimeoutMs)||aA;await U({flow:"swap",phase:"start",sessionName:t,from:o,to:s,cwd:r.cwd});let i=!1;if(!await ur(t)&&(await lA(t,o),!await ul(t,a)&&(i=!0,await U({flow:"swap",phase:"respawn-forced",sessionName:t,from:o}),await vh(t),!await ul(t,xh))))throw new Error(`pane shell did not come back after respawn-pane in "${t}"`);let c=s==="claude"&&In(n),l=c?Rs():null,u=await uA(t,"ANTHROPIC_API_KEY"),f=!1;try{l?(await Ar(N,P("set-environment","-t",t,"ANTHROPIC_API_KEY",l)),f=!0):u&&(await Ar(N,P("set-environment","-u","-t",t,"ANTHROPIC_API_KEY")),f=!0)}catch(y){throw l?Fs(y,l):y}if(f&&!i&&(await vh(t),!await ul(t,xh)))throw new Error(`pane shell did not come back after env respawn in "${t}"`);let d=!!e.resumeSessionId,p=e.resumeSessionId||sA(),m=d?n:oc(n,p),g=d?e.pinned===!0:/(^|\s)--session-id(\s|=)/.test(m);return c&&await Ge(r.cwd),s==="codex"&&await rr(r.cwd),s==="cursor"&&await or(r.cwd),await On(t),await Ar(N,P("set-option","-t",t,"@bostrat-sid",p)),await Ar(N,P("set-option","-t",t,"@bostrat-agent",s)),await Oa(cA),await St(t,m,{kind:"swap-launch"}),await U({flow:"swap",phase:"done",sessionName:t,from:o,to:s,sid:p,forced:i,envRespawned:f}),{name:t,cwd:r.cwd,sessionId:p,agent:s,pinned:g,forced:i,envRespawned:f}}import WA from"node:fs/promises";function dA(t){let e=[],n="",r=null;for(let o=0;o<t.length;o++){let s=t[o];if(r){n+=s,s===r&&t[o-1]!=="\\"&&(r=null);continue}if(s==="'"||s==='"'||s==="`"){r=s,n+=s;continue}if(s==="&"&&t[o+1]==="&"){e.push(n),n="",o++;continue}if(s==="|"&&t[o+1]==="|"){e.push(n),n="",o++;continue}if(s==="|"||s===";"||s===`
|
|
91
|
-
`){e.push(n),n="";continue}n+=s}return e.push(n),e.map(o=>o.trim()).filter(Boolean)}function
|
|
92
|
-
`,n);i!==-1&&(e=e.slice(0,i))}let r=
|
|
93
|
-
`).map(n=>n.trim()).find(Boolean)||t;return e.length>88?`${e.slice(0,88).trimEnd()}\u2026`:e}function ln(t,e=""){let n=e.trim();return n||
|
|
94
|
-
`)){let a=s.trim();if(!a||a[0]!=="{")continue;let i;try{i=JSON.parse(a)}catch{continue}let c=i.message;if(!c)continue;let l=[];if(
|
|
95
|
-
`)){let c=i.trim();if(!c||c[0]!=="{")continue;let l;try{l=JSON.parse(c)}catch{continue}if(l.isSidechain)continue;let u=l.message?.content;if(!Array.isArray(u))continue;let f=l.timestamp?Date.parse(l.timestamp):null;for(let d of u)if(!(!d||typeof d!="object"))if(d.type==="tool_use"&&d.id){if(d.name==="Agent"){let p=d.input||{};o.set(d.id,{id:d.id,kind:"agent",name:String(p.description||"").trim()||null,detail:String(p.subagent_type||"").trim()||null,ts:f,proven:!1})}else if(d.name==="Bash"&&d.input?.run_in_background===!0){let p=String(d.input.command||"").trim();o.set(d.id,{id:d.id,kind:"command",name:String(d.input.description||"").trim()||(p?
|
|
96
|
-
`).trim():"",
|
|
97
|
-
`);if(i!==-1)return o.slice(0,i);if(s+=a,o.length>=e)break}return o}finally{await n.close()}}async function
|
|
98
|
-
`).trim():"";async function
|
|
99
|
-
`)){let p=d.trim();if(!p||p[0]!=="{")continue;let m;try{m=JSON.parse(p)}catch{continue}if((m.type==="turn_context"&&m.payload?.model||m.type==="session_meta"&&m.payload?.model)&&(i=m.payload.model),m.type==="event_msg"){let S=m.payload?.type;S==="task_started"?(l=!0,u=!1,f=m.payload?.turn_id||null):S==="task_complete"&&l&&(l=!1,!u&&m.payload?.last_agent_message==null&&a.push({role:"assistant",failed:!0,text:
|
|
100
|
-
`;else if(s==="t")n+=" ";else if(s==="r")n+="\r";else if(s==="u")if(t[r+1]==="{"){let a=t.indexOf("}",r+2);if(a===-1)return null;n+=String.fromCodePoint(parseInt(t.slice(r+2,a),16)),r=a}else n+=String.fromCharCode(parseInt(t.slice(r+1,r+5),16)),r+=4;else n+=s}else{if(o===e)return n;if(e==="`"&&o==="$"&&t[r+1]==="{")return null;n+=o}}return null}function
|
|
101
|
-
`)){let c=i.trim();if(!c||c[0]!=="{")continue;let l;try{l=JSON.parse(c)}catch{continue}if(l.type!=="response_item")continue;let u=l.payload;if(!u)continue;let f=[];if(u.type==="function_call"&&
|
|
102
|
-
`),o=-1;for(let u=r.length-1;u>=0;u--)if(/^\s*Switch model\?\s*$/.test(r[u])){o=u;break}if(o<0)return null;let s=[],a=[];for(let u=o+1;u<r.length;u++){let f=r[u].match(
|
|
103
|
-
`),r=n.length-1;for(;r>=0&&n[r].trim()==="";)r--;if(r<0||!
|
|
104
|
-
`),o=[];for(let c=0;c<r.length;c++){let l=r[c].match(
|
|
105
|
-
`),o=-1;for(let u=r.length-1;u>=0;u--)if(
|
|
106
|
-
`),o=
|
|
107
|
-
`),o=
|
|
108
|
-
`),o="";for(let s=0;s<r.length;s++)if(/^\s*https:\/\//.test(r[s])){o=
|
|
109
|
-
`)].reverse().find(a=>
|
|
110
|
-
`),o=
|
|
111
|
-
`),o=-1;for(let l=r.length-1;l>=0;l--)if(r[l].trim()!==""){o=l;break}if(o<0)return null;let s=r[o].match(
|
|
112
|
-
`),o=
|
|
113
|
-
`).pop()||"")?!0:
|
|
114
|
-
`).pop()||""):t==="codex"&&/Sign in with ChatGPT|one-time code|Paste or type your API key|link to authenticate/i.test(n)?!0:t==="cursor"||t==="codex"?/\b(allow|approve|apply (this )?patch|run (this )?command|do you want to run|grant)\b/i.test(n):!1:!1}function
|
|
115
|
-
`).map(c=>c.replace(/\s+$/,"")),r=-1;for(let c=n.length-1;c>=0;c--)if(
|
|
85
|
+
}`,{contentType:n,filename:e,size:r.length}),s=o.fileUpload?.uploadFile;if(!o.fileUpload?.success||!s?.uploadUrl)throw new Error("linear: fileUpload did not succeed");let a={"content-type":n};for(let c of s.headers||[])a[c.key]=c.value;let i=await fetch(s.uploadUrl,{method:"PUT",headers:a,body:r});if(!i.ok)throw new Error(`linear: asset PUT failed (HTTP ${i.status})`);return s.assetUrl}async function UT(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 HT(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 Tr(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 Lm=255;async function zc(t,{teamId:e,name:n,description:r}){let o=r||null,s=!!o&&o.length>Lm,a=s?`${o.slice(0,Lm-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 Jc(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 Gm={linear:{api:Xc,config:jn},jira:{api:Gc,config:va}};async function rl(){let t=[];for(let[e,{api:n,config:r}]of Object.entries(Gm)){let o=await r();o&&t.push({name:e,api:n,cfg:o})}return t}async function Un(t){let e=Gm[t?.provider];if(!e)return null;let n=await e.config();return n?{name:t.provider,api:e.api,cfg:n}:null}function ol(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 Vm=()=>process.env.BOSTRAT_STATE_DIR||qm.join(process.env.HOME||WT.tmpdir(),".bostrat"),tl=()=>qm.join(Vm(),"tickets.json"),zm=()=>(process.env.BOSTRAT_PUBLIC_URL||"http://bostrat.globopi").replace(/\/$/,""),Aa=t=>`${zm()}/track/${t}`,on=null,Yc=null,Zc=Promise.resolve();async function Do(){return on||(Yc??=Po.readFile(tl(),"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 At("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 el().catch(()=>{})}}return on}).finally(()=>Yc=null),Yc)}async function el(){let t=JSON.stringify({tickets:on},null,2),e=`${tl()}.${process.pid}.tmp`;await Po.mkdir(Vm(),{recursive:!0}),await Po.writeFile(e,t),await Po.rename(e,tl())}async function sn(t=null){if(Zc=Zc.then(el,el),await Zc.catch(()=>{}),!t)return;let e=on?.[t];e?$t("tickets",{id:t,updatedAt:Date.now(),...e},{trackId:t}).catch(()=>{}):Qt("tickets",t).catch(()=>{})}async function ge(t){return(await Do())[t]??null}async function Ea(){return{...await Do()}}async function Jm(){return(await rl()).length>0}async function Ar(t,e,{source:n="api"}={}){let r=await rl();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 Do())[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 Bo(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 No(c,`**Bostrat** \u2014 Track **${c.track.name}** linked${l.length?` (${l.join(", ")})`:""}.`),await Zm(t.id,c,"linked"),c}async function sl(t){let e=await Do();return e[t]?(delete e[t],await sn(t),!0):!1}var nl=t=>`$${t>=100?Math.round(t):t.toFixed(2)}`;async function Xm(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 qT(t){let e=Object.entries(t.byProvider).sort((r,o)=>o[1].usd-r[1].usd).map(([r,o])=>`${r} ${nl(o.usd)}`).join(" \xB7 "),n=Object.keys(t.byBox).length;return`${nl(t.totalUsd)}${e?` (${e})`:""}${n>1?` across ${n} boxes`:""}`}function KT(t,e){let n=[];return t.track.leadAgent&&n.push(`lead ${t.track.leadAgent}`),t.track.branch&&n.push(t.track.branch),t.track.repos?.length&&n.push(t.track.repos.join(" + ")),t.pr?.number&&n.push(`PR #${t.pr.number} ${t.pr.change==="merged"?"merged":"open"}`),e&&n.push(nl(e.totalUsd)),n.join(" \xB7 ")||"linked"}async function Bo(t,e){let n=await Un(e);if(!n)return;let r=await Xm(e,t).catch(()=>null);await n.api.upsertCard(n.cfg,e,{url:Aa(t),title:`Bostrat Track: ${e.track.name}`,subtitle:KT(e,r)})}function _a(t){let e=new URL(zm());return`${e.protocol}//${t}.${e.host}/`}var GT={linked:"linked",opened:"prOpened",merged:"prMerged"};function VT(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 Ym(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 Zm(t,e,n){let r=await Un(e);if(!r)return;let o=r.cfg.ticketFlow?.[GT[n]]??"prompt";if(o==="off")return;let{current:s,states:a}=await r.api.issueStates(r.cfg,e),i=ol(s,a),c=VT(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 Ym(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:Km(),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 Qm(t){let e=await ge(t);if(!e)throw new Error("no ticket linked to this Track");let n=await Un(e);if(!n)throw new Error("no ticketing integration configured");let{current:r,states:o}=await n.api.issueStates(n.cfg,e),s=ol(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 al(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 Un(r);if(!o)throw new Error("no ticketing integration configured");let{current:s,states:a}=await o.api.issueStates(o.cfg,r),i=ol(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 Ym(t,r,{from:l,to:c.name,mode:n}),{key:r.key,from:l,to:c.name,moved:!0})}async function th(t,e){let n=await ge(t);if(!n)throw new Error("no ticket linked to this Track");let r=await Un(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 eh(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 al(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 Fm=50;async function nh(){let t=await jn();if(!t)throw new Error("no ticketing integration configured");return Tr(t)}function zT(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>Fm)throw new Error(`too many issues (${n.length}); cap is ${Fm} per proposal`);return{epic:r,issues:n,team:e.team?String(e.team).trim():null,linkCurrentTrack:e.linkCurrentTrack===!0}}async function JT(t,e,n,r){let o=r??await Tr(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 rh(t,e){let n=await jn();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=zT(e),o=await Tr(n),s=await JT(n,r,t,o),a=s?{id:s.id,key:s.key,name:s.name}:null,i=Km();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 oh(t,e,n,{teamId:r=null}={}){if(!e)throw new Error("proposalId required");let o=await L({id:t.id,cwd:t.cwd}),s=o.find(m=>m.type==="ticket.create.offered"&&m.payload?.proposalId===e);if(!s)throw new Error("unknown proposal");if(o.some(m=>m.type==="ticket.create.answered"&&m.payload?.proposalId===e))throw new Error("proposal already answered");if(!n)return await R({id:t.id,cwd:t.cwd},{type:"ticket.create.answered",actor:"user",source:"orchestrator",payload:{ts:Date.now(),proposalId:e,action:"dismiss"}}),{proposalId:e,action:"dismiss"};let a=await jn();if(!a)throw new Error("no ticketing integration configured");let i=s.payload,c=i.team??null;if(!c&&r){let m=(await Tr(a)).find(h=>h.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 zc(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 h=await Jc(a,{teamId:c.id,title:m.title,description:m.description,projectId:f,priority:m.priority});l.push({kind:"issue",key:h.key,title:h.title,url:h.url})}catch(h){u.push({title:m.title,message:h?.message||String(h)})}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 Ar(t,d[0].key,{source:"created"}).catch(()=>null)),{proposalId:e,action:"created",created:l,failed:u,linked:p?p.key:null}}async function No(t,e){let n=await Un(t);n&&await n.api.createComment(n.cfg,t,e)}async function XT(t,e,n){let r=await Un(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 Po.readFile(Wt(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 Um=()=>Number(process.env.BOSTRAT_TICKET_COST_SYNC_MS??12e4),Qc=new Map;function YT(t,e){if(Um()<=0)return Bo(t,e);if(Qc.has(t))return;let n=setTimeout(()=>{Qc.delete(t),ge(t).then(r=>r?Bo(t,r):null).catch(()=>{})},Um());n.unref?.(),Qc.set(t,n)}var Hm=new Map;async function Wm(t,e,n){let r=Hm.get(t)??0;Date.now()-r<600*1e3||(Hm.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 ZT(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 rl())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 Ar(s,o,{source:"chat"})}catch(a){await Wm(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 Bo(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 Bo(t.trackId,n),r.change==="opened"||r.change==="merged"){let o=r.change==="merged"?await Xm(n,t.trackId).catch(()=>null):null;await No(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: ${qT(o)}.`:"")),await Zm(t.trackId,n,r.change)}break}case"mock.state.changed":{if(r.change==="ready"&&r.mockId){let o=await XT(n,r.mockId,r.version??null);if(!o)break;let s=(r.version??1)>1?` revised (v${r.version})`:"";await No(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 No(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 No(n,`**Bostrat** \u2014 live preview **${r.name}**: ${_a(r.slug)}
|
|
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 Wm(t.trackId,s?.cwd??null,o)}}function sh({resolveTrack:t}){Jt.on("event",e=>{ZT(e,{resolveTrack:t}).catch(()=>{})})}async function ah(){if(!pt())return{pushed:0,source:"unconfigured"};let{records:t,source:e}=await At("tickets");if(e!=="plane"||t.length)return{pushed:0,source:e};let n=await Do(),r=0;for(let[o,s]of Object.entries(n))await $t("tickets",{id:o,updatedAt:Date.now(),...s},{trackId:o}).catch(()=>{}),r++;return{pushed:r,source:"plane"}}import QT from"node:crypto";import Ia from"node:fs/promises";import ih from"node:path";import tA from"node:os";var ch=process.env.BOSTRAT_STATE_DIR||ih.join(process.env.HOME||tA.tmpdir(),".bostrat"),ll=ih.join(ch,"pr-links.json"),K=(t,e)=>`${t}#${Number(e)}`,lh=t=>{let e=t.replace(/[^A-Za-z0-9._-]+/g,"-").replace(/^[^A-Za-z0-9]+/,"").slice(0,55),n=QT.createHash("sha256").update(t).digest("hex").slice(0,8);return`${e||"pr"}-${n}`},an=null,il=null,cl=Promise.resolve();async function yt(){return an||(il??=Ia.readFile(ll,"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 At("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[K(i.repo,i.number)]=i}Object.keys(an).length&&await ul().catch(()=>{})}}return an}).finally(()=>il=null),il)}async function ul(){let t=JSON.stringify({links:an},null,2),e=`${ll}.${process.pid}.tmp`;await Ia.mkdir(ch,{recursive:!0}),await Ia.writeFile(e,t),await Ia.rename(e,ll)}async function vt(t){cl=cl.then(ul,ul),await cl.catch(()=>{});let e=an?.[t];e&&$t("prlinks",{id:lh(t),updatedAt:Date.now(),...e},{trackId:e.trackId??null}).catch(()=>{})}var eA=new Set(["api","observed","reconciled"]);async function qt({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 yt(),c=K(t,e);return i[c]?{link:i[c],created:!1}:(i[c]={repo:t,number:Number(e),trackId:n,url:r,headRef:o,title:s,source:a,announced:eA.has(a),firstSeen:Date.now()},await vt(K(t,e)),{link:i[c],created:!0})}async function jo(t,e){if(!t||e==null)return null;let n=(await yt())[K(t,e)];return!n||n.announced?null:(n.announced=!0,await vt(K(t,e)),n)}async function Ot(t,e){return!t||e==null?null:(await yt())[K(t,e)]??null}async function cn(t,e,n){if(!t||e==null)return null;let r=(await yt())[K(t,e)];return r?(r.state=n,r.resolvedAt=Date.now(),delete r.armedAt,delete r.autoMerge,delete r.archiveOnMerge,delete r.notifyOnMerge,delete r.notifyPending,await vt(K(t,e)),r):null}async function uh(t,e,{headSha:n=null,trackId:r=null}={}){if(!t||e==null)return null;let o=(await yt())[K(t,e)];return o?(o.ciFix={delegatedAt:Date.now(),headSha:n,trackId:r},await vt(K(t,e)),o):null}async function dh(t,e,{headSha:n=null,trackId:r=null}={}){if(!t||e==null)return null;let o=(await yt())[K(t,e)];return o?(o.reconcile={delegatedAt:Date.now(),headSha:n,trackId:r},await vt(K(t,e)),o):null}async function Ra(t,e,{headSha:n=null}={}){if(!t||e==null)return null;let r=(await yt())[K(t,e)];return!r||!r.ciFix?null:(r.ciFixAwait={since:Date.now(),trackId:r.ciFix.trackId??null,headSha:n},delete r.ciFix,await vt(K(t,e)),r)}async function $a(t,e){if(!t||e==null)return null;let n=(await yt())[K(t,e)];return!n||!n.ciFixAwait?null:(delete n.ciFixAwait,await vt(K(t,e)),n)}async function Oa(t,e,{headSha:n=null}={}){if(!t||e==null)return null;let r=(await yt())[K(t,e)];return!r||!r.reconcile?null:(r.reconcileAwait={since:Date.now(),trackId:r.reconcile.trackId??null,headSha:n},delete r.reconcile,await vt(K(t,e)),r)}async function Ca(t,e){if(!t||e==null)return null;let n=(await yt())[K(t,e)];return!n||!n.reconcileAwait?null:(delete n.reconcileAwait,await vt(K(t,e)),n)}async function fh(t,e,{at:n=Date.now()}={}){if(!t||e==null)return null;let r=(await yt())[K(t,e)];return r?(r.baseFoldAt=n,await vt(K(t,e)),r):null}async function ph(t,e){if(!t||e==null)return null;let n=(await yt())[K(t,e)];return!n||!n.baseFoldAt?null:(delete n.baseFoldAt,await vt(K(t,e)),n)}async function mh(t,e,n){if(!t||e==null||!n)return null;let r=(await yt())[K(t,e)];return r?(r.lastHeadSha=n,await vt(K(t,e)),r):null}async function Lo(t,e,{merge:n=null}={}){if(!t||e==null)return null;let r=(await yt())[K(t,e)];return r?(r.armedAt=Date.now(),n&&(r.autoMerge={method:n.method||"merge",armedAt:r.armedAt}),await vt(K(t,e)),r):null}async function Hn(t,e,n=!0){if(!t||e==null)return null;let r=(await yt())[K(t,e)];return r?(n?r.archiveOnMerge=!0:delete r.archiveOnMerge,await vt(K(t,e)),r):null}async function Fo(t,e,n=!0){if(!t||e==null)return null;let r=(await yt())[K(t,e)];return r?(n?r.notifyOnMerge=!0:delete r.notifyOnMerge,await vt(K(t,e)),r):null}async function hh(t,e,n){if(!t||e==null)return null;let r=(await yt())[K(t,e)];if(!r)return null;r.notifyOnMerge=!0;let o=new Map((r.notifyPending||[]).map(s=>[`${s.repo}#${s.number}`,s]));for(let s of n||[])s&&s.number!=null&&o.set(`${s.repo}#${s.number}`,s);return r.notifyPending=[...o.values()],await vt(K(t,e)),r}async function Uo(t,e){if(!t||e==null)return null;let n=(await yt())[K(t,e)];return!n||!n.armedAt&&!n.autoMerge?null:(delete n.armedAt,delete n.autoMerge,delete n.archiveOnMerge,delete n.notifyOnMerge,delete n.notifyPending,await vt(K(t,e)),n)}var nA=new Set(["merged","closed","lost"]);async function wh(){return Object.values(await yt()).filter(t=>!nA.has(t.state))}async function ye(){return Object.values(await yt()).sort((t,e)=>e.firstSeen-t.firstSeen)}async function gh(){if(!pt())return{pushed:0,source:"unconfigured"};let{records:t,source:e}=await At("prlinks");if(e!=="plane"||t.length)return{pushed:0,source:e};let n=await yt(),r=0;for(let[o,s]of Object.entries(n))await $t("prlinks",{id:lh(o),updatedAt:Date.now(),...s},{trackId:s.trackId??null}).catch(()=>{}),r++;return{pushed:r,source:"plane"}}var bh=t=>Math.round(t*100)/100;function rA(t){return!t||t.kind==="setup"?[]:jt(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():yh.basename(e.base)}))}function kh(t){let e=new Map;for(let n of t||[])for(let r of rA(n)){let o=e.get(r.key);o||e.set(r.key,o={...r,tracks:[]}),o.tracks.push(n)}return e}function Ma(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 dl=(t,e)=>`${t} ${e}${t===1?"":"s"}`;function oA(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 fl(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(dl(s,"PR")+" opened");let a=Object.keys(t?.subtasks||{}).length||r.filter(u=>u.type==="subtask-delegated").length;a&&n.push(dl(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(`${dl(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)?oA(t.archivedAt-t.createdAt):null;return n.length&&l&&n.push(l),n.length?n.join(" \xB7 "):null}function sA(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 xh(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=Ma(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:bh(l),firstActivityAt:c,lastActivityAt:i}}async function Sh(t){let e=kh(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 L(i).catch(()=>[]);r.set(i.id,ue(c).totalUsd)}let s=[...e.values()].map(a=>xh(a,r,n)).sort((a,i)=>(i.lastActivityAt??0)-(a.lastActivityAt??0));return{generatedAt:Date.now(),arcs:s}}async function vh(t,e){let r=kh(t).get(e);if(!r)return null;let[o,s,a,i,c]=await Promise.all([ye().catch(()=>[]),Pn().catch(()=>[]),ga().catch(()=>[]),ka().catch(()=>[]),Ea().catch(()=>({}))]),l=new Map,u=[],f=(d,p)=>(Ma(p)||p.createdAt||0)-(Ma(d)||d.createdAt||0);for(let d of[...r.tracks].sort(f)){let p=await L(d).catch(()=>[]),m=ue(p);l.set(d.id,m.totalUsd);let h=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:sA(d),handoffs:(d.events||[]).filter(y=>y.type==="lead-handoff").length,subtasks:Object.keys(d.subtasks||{}).length,repos:jt(d.workspace).map(y=>y.repo||yh.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:h?{provider:h.provider,key:h.key,url:h.url??null}:null,usd:bh(m.totalUsd),lastActivityAt:Ma(d)})}return{...xh(r,l,o),generatedAt:Date.now(),segments:u}}import{execFile as aA}from"node:child_process";import{promisify as iA}from"node:util";import{randomUUID as cA}from"node:crypto";var Er=iA(aA),Na=t=>new Promise(e=>setTimeout(e,t)),lA=Number(process.env.BOSTRAT_SWAP_EXIT_TIMEOUT_MS)||15e3,Th=Number(process.env.BOSTRAT_SWAP_SHELL_TIMEOUT_MS)||8e3,uA=250,dA=400,Ah={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 pl(t,e){let n=Date.now()+e;for(;;){if(await fr(t))return!0;if(Date.now()>n)return!1;await Na(uA)}}async function fA(t,e){let n=Ah[e]||Ah.default;n.escape&&(await bt(t,["Escape"],{kind:"swap-exit"}),await Na(150)),n.wipe&&await zs(t,400,{kind:"swap-exit"}),await Js(t,n.text,{perCharDelayMs:90,kind:"swap-exit"});let r=_e(e).submitDelayMs||0;await Na(Math.max(r,250)),await bt(t,["Enter"],{kind:"swap-exit"})}async function pA(t,e){try{let{stdout:n}=await Er(P,B("show-environment","-t",t,e));return!String(n||"").trimStart().startsWith("-")}catch{return!1}}async function Eh(t){await Er(P,B("respawn-pane","-k","-t",t))}async function ml(t,e={}){if(!gt(t))throw new Error("invalid session name");let n=String(e.agentCmd||"").trim();if(!n)throw new Error("agentCmd is required");let r=await Q(t);if(!r)throw new Error(`session "${t}" not found`);if(!r.cwd)throw new Error(`can't resolve cwd for "${t}"`);let o=r.agent||"agent",s=e.agent||Xe(n),a=Number(e.exitTimeoutMs)||lA;await q({flow:"swap",phase:"start",sessionName:t,from:o,to:s,cwd:r.cwd});let i=!1;if(!await fr(t)&&(await fA(t,o),!await pl(t,a)&&(i=!0,await q({flow:"swap",phase:"respawn-forced",sessionName:t,from:o}),await Eh(t),!await pl(t,Th))))throw new Error(`pane shell did not come back after respawn-pane in "${t}"`);let c=s==="claude"&&Rn(n),l=c?Ms():null,u=await pA(t,"ANTHROPIC_API_KEY"),f=!1;try{l?(await Er(P,B("set-environment","-t",t,"ANTHROPIC_API_KEY",l)),f=!0):u&&(await Er(P,B("set-environment","-u","-t",t,"ANTHROPIC_API_KEY")),f=!0)}catch(y){throw l?qs(y,l):y}if(f&&!i&&(await Eh(t),!await pl(t,Th)))throw new Error(`pane shell did not come back after env respawn in "${t}"`);let d=!!e.resumeSessionId,p=e.resumeSessionId||cA(),m=d?n:ic(n,p),h=d?e.pinned===!0:/(^|\s)--session-id(\s|=)/.test(m);return c&&await Ge(r.cwd),s==="codex"&&await sr(r.cwd),s==="cursor"&&await ar(r.cwd),await Cn(t),await Er(P,B("set-option","-t",t,"@bostrat-sid",p)),await Er(P,B("set-option","-t",t,"@bostrat-agent",s)),await Na(dA),await St(t,m,{kind:"swap-launch"}),await q({flow:"swap",phase:"done",sessionName:t,from:o,to:s,sid:p,forced:i,envRespawned:f}),{name:t,cwd:r.cwd,sessionId:p,agent:s,pinned:h,forced:i,envRespawned:f}}import GA from"node:fs/promises";function mA(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 Ho(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 Wo(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 Ir=(t,e)=>t.length>e?`${t.slice(0,e).trimEnd()}\u2026`:t,_t=t=>t&&!/[$`]/.test(t)?t:null,mt=t=>t.replace(/\/+$/,"").split("/").pop()||t,_r=t=>{let e=t.replace(/\/+$/,"").split("/").filter(Boolean);return e.length>3?e.slice(-2).join("/"):t},Rh=t=>t.startsWith("-"),_h=new Set(["-g","--glob","-t","--type","-A","-B","-C","-m","--max-count","-e","--regexp","-f","--file"]);function ct(t,e=new Set){let n=[];for(let r=1;r<t.length;r++){let o=t[r];if(o==="<<")break;if(Rh(o)){e.has(o)&&r++;continue}n.push(o)}return n}var hA={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"},wA={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 $h(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 gA(t){let e=$h(t),n=_t(ct(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 hl(t){if(t.includes("--files")){let s=_t(ct(t,_h)[0]);return s&&s!=="."?`List files in ${_r(s)}`:"List files"}let e=ct(t,_h),n=e[0];if(!n)return null;let r=_t(e.slice(1).find(s=>s!==".")),o=Ir(n,40);return r?`Search for \u201C${o}\u201D in ${_r(r)}`:`Search for \u201C${o}\u201D`}function wl(t){let e=ct(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 gl(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=ct(t)[0];return n?`Run ${mt(n)}`:null}function yA(t){let e=ct(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 Ih(t){return t.find(e=>/^https?:\/\//.test(e))||null}var bA={git:t=>{let e=ct(t,new Set(["-C","-c"])),n=hA[e[0]];return n?n(e,t):null},gh:t=>{let e=ct(t),n=wA[e[0]];return n?n(e):null},sed:gA,rg:hl,grep:hl,egrep:hl,nl:t=>{let e=_t(ct(t)[0]);return e?`Read ${mt(e)}`:null},cat:t=>{let e=_t(ct(t)[0]);return e?`Read ${mt(e)}`:null},head:t=>{let e=_t(ct(t,new Set(["-n","-c"])).find(n=>!/^\d+$/.test(n)));return e?`Read the start of ${mt(e)}`:null},tail:t=>{let e=_t(ct(t,new Set(["-n","-c"])).find(n=>!/^\d+$/.test(n)));return e?`Read the end of ${mt(e)}`:null},wc:t=>{let e=_t(ct(t)[0]);return e?`Count lines in ${mt(e)}`:"Count input lines"},ls:t=>{let e=_t(ct(t)[0]);return e&&e!=="."?`List files in ${_r(e)}`:"List files"},find:t=>_t(t[1])&&!Rh(t[1])&&t[1]!=="."?`Find files in ${_r(t[1])}`:"Find files",tree:()=>"List the directory tree",mkdir:t=>{let e=_t(ct(t)[0]);return e?`Create directory ${_r(e)}`:null},touch:t=>{let e=_t(ct(t)[0]);return e?`Create ${mt(e)}`:null},rm:t=>{let e=_t(ct(t)[0]);return e?`Delete ${_r(e)}`:null},mv:t=>{let e=_t(ct(t)[0]);return e?`Move ${mt(e)}`:null},cp:t=>{let e=_t(ct(t)[0]);return e?`Copy ${mt(e)}`:null},chmod:t=>{let e=_t(ct(t)[1]);return e?`Change permissions on ${mt(e)}`:null},diff:t=>{let e=ct(t);return e.length>=2?`Compare ${mt(e[0])} and ${mt(e[1])}`:null},curl:t=>{let e=Ih(t);return e?`Fetch ${Ir(e,56)}`:"Fetch a URL"},wget:t=>{let e=Ih(t);return e?`Fetch ${Ir(e,56)}`:"Fetch a URL"},npm:wl,pnpm:wl,yarn:wl,npx:t=>{let e=ct(t)[0];return e?`Run ${e}`:null},node:t=>gl(t,"Node"),python:t=>gl(t,"Python"),python3:t=>gl(t,"Python"),make:t=>{let e=ct(t)[0];return e?`Run make ${e}`:"Run make"},systemctl:yA,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=ct(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=ct(t)[0];return e?`Locate ${e}`:null},command:t=>t[1]==="-v"&&t[2]?`Locate ${t[2]}`:null,fly:t=>{let e=ct(t)[0];return e==="deploy"?"Deploy to Fly":e?`Run fly ${e}`:null}},kA=new Set(["pwd","echo","cd","sleep","true","false","test","[","set","export","exit","read"]);function xA(t){let e=Wo(Ho(t));if(!e.length)return null;if(/^(ba|z)?sh$/.test(mt(e[0]))&&(e[1]==="-lc"||e[1]==="-c")&&e[2])return yl(e[2]);let n=mt(e[0]);if(kA.has(n))return null;if(e.includes("--version"))return`Check ${n} version`;if(n.endsWith(".sh"))return`Run ${n}`;let r=bA[n];return r?r(e.map((o,s)=>s===0?n:o)):null}function yl(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=mA(e);for(let i=0;i<r.length-1;i++){let c=Wo(Ho(r[i])),l=Wo(Ho(r[i+1]));if(!(!c.length||!l.length)&&["nl","cat"].includes(mt(c[0]))&&mt(l[0])==="sed"){let u=$h(l),f=ct(c)[0];if(u&&f)return Ir(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=xA(i);c&&o.push({d:c,head:mt(Wo(Ho(i))[0]||""),sub:ct(Wo(Ho(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":Ir(s?s.d:o[0].d,88)}var kl=new Set(["Bash"]);function Rr(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||yl(t)||Rr(t)}function bl(t,e){if(t){if(Array.isArray(t)){for(let n of t)bl(n,e);return}if(typeof t=="object"){if(t.type==="tool_use"&&kl.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))bl(n,e)}}}async function Oh(t,{maxBytes:e=1048576,limit:n=400}={}){let r;try{r=await Pt(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(bl(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 Ch(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,h)=>h>=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 SA=/^Async (?:agent|task) launched/i,vA=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 Mh(t,{maxBytes:e=2097152,limit:n=100}={}){let r;try{r=await Pt(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?Rr(p):null),detail:p?Rr(p):null,ts:f,proven:!0})}}else d.type==="tool_result"&&d.tool_use_id&&SA.test(vA(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 qo from"node:fs";import AA from"node:fs/promises";import Wn from"node:path";import EA from"node:os";import _A from"node:crypto";import IA from"node:zlib";var Pa=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,$r=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 xl=t=>`"${String(t).replace(/"/g,'\\"')}"`;function TA(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 Nh(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 ${$r(r)}`:`Read ${$r(r)}`}}case"Grep":{let r=e?.pattern;if(!r)return null;let o=e?.path?un(e.path,n):null,s=e?.glob?` --glob ${xl(e.glob)}`:"",a=ne(String(r),40);return{command:ne(`grep ${xl(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 ${xl(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 ${$r(e.path)}`}:null;case"Write":return e?.path?{command:ne(`write ${un(e.path,n)}`,140),description:`Write ${$r(e.path)}`}:null;case"Delete":return e?.path?{command:ne(`delete ${un(e.path,n)}`,140),description:`Delete ${$r(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=TA(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 ${$r(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 RA=process.env.BOSTRAT_CURSOR_CHATS||Wn.join(EA.homedir(),".cursor","chats");function Dh(t){let e=_A.createHash("md5").update(String(t)).digest("hex");return Wn.join(RA,e)}function jh(t){try{let e=JSON.parse(qo.readFileSync(Wn.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 $A(t){let e=Dh(t),n;try{n=qo.readdirSync(e,{withFileTypes:!0}).filter(o=>o.isDirectory())}catch{return null}let r=null;for(let o of n){let s=Wn.join(e,o.name),a=Wn.join(s,"store.db");if(!qo.existsSync(a))continue;let i=jh(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 Ba(t,e){if(!t)return null;if(e){let n=Wn.join(Dh(t),e),r=Wn.join(n,"store.db");if(qo.existsSync(r))return{id:e,dir:n,store:r,...jh(n)||{createdAtMs:null,updatedAtMs:null}}}return $A(t)}function Ph(t,e){let n=t.prepare("SELECT data FROM blobs WHERE id = ?").get(e);return n?Buffer.from(n.data):null}function OA(t){if(!t)return null;let e=t;if(e.length>1&&e[0]===31&&e[1]===139)try{e=IA.gunzipSync(e)}catch{}try{return JSON.parse(e.toString("utf8"))}catch{return null}}function CA(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 MA(t,e){if(!Array.isArray(t))return[];let n=[];for(let r of t){if(!r||r.type!=="tool-call"||!Pa.has(r.toolName))continue;let o=Nh(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 NA=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():"",PA=t=>Array.isArray(t)?t.filter(e=>e&&e.type==="tool-call"&&e.toolName).map(e=>e.toolName):[],Bh=process.getBuiltinModule?.("node:sqlite"),Or={turns:[],trailingCommands:[],model:null,requestIds:[]};async function Sl(t,e){if(!Bh)return Or;let{DatabaseSync:n}=Bh,r;try{r=new n(t,{readOnly:!0})}catch{return Or}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 Or;let s;try{s=JSON.parse(Buffer.from(String(o.value),"hex").toString("utf8"))}catch{return Or}let a=s.lastUsedModel||null,i=s.latestRootBlobId?Ph(r,s.latestRootBlobId):null;if(!i)return{...Or,model:a};let c=[],l=[],u=[],f=null,d=[];for(let p of CA(i)){let m=OA(Ph(r,p));if(!m)continue;let h=m.providerOptions?.cursor;if(h?.requestId&&d.push(h.requestId),h?.modelProviderMessageId&&d.push(h.modelProviderMessageId),m.role!=="assistant")continue;if(Array.isArray(m.content))for(let S of m.content){let I=S?.providerOptions?.cursor?.modelName;I&&(f=I)}l.push(...MA(m.content,e).map((S,I)=>({...S,id:S.id||`${p}:${I}`}))),u.push(...PA(m.content));let y=NA(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 Or}finally{try{r.close()}catch{}}}function Lh(t,e){let n=t.updatedAtMs;if(!n)try{n=Math.round(qo.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 Fh(t,e,{limit:n=80}={}){let r=Ba(t,e);if(!r)return[];let{turns:o,model:s}=await Sl(r.store,t);if(!o.length)return[];let a=o.slice(-n),{tsAt:i}=Lh(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 Uh(t,e){let n=Ba(t,e);if(!n)return null;let{turns:r,requestIds:o,model:s}=await Sl(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 Hh(t,e,{limit:n=80}={}){let r=Ba(t,e);if(!r)return[];let{turns:o,trailingCommands:s}=await Sl(r.store,t);if(!o.length&&!s.length)return[];let a=o.slice(-n),{end:i,tsAt:c}=Lh(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 Wh(t,e){let n=Ba(t,e);if(!n)return null;if(n.updatedAtMs)return n.updatedAtMs;try{return Math.round((await AA.stat(n.store)).mtimeMs)}catch{return null}}import qh from"node:fs";import Tl from"node:fs/promises";import vl from"node:path";import BA from"node:os";var DA=process.env.BOSTRAT_CODEX_SESSIONS||vl.join(process.env.CODEX_HOME||vl.join(BA.homedir(),".codex"),"sessions");async function Kh(t){try{return await Tl.realpath(t)}catch{return t}}function jA(t=DA){let e=[],n=r=>{let o;try{o=qh.readdirSync(r,{withFileTypes:!0})}catch{return}for(let s of o){let a=vl.join(r,s.name);if(s.isDirectory())n(a);else if(s.isFile()&&s.name.startsWith("rollout-")&&s.name.endsWith(".jsonl")){let i=/-([0-9a-f-]{36})\.jsonl$/i.exec(s.name);try{e.push({id:i?i[1]:null,file:a,mtimeMs:qh.statSync(a).mtimeMs})}catch{}}}};return n(t),e.sort((r,o)=>o.mtimeMs-r.mtimeMs)}async function LA(t,e=1048576){let n;try{n=await Tl.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 FA(t){let e=await LA(t);if(!e)return null;let n;try{n=JSON.parse(e)}catch{return null}return n?.type==="session_meta"&&n?.payload?.cwd||null}async function qn(t,e){if(!t&&!e)return null;let n=jA();if(e){let o=n.find(s=>s.id===e);return o||null}let r=await Kh(t);for(let o of n){let s=await FA(o.file);if(s&&(s===t||await Kh(s)===r))return o}return null}var UA=new Set(["output_text","text"]),HA=t=>typeof t=="string"?t.trim():Array.isArray(t)?t.filter(e=>e&&typeof e.text=="string"&&UA.has(e.type)).map(e=>e.text).join(`
|
|
98
|
+
`).trim():"";async function Gh(t,e,{limit:n=80,maxBytes:r=524288}={}){let o=await qn(t,e);if(!o)return[];let s;try{s=await Pt(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:El,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 h=m.payload;if(!h)continue;if((h.type==="function_call"||h.type==="custom_tool_call")&&h.name){u=!0,Al.has(h.name)||c.push(h.name);continue}if(h.type!=="message"||h.role!=="assistant")continue;u=!0;let y=HA(h.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 Ko=new Set(["exec_command","shell","exec"]),Al=new Set(["wait","write_stdin"]),El="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 WA(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 qA(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 KA(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=qA(i.slice(c.index+c[0].length).trimStart());l&&l.trim()&&o.push(l.trim())}),o}async function Vh(t,e,{limit:n=400,maxBytes:r=524288}={}){let o=await qn(t,e);if(!o)return[];let s;try{s=await Pt(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"&&Ko.has(u.name)){let p;try{p=JSON.parse(u.arguments||"{}")}catch{continue}let m=WA(p);m&&(f=[m])}else u.type==="custom_tool_call"&&u.name==="exec"&&(f=KA(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 zh(t,e){let n=await qn(t,e);if(!n)return null;try{return Math.round((await Tl.stat(n.file)).mtimeMs)}catch{return n.mtimeMs||null}}var Cr=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}},_l=class extends Cr{transcript(e){return!e?.cwd||!e?.sessionId?null:Ae(e.cwd,e.sessionId)}async readMessages(e,n){let r=this.transcript(e);return r?wc(r,n):[]}async readCommands(e,n){let r=this.transcript(e);return r?Oh(r,n):[]}async readJobs(e,n){let r=this.transcript(e);return r?Mh(r,n):[]}async updatedAt(e){let n=this.transcript(e);if(!n)return null;try{return Math.round((await GA.stat(n)).mtimeMs)}catch{return null}}},Il=class extends Cr{async readMessages(e,n){return Fh(e.cwd,e.sessionId,n)}async readCommands(e,n){return Hh(e.cwd,e.sessionId,n)}async updatedAt(e){return Wh(e.cwd,e.sessionId)}},Rl=class extends Cr{sessionId(e){return e?.pinned?e.sessionId:null}async readMessages(e,n){return Gh(e.cwd,this.sessionId(e),n)}async readCommands(e,n){return Vh(e.cwd,this.sessionId(e),n)}async updatedAt(e){return zh(e.cwd,this.sessionId(e))}},VA=new Cr("unknown"),Jh=new Map;function $l(t){return Jh.set(t.agent,t),t}function Go(t){return Jh.get(t)||VA}$l(new _l("claude",kl));$l(new Il("cursor",Pa));$l(new Rl("codex",Ko));var Ol=t=>e=>({...e,box:t.name,agent:t.agent,boxRole:t.role});async function Cl(t,e={}){return(await Go(t.agent).readMessages(t,e)).map(Ol(t))}async function Xh(t,e={}){return(await Go(t.agent).readCommands(t,e)).map(Ol(t))}async function Yh(t,e={}){return(await Go(t.agent).readJobs(t,e)).map(Ol(t))}var tw=/to navigate\b.*\bEsc to cancel|Enter to select\b/,Pl=/Chat about this/i,Kn=/^\s*(❯)?\s*(\d+)\.\s+(?:\[([ xX✔])\]\s+)?(.+?)\s*$/,Zh=/^(type something|chat about this|submit)\b/i,Gn=/^[─—-]{6,}\s*$/,Bl=/[\u2500-\u257F\u2550-\u256F┌┐└┘├┤│┬┴┼─✂]/;function Vn(t){let e=t.search(Bl);return(e>=0?t.slice(0,e):t).trim()}function zA(t){if(!t)return"";let e=t.replace(/[│┌└├─┘┤┬┴┼─✂].*$/,"").trim();return!e||/hidden|\d+\s+lines?/i.test(e)?"":e}function JA(t){let e=String(t).trim();return!!(!e||Gn.test(t)||Bl.test(e)||/hidden|\d+\s+lines?/i.test(e)||/notespress|press n to add/i.test(e)||/^\s*notes?\s*$/i.test(e))}function XA(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=YA(n,e);return n.forEach((a,i)=>{a.current=i===s}),{tabs:n,questionIndex:s,questionTotal:n.length,currentTabLabel:n[s].label}}function YA(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 Qh(t){return t.replace(/[←→✔☐☒●▶]/g," ").replace(/\bSubmit\b/g," ").replace(/\s+/g," ").trim()}var ew=/^\s*Switch model\?\s*$/m;function ZA(t,e=""){let n=String(t);if(!ew.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(Kn);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&&!Gn.test(r[u])&&a.push(d)}}if(s.length<2)return null;let i=s.map(u=>({label:Vn(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 nw=/^\s*Enter to confirm\s*·\s*Esc to cancel\s*$/,QA=/^\s*[─—▔-]{6,}\s*$/;function tE(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||!nw.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(Kn);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&&!(QA.test(n[c])||Gn.test(n[c]));c--){let l=n[c].trim();l&&a.unshift(l)}let i=s.map(c=>({label:Vn(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 eE(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(Kn);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 Ml=/Resume from summary/i,nE=/^\s*This session is\b/;function rE(t,e=""){let n=String(t);if(!Ml.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
105
|
+
`),o=-1;for(let u=r.length-1;u>=0;u--)if(nE.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(Kn);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&&!Gn.test(r[u])&&a.push(d)}}if(s.length<2||!s.some(u=>Ml.test(u.label)))return null;let i=s.map(u=>({label:Vn(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 zn=/shift\+tab to cycle|⏵⏵ bypass permissions on|⏸ manual mode on/;function Dl(t){let e=[];for(let r=0;r<t.length;r++){let o=t[r].match(Kn);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 rw=/Choose the text style that looks best with your terminal/,ow=/Let's get started\./;function oE(t,e=""){let n=String(t);if(!rw.test(n)||!ow.test(n)||zn.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
106
|
+
`),o=Dl(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:Vn(s.label),description:""}))}]}:null}var sw=/Select login method:/;function sE(t,e=""){let n=String(t);if(!sw.test(n)||zn.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
107
|
+
`),o=Dl(r);if(!o)return null;let s=o.map(a=>{let[i,...c]=a.label.split("\xB7");return{label:Vn(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 jl=/Paste code here if prompted/,aE=/^[A-Za-z0-9%&=+._~#?/:\-]+$/;function iE(t,e){let n=t[e].trim();for(let r=e+1;r<t.length;r++){let o=t[r].trim();if(!o||!aE.test(o)||jl.test(t[r]))break;n+=o}return n}function cE(t,e=""){let n=String(t);if(!jl.test(n)||zn.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=iE(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 Nl=/OAuth error:/,lE=/Press Enter to retry\./;function uE(t,e=""){let n=String(t);if(!Nl.test(n)||!lE.test(n)||zn.test(n))return null;let s=([...n.replace(/\r/g,"").split(`
|
|
109
|
+
`)].reverse().find(a=>Nl.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 aw=/Quick safety check: Is this a project you created or one you trust/;function dE(t,e=""){let n=String(t);if(!aw.test(n)||zn.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
110
|
+
`),o=Dl(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:Vn(s.label),description:""}))}]}:null}var iw=/^(.{0,60}?)Press\s+Enter\s+to\s+continue\s*[.…]{0,3}\s*$/;function fE(t,e=""){let n=String(t);if(zn.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(iw);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(Gn.test(r[l])||Bl.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 pE(t){let e=-1;for(let r=t.length-1;r>=0;r--)if(Pl.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 mE(t,e=""){let n=String(t);if(!tw.test(n)||!Pl.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
112
|
+
`),o=pE(r);if(!o)return null;let{headerLine:s,chatLine:a,optStart:i,optEnd:c}=o,l=[],u=-1;for(let T=i;T<c;T++){let C=r[T].match(Kn);C&&(u<0&&(u=T),l.push({line:T,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 T of l)for(let C=T.line+1;C<Math.min(r.length,T.line+4)&&!(Kn.test(r[C])||Gn.test(r[C]));C++){let D=r[C].trim();if(!(!D||JA(r[C])||Zh.test(D))){T.desc=zA(D);break}}let f=l.filter(T=>!Zh.test(T.label));if(!f.length)return null;let d=f.some(T=>T.hasCheckbox),p=Math.max(0,f.findIndex(T=>T.cursor)),m=s>=0?s+1:0,h=[];for(let T=m;T<u;T++){let C=r[T].trim();C&&!Gn.test(r[T])&&!/[☐☒]/.test(r[T])&&h.push(C)}let y=h.join(" ")||(s>=0?Qh(r[s]):"")||"The agent is asking a question",b=s>=0?XA(r[s],y):null,S=b?.currentTabLabel||(s>=0?Qh(r[s]):""),I=f.map(T=>({label:Vn(T.label),description:T.desc})),M=I.map(T=>T.label).join("|"),$=`claude:${b?`q${b.questionIndex+1}/${b.questionTotal}`:"q1/1"}:${S}:${y}:${M}`.slice(0,240),F=b?`claude:ask:${b.tabs.map(T=>T.label).join("|")}`.slice(0,240):$;return{provider:"claude",box:e,multiSelect:d,cursorIndex:p,promptId:$,groupId:F,...b?{questionIndex:b.questionIndex,questionTotal:b.questionTotal,currentTabLabel:b.currentTabLabel,tabs:b.tabs}:{},questions:[{question:y,header:S,multiSelect:d,options:I}]}}function Mr(t,e){let n=String(e||"");return n?t==="claude"?tw.test(n)&&Pl.test(n)||ew.test(n)||Ml.test(n)||nw.test(n.trimEnd().split(`
|
|
113
|
+
`).pop()||"")?!0:zn.test(n)?!1:rw.test(n)&&ow.test(n)||sw.test(n)||jl.test(n)||Nl.test(n)||aw.test(n)||iw.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 Jn(t,e){return!t||!e?null:t.agent==="claude"?eE(e,t.name)||mE(e,t.name)||ZA(e,t.name)||rE(e,t.name)||uE(e,t.name)||cE(e,t.name)||sE(e,t.name)||oE(e,t.name)||dE(e,t.name)||fE(e,t.name)||tE(e,t.name):t.agent==="cursor"||t.agent==="codex"?Xf(t.agent,e,t.name):null}async function Ll(t){if(!t)return null;let e=await Ze(t.name).catch(()=>"");return Jn(t,e)}function cw(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 lw(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 Nr(t){return typeof t=="string"?t.trim():""}var uw=/^\s*─{8,}\s*$/,hE=/^❯[ ]?(.*)$/;function Vo(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(uw.test(n[c])){r=c;break}if(r<=0)return null;let o=-1;for(let c=r-1;c>=0;c--)if(uw.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(hE);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 Tt(t){return String(t||"").replace(/\s+/g," ").trim()}function dw(t,e,{slackMs:n=2e4}={}){let r=Tt(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&&Tt(o.text).includes(r))}function fw(t,e,n,{busy:r=!1}={}){let o=Vo(t,n);return o&&Fl(e,o)?"stuck":r||o?"submitted":"unknown"}function Fl(t,e){let n=Tt(t),r=Tt(e).replace(/^…\s*/,"").replace(/\s*…$/,"");return!n||!r||r.length<Math.min(n.length,12)?!1:n===r||n.includes(r)}import wE from"node:crypto";var Ul=new Set(["cursor","codex"]),mw=4,gE=4,yE=2,bE=24,pw=/^[ \t]{0,3}(`{3,})bostrat-question[ \t]*\r?\n([\s\S]*?)\r?\n[ \t]{0,3}\1`*[ \t]*$/gm,zo=t=>typeof t=="string"?t.trim():"";function kE(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,mw)){let o=zo(r?.question);if(!o)return null;let s=Array.isArray(r?.options)?r.options:[],a=[];for(let i of s.slice(0,gE)){let c=zo(i?.label);if(!c)return null;let l=zo(i?.description);a.push(l?{label:c,description:l}:{label:c})}if(a.length<yE)return null;n.push({question:o,header:zo(r?.header).slice(0,bE),multiSelect:r?.multiSelect===!0,options:a})}return n.length?n:null}function Hl(t){if(typeof t!="string"||!t.includes("```bostrat-question"))return null;pw.lastIndex=0;let e=null;for(let o;o=pw.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=kE(n);return r?{text:t.slice(0,e.index).trimEnd(),questions:r}:null}function Wl(t,e,n){let r=wE.createHash("sha256").update(JSON.stringify(n)).digest("hex").slice(0,8);return`${t}:ask:${e||"turn"}:${r}`}function ql(t,{leadBoxId:e,provider:n,isAnswered:r,maxQuestions:o=mw}={}){if(!Array.isArray(t)||!e||!Ul.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||Hl(s.text)?.questions;if(!a?.length)return null;let i=a.slice(0,Math.max(1,o)),c=Wl(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 hw(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=zo(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 ww=`
|
|
119
119
|
When you are BLOCKED on a decision only the operator can make \u2014 an ambiguous
|
|
120
120
|
requirement, an irreversible choice, several valid approaches \u2014 do not bury the
|
|
121
121
|
question in prose. End your message with exactly ONE fenced code block tagged
|
|
@@ -135,44 +135,42 @@ your turn. The operator sees the questions as an interactive card in their chat
|
|
|
135
135
|
answer. Never use the block for rhetorical questions, progress updates, or
|
|
136
136
|
anything you can decide yourself; keep asking in prose for questions without
|
|
137
137
|
enumerable options.
|
|
138
|
-
`;import{execFile as
|
|
139
|
-
`).filter(Boolean);return e&&(r=r.filter(o=>{let s=o.slice(3);return!(s===".bostrat"||s===".bostrat/"||s.startsWith(".bostrat/")||o.startsWith("??")&&(s==="AGENTS.md"||s==="CLAUDE.md"))})),r.length>0}async function
|
|
140
|
-
`).map(s=>s.trim()).filter(Boolean)}async function
|
|
141
|
-
`).map(n=>n.trim()).filter(Boolean)}catch{return[]}}import
|
|
142
|
-
`),n=e.findIndex(o=>/Select Model and Effort/i.test(o));if(n<0)return null;let r=[];for(let o=n+1;o<e.length;o++){let s=e[o].match(/^\s*(›\s*)?(\d+)\.\s+(\S+?)(?:\s+\((current|default)\))?(?:\s{2,}|\s*$)/);if(s)r.push({n:Number(s[2]),id:s[3],current:s[4]==="current",active:!!s[1]});else if(r.length&&/Press enter|esc to go back/i.test(e[o]))break}return r.length?r:null}function
|
|
143
|
-
`),n=e.findIndex(o=>/Select Reasoning Level/i.test(o));if(n<0)return null;let r=[];for(let o=n+1;o<e.length;o++){let s=e[o].match(/^\s*(›\s*)?(\d+)\.\s+([A-Za-z ]+?)(?:\s+\((default|current)\))?(?:\s{2,}|\s*$)/);if(s)r.push({n:Number(s[2]),label:s[3].trim(),isDefault:!!s[4],active:!!s[1]});else if(r.length&&/Press enter|esc to go back/i.test(e[o]))break}return r.length?r:null}function
|
|
144
|
-
`);for(let n=e.length-1;n>=0;n--){let r=e[n].match(/Model changed to\s+(\S+)(?:\s+(\S+))?\s*$/i);if(r)return{model:r[1],effort:r[2]||null}}return null}function
|
|
145
|
-
`),n=e.findIndex(s=>/Available models|Models matching/i.test(s));if(n<0)return null;let r=/Models matching/i.test(e[n]),o=[];for(let s=n+1;s<e.length;s++){let a=e[s];if(/^\s*Filter:/i.test(a)||!a.trim())continue;if(/to filter|Enter to select|^\s*\d+-\d+ of \d+/i.test(a))break;let i=a.match(/^\s*(→\s+)?(.+?)\s*$/);if(!i)continue;let c=i[2].replace(/\s*\(Tab to modify\)\s*$/i,""),l=null,u=c.split(/\s{2,}/);u.length>1&&(c=u[0],l=u.slice(1).join(" ").trim()||null),c&&o.push({label:c.trim(),params:l,active:!!i[1]})}return o.length?{filtered:r,rows:o}:null}function _w(t,e){if(typeof e!="string"||!e.trim())throw new Error("model (string) required");let n=e.trim();if(t==="codex"){let[r,o]=n.split(/[\s:]+/);if(o&&!Tw.has(o))throw new Error(`unknown codex effort "${o}" (low | medium | high | xhigh)`);return{model:r,effort:o||null}}if(t==="cursor"){let r=Aw.find(o=>o.alias===n);return{model:n,filter:r?r.label:n}}if(!IE.has(n))throw new Error(`unknown model "${n}"`);return{model:n}}async function Vo(t,e,{timeoutMs:n=EE}={}){let r=Date.now()+n;for(;;){let o=await ft(t,{lines:80}).catch(()=>""),s=e(o);if(s)return s;if(Date.now()>r)return null;await Ba(_E)}}async function OE(t,e){return await St(t,`/model ${e.model}`,_e("claude")),{verified:!1,note:"confirmed when the next turn's transcript model flips"}}async function CE(t,e){await Ws(t),await St(t,"/model",{enter:!1}),await Ba(250),await bt(t,["Enter"],{kind:"model-switch"});let n=await Vo(t,kw);if(!n)throw new Error("codex model picker didn't open");let r=n.find(i=>i.id===e.model);if(!r)throw await bt(t,["Escape"],{kind:"model-switch"}).catch(()=>{}),new Error(`unknown model "${e.model}" \u2014 codex offers: ${n.map(i=>i.id).join(", ")}`);let o=i=>{let c=$E(i);return!c||c.model!==e.model||e.effort&&c.effort&&c.effort.toLowerCase()!==e.effort.toLowerCase()?null:c};await St(t,String(r.n),{enter:!1,kind:"model-switch"});let s=await Vo(t,i=>xw(i)||!kw(i)&&o(i)&&{done:o(i)});if(!s)throw await bt(t,["Escape"],{kind:"model-switch"}).catch(()=>{}),new Error("codex reasoning-level picker didn't open");if(!s.done){let i=e.effort?Tw.get(e.effort):null,c=i?s.find(l=>l.label.toLowerCase()===i.toLowerCase()):s.find(l=>l.isDefault)||s[0];if(!c)throw await bt(t,["Escape","Escape"],{kind:"model-switch"}).catch(()=>{}),new Error(`codex doesn't offer effort "${e.effort}" for ${e.model}`);await St(t,String(c.n),{enter:!1,kind:"model-switch"})}let a=s.done||await Vo(t,i=>!xw(i)&&o(i));if(!a)throw new Error("codex never confirmed the model change");return{verified:!0,model:a.model,effort:a.effort}}async function ME(){let t=bw.join(process.env.CURSOR_CONFIG_DIR||bw.join(AE.homedir(),".cursor"),"cli-config.json");try{return JSON.parse(await TE.readFile(t,"utf8"))?.model||null}catch{return null}}async function NE(t,e){await Ws(t),await Ks(t,`/model ${e.filter}`,{kind:"model-switch"});let n=await Vo(t,Sw),r=c=>String(c||"").toLowerCase(),o=c=>r(c.label).startsWith(r(e.filter)),s=n?.rows.find(c=>c.active)||null;if(n&&!(s&&o(s)))for(let c=0;c<12;c++){let l=await Vo(t,Sw,{timeoutMs:1500});if(s=l?.rows.find(u=>u.active)||null,s&&o(s))break;if(!l||!l.rows.some(o)){s=null;break}await bt(t,["Down"],{kind:"model-switch"}),await Ba(250)}if(!n||!s||!o(s)){await bt(t,["Escape"],{kind:"model-switch"}).catch(()=>{});let c=Array(`/model ${e.filter}`.length).fill("BSpace");throw await bt(t,c,{settleMs:15,kind:"model-switch"}).catch(()=>{}),new Error(n?`cursor picker has no model matching "${e.filter}"`:"cursor model picker didn't open")}await bt(t,["Enter"],{kind:"model-switch"}),await Ba(800);let a=await ME();return{verified:!!a&&(a.modelId===e.model||a.displayModelId===e.model||r(a.displayName).startsWith(r(e.filter))),model:a?.modelId||null,note:"cursor applies this as the global default for new sessions too"}}async function Iw(t,e,n){return t==="codex"?CE(e,n):t==="cursor"?NE(e,n):OE(e,n)}var Kt=t=>t||null;function ja(t){let e=t||{},n=PE(e);return n&&(n.payload={ts:e.at,...n.payload}),n}function PE(t){switch(t.type){case"said":return{type:"turn.user",actor:"user",source:"orchestrator",boxId:t.box,provider:Kt(t.agent),payload:{text:t.text,boxRole:t.boxRole,...t.kind?{kind:t.kind}:{},...t.attachments?.length?{attachments:t.attachments}:{}}};case"unsaid":return{type:"turn.retracted",actor:"user",source:"orchestrator",boxId:t.box,provider:Kt(t.agent),payload:{saidTs:t.saidTs,text:t.text,boxRole:t.boxRole}};case"orchestration-prompt":return{type:"turn.user",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Kt(t.agent),payload:{text:t.prompt,kind:t.kind,boxRole:t.boxRole}};case"lead-handoff":return{type:"handoff.done",actor:"orchestrator",source:"orchestrator",boxId:t.to,provider:Kt(t.toAgent),payload:{from:t.from,fromAgent:t.fromAgent,to:t.to,toAgent:t.toAgent,reason:t.reason,handoffStartedAt:t.handoffStartedAt,fromSessionId:t.fromSessionId,toSessionId:t.toSessionId,epoch:t.epoch,singleBox:t.singleBox,forced:t.forced,recovered:t.recovered}};case"lead-handoff-failed":return{type:"handoff.failed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Kt(t.toAgent),payload:{box:t.box,fromAgent:t.fromAgent,toAgent:t.toAgent,reason:t.reason,error:t.error,rolledBack:t.rolledBack,handoffStartedAt:t.handoffStartedAt}};case"subtask-delegated":return{type:"subtask.delegated",actor:"orchestrator",source:"orchestrator",boxId:t.to,provider:Kt(t.toAgent),payload:{task:t.task,subtaskId:t.subtaskId,reason:t.reason}};case"subtask-merged":return{type:"subtask.merged",actor:"orchestrator",source:"orchestrator",boxId:t.box||t.from,provider:Kt(t.agent),payload:{...Pr(t)}};case"subtask-failed":return{type:"error",actor:"system",source:"orchestrator",boxId:t.box||t.to,provider:Kt(t.agent||t.toAgent),payload:{kind:"subtask-failed",...Pr(t)}};case"subtask-resumed":return{type:"box.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Kt(t.agent),payload:{change:"subtask-resumed",...Pr(t)}};case"model-change":return{type:"box.state.changed",actor:"user",source:"orchestrator",boxId:t.box,payload:{change:"model",model:t.to,from:t.from??null,verified:t.verified??null}};case"codex-update":return{type:"box.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:"codex",payload:{change:"cli-update",from:t.from??null,to:t.to??null,status:t.status,...t.error?{error:t.error}:{}}};case"opened":return{type:"box.state.changed",actor:"user",source:"orchestrator",boxId:t.box,payload:{change:"opened",...Pr(t)}};case"archived":case"unarchived":case"track-deleted":return{type:"box.state.changed",actor:"user",source:"orchestrator",payload:{change:t.type,...Pr(t)}};case"rehydrated":return{type:"box.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Kt(t.agent),payload:{change:"rehydrated"}};case"retagged":return{type:"box.state.changed",actor:"user",source:"orchestrator",payload:{change:"retagged",...Pr(t)}};case"rehydrate-failed":return{type:"error",actor:"system",source:"orchestrator",boxId:t.box,provider:Kt(t.agent),payload:{kind:"rehydrate-failed",reason:t.reason}};default:return null}}var Da=Promise.resolve(),Yl=t=>(Da=Da.then(t).catch(()=>{}),Da),$w=()=>Da;function Ow(t,e){let n=ja(e);if(!n)return;let r=e?.type==="track-deleted"?{id:t.id}:t;Yl(()=>R(r,n))}var Rw=new Set;async function Cw(t){if(!(!t?.id||Rw.has(t.id))){Rw.add(t.id);try{let e=await D(t),n=new Set(e.filter(r=>r.source==="orchestrator").map(r=>`${r.type}|${r.payload?.ts??"?"}`));for(let r of t.events||[]){let o=ja(r);!o||n.has(`${o.type}|${o.payload?.ts??"?"}`)||await R(t,o)}}catch{}}}function Pr(t){let{type:e,at:n,...r}=t;return r}var Xl=new Map;function Mw(t,e,n){!n?.promptId||Xl.get(t.id)===n.promptId||(Xl.set(t.id,n.promptId),Yl(()=>R(t,{type:"prompt.raised",actor:"agent",source:"pane",boxId:e?.name,provider:Kt(n.provider||e?.agent),payload:{promptId:n.promptId,question:n.question||n.title||null,options:(n.options||[]).map(r=>r?.label??r).slice(0,12)}})))}function Zl(t,e,n,r={}){Xl.delete(t.id),Yl(()=>R(t,{type:"prompt.answered",actor:"user",source:"orchestrator",boxId:e?.name,provider:Kt(n?.provider||e?.agent),payload:{promptId:n?.promptId,...r}}))}import{randomUUID as p_}from"node:crypto";var Pw=/(?<!\b(?:a|an|the|this|that|another|one|each|any|no|separate|single|new|initial|final|first|second|third|last|later|next|subsequent|extra|additional|amend(?:ed)?|squash|merge|follow[\s-]?up|clean[\s-]?up)\s)commit(?!s\b)(?!\s+(?:history|log|message|hash|sha)s?\b)/.source,Bw=/(?<!\b(?:a|an|the|this|that|another|one|each|any|no|separate|single|new|initial|final|first|second|third|last|later|next|subsequent|extra|additional)\s)push(?!\s+notifications?\b)(?:\s+(?:it|this|that|them|everything|the\s+\w+))?\s*(?:up)?/.source,Dw=/open(?:\s+up)?\s+(?:a|the|that)\s+(?:draft\s+)?(?:pr|pull\s+request)|creat(?:e|ing)\s+(?:a|the)\s+(?:draft\s+)?(?:pr|pull\s+request)|rais(?:e|ing)\s+(?:a|the)\s+(?:pr|pull\s+request)|put\s+up\s+(?:a|the)\s+(?:pr|pull\s+request)|send\s+(?:up\s+)?(?:a|the)\s+(?:pr|pull\s+request)/.source,jw=/(?<!\b(?:a|an|the|this|that|another|one|each|any|no|clean|interactive)\s)rebase(?!s\b)(?:\s+(?:it|this|that|them|(?:this|that|the)\s+branch))?(?:\s+(?:on(?:to)?|against|atop|over)\s+\S+)?/.source,BE=new RegExp(`\\b(?:${Pw}|${Bw}|${Dw}|${jw})\\b`,"i"),DE=/\b(?:want\s+me\s+to|would\s+you\s+like(?:\s+me)?(?:\s+to)?|should\s+i|shall\s+i|do\s+you\s+want(?:\s+me)?(?:\s+to)?|happy\s+to|ready\s+for\s+me\s+to|let\s+me\s+know\s+(?:if|when|whether|and)|say\s+the\s+word|give\s+me\s+the\s+(?:word|go[\s-]?ahead)|if\s+you(?:'d|\s+would)?\s+(?:like|want|prefer)|just\s+say\s+(?:so|yes|go))\b/i,jE=/^(?:commit|push|open|create|raise|rebase|ready|ok(?:ay)?\s+to|good\s+to)\b/i,LE=90;function FE(t){return String(t).split(/(?<=[.?!])\s+|\n+/).map(e=>e.trim()).filter(Boolean)}function Lw(t){let e=String(t||"").trim();if(!e)return null;let n=e.slice(-600);for(let r of FE(n).reverse()){if(!BE.test(r))continue;let o=r.endsWith("?")&&r.length<=LE&&jE.test(r);if(DE.test(r)||o)return r.length>200?r.slice(0,197)+"\u2026":r}return null}var Fw=["commit","rebase","push","pr"],UE=new RegExp(`\\b(?:${Pw})\\b`,"i"),HE=new RegExp(`\\b(?:${jw})\\b`,"i"),WE=new RegExp(`\\b(?:${Bw})\\b`,"i"),qE=new RegExp(`\\b(?:${Dw})\\b`,"i"),KE=/\b(?:to|into|onto|on|updat(?:e|es|ing))\s+(?:the|that|this)\s+(?:open\s+|existing\s+|current\s+|same\s+)?(?:draft\s+)?(?:pr|pull\s+request)\b/i;function Uw(t){let e=String(t||""),n=[];UE.test(e)&&n.push("commit"),HE.test(e)&&n.push("rebase"),WE.test(e)&&n.push("push");let r=KE.test(e);!r&&qE.test(e)&&n.push("pr");let o=null;if(r){let s=e.match(/(?:pr|pull\s+request)\s*\(?\s*#(\d+)\)?/i)||e.match(/#(\d+)/);o={number:s?Number(s[1]):null}}return{offered:n.length?n:null,toPr:o}}function La(t,{branch:e=null,base:n=null,toPr:r=null,repos:o=null,offeredRebase:s=!1}={}){Array.isArray(t)||(t=t==="push"?["commit","push"]:["commit","push","pr"]);let a=new Set(t),i=Array.isArray(o)&&o.length>0,c=i?o.map(p=>`${p.name}/`).join(", "):null,l=e?` on this branch (${e})`:"",u=[];if(a.has("commit")&&u.push(i?`commit the work in each changed checkout (${c})${l} with a clear commit message per repo`:`commit the work${l} with a clear commit message`),a.has("rebase")){let p=a.has("commit")?"the branch":`this branch${e?` (${e})`:""}`;u.push(i?`rebase ${p} in each changed checkout (${c}) onto its own base branch (fetch origin first), resolving any conflicts so both sides' intent is preserved`:`rebase ${p} onto ${n?`the latest ${n}`:"its base branch"} (fetch origin first), resolving any conflicts so both sides' intent is preserved`)}if(a.has("push")||a.has("pr")){let p=r&&!a.has("pr")?` (the open PR${r.number?` #${r.number}`:""} picks it up)`:"",m=a.has("rebase")?" with `--force-with-lease` (the rebase rewrote history)":"";i?u.push(a.has("commit")||a.has("rebase")?`push each one to its remote${m}${p}`:`push this branch${e?` (${e})`:""} from each changed checkout (${c}) to its remote${p}`):u.push(a.has("commit")||a.has("rebase")?`push it to the remote${m}${p}`:`push this branch${l} to the remote${p}`)}a.has("pr")&&u.push(i?"open a pull request in each changed repo with a concise title and a description of what changed and why (run `gh pr create` inside each checkout)":`open a pull request${n?` against ${n}`:""} with a concise title and a description of what changed and why (use \`gh pr create\`)`);let f=[];a.has("commit")||f.push("don't create any new commits \u2014 ship what's already committed"),s&&!a.has("rebase")&&f.push("don't rebase \u2014 leave the branch's history as it is"),!a.has("push")&&!a.has("pr")&&f.push("don't push"),a.has("pr")||f.push("don't open a pull request");let d=`Yes \u2014 go ahead: ${u.join(", then ")}.`;return f.length&&(d+=` ${f.join(", and ").replace(/^./,p=>p.toUpperCase())}.`),a.has("pr")&&(d+=i?" If a repo's PR for this branch is already open, just push that repo's update instead.":" If a PR for this branch is already open, just push the update instead."),d}var GE=3e4;function Hw(t,e=Date.now()){let n=[],r=null;for(let o of t||[]){let s=o.payload||{},a=s.ts??o.ts;if(o.type==="pr.offer.answered"&&s.action==="pr"){r={box:o.boxId,from:a,lastActivity:a};continue}!r||a==null||a<r.from||(o.type==="pr.offer.answered"?r=null:o.type==="pr.state.changed"&&s.change==="opened"?(n.push({box:r.box,from:r.from,to:a,settled:!0}),r=null):o.type==="turn.user"&&o.source!=="hook"&&!s.kind&&String(s.text||"").trim()?r=null:o.boxId===r.box&&/^(?:turn|tool)\./.test(o.type)&&(r.lastActivity=Math.max(r.lastActivity,a)))}return r&&e-r.lastActivity<=GE&&n.push({box:r.box,from:r.from,to:e+1e12}),n}function Ww(t,e){let n=new Set;for(let r of e||[]){if(!r.settled)continue;let o=null;for(let s of t||[])s.kind!=="message"||s.role!=="assistant"||s.box!==r.box||s.ts==null||s.ts<r.from||s.ts>r.to||(!o||s.ts>=o.ts)&&(o=s);o&&n.add(o)}return n}var Nw={commit:"commit",rebase:"rebase",push:"push",pr:"open a PR"};function qw(t){let e=(Array.isArray(t)?t:[]).filter(r=>Nw[r]).map(r=>Nw[r]);return`The ship you were given the go-ahead for${e.length?` (${e.join(", then ")})`:""} should now be complete. This is an automated ship notification the operator armed on the offer: carry straight on with this Track's follow-up work (dependent tasks, cleanup, next steps) \u2014 don't wait for review or merge. If a ship step failed or is unfinished, finish the ship first. If nothing needs doing, say so briefly.`}import{execFile as YE}from"node:child_process";import{promisify as ZE}from"node:util";import VE from"node:fs";import Ql from"node:fs/promises";import zw from"node:path";import zE from"node:os";var Jw=process.env.BOSTRAT_STATE_DIR||zw.join(process.env.HOME||zE.tmpdir(),".bostrat"),Fa=zw.join(Jw,"github-metrics.json"),Xw=60*6e4,Kw=5*6e4,JE=6e4,Br=Date.now(),je=new Map,dn=null,zn=0,Gw=0,Ua=t=>[...t.values()].reduce((e,n)=>e+n,0);try{let t=JSON.parse(VE.readFileSync(Fa,"utf8"));t?.windowStart&&(Date.now()-t.windowStart<Xw?(Br=t.windowStart,je=new Map(Object.entries(t.byCaller||{})),dn=t.lastHour||null):Ua(new Map(Object.entries(t.byCaller||{})))&&(dn={start:t.windowStart,total:Ua(new Map(Object.entries(t.byCaller||{}))),byCaller:t.byCaller||{}}))}catch{}var Vw=Promise.resolve();function Yw(t,e=!1){if(!e&&t-Gw<JE)return;Gw=t;let n=JSON.stringify({windowStart:Br,byCaller:Object.fromEntries(je),lastHour:dn});Vw=Vw.then(()=>Ql.mkdir(Jw,{recursive:!0})).then(()=>Ql.writeFile(`${Fa}.tmp`,n)).then(()=>Ql.rename(`${Fa}.tmp`,Fa)).catch(()=>{})}function Zw(t){t-Br<Xw||(dn={start:Br,total:Ua(je),byCaller:Object.fromEntries(je)},dn.total&&console.log(`[github] ${dn.total} calls last hour`,dn.byCaller),Br=t,je=new Map,Yw(t,!0))}function Le(t="other",e=Date.now()){Zw(e),je.set(t,(je.get(t)||0)+1),Yw(e)}function Qw(t=Date.now()){return Zw(t),{windowStart:Br,total:Ua(je),byCaller:Object.fromEntries(je),lastHour:dn,backoffUntil:zn>t?zn:null}}var XE=/rate limit|too quickly|abuse detection|HTTP 429/i;function fn(t){return XE.test(String(t?.message||t||""))}function pn(t,e=Date.now()){return zn<=e&&console.warn(`[github] rate limited \u2014 backing off ${Kw/6e4} min:`,String(t?.message||t||"").slice(0,200)),zn=Math.max(zn,e+Kw),zn}function Ha(t=Date.now()){return Math.max(0,zn-t)}async function tu(t,e){let n={working:!1,prompted:!1};if(!t)return n;let r=await ft(t,{lines:xt}).catch(()=>"");if(!Ee(e,r))return Yf(e,r)?{working:!0,prompted:!1}:n;if(Cr(e,r)){let o=await Ze(t).catch(()=>r);if(Vn({name:t,agent:e},o))return{working:!1,prompted:!0}}return{working:!0,prompted:!1}}async function Wa(t,e){return(await tu(t,e)).working}var tg=ZE(YE),eg=[4e3,15e3,45e3],QE=2500,t_=5*6e4;async function eu(t,e,{midTurn:n=Wa,pollMs:r=QE,capMs:o=t_}={}){let s=Date.now()+o;for(;await n(t,e).catch(()=>!1);){if(Date.now()>=s)return!1;await new Promise(a=>setTimeout(a,r))}return!0}var e_=6e4,n_=9e4,r_=2e3,o_=/(?:^|[;&|]\s*|\)\s*)gh\s+(?:[^\s;&|"'`$]+\s+)*?pr\s+create\b/,s_=/(?:^|[;&|]\s*|\)\s*)gh\s+(?:[^\s;&|"'`$]+\s+)*?pr\s+merge\b/,a_=/\s--web\b/,ng=/(?:--repo|-R)[\s=]+([\w.-]+\/[\w.-]+)/;function nu(t,e){return t.slice(e).replace(/^[\s;&|)]+/,"").replace(/"[^"]*"|'[^']*'/g,'""').split(/[;&|]/)[0]}function rg(t){let e=String(t||""),n=e.match(o_);if(!n)return null;let r=nu(e,n.index);return a_.test(r)?null:{repo:r.match(ng)?.[1]??null}}var i_=new Set(["--subject","-t","--body","-b","--body-file","-F","--match-head-commit","--author-email","-A","--repo","-R"]);function og(t){let e=String(t||""),n=e.match(s_);if(!n)return null;let r=nu(e,n.index),o=nu(e,n.index+n[0].length),s=null,a=o.trim().split(/\s+/).filter(Boolean);for(let i=0;i<a.length;i++){let c=a[i];if(c.startsWith("-")){i_.has(c)&&i++;continue}s=c.replace(/^["']+|["']+$/g,"")||null;break}return{repo:r.match(ng)?.[1]??null,selector:s,auto:/\s--auto\b/.test(r)}}var c_="number,url,title,headRefName,createdAt";async function l_(t,e){let n=async a=>{Le("observer");let{stdout:i}=await tg("gh",["pr","list","--repo",t,"--author","@me","--state","all","--limit","10","--json",c_],{timeout:3e4,maxBuffer:4*1024*1024,env:{...process.env,...a}}).catch(c=>{throw fn(c)&&pn(c),c});return JSON.parse(i||"[]")},r=a=>(a||[]).filter(i=>Date.parse(i.createdAt)>=e-e_),o=await ae({slug:t}),s=r(await n(o));return!s.length&&o.GH_TOKEN&&(s=r(await n({}).catch(()=>[]))),s.sort((a,i)=>Date.parse(i.createdAt)-Date.parse(a.createdAt))}async function u_(t,e){if(e)return[e];let n=[];for(let r of sg(t)){let o=await tt(r).catch(()=>null);o&&!n.includes(o)&&n.push(o)}for(let r of await ve().catch(()=>[])){let o=await tt(r.path).catch(()=>null);o&&!n.includes(o)&&n.push(o)}return n}function sg(t){let e=t.workspace;return e?.kind==="multi"?(e.repos??[]).map(n=>n?.base).filter(Boolean):[e?.base||t.cwd].filter(Boolean)}async function d_(t,e,n){let r=t.workspace?.branch,o=[];for(let s of await u_(t,e)){let a;try{a=await l_(s,n)}catch{continue}for(let i of a){let c=await $t(s,i.number);if(!(c&&(c.trackId!==t.id||c.announced))){if(r&&i.headRefName===r)return{slug:s,pr:i};o.push({slug:s,pr:i})}}}return o.length?(o.sort((s,a)=>Date.parse(a.pr.createdAt)-Date.parse(s.pr.createdAt)),o[0]):null}var mn=new Set;function ag(t,e,{delays:n=eg,turnEnd:r}={}){let o=rg(e.command);if(!o)return null;let s=!!og(e.command),a=`${t.id}|${e.id??e.ts??e.command}`;if(mn.has(a))return null;mn.add(a);let i=e.ts??Date.now(),c=(async()=>{try{for(let l of n){await new Promise(g=>setTimeout(g,l));let u=await d_(t,o.repo,i).catch(()=>null);if(!u)continue;let{slug:f,pr:d}=u,{link:p,created:m}=await Wt({repo:f,number:d.number,trackId:t.id,url:d.url,headRef:d.headRefName,title:d.title,source:"observed"});return!m&&!(p?.trackId===t.id&&await Bo(f,d.number))?null:(await eu(e.box,e.agent,r),await R({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:Date.now(),change:"opened",number:d.number,url:d.url,title:d.title,branch:d.headRefName,repo:f,...s?{selfMerge:!0}:{}}}),d)}return null}finally{mn.delete(a)}})();return c.catch(()=>mn.delete(a)),c}var f_="number,url,title,headRefName,state";function ig(t,e,{delays:n=eg,turnEnd:r,openedWait:o}={}){let s=og(e.command);if(!s)return null;let a=`merge|${t.id}|${e.id??e.ts??e.command}`;if(mn.has(a))return null;mn.add(a);let i=(async()=>{try{let c=s.repo,l=s.selector,u=l?.match(/github\.com\/([\w.-]+\/[\w.-]+)\/pull\/(\d+)/);u&&([,c,l]=u);let f=[];if(c)f=[c];else{let m=sg(t);if(m.length>1&&/^\d+$/.test(String(s.selector??"")))return null;for(let g of m){let y=await tt(g).catch(()=>null);y&&!f.includes(y)&&f.push(y)}}if(l||=t.workspace?.branch,!f.length||!l)return null;c=f[0];let d=async(m,g)=>{let y=await $t(c,m.number);return y&&y.trackId!==t.id?null:(y||await Wt({repo:c,number:m.number,trackId:t.id,url:m.url,headRef:m.headRefName,title:m.title,source:"merge-observed"}),await Do(c,m.number),g&&((await D(t).catch(()=>[])).some(E=>E.type==="pr.state.changed"&&(E.payload?.change==="auto-armed"||E.payload?.change==="merged")&&E.payload?.number===m.number)||(await eu(e.box,e.agent,r),await R({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:Date.now(),change:"auto-armed",number:m.number,url:m.url,title:m.title,branch:m.headRefName,repo:c}}))),m)},p=null;for(let m of n){await new Promise($=>setTimeout($,m));let g=[];for(let $ of f)try{Le("observer");let{stdout:j}=await tg("gh",["pr","view",String(l),"--repo",$,"--json",f_],{timeout:3e4,maxBuffer:4*1024*1024,env:{...process.env,...await ae({slug:$})}}),v=JSON.parse(j||"null");v?.number&&g.push({slug:$,pr:v})}catch(j){fn(j)&&pn(j)}let y=g.find($=>$.pr.state==="MERGED")??(g.length===1?g[0]:null);if(!y)continue;c=y.slug;let b=y.pr;if(!b?.number)continue;if(b.state==="CLOSED")return null;if(b.state!=="MERGED"){if(p=b,s.auto)return d(b,!0);continue}let S=await $t(c,b.number);if(S&&S.trackId!==t.id)return null;S||await Wt({repo:c,number:b.number,trackId:t.id,url:b.url,headRef:b.headRefName,title:b.title,source:"merge-observed"}),await eu(e.box,e.agent,r);let E=await D(t).catch(()=>[]);if(E.some($=>$.type==="pr.state.changed"&&$.payload?.change==="merged"&&$.payload?.number===b.number))return null;let M=$=>$.some(j=>j.type==="pr.state.changed"&&j.payload?.change==="opened"&&j.payload?.number===b.number),J=0;if(!M(E)){if(rg(e.command)&&await Bo(c,b.number)){let $=Date.now();J=$+1,await R({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:$,change:"opened",number:b.number,url:b.url,title:b.title,branch:b.headRefName,repo:c,selfMerge:!0}})}else if((await $t(c,b.number))?.announced){let $=o?.capMs??n_,j=o?.pollMs??r_,v=Date.now()+$;for(;Date.now()<v&&(await new Promise(C=>setTimeout(C,j)),!M(await D(t).catch(()=>[]))););}}return await R({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:Math.max(Date.now(),J),change:"merged",number:b.number,url:b.url,title:b.title,branch:b.headRefName,repo:c}}),await wn(c,b.number).catch(()=>{}),await hn(c,b.number).catch(()=>{}),await cn(c,b.number,"merged"),b}return p?d(p,!1):null}finally{mn.delete(a)}})();return i.catch(()=>mn.delete(a)),i}function ou(t){return t.sourceId?`${t.role}|id:${t.sourceId}`:`${t.role}|${t.ts??"?"}|${String(t.text||"").slice(0,40)}`}function cg(t){return t.id?`cmd|${t.box}|id:${t.id}`:`cmd|${t.box}|${t.ts??"?"}|${String(t.command||"").slice(0,40)}`}function lg(t){return`job|${t.box}|id:${t.id}`}var ru=new Map,qa=new Map;async function su(t){return ru.has(t.id)?ru.get(t.id):(qa.has(t.id)||qa.set(t.id,D(t).then(e=>{let n={keys:new Set,cmdN:0};for(let r of e)if((r.type==="turn.user"||r.type==="turn.agent")&&r.payload?.text!=null){let o=r.type==="turn.user"?"user":"assistant";r.payload.sourceId&&n.keys.add(ou({role:o,sourceId:r.payload.sourceId})),n.keys.add(ou({role:o,ts:r.payload.ts,text:r.payload.text}))}else if(r.type==="tool.called"&&r.source==="transcript"&&r.payload?.command){let o=r.payload;n.keys.add(cg({id:o.id,box:r.boxId,ts:o.ts,command:o.command})),Number(o.n)>n.cmdN&&(n.cmdN=Number(o.n))}else r.type==="job.started"&&r.payload?.id&&n.keys.add(lg({id:r.payload.id,box:r.boxId}));return ru.set(t.id,n),n}).finally(()=>qa.delete(t.id))),qa.get(t.id))}async function ug(t,e){try{let n=(await su(t)).keys;for(let r of e||[]){if(!r||r.ts==null||!String(r.text||"").trim()&&!r.images?.length)continue;let o=ou(r);if(!n.has(o)&&(n.add(o),await R(t,{type:r.role==="user"?"turn.user":"turn.agent",actor:r.role==="user"?"user":"agent",source:"transcript",boxId:r.box,provider:r.agent||null,payload:{text:r.text,ts:r.ts,model:r.model||null,tools:r.tools||void 0,boxRole:r.boxRole,sourceId:r.sourceId||void 0,note:r.note||void 0,images:r.images?.length?r.images:void 0}}),r.role==="assistant"&&r.box===t.leadBox&&t.workspace?.branch)){let s=Lw(r.text);if(s){let{offered:a,toPr:i}=Uw(s),c;if(t.workspace?.kind==="multi"){let l=await zo(t).catch(()=>[]);c=l.length?l.map(u=>({name:u.name,slug:u.repo??null})):void 0}await R(t,{type:"pr.offered",actor:"agent",source:"transcript",boxId:r.box,provider:r.agent||null,payload:{offerId:p_(),ts:r.ts,excerpt:s,branch:t.workspace.branch,base:t.workspace.baseRef?.replace(/^origin\//,"")||null,boxRole:r.boxRole,offered:a||void 0,toPr:i||void 0,repos:c}})}}}}catch{}}async function dg(t,e){try{let n=await su(t),r=[...e||[]].filter(o=>o&&String(o.command||"").trim()).sort((o,s)=>(o.ts||0)-(s.ts||0));for(let o of r){let s=cg(o);n.keys.has(s)||(n.keys.add(s),n.cmdN+=1,await R(t,{type:"tool.called",actor:"agent",source:"transcript",boxId:o.box,provider:o.agent||null,payload:{id:o.id??null,tool:o.tool||null,command:o.command,description:o.description||"",summary:o.summary||o.command,ts:o.ts??null,boxRole:o.boxRole,n:n.cmdN}}),ag(t,o),ig(t,o))}}catch{}}async function fg(t,e){try{let n=await su(t),r=[...e||[]].filter(o=>o&&o.id).sort((o,s)=>(o.ts||0)-(s.ts||0));for(let o of r){let s=lg(o);n.keys.has(s)||(n.keys.add(s),await R(t,{type:"job.started",actor:"agent",source:"transcript",boxId:o.box,provider:o.agent||null,payload:{id:o.id,kind:o.kind||"agent",name:o.name||null,detail:o.detail||null,ts:o.ts??null,boxRole:o.boxRole}}))}}catch{}}var m_=/^Base directory for this skill:\s*(\S+)/,h_=/^Run the "([^"]+)" workflow\./,w_=/<command-name>\s*\/?([a-z0-9:_-]+)\s*<\/command-name>/i,g_=/^<local-command-(?:stdout|caveat)>/i,y_=/^\[Image: original \d+x\d+, displayed at \d+x\d+\.\s*Multiply coordinates by [\d.]+ to map to original image\.\]$/,b_=/^Continue from where you left off\.$/,k_=/^<task-notification>/i,x_=/<summary>([\s\S]*?)<\/summary>/i,S_=/<task-id>([\s\S]*?)<\/task-id>/i,v_=/<tool-use-id>([\s\S]*?)<\/tool-use-id>/i,T_=/<status>([\s\S]*?)<\/status>/i,A_=/<result>([\s\S]*?)(?:<\/result>|$)/i;var au=new Set(["model","clear","compact","login","exit","quit"]);function pg(t){return au.has(String(t||"").replace(/^\//,"").toLowerCase())}var E_=/^\/([a-z0-9:_-]+)$/i;function Ka(t){let e=String(t||"").trim();if(!e)return null;let n=e.match(m_);if(n)return{kind:"skill",name:n[1].split("/").filter(Boolean).pop()||"skill"};let r=e.match(h_);if(r)return{kind:"skill",name:r[1]};let o=e.match(w_);if(o){let a=o[1].toLowerCase();return au.has(a)?{kind:"suppress"}:{kind:"command",command:`/${a}`}}let s=e.match(E_);if(s&&au.has(s[1].toLowerCase()))return{kind:"suppress"};if(g_.test(e))return{kind:"suppress"};if(b_.test(e))return{kind:"suppress"};if(y_.test(e))return{kind:"suppress"};if(k_.test(e)){let a=c=>__(e.match(c)?.[1].trim()||"")||null,i=a(A_);return{kind:"task",summary:a(x_)||"",taskId:a(S_),toolUseId:a(v_),status:a(T_)?.toLowerCase()||null,resultHead:i?i.length>600?`${i.slice(0,600).trimEnd()}\u2026`:i:null}}return null}function __(t){return t.replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/'/g,"'").replace(/&/g,"&")}function mg(t){return t?.kind==="skill"?`Ran the ${t.name} skill`:t?.kind==="command"?`Ran ${t.command}`:t?.kind==="task"?t.summary||"A background task finished":""}function hg(t){return t?.kind==="skill"?"skill-run":t?.kind==="task"?"task-done":"command-run"}var it=t=>t.payload?.ts??t.ts,I_=new Set(["opened","ci-fixed","reconciled","pushed"]),wg=t=>`${t.repo??""}#${t.number}`;function R_(t,e,n){let r=wg(n),o=t.has(r)?r:null;if(!o&&n.number!=null)for(let[i,c]of t){if(Number(c.number)!==Number(n.number))continue;let l=!!(n.url&&c.url&&n.url===c.url),u=!c.repo&&!!n.repo||!!c.repo&&!n.repo;if(l||u&&(!c.url||!n.url||c.url===n.url)){o=i;break}}let s=n.repo||!o?r:o,a=o?t.get(o):t.get(s);if(o&&o!==s){t.delete(o);let i=e.get(o);if(e.delete(o),i?.length){let c=e.get(s)??[];e.set(s,c.concat(i))}}return t.set(s,{number:n.number,url:n.url??a?.url??null,title:n.title??a?.title??null,repo:n.repo??a?.repo??null,branch:n.branch??a?.branch??null}),s}function Jo(t){let e=[],n=new Set,r=new Set,o=new Map,s=new Map,a=new Set,i=new Map,c=new Set,l=new Map,u=/^Agent "([\s\S]+)" finished$/,f=new Map;for(let w of t||[]){if(w.type!=="tool.called"||w.source!=="transcript")continue;let h=String(w.payload?.command||"");if(/\bbostrat-mock\s+(?:add|update)\b/.test(h))for(let T of h.matchAll(/[\w./~-]+\.(?:png|jpe?g|gif|webp|bmp|svg)\b/gi)){let _=T[0].split("/").pop().toLowerCase();f.has(_)||f.set(_,[]),f.get(_).push(w.payload?.ts??w.ts)}}let d=3*6e4,p=20*6e4,m=(w,h)=>!!(w?.name&&f.get(String(w.name).toLowerCase()))?.some(_=>_>=h-d&&_<=h+p);for(let w of t||[])if(w.type==="turn.retracted"){r.add(`${w.boxId}|${w.payload?.saidTs}`);let h=qt(w.payload?.text);h&&(o.has(w.boxId)||o.set(w.boxId,[]),o.get(w.boxId).push(h))}else if(w.type==="turn.user"&&w.source==="orchestrator"){let h=qt(w.payload?.text);h&&(s.has(w.boxId)||s.set(w.boxId,new Set),s.get(w.boxId).add(h))}else if(w.type==="job.started"&&w.payload?.id)a.add(w.payload.id),(w.payload.kind||"agent")==="agent"&&w.payload.name&&c.add(w.payload.name);else if(w.type==="turn.user"&&w.payload?.note?.kind==="task"){let h=w.payload.note,T={status:h.status||null,summary:h.summary||null,resultHead:h.resultHead??null,ts:it(w)};h.toolUseId&&i.set(h.toolUseId,T);let _=String(h.summary||"").match(u);_&&l.set(_[1],T)}let g=new Map,y=new Map,b=null,S=new Map,E=new Map,M=new Map,J=new Map,$=new Map,j=new Map,v=new Map,C=()=>{for(let w of v.values())w.questions.length&&e.push(w);v.clear()},q=6e4,rt=new Map,st=(w,h)=>{let T=o.get(w);if(!T?.length)return!1;let _=qt(h),O=s.get(w);return T.some(I=>{if(!_.startsWith(I))return!1;let A=qt(_.slice(I.length));return!!A&&!!O?.has(A)})};for(let w of t||[]){let h=w.payload||{};switch(w.type){case"turn.user":{if(w.source==="hook"||!String(h.text||"").trim()&&!h.images?.length)break;{let T=h.note||Ka(h.text);if(T){if(T.kind==="task"){if(T.toolUseId&&a.has(T.toolUseId))break;let _=String(T.summary||"").match(u);if(!T.toolUseId&&_&&c.has(_[1]))break}if(T.kind==="command"&&pg(T.command))break;T.kind!=="suppress"&&e.push({id:w.id,kind:"event",type:hg(T),name:T.name??null,command:T.command??null,summary:T.summary??null,ts:it(w),box:w.boxId,agent:w.provider});break}}if(r.has(`${w.boxId}|${it(w)}`)||w.source==="transcript"&&st(w.boxId,h.text))break;if(h.kind==="merge-notify"||h.kind==="ship-notify"){e.push({id:w.id,kind:"event",type:h.kind==="ship-notify"?"ship-notified":"merge-notified",text:h.text,ts:it(w),box:w.boxId,agent:w.provider});break}if(h.kind==="mock-picked"||h.kind==="ci-fix"||h.kind==="reconcile"||h.kind==="pr-go"||h.kind==="push-go"||h.kind==="spin-up-view"||h.kind==="prompt-answer")break;for(let T of S.values())T.status="stale";S.clear(),e.push({id:w.id,kind:"message",role:"user",text:h.text,ts:it(w),box:w.boxId,agent:w.provider,boxRole:h.boxRole,...w.actor==="orchestrator"?{orchestration:h.kind||!0}:{},...h.sourceId?{sourceId:h.sourceId}:{},...h.attachments?.length?{attachments:h.attachments}:{},...h.images?.length?{images:h.images}:{}});break}case"turn.agent":{if(h.model==="<synthetic>"&&/^No response requested\.$/.test(String(h.text||"").trim()))break;C();let T=(h.images||[]).filter(A=>!m(A,it(w)));if(!String(h.text||"").trim()&&!T.length)break;if(w.provider==="cursor"){let A=`${w.boxId}|${h.text}`;if(n.has(A))break;n.add(A)}let _=w.provider==="codex"?(h.tools||[]).filter(A=>!Sl.has(A)&&!Ho.has(A)):h.tools||[],O=h.text,I=null;if(jl.has(w.provider)){let A=Ll(String(h.text||""));A&&(O=A.text,I={questions:A.questions})}e.push({id:w.id,kind:"message",role:"assistant",text:O,ts:it(w),box:w.boxId,agent:w.provider,boxRole:h.boxRole,...I?{ask:I}:{},..._.length?{tools:_}:{},...h.model?{model:h.model}:{},...h.sourceId?{sourceId:h.sourceId}:{},...T.length?{images:T}:{}});break}case"job.started":{if(!h.id)break;let T=it(w),_=i.get(h.id);if(!_&&(h.kind||"agent")==="agent"&&h.name){let K=l.get(h.name);K&&K.ts>=T&&(_=K)}let O={jobId:h.id,jobKind:h.kind||"agent",name:h.name||null,detail:h.detail||null,status:_?_.status&&_.status!=="completed"?"failed":"done":"running",startedTs:T,finishedTs:_?.ts??null,summary:_?.summary??null,resultHead:_?.resultHead??null},I=rt.get(w.boxId),A=I?.jobs[I.jobs.length-1]?.startedTs;if(I&&A!=null&&T-A<=q)I.jobs.push(O);else{let K={id:w.id,kind:"job-dock",dockId:h.id,ts:T,box:w.boxId,agent:w.provider,jobs:[O]};rt.set(w.boxId,K),e.push(K)}break}case"handoff.done":e.push(gn(w,"lead-handoff"));break;case"subtask.delegated":e.push({...gn(w,"subtask-delegated"),to:w.boxId,toAgent:w.provider});break;case"subtask.merged":e.push(gn(w,"subtask-merged"));break;case"pr.state.changed":{if(!h.change)break;if(h.change==="opened"){for(let _ of S.values())_.status="stale";S.clear()}let T=gn(w,`pr-${h.change}`);if(I_.has(h.change)&&h.number!=null){let _=R_(g,y,h);if(g.size>=2){for(let I of y.values())for(let A of I)A.grouped=!0;T.groupPrs=[...g.values()];let O=[T];for(let I of g.keys())y.set(I,O)}else{let O=y.get(_)??[];O.push(T),y.set(_,O)}}else if(h.change==="merged"||h.change==="closed"||h.change==="lost"){let _=wg(h),O=b&&!b.complete||g.size>=2;if(g.has(_))g.delete(_),y.delete(_);else if(h.number!=null)for(let I of[...g.keys()])I.endsWith(`#${h.number}`)&&(g.delete(I),y.delete(I));if(O&&h.number!=null){(!b||b.complete)&&(b={id:w.id,kind:"event",type:"pr-group-settled",prs:[],remaining:[],complete:!1,at:it(w),ts:it(w),box:w.boxId,agent:w.provider},e.push(b)),b.prs.push({number:h.number,url:h.url??null,title:h.title??null,repo:h.repo??null,branch:h.branch??null,change:h.change,ts:it(w)}),b.remaining=[...g.values()],b.complete=g.size===0;break}}e.push(T);break}case"pr.offered":{if(!h.offerId)break;for(let _ of S.values())_.status="stale";S.clear();let T={id:w.id,kind:"pr-offer",offerId:h.offerId,excerpt:h.excerpt||null,branch:h.branch||null,base:h.base||null,offered:Array.isArray(h.offered)&&h.offered.length?h.offered:null,toPr:h.toPr&&typeof h.toPr=="object"?{number:h.toPr.number??null}:null,repos:Array.isArray(h.repos)&&h.repos.length?h.repos:null,status:"open",ts:it(w),box:w.boxId,agent:w.provider};S.set(h.offerId,T),E.set(h.offerId,T),e.push(T);break}case"pr.offer.answered":{let T=E.get(h.offerId);T&&(T.status=h.action||"dismiss",T.actions=Array.isArray(h.actions)?h.actions:null,T.notify=h.notify===!0,S.delete(h.offerId));break}case"box.state.changed":{let T=h.change==="model"?"model-change":h.change;if(!T)break;let _=gn(w,T);T==="model-change"&&(_.to=h.model),e.push(_);break}case"view.state.changed":{if(h.change!=="created"||h.restarted)break;e.push({id:w.id,kind:"view-card",viewId:h.viewId,name:h.name,slug:h.slug,port:h.port,origin:h.origin,ts:it(w),box:w.boxId,agent:w.provider});break}case"view.spinup.failed":{e.push({id:w.id,kind:"view-spinup-failed",name:h.name||null,reason:h.reason||"no-view",ts:it(w),box:w.boxId,agent:w.provider});break}case"mock.state.changed":{if(h.change==="picked"){e.push({id:w.id,kind:"mock-picked",mockId:h.mockId,name:h.name,group:h.group??null,version:h.version??null,siblings:h.siblings??[],ts:it(w),box:w.boxId,agent:w.provider});break}if(h.change!=="created")break;e.push({id:w.id,kind:"mock-card",mockId:h.mockId,name:h.name,group:h.group??null,ts:it(w),box:w.boxId,agent:w.provider});break}case"flow.state.changed":{if(h.change!=="created")break;e.push({id:w.id,kind:"flow-card",flowId:h.flowId,name:h.name,stepCount:h.stepCount??null,mockIds:h.mockIds??[],ts:it(w),box:w.boxId,agent:w.provider});break}case"runbook.state.changed":{if(h.change!=="created")break;e.push({id:w.id,kind:"runbook-card",runbookId:h.runbookId,name:h.name,ts:it(w),box:w.boxId,agent:w.provider});break}case"ticket.linked":{e.push({id:w.id,kind:"ticket-card",provider:h.provider||"linear",key:h.key,url:h.url,title:h.title??null,linkSource:h.linkSource??null,ts:it(w),box:w.boxId,agent:w.provider});break}case"ticket.move.offered":{if(!h.offerId)break;for(let _ of M.values())_.key===h.key&&(_.status="stale",M.delete(_.offerId));let T={id:w.id,kind:"ticket-offer",offerId:h.offerId,provider:h.provider||"linear",key:h.key,url:h.url??null,phase:h.phase??null,from:h.from??null,to:h.to?.name??null,prNumber:h.prNumber??null,status:"open",ts:it(w),box:w.boxId,agent:w.provider};M.set(h.offerId,T),J.set(h.offerId,T),e.push(T);break}case"ticket.move.answered":{let T=J.get(h.offerId);T&&(T.status=h.action==="moved"?"moved":"dismiss",M.delete(h.offerId));break}case"ticket.state.changed":{for(let T of M.values())T.key===h.key&&T.to===h.to&&(T.status="stale",M.delete(T.offerId));if(h.mode==="offer")break;e.push(gn(w,"ticket-state"));break}case"ticket.commented":{e.push({id:w.id,kind:"ticket-comment",key:h.key,url:h.url??null,excerpt:h.excerpt??null,ts:it(w),box:w.boxId,agent:w.provider});break}case"ticket.flow.changed":e.push(gn(w,"ticket-flow"));break;case"ticket.create.offered":{if(!h.proposalId)break;for(let _ of $.values())_.status="stale";$.clear();let T={id:w.id,kind:"ticket-create-offer",proposalId:h.proposalId,provider:h.provider||"linear",epic:h.epic??null,issues:Array.isArray(h.issues)?h.issues:[],team:h.team??null,needsTeam:!!h.needsTeam,linkCurrentTrack:!!h.linkCurrentTrack,status:"open",ts:it(w),box:w.boxId,agent:w.provider};$.set(h.proposalId,T),j.set(h.proposalId,T),e.push(T);break}case"ticket.create.answered":{let T=j.get(h.proposalId);T&&(T.status=h.action==="created"?"created":"dismiss",T.createdCount=h.createdCount??null,T.failedCount=h.failedCount??null,$.delete(h.proposalId));break}case"ticket.created":{let T={items:Array.isArray(h.items)?h.items:[],failed:Array.isArray(h.failed)?h.failed:[],team:h.team??null},_=h.proposalId?j.get(h.proposalId):null;if(_){_.created=T;break}e.push({id:w.id,kind:"ticket-created",...T,ts:it(w),box:w.boxId,agent:w.provider});break}case"error":{if(!h.kind)break;e.push(gn(w,h.kind));break}case"provider.connected":{e.push({id:w.id,kind:"event",type:"provider-connected",provider:h.provider||w.provider,ts:it(w),at:it(w),box:w.boxId,agent:w.provider});break}case"prompt.answered":{if(h.control||!h.question)break;let T=h.groupId||h.promptId;if(!T)break;let _=v.get(T);_||(_={id:w.id,kind:"prompt",groupId:T,provider:w.provider,box:w.boxId,agent:w.provider,questionTotal:h.questionTotal??1,questions:[],ts:it(w)},v.set(T,_));let O=h.questionIndex??0,I={index:O,header:h.header||"",question:h.question,multiSelect:!!h.multiSelect,chosen:Array.isArray(h.chosen)?h.chosen:[]},A=_.questions.findIndex(K=>K.index===O&&qt(K.question)===qt(I.question));A>=0?_.questions[A]=I:_.questions.push(I),_.questions.sort((K,dt)=>K.index-dt.index),_.questionTotal=Math.max(_.questionTotal,h.questionTotal??1),_.questions.length>=_.questionTotal&&(e.push(_),v.delete(T));break}default:break}}return O_(e.sort((w,h)=>(w.ts??0)-(h.ts??0)))}var $_=2*6e4;function O_(t){let e=new Map,n=[];for(let r of t){if(r.kind!=="message"||!r.box){n.push(r);continue}let o=r.role==="assistant"&&!String(r.text||"").trim()&&r.images?.length,s=e.get(r.box);if(o&&s&&(r.ts??0)-s.lastTs<=$_){s.item.images.push(...r.images),s.lastTs=r.ts??s.lastTs;continue}if(o){let a={...r,images:[...r.images]};e.set(r.box,{item:a,lastTs:r.ts??0}),n.push(a)}else e.delete(r.box),n.push(r)}return n}function iu(t,e=null){let n=[];for(let o of t||[]){if(o.type!=="tool.called"||o.source!=="transcript")continue;let s=o.payload||{};String(s.command||"").trim()&&n.push({id:s.id??null,tool:s.tool||null,command:s.command,description:s.description||"",summary:C_(s),ts:s.ts??o.ts,n:s.n,box:o.boxId,agent:o.provider,boxRole:s.boxRole})}if(!n.length)return[];let r=(e??Jo(t)).filter(o=>o.kind==="message");return Rh(n,r)}function C_(t){let e=String(t.summary||"").trim();return e&&e!==t.command&&e!==Ir(t.command)?e:ln(t.command,t.description||"")}function gn(t,e){let{ts:n,change:r,...o}=t.payload||{};return{id:t.id,kind:"event",type:e,...o,at:it(t),ts:it(t)}}function M_(t){let e=[],n=null;for(let r of String(t||"").split(`
|
|
146
|
-
`)){let o=r.trim();if(!o||o[0]!=="{")continue;let s;try{s=JSON.parse(o)}catch{continue}let a=s.payload||{};if(s.type==="turn_context"&&a.model&&(n=a.model),s.type==="event_msg"&&a.type==="token_count"&&a.info?.last_token_usage){let i=a.info.last_token_usage;e.push({ts:s.timestamp?Date.parse(s.timestamp):null,model:n,last:{input:i.input_tokens||0,cachedInput:i.cached_input_tokens||0,output:i.output_tokens||0,reasoningOutput:i.reasoning_output_tokens||0},occupancy:i.total_tokens??null,contextWindow:a.info.model_context_window??null})}}return e}var
|
|
147
|
-
`)}catch{}}function Ag(){let t={};for(let[e,n]of fu)t[e]={count:n.count,measured:n.measured,avgMs:n.measured?Math.round(n.totalMs/n.measured):null,maxMs:n.measured?n.maxMs:null};return{byChange:t,recent:za.slice(-20)}}async function Y_(t){let e=/workspaces[/\\]([0-9a-f]{8})(?:[/\\]|$)/.exec(t||"")?.[1];if(e)try{let[{stdout:n},{stdout:r}]=await Promise.all([jr("git",["-C",t,"rev-parse","HEAD"]),jr("git",["-C",t,"rev-parse","--abbrev-ref","HEAD"])]);await R({id:e,cwd:t},{type:"code.pushed",actor:"orchestrator",payload:{ref:r.trim(),sha:n.trim(),wip:!1,ts:Date.now()}})}catch{}}var jr=X_(J_),Z_={timeout:3e4,maxBuffer:8*1024*1024};async function Yt(t,e,n={}){let{caller:r="prs",...o}=n,s=await ae({cwd:t});Le(r);try{let{stdout:a}=await jr("gh",e,{cwd:t,...Z_,...o,env:{...process.env,...s,...o.env||{}}});return String(a||"")}catch(a){throw fn(a)&&pn(a),a}}var Xa=null;async function hu(){if(Xa&&Date.now()-Xa.at<6e4)return Xa.data;let t;try{Le("auth");let e=await jr("gh",["auth","status"],{timeout:1e4});t={installed:!0,authenticated:!0,login:`${e.stdout||""}${e.stderr||""}`.match(/account\s+(\S+)/)?.[1]??null}}catch(e){t=e?.code==="ENOENT"||e?.code===127?{installed:!1,authenticated:!1,login:null}:{installed:!0,authenticated:!1,login:null}}if(t.installed&&!t.authenticated)try{let e=await co();e.configured&&(t={installed:!0,authenticated:!0,login:e.username,via:"broker"})}catch{}return Xa={at:Date.now(),data:t},t}async function Q_(t){try{let{stdout:e}=await jr("git",["-C",t,"config","--get","remote.origin.url"],{timeout:5e3});return/github\.com/.test(e)}catch{return!1}}function _g(t){let e=t.name||t.context||"check",n=t.detailsUrl||t.targetUrl||null,r=t.completedAt||t.createdAt||null,o;return t.__typename==="StatusContext"?o=t.state==="SUCCESS"?"pass":t.state==="PENDING"||t.state==="EXPECTED"?"pending":"fail":t.status&&t.status!=="COMPLETED"?o="pending":["SUCCESS","NEUTRAL","SKIPPED"].includes(t.conclusion)?o="pass":o="fail",{name:e,state:o,url:n,completedAt:r}}function pu(t){let e=(t||[]).map(_g),n=e.filter(s=>s.state==="pending").length,r=e.map(s=>Date.parse(s.completedAt||"")).filter(s=>s>0),o=e.length&&!n&&r.length?Math.max(...r):null;return{total:e.length,passed:e.filter(s=>s.state==="pass").length,failed:e.filter(s=>s.state==="fail").length,pending:n,failing:e.filter(s=>s.state==="fail").map(({name:s,url:a})=>({name:s,url:a})),settledAt:o}}var wu="number,title,url,headRefName,headRefOid,baseRefName,isDraft,author,reviewDecision,mergeable,mergeStateStatus,statusCheckRollup,autoMergeRequest,additions,deletions,createdAt,updatedAt",Ig=wu.replace("statusCheckRollup,","");function gu(t){return/Resource not accessible by personal access token/.test(String(t?.stderr||t?.message||""))}async function Rg(t,e){if(!e)return null;let n=await tt(t);if(!n)return null;try{let r=await Yt(t,["api",`repos/${n}/actions/runs?head_sha=${encodeURIComponent(e)}&per_page=100`,"--jq",".workflow_runs"]),o=new Map;for(let s of JSON.parse(r||"[]"))o.has(s.name)||o.set(s.name,s);return[...o.values()]}catch{return null}}function $g(t){return(t||[]).map(e=>({name:e.name,detailsUrl:e.html_url||null,status:String(e.status||"").toUpperCase(),conclusion:e.conclusion?String(e.conclusion).toUpperCase():null,completedAt:e.status==="completed"&&e.updated_at||null}))}async function Og(t,e){let n=await Rg(t,e);return n===null?{...pu([]),unavailable:!0}:pu($g(n))}function Ya(t,e=pu(t.statusCheckRollup)){return{number:t.number,title:t.title,url:t.url,author:t.author?.login??null,headRefName:t.headRefName,headSha:t.headRefOid??null,baseRefName:t.baseRefName,isDraft:!!t.isDraft,reviewDecision:t.reviewDecision||null,mergeable:t.mergeable||"UNKNOWN",mergeStateStatus:t.mergeStateStatus||"UNKNOWN",additions:t.additions??0,deletions:t.deletions??0,createdAt:t.createdAt,updatedAt:t.updatedAt,checks:e,green:e.failed===0&&e.pending===0&&t.mergeStateStatus==="CLEAN",autoMergeArmed:!!t.autoMergeRequest,mergeabilityPending:!1}}var tI=9e4,Dr=new Map;function eI(t,e,n){let r=new Set;for(let o of e){let s=`${t}#${o.number}`;if(o.mergeable!=="UNKNOWN"&&o.mergeStateStatus!=="UNKNOWN"){Dr.delete(s);continue}r.add(s),Dr.has(s)||Dr.set(s,n),o.mergeabilityPending=n-Dr.get(s)<tI}for(let o of Dr.keys())o.startsWith(`${t}#`)&&!r.has(o)&&Dr.delete(o);return e}async function Lr(t,e){let n=await Fe(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");try{let o=await Yt(n.path,["pr","view",String(r),"--json",wu]);return Ya(JSON.parse(o))}catch(o){if(!gu(o))throw o;let s=await Yt(n.path,["pr","view",String(r),"--json",Ig]),a=JSON.parse(s);return Ya(a,await Og(n.path,a.headRefOid))}}var Eg=(t,e)=>t.number-e.number;async function Yo(t,{caller:e="prs"}={}){try{let n=await Yt(t,["pr","list","--json",wu,"--limit","50"],{caller:e});return JSON.parse(n||"[]").map(r=>Ya(r)).sort(Eg)}catch(n){if(!gu(n))throw n;let r=await Yt(t,["pr","list","--json",Ig,"--limit","50"],{caller:e});return(await Promise.all(JSON.parse(r||"[]").map(async o=>Ya(o,await Og(t,o.headRefOid))))).sort(Eg)}}var nI=3e4,rI=1500,Xo=new Map;function ke(){Xo.clear()}async function oI(t,e){if(!t)return e;for(let n of e){let r=await $t(t,n.number).catch(()=>null);if(!r)continue;r.trackId&&(n.trackId=r.trackId),r.autoMerge&&(n.autoMergeArmed=!0),r.archiveOnMerge&&(n.archiveOnMerge=!0),r.notifyOnMerge&&(n.notifyOnMerge=!0);let o=s=>n.headSha&&s.headSha&&n.headSha!==s.headSha;r.ciFix&&(o(r.ciFix)?await Aa(t,n.number,{headSha:n.headSha}).catch(()=>{}):n.ciFix={delegatedAt:r.ciFix.delegatedAt,trackId:r.ciFix.trackId??null}),r.reconcile&&(o(r.reconcile)?await _a(t,n.number,{headSha:n.headSha}).catch(()=>{}):n.reconcile={delegatedAt:r.reconcile.delegatedAt,trackId:r.reconcile.trackId??null})}return e}async function Cg(){let t=await hu();if(!t.authenticated)return{auth:t,repos:[]};let e=[];for(let a of await ve())await Q_(a.path)&&e.push(a);let n=Date.now(),r=Ha(n)>0,o=await Promise.all(e.map(async a=>{let i=Xo.get(a.path),c=i?.pending?rI:nI;if(i&&(r||n-i.at<c))return i.entry;try{let l=await tt(a.path).catch(()=>null),u=eI(a.path,await Yo(a.path,{caller:"tab"}),n);if(await oI(l,u),i?.entry?.prs)for(let p of u)i.entry.prs.find(g=>g.number===p.number)?.checks?.pending>0&&p.checks.total>0&&p.checks.pending===0&&Ja({change:p.checks.failed>0?"checks-failed":"checks-green",repo:l||a.name,number:p.number,githubAt:p.checks.settledAt,source:"tab"});let f={name:a.name,path:a.path,prs:u},d=u.some(p=>p.mergeabilityPending||p.checks.pending>0||p.autoMergeArmed);return Xo.set(a.path,{at:Date.now(),entry:f,pending:d}),f}catch(l){return i?i.entry:{name:a.name,path:a.path,prs:[],error:be(l)}}})),s=new Set(e.map(a=>a.path));for(let a of Xo.keys())s.has(a)||Xo.delete(a);return{auth:t,repos:o}}async function Fe(t){let e=(await ve()).find(n=>n.path===t);if(!e)throw new Error(`unknown repo: ${t}`);return e}async function Jn(t){return ao(t)}var Zo=new Set(["squash","merge","rebase"]);async function Fr(t,e,{method:n="squash"}={}){let r=await Fe(t);if(!Zo.has(n))throw new Error(`invalid merge method: ${n}`);let o=Number(e);if(!Number.isInteger(o)||o<=0)throw new Error("invalid PR number");let s=["pr","merge",String(o),`--${n}`];if(n!=="rebase"&&(await ae({cwd:r.path})).GH_TOKEN){let a=await Ui().catch(()=>null);a&&s.push("--body",`Merged by bostrat on behalf of @${a}`)}try{await Yt(r.path,s)}catch(a){throw new Error(be(a))}finally{ke()}return{ok:!0,number:o}}async function Qo(t,e){let n=await Fe(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");try{await Yt(n.path,["api","-X","PUT",`repos/{owner}/{repo}/pulls/${r}/update-branch`])}catch(s){throw new Error(be(s))}finally{ke()}let o=await tt(n.path).catch(()=>null);return o&&await lh(o,r).catch(()=>{}),{ok:!0,number:r}}async function yu(t,e){let n=await Fe(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");try{await Yt(n.path,["pr","merge",String(r),"--disable-auto"])}catch(o){throw new Error(be(o))}finally{ke()}return{ok:!0,number:r}}var sI=2500,aI=9e4,iI=t=>new Promise(e=>setTimeout(e,t));function bu(t){return t.green&&!t.isDraft}function cI(t){return t.isDraft?"draft":t.mergeable==="CONFLICTING"?`conflicts with ${t.baseRefName}`:t.checks.failed>0?"checks failing":t.checks.pending>0?"checks running":t.mergeable==="UNKNOWN"||t.mergeStateStatus==="UNKNOWN"?"mergeability unresolved":t.mergeStateStatus==="BEHIND"?`behind ${t.baseRefName}`:t.mergeStateStatus==="BLOCKED"?"blocked by branch protection":`not clean (${t.mergeStateStatus})`}function Mg(t,{update:e=!0}={}){return t.isDraft?{action:"skip",reason:"draft"}:bu(t)?{action:"merge"}:e&&t.mergeStateStatus==="BEHIND"&&t.mergeable!=="CONFLICTING"&&t.checks.failed===0?{action:"update-arm"}:{action:"arm"}}function Ng(t){return[...t.filter(e=>e.plan.action!=="merge"),...t.filter(e=>e.plan.action==="merge")]}var mu=new Map;function Pg(){let t={};for(let[e,n]of mu)t[e]={...n};return t}async function Bg(t,{method:e="merge",onMerged:n=null}={}){let r=await Fe(t);if(!Zo.has(e))throw new Error(`invalid merge method: ${e}`);if(mu.get(r.path)?.running)throw new Error("a merge-all is already running for this repo");let o=(await Yo(r.path)).filter(bu).sort((a,i)=>Date.parse(a.createdAt)-Date.parse(i.createdAt));if(!o.length)throw new Error("no PRs ready to merge");let s={running:!0,startedAt:Date.now(),finishedAt:null,method:e,total:o.length,current:o[0].number,queue:o.map(a=>({number:a.number,title:a.title})),merged:[],skipped:[],failed:[]};return mu.set(r.path,s),uI(s,o,{listPrs:()=>Yo(r.path),merge:a=>Fr(r.path,a,{method:e}),sleep:iI,onMerged:n}).catch(()=>{}),{ok:!0,repo:r.path,total:s.total,queue:o.map(a=>a.number)}}async function lI(t,e){let n=Date.now()+(e.settleMaxMs??aI),r=null;for(;;){let o=null;try{if(o=await e.listPrs(),r=null,!o.some(a=>t.includes(a.number)&&(a.mergeable==="UNKNOWN"||a.mergeStateStatus==="UNKNOWN")))return o}catch(s){r=s}if(Date.now()>=n){if(o)return o;if(r)throw r;return[]}await e.sleep(sI)}}async function uI(t,e,n){try{let r=e;for(let o=0;o<e.length;o++){let s=e[o];if(t.current=s.number,o>0)try{r=await lI(e.slice(o).map(i=>i.number),n)}catch(i){for(let c of e.slice(o))t.failed.push({number:c.number,title:c.title,reason:`couldn't re-check PR state: ${be(i)}`});break}let a=r.find(i=>i.number===s.number);if(!a){t.skipped.push({number:s.number,title:s.title,reason:"no longer open"});continue}if(!bu(a)){t.skipped.push({number:a.number,title:a.title,reason:cI(a)});continue}try{await n.merge(a.number),t.merged.push({number:a.number,title:a.title});try{await n.onMerged?.(a)}catch{}}catch(i){t.failed.push({number:a.number,title:a.title,reason:be(i)})}}}finally{t.current=null,t.running=!1,t.finishedAt=Date.now(),ke()}}async function ku(t,{title:e,body:n="",base:r=null}={}){if(typeof e!="string"||!e.trim())throw new Error("title required");try{await jr("git",["-C",t,"push","-u","origin","HEAD"],{timeout:12e4,maxBuffer:4*1024*1024})}catch(l){throw new Error(`push failed: ${be(l)}`)}await Y_(t);let o=n||"";if((await ae({cwd:t})).GH_TOKEN){let l=await Ui().catch(()=>null);l&&(o=`${o?`${o}
|
|
148
|
-
|
|
149
|
-
`:""}Opened by bostrat on behalf of @${l}`)}let s=["pr","create","--title",e.trim(),"--body",o];r&&s.push("--base",r);let a;try{a=await 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
|
|
138
|
+
`;import{execFile as xE}from"node:child_process";import{promisify as SE}from"node:util";var Gl=SE(xE),gw=64*1024*1024;async function ht(t,e,{input:n}={}){let r=Gl("git",["-C",t,...e],{maxBuffer:gw});return n!==void 0&&r.child.stdin&&(r.child.stdin.on("error",()=>{}),r.child.stdin.end(n)),r}async function Da(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 yw(t){let{stdout:e}=await ht(t,["rev-parse","--show-toplevel"]);return e.trim()}async function vE(t){try{return await ht(t,["rev-parse","--verify","HEAD"]),!0}catch{return!1}}async function Jo(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)=>Gl("git",["-C",t,...a],{maxBuffer:gw,env:o,...i});try{let a=await vE(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 Gl("rm",["-f",r])}catch{}}}async function Vl(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 zl(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 Jl(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 Pr(t,e,n,r){return await ht(t,["worktree","add","--no-track","-b",n,e,r]),e}async function bw(t,e){try{return await ht(t,["rev-parse","--verify","--quiet",`refs/heads/${e}`]),!0}catch{return!1}}async function kw(t,e,n){return await ht(t,["worktree","add",e,n]),e}async function ja(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 xw(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 TE(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 Xl(t,e){return!e||!e.length?t:[...t,"--",".",...e.map(n=>`:(exclude)${n}`)]}async function Yl(t,e,n,r){let{stdout:o}=await ht(t,Xl(["diff","--shortstat",e,n],r));return TE(o)}async function Zl(t,e,n,r){let{stdout:o}=await ht(t,Xl(["diff","--name-only",e,n],r));return o.split(`
|
|
140
|
+
`).map(s=>s.trim()).filter(Boolean)}async function Ql(t,e,n,r){let{stdout:o}=await ht(t,Xl(["diff","--binary",e,n],r));if(!o.trim())return{applied:!0,empty:!0,conflicts:[]};if((await Kl(t,o,["--whitespace=nowarn"])).ok)return{applied:!0,empty:!1,conflicts:[]};let s=await Kl(t,o,["--3way","--whitespace=nowarn"]),a=await AE(t);return a.length>0?{applied:!1,empty:!1,conflicts:a}:s.ok?{applied:!0,empty:!1,conflicts:[]}:(await Kl(t,o,["--reject","--whitespace=nowarn"]),{applied:!1,empty:!1,conflicts:[`patch did not apply cleanly: ${s.err}`]})}async function Kl(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 AE(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 EE from"node:fs/promises";import _E from"node:os";import Sw from"node:path";var La=t=>new Promise(e=>setTimeout(e,t)),IE=Number(process.env.BOSTRAT_MODEL_PICKER_TIMEOUT_MS)||8e3,RE=300,Ew=[{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"}],$E=new Set([...Ew.map(t=>t.alias),"best","opus[1m]","sonnet[1m]","claude-opus-4-8","claude-sonnet-4-6","claude-haiku-4-5","claude-fable-5"]),OE=[{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"}],_w=new Map([["low","Low"],["medium","Medium"],["high","High"],["xhigh","Extra high"]]),Iw=[{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 Rw(t){return t==="codex"?OE:t==="cursor"?Iw:Ew}function vw(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 Tw(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 CE(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 Aw(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 $w(t,e){if(typeof e!="string"||!e.trim())throw new Error("model (string) required");let n=e.trim();if(t==="codex"){let[r,o]=n.split(/[\s:]+/);if(o&&!_w.has(o))throw new Error(`unknown codex effort "${o}" (low | medium | high | xhigh)`);return{model:r,effort:o||null}}if(t==="cursor"){let r=Iw.find(o=>o.alias===n);return{model:n,filter:r?r.label:n}}if(!$E.has(n))throw new Error(`unknown model "${n}"`);return{model:n}}async function Xo(t,e,{timeoutMs:n=IE}={}){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 La(RE)}}async function ME(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 NE(t,e){await Vs(t),await St(t,"/model",{enter:!1}),await La(250),await bt(t,["Enter"],{kind:"model-switch"});let n=await Xo(t,vw);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=CE(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 Xo(t,i=>Tw(i)||!vw(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?_w.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 Xo(t,i=>!Tw(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 PE(){let t=Sw.join(process.env.CURSOR_CONFIG_DIR||Sw.join(_E.homedir(),".cursor"),"cli-config.json");try{return JSON.parse(await EE.readFile(t,"utf8"))?.model||null}catch{return null}}async function BE(t,e){await Vs(t),await Js(t,`/model ${e.filter}`,{kind:"model-switch"});let n=await Xo(t,Aw),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 Xo(t,Aw,{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 La(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 La(800);let a=await PE();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 Ow(t,e,n){return t==="codex"?NE(e,n):t==="cursor"?BE(e,n):ME(e,n)}var Kt=t=>t||null;function Ua(t){let e=t||{},n=DE(e);return n&&(n.payload={ts:e.at,...n.payload}),n}function DE(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:{...Br(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",...Br(t)}};case"subtask-resumed":return{type:"box.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Kt(t.agent),payload:{change:"subtask-resumed",...Br(t)}};case"model-change":return{type:"box.state.changed",actor:"user",source:"orchestrator",boxId:t.box,payload:{change:"model",model:t.to,from:t.from??null,verified:t.verified??null}};case"codex-update":return{type:"box.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:"codex",payload:{change:"cli-update",from:t.from??null,to:t.to??null,status:t.status,...t.error?{error:t.error}:{}}};case"opened":return{type:"box.state.changed",actor:"user",source:"orchestrator",boxId:t.box,payload:{change:"opened",...Br(t)}};case"archived":case"unarchived":case"track-deleted":return{type:"box.state.changed",actor:"user",source:"orchestrator",payload:{change:t.type,...Br(t)}};case"rehydrated":return{type:"box.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Kt(t.agent),payload:{change:"rehydrated"}};case"retagged":return{type:"box.state.changed",actor:"user",source:"orchestrator",payload:{change:"retagged",...Br(t)}};case"rehydrate-failed":return{type:"error",actor:"system",source:"orchestrator",boxId:t.box,provider:Kt(t.agent),payload:{kind:"rehydrate-failed",reason:t.reason}};default:return null}}var Fa=Promise.resolve(),eu=t=>(Fa=Fa.then(t).catch(()=>{}),Fa),Mw=()=>Fa;function Nw(t,e){let n=Ua(e);if(!n)return;let r=e?.type==="track-deleted"?{id:t.id}:t;eu(()=>R(r,n))}var Cw=new Set;async function Pw(t){if(!(!t?.id||Cw.has(t.id))){Cw.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=Ua(r);!o||n.has(`${o.type}|${o.payload?.ts??"?"}`)||await R(t,o)}}catch{}}}function Br(t){let{type:e,at:n,...r}=t;return r}var tu=new Map;function Bw(t,e,n){!n?.promptId||tu.get(t.id)===n.promptId||(tu.set(t.id,n.promptId),eu(()=>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 nu(t,e,n,r={}){tu.delete(t.id),eu(()=>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 h_}from"node:crypto";var jw=/(?<!\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,Lw=/(?<!\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,Fw=/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,Uw=/(?<!\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,jE=new RegExp(`\\b(?:${jw}|${Lw}|${Fw}|${Uw})\\b`,"i"),LE=/\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,FE=/^(?:commit|push|open|create|raise|rebase|ready|ok(?:ay)?\s+to|good\s+to)\b/i,UE=90;function HE(t){return String(t).split(/(?<=[.?!])\s+|\n+/).map(e=>e.trim()).filter(Boolean)}function Hw(t){let e=String(t||"").trim();if(!e)return null;let n=e.slice(-600);for(let r of HE(n).reverse()){if(!jE.test(r))continue;let o=r.endsWith("?")&&r.length<=UE&&FE.test(r);if(LE.test(r)||o)return r.length>200?r.slice(0,197)+"\u2026":r}return null}var Ww=["commit","rebase","push","pr"],WE=new RegExp(`\\b(?:${jw})\\b`,"i"),qE=new RegExp(`\\b(?:${Uw})\\b`,"i"),KE=new RegExp(`\\b(?:${Lw})\\b`,"i"),GE=new RegExp(`\\b(?:${Fw})\\b`,"i"),VE=/\b(?:to|into|onto|on|updat(?:e|es|ing))\s+(?:the|that|this)\s+(?:open\s+|existing\s+|current\s+|same\s+)?(?:draft\s+)?(?:pr|pull\s+request)\b/i;function qw(t){let e=String(t||""),n=[];WE.test(e)&&n.push("commit"),qE.test(e)&&n.push("rebase"),KE.test(e)&&n.push("push");let r=VE.test(e);!r&&GE.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 Ha(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 zE=3e4;function Kw(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<=zE&&n.push({box:r.box,from:r.from,to:e+1e12}),n}function Gw(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 Dw={commit:"commit",rebase:"rebase",push:"push",pr:"open a PR"};function Vw(t){let e=(Array.isArray(t)?t:[]).filter(r=>Dw[r]).map(r=>Dw[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 QE}from"node:child_process";import{promisify as t_}from"node:util";import JE from"node:fs";import ru from"node:fs/promises";import Yw from"node:path";import XE from"node:os";var Zw=process.env.BOSTRAT_STATE_DIR||Yw.join(process.env.HOME||XE.tmpdir(),".bostrat"),Wa=Yw.join(Zw,"github-metrics.json"),Qw=60*6e4,zw=5*6e4,YE=6e4,Dr=Date.now(),je=new Map,dn=null,Xn=0,Jw=0,qa=t=>[...t.values()].reduce((e,n)=>e+n,0);try{let t=JSON.parse(JE.readFileSync(Wa,"utf8"));t?.windowStart&&(Date.now()-t.windowStart<Qw?(Dr=t.windowStart,je=new Map(Object.entries(t.byCaller||{})),dn=t.lastHour||null):qa(new Map(Object.entries(t.byCaller||{})))&&(dn={start:t.windowStart,total:qa(new Map(Object.entries(t.byCaller||{}))),byCaller:t.byCaller||{}}))}catch{}var Xw=Promise.resolve();function tg(t,e=!1){if(!e&&t-Jw<YE)return;Jw=t;let n=JSON.stringify({windowStart:Dr,byCaller:Object.fromEntries(je),lastHour:dn});Xw=Xw.then(()=>ru.mkdir(Zw,{recursive:!0})).then(()=>ru.writeFile(`${Wa}.tmp`,n)).then(()=>ru.rename(`${Wa}.tmp`,Wa)).catch(()=>{})}function eg(t){t-Dr<Qw||(dn={start:Dr,total:qa(je),byCaller:Object.fromEntries(je)},dn.total&&console.log(`[github] ${dn.total} calls last hour`,dn.byCaller),Dr=t,je=new Map,tg(t,!0))}function Le(t="other",e=Date.now()){eg(e),je.set(t,(je.get(t)||0)+1),tg(e)}function ng(t=Date.now()){return eg(t),{windowStart:Dr,total:qa(je),byCaller:Object.fromEntries(je),lastHour:dn,backoffUntil:Xn>t?Xn:null}}var ZE=/rate limit|too quickly|abuse detection|HTTP 429/i;function fn(t){return ZE.test(String(t?.message||t||""))}function pn(t,e=Date.now()){return Xn<=e&&console.warn(`[github] rate limited \u2014 backing off ${zw/6e4} min:`,String(t?.message||t||"").slice(0,200)),Xn=Math.max(Xn,e+zw),Xn}function Ka(t=Date.now()){return Math.max(0,Xn-t)}async function ou(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 tp(e,r)?{working:!0,prompted:!1}:n;if(Mr(e,r)){let o=await Ze(t).catch(()=>r);if(Jn({name:t,agent:e},o))return{working:!1,prompted:!0}}return{working:!0,prompted:!1}}async function Ga(t,e){return(await ou(t,e)).working}var rg=t_(QE),og=[4e3,15e3,45e3],e_=2500,n_=5*6e4;async function su(t,e,{midTurn:n=Ga,pollMs:r=e_,capMs:o=n_}={}){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 r_=6e4,o_=9e4,s_=2e3,a_=/(?:^|[;&|]\s*|\)\s*)gh\s+(?:[^\s;&|"'`$]+\s+)*?pr\s+create\b/,i_=/(?:^|[;&|]\s*|\)\s*)gh\s+(?:[^\s;&|"'`$]+\s+)*?pr\s+merge\b/,c_=/\s--web\b/,sg=/(?:--repo|-R)[\s=]+([\w.-]+\/[\w.-]+)/;function au(t,e){return t.slice(e).replace(/^[\s;&|)]+/,"").replace(/"[^"]*"|'[^']*'/g,'""').split(/[;&|]/)[0]}function ag(t){let e=String(t||""),n=e.match(a_);if(!n)return null;let r=au(e,n.index);return c_.test(r)?null:{repo:r.match(sg)?.[1]??null}}var l_=new Set(["--subject","-t","--body","-b","--body-file","-F","--match-head-commit","--author-email","-A","--repo","-R"]);function ig(t){let e=String(t||""),n=e.match(i_);if(!n)return null;let r=au(e,n.index),o=au(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("-")){l_.has(c)&&i++;continue}s=c.replace(/^["']+|["']+$/g,"")||null;break}return{repo:r.match(sg)?.[1]??null,selector:s,auto:/\s--auto\b/.test(r)}}var u_="number,url,title,headRefName,createdAt";async function d_(t,e){let n=async a=>{Le("observer");let{stdout:i}=await rg("gh",["pr","list","--repo",t,"--author","@me","--state","all","--limit","10","--json",u_],{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-r_),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 f_(t,e){if(e)return[e];let n=[];for(let r of cg(t)){let o=await et(r).catch(()=>null);o&&!n.includes(o)&&n.push(o)}for(let r of await ve().catch(()=>[])){let o=await et(r.path).catch(()=>null);o&&!n.includes(o)&&n.push(o)}return n}function cg(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 p_(t,e,n){let r=t.workspace?.branch,o=[];for(let s of await f_(t,e)){let a;try{a=await d_(s,n)}catch{continue}for(let i of a){let c=await Ot(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 lg(t,e,{delays:n=og,turnEnd:r}={}){let o=ag(e.command);if(!o)return null;let s=!!ig(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(h=>setTimeout(h,l));let u=await p_(t,o.repo,i).catch(()=>null);if(!u)continue;let{slug:f,pr:d}=u,{link:p,created:m}=await qt({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 jo(f,d.number))?null:(await su(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 m_="number,url,title,headRefName,state";function ug(t,e,{delays:n=og,turnEnd:r,openedWait:o}={}){let s=ig(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=cg(t);if(m.length>1&&/^\d+$/.test(String(s.selector??"")))return null;for(let h of m){let y=await et(h).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,h)=>{let y=await Ot(c,m.number);return y&&y.trackId!==t.id?null:(y||await qt({repo:c,number:m.number,trackId:t.id,url:m.url,headRef:m.headRefName,title:m.title,source:"merge-observed"}),await Lo(c,m.number),h&&((await L(t).catch(()=>[])).some(I=>I.type==="pr.state.changed"&&(I.payload?.change==="auto-armed"||I.payload?.change==="merged")&&I.payload?.number===m.number)||(await su(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 h=[];for(let $ of f)try{Le("observer");let{stdout:F}=await rg("gh",["pr","view",String(l),"--repo",$,"--json",m_],{timeout:3e4,maxBuffer:4*1024*1024,env:{...process.env,...await ae({slug:$})}}),T=JSON.parse(F||"null");T?.number&&h.push({slug:$,pr:T})}catch(F){fn(F)&&pn(F)}let y=h.find($=>$.pr.state==="MERGED")??(h.length===1?h[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 Ot(c,b.number);if(S&&S.trackId!==t.id)return null;S||await qt({repo:c,number:b.number,trackId:t.id,url:b.url,headRef:b.headRefName,title:b.title,source:"merge-observed"}),await su(e.box,e.agent,r);let I=await L(t).catch(()=>[]);if(I.some($=>$.type==="pr.state.changed"&&$.payload?.change==="merged"&&$.payload?.number===b.number))return null;let M=$=>$.some(F=>F.type==="pr.state.changed"&&F.payload?.change==="opened"&&F.payload?.number===b.number),z=0;if(!M(I)){if(ag(e.command)&&await jo(c,b.number)){let $=Date.now();z=$+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 Ot(c,b.number))?.announced){let $=o?.capMs??o_,F=o?.pollMs??s_,T=Date.now()+$;for(;Date.now()<T&&(await new Promise(C=>setTimeout(C,F)),!M(await L(t).catch(()=>[]))););}}return await R({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:Math.max(Date.now(),z),change:"merged",number: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 cu(t){return t.sourceId?`${t.role}|id:${t.sourceId}`:`${t.role}|${t.ts??"?"}|${String(t.text||"").slice(0,40)}`}function dg(t){return t.id?`cmd|${t.box}|id:${t.id}`:`cmd|${t.box}|${t.ts??"?"}|${String(t.command||"").slice(0,40)}`}function fg(t){return`job|${t.box}|id:${t.id}`}var iu=new Map,Va=new Map;async function lu(t){return iu.has(t.id)?iu.get(t.id):(Va.has(t.id)||Va.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(cu({role:o,sourceId:r.payload.sourceId})),n.keys.add(cu({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(dg({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(fg({id:r.payload.id,box:r.boxId}));return iu.set(t.id,n),n}).finally(()=>Va.delete(t.id))),Va.get(t.id))}async function pg(t,e){try{let n=(await lu(t)).keys;for(let r of e||[]){if(!r||r.ts==null||!String(r.text||"").trim()&&!r.images?.length)continue;let o=cu(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=Hw(r.text);if(s){let{offered:a,toPr:i}=qw(s),c;if(t.workspace?.kind==="multi"){let l=await Yo(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:h_(),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 mg(t,e){try{let n=await lu(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=dg(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}}),lg(t,o),ug(t,o))}}catch{}}async function hg(t,e){try{let n=await lu(t),r=[...e||[]].filter(o=>o&&o.id).sort((o,s)=>(o.ts||0)-(s.ts||0));for(let o of r){let s=fg(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 w_=/^Base directory for this skill:\s*(\S+)/,g_=/^Run the "([^"]+)" workflow\./,y_=/<command-name>\s*\/?([a-z0-9:_-]+)\s*<\/command-name>/i,b_=/^<local-command-(?:stdout|caveat)>/i,k_=/^\[Image: original \d+x\d+, displayed at \d+x\d+\.\s*Multiply coordinates by [\d.]+ to map to original image\.\]$/,x_=/^Continue from where you left off\.$/,S_=/^<task-notification>/i,v_=/<summary>([\s\S]*?)<\/summary>/i,T_=/<task-id>([\s\S]*?)<\/task-id>/i,A_=/<tool-use-id>([\s\S]*?)<\/tool-use-id>/i,E_=/<status>([\s\S]*?)<\/status>/i,__=/<result>([\s\S]*?)(?:<\/result>|$)/i;var uu=new Set(["model","clear","compact","login","exit","quit"]);function wg(t){return uu.has(String(t||"").replace(/^\//,"").toLowerCase())}var I_=/^\/([a-z0-9:_-]+)$/i;function za(t){let e=String(t||"").trim();if(!e)return null;let n=e.match(w_);if(n)return{kind:"skill",name:n[1].split("/").filter(Boolean).pop()||"skill"};let r=e.match(g_);if(r)return{kind:"skill",name:r[1]};let o=e.match(y_);if(o){let a=o[1].toLowerCase();return uu.has(a)?{kind:"suppress"}:{kind:"command",command:`/${a}`}}let s=e.match(I_);if(s&&uu.has(s[1].toLowerCase()))return{kind:"suppress"};if(b_.test(e))return{kind:"suppress"};if(x_.test(e))return{kind:"suppress"};if(k_.test(e))return{kind:"suppress"};if(S_.test(e)){let a=c=>R_(e.match(c)?.[1].trim()||"")||null,i=a(__);return{kind:"task",summary:a(v_)||"",taskId:a(T_),toolUseId:a(A_),status:a(E_)?.toLowerCase()||null,resultHead:i?i.length>600?`${i.slice(0,600).trimEnd()}\u2026`:i:null}}return null}function R_(t){return t.replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/'/g,"'").replace(/&/g,"&")}function gg(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 yg(t){return t?.kind==="skill"?"skill-run":t?.kind==="task"?"task-done":"command-run"}var st=t=>t.payload?.ts??t.ts;function $_(t){return{id:t.id,kind:"prompt",groupId:t.groupId,provider:t.provider,box:t.box,agent:t.provider,questionTotal:t.questions.length,answeredVia:"chat",questions:t.questions.map((e,n)=>({index:n,header:e.header||"",question:e.question,multiSelect:!!e.multiSelect,chosen:[]})),ts:t.ts}}var O_=new Set(["opened","ci-fixed","reconciled","pushed"]),bg=t=>`${t.repo??""}#${t.number}`;function C_(t,e,n){let r=bg(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 Zo(t){let e=[],n=new Set,r=new Set,o=new Map,s=new Map,a=new Set,i=new Map,c=new Set,l=new Map,u=/^Agent "([\s\S]+)" finished$/,f=new Map;for(let g of t||[]){if(g.type!=="tool.called"||g.source!=="transcript")continue;let w=String(g.payload?.command||"");if(/\bbostrat-mock\s+(?:add|update)\b/.test(w))for(let _ of w.matchAll(/[\w./~-]+\.(?:png|jpe?g|gif|webp|bmp|svg)\b/gi)){let v=_[0].split("/").pop().toLowerCase();f.has(v)||f.set(v,[]),f.get(v).push(g.payload?.ts??g.ts)}}let d=3*6e4,p=20*6e4,m=(g,w)=>!!(g?.name&&f.get(String(g.name).toLowerCase()))?.some(v=>v>=w-d&&v<=w+p);for(let g of t||[])if(g.type==="turn.retracted"){r.add(`${g.boxId}|${g.payload?.saidTs}`);let w=Tt(g.payload?.text);w&&(o.has(g.boxId)||o.set(g.boxId,[]),o.get(g.boxId).push(w))}else if(g.type==="turn.user"&&g.source==="orchestrator"){let w=Tt(g.payload?.text);w&&(s.has(g.boxId)||s.set(g.boxId,new Set),s.get(g.boxId).add(w))}else if(g.type==="job.started"&&g.payload?.id)a.add(g.payload.id),(g.payload.kind||"agent")==="agent"&&g.payload.name&&c.add(g.payload.name);else if(g.type==="turn.user"&&g.payload?.note?.kind==="task"){let w=g.payload.note,_={status:w.status||null,summary:w.summary||null,resultHead:w.resultHead??null,ts:st(g)};w.toolUseId&&i.set(w.toolUseId,_);let v=String(w.summary||"").match(u);v&&l.set(v[1],_)}let h=new Map,y=new Map,b=null,S=new Map,I=new Map,M=new Map,z=new Map,$=new Map,F=new Map,T=new Map,C=()=>{for(let g of T.values())g.questions.length&&e.push(g);T.clear()},D=null,it=6e4,ot=new Map,A=(g,w)=>{let _=o.get(g);if(!_?.length)return!1;let v=Tt(w),O=s.get(g);return _.some(E=>{if(!v.startsWith(E))return!1;let N=Tt(v.slice(E.length));return!!N&&!!O?.has(N)})};for(let g of t||[]){let w=g.payload||{};switch(g.type){case"turn.user":{if(g.source==="hook"||!String(w.text||"").trim()&&!w.images?.length)break;{let _=w.note||za(w.text);if(_){if(_.kind==="task"){if(_.toolUseId&&a.has(_.toolUseId))break;let v=String(_.summary||"").match(u);if(!_.toolUseId&&v&&c.has(v[1]))break}if(_.kind==="command"&&wg(_.command))break;_.kind!=="suppress"&&e.push({id:g.id,kind:"event",type:yg(_),name:_.name??null,command:_.command??null,summary:_.summary??null,ts:st(g),box:g.boxId,agent:g.provider});break}}if(r.has(`${g.boxId}|${st(g)}`)||g.source==="transcript"&&A(g.boxId,w.text))break;if(w.kind==="merge-notify"||w.kind==="ship-notify"){e.push({id:g.id,kind:"event",type:w.kind==="ship-notify"?"ship-notified":"merge-notified",text:w.text,ts:st(g),box:g.boxId,agent:g.provider});break}if(w.kind==="mock-picked"||w.kind==="ci-fix"||w.kind==="reconcile"||w.kind==="pr-go"||w.kind==="push-go"||w.kind==="spin-up-view"||w.kind==="prompt-answer"){w.kind==="prompt-answer"&&(D=null);break}for(let _ of S.values())_.status="stale";S.clear(),D&&D.box===g.boxId&&(e.push($_(D)),D=null),e.push({id:g.id,kind:"message",role:"user",text:w.text,ts:st(g),box:g.boxId,agent:g.provider,boxRole:w.boxRole,...g.actor==="orchestrator"?{orchestration:w.kind||!0}:{},...w.sourceId?{sourceId:w.sourceId}:{},...w.attachments?.length?{attachments:w.attachments}:{},...w.images?.length?{images:w.images}:{}});break}case"turn.agent":{if(w.model==="<synthetic>"&&/^No response requested\.$/.test(String(w.text||"").trim()))break;C();let _=(w.images||[]).filter(N=>!m(N,st(g)));if(!String(w.text||"").trim()&&!_.length)break;if(g.provider==="cursor"){let N=`${g.boxId}|${w.text}`;if(n.has(N))break;n.add(N)}(!D||D.box===g.boxId)&&(D=null);let v=g.provider==="codex"?(w.tools||[]).filter(N=>!Al.has(N)&&!Ko.has(N)):w.tools||[],O=w.text,E=null;if(Ul.has(g.provider)){let N=Hl(String(w.text||""));N&&(O=N.text,E={questions:N.questions})}e.push({id:g.id,kind:"message",role:"assistant",text:O,ts:st(g),box:g.boxId,agent:g.provider,boxRole:w.boxRole,...E?{ask:E}:{},...v.length?{tools:v}:{},...w.model?{model:w.model}:{},...w.sourceId?{sourceId:w.sourceId}:{},..._.length?{images:_}:{}}),E&&(D={id:g.id,groupId:Wl(g.provider,w.sourceId||g.id,E.questions),provider:g.provider,box:g.boxId,questions:E.questions,ts:st(g)});break}case"job.started":{if(!w.id)break;let _=st(g),v=i.get(w.id);if(!v&&(w.kind||"agent")==="agent"&&w.name){let Z=l.get(w.name);Z&&Z.ts>=_&&(v=Z)}let O={jobId:w.id,jobKind:w.kind||"agent",name:w.name||null,detail:w.detail||null,status:v?v.status&&v.status!=="completed"?"failed":"done":"running",startedTs:_,finishedTs:v?.ts??null,summary:v?.summary??null,resultHead:v?.resultHead??null},E=ot.get(g.boxId),N=E?.jobs[E.jobs.length-1]?.startedTs;if(E&&N!=null&&_-N<=it)E.jobs.push(O);else{let Z={id:g.id,kind:"job-dock",dockId:w.id,ts:_,box:g.boxId,agent:g.provider,jobs:[O]};ot.set(g.boxId,Z),e.push(Z)}break}case"handoff.done":e.push(gn(g,"lead-handoff"));break;case"subtask.delegated":e.push({...gn(g,"subtask-delegated"),to:g.boxId,toAgent:g.provider});break;case"subtask.merged":e.push(gn(g,"subtask-merged"));break;case"pr.state.changed":{if(!w.change)break;if(w.change==="opened"){for(let v of S.values())v.status="stale";S.clear()}let _=gn(g,`pr-${w.change}`);if(O_.has(w.change)&&w.number!=null){let v=C_(h,y,w);if(h.size>=2){for(let E of y.values())for(let N of E)N.grouped=!0;_.groupPrs=[...h.values()];let O=[_];for(let E of h.keys())y.set(E,O)}else{let O=y.get(v)??[];O.push(_),y.set(v,O)}}else if(w.change==="merged"||w.change==="closed"||w.change==="lost"){let v=bg(w),O=b&&!b.complete||h.size>=2;if(h.has(v))h.delete(v),y.delete(v);else if(w.number!=null)for(let E of[...h.keys()])E.endsWith(`#${w.number}`)&&(h.delete(E),y.delete(E));if(O&&w.number!=null){(!b||b.complete)&&(b={id:g.id,kind:"event",type:"pr-group-settled",prs:[],remaining:[],complete:!1,at:st(g),ts:st(g),box:g.boxId,agent:g.provider},e.push(b)),b.prs.push({number:w.number,url:w.url??null,title:w.title??null,repo:w.repo??null,branch:w.branch??null,change:w.change,ts:st(g)}),b.remaining=[...h.values()],b.complete=h.size===0;break}}e.push(_);break}case"pr.offered":{if(!w.offerId)break;for(let v of S.values())v.status="stale";S.clear();let _={id:g.id,kind:"pr-offer",offerId:w.offerId,excerpt:w.excerpt||null,branch:w.branch||null,base:w.base||null,offered:Array.isArray(w.offered)&&w.offered.length?w.offered:null,toPr:w.toPr&&typeof w.toPr=="object"?{number:w.toPr.number??null}:null,repos:Array.isArray(w.repos)&&w.repos.length?w.repos:null,status:"open",ts:st(g),box:g.boxId,agent:g.provider};S.set(w.offerId,_),I.set(w.offerId,_),e.push(_);break}case"pr.offer.answered":{let _=I.get(w.offerId);_&&(_.status=w.action||"dismiss",_.actions=Array.isArray(w.actions)?w.actions:null,_.notify=w.notify===!0,S.delete(w.offerId));break}case"box.state.changed":{let _=w.change==="model"?"model-change":w.change;if(!_)break;let v=gn(g,_);_==="model-change"&&(v.to=w.model),e.push(v);break}case"view.state.changed":{if(w.change!=="created"||w.restarted)break;e.push({id:g.id,kind:"view-card",viewId:w.viewId,name:w.name,slug:w.slug,port:w.port,origin:w.origin,ts:st(g),box:g.boxId,agent:g.provider});break}case"view.spinup.failed":{e.push({id:g.id,kind:"view-spinup-failed",name:w.name||null,reason:w.reason||"no-view",ts:st(g),box:g.boxId,agent:g.provider});break}case"mock.state.changed":{if(w.change==="picked"){e.push({id:g.id,kind:"mock-picked",mockId:w.mockId,name:w.name,group:w.group??null,version:w.version??null,siblings:w.siblings??[],ts:st(g),box:g.boxId,agent:g.provider});break}if(w.change!=="created")break;e.push({id:g.id,kind:"mock-card",mockId:w.mockId,name:w.name,group:w.group??null,ts:st(g),box:g.boxId,agent:g.provider});break}case"flow.state.changed":{if(w.change!=="created")break;e.push({id:g.id,kind:"flow-card",flowId:w.flowId,name:w.name,stepCount:w.stepCount??null,mockIds:w.mockIds??[],ts:st(g),box:g.boxId,agent:g.provider});break}case"runbook.state.changed":{if(w.change!=="created")break;e.push({id:g.id,kind:"runbook-card",runbookId:w.runbookId,name:w.name,ts:st(g),box:g.boxId,agent:g.provider});break}case"ticket.linked":{e.push({id:g.id,kind:"ticket-card",provider:w.provider||"linear",key:w.key,url:w.url,title:w.title??null,linkSource:w.linkSource??null,ts:st(g),box:g.boxId,agent:g.provider});break}case"ticket.move.offered":{if(!w.offerId)break;for(let v of M.values())v.key===w.key&&(v.status="stale",M.delete(v.offerId));let _={id:g.id,kind:"ticket-offer",offerId:w.offerId,provider:w.provider||"linear",key:w.key,url:w.url??null,phase:w.phase??null,from:w.from??null,to:w.to?.name??null,prNumber:w.prNumber??null,status:"open",ts:st(g),box:g.boxId,agent:g.provider};M.set(w.offerId,_),z.set(w.offerId,_),e.push(_);break}case"ticket.move.answered":{let _=z.get(w.offerId);_&&(_.status=w.action==="moved"?"moved":"dismiss",M.delete(w.offerId));break}case"ticket.state.changed":{for(let _ of M.values())_.key===w.key&&_.to===w.to&&(_.status="stale",M.delete(_.offerId));if(w.mode==="offer")break;e.push(gn(g,"ticket-state"));break}case"ticket.commented":{e.push({id:g.id,kind:"ticket-comment",key:w.key,url:w.url??null,excerpt:w.excerpt??null,ts:st(g),box:g.boxId,agent:g.provider});break}case"ticket.flow.changed":e.push(gn(g,"ticket-flow"));break;case"ticket.create.offered":{if(!w.proposalId)break;for(let v of $.values())v.status="stale";$.clear();let _={id:g.id,kind:"ticket-create-offer",proposalId:w.proposalId,provider:w.provider||"linear",epic:w.epic??null,issues:Array.isArray(w.issues)?w.issues:[],team:w.team??null,needsTeam:!!w.needsTeam,linkCurrentTrack:!!w.linkCurrentTrack,status:"open",ts:st(g),box:g.boxId,agent:g.provider};$.set(w.proposalId,_),F.set(w.proposalId,_),e.push(_);break}case"ticket.create.answered":{let _=F.get(w.proposalId);_&&(_.status=w.action==="created"?"created":"dismiss",_.createdCount=w.createdCount??null,_.failedCount=w.failedCount??null,$.delete(w.proposalId));break}case"ticket.created":{let _={items:Array.isArray(w.items)?w.items:[],failed:Array.isArray(w.failed)?w.failed:[],team:w.team??null},v=w.proposalId?F.get(w.proposalId):null;if(v){v.created=_;break}e.push({id:g.id,kind:"ticket-created",..._,ts:st(g),box:g.boxId,agent:g.provider});break}case"error":{if(!w.kind)break;e.push(gn(g,w.kind));break}case"provider.connected":{e.push({id:g.id,kind:"event",type:"provider-connected",provider:w.provider||g.provider,ts:st(g),at:st(g),box:g.boxId,agent:g.provider});break}case"prompt.answered":{if(D=null,w.control||!w.question)break;let _=w.groupId||w.promptId;if(!_)break;let v=T.get(_);v||(v={id:g.id,kind:"prompt",groupId:_,provider:g.provider,box:g.boxId,agent:g.provider,questionTotal:w.questionTotal??1,questions:[],ts:st(g)},T.set(_,v));let O=w.questionIndex??0,E={index:O,header:w.header||"",question:w.question,multiSelect:!!w.multiSelect,chosen:Array.isArray(w.chosen)?w.chosen:[]},N=v.questions.findIndex(Z=>Z.index===O&&Tt(Z.question)===Tt(E.question));N>=0?v.questions[N]=E:v.questions.push(E),v.questions.sort((Z,wt)=>Z.index-wt.index),v.questionTotal=Math.max(v.questionTotal,w.questionTotal??1),v.questions.length>=v.questionTotal&&(e.push(v),T.delete(_));break}default:break}}return N_(e.sort((g,w)=>(g.ts??0)-(w.ts??0)))}var M_=2*6e4;function N_(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<=M_){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 du(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:P_(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??Zo(t)).filter(o=>o.kind==="message");return Ch(n,r)}function P_(t){let e=String(t.summary||"").trim();return e&&e!==t.command&&e!==Rr(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:st(t),ts:st(t)}}function B_(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 fu=new Map;async function D_(t,e){let n=`${t.id}:${e.name}`;if(!fu.has(n)){let r=await L(t),o=new Set;for(let s of r)s.type==="cost.recorded"&&s.boxId===e.name&&s.payload?.codexTs!=null&&o.add(s.payload.codexTs);fu.set(n,o)}return fu.get(n)}async function kg(t,e){try{if(e?.agent!=="codex"||!e.cwd)return;let n=await qn(e.cwd,e.pinned?e.sessionId:null);if(!n)return;let r=await D_(t,e),o=B_(await Pt(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 Ja from"node:fs";import mu from"node:path";import j_ from"node:os";var L_=process.env.BOSTRAT_STATE_DIR||mu.join(process.env.HOME||j_.tmpdir(),".bostrat"),Sg=(t=process.env)=>!!t.BOSTRAT_CURSOR_USAGE_COOKIE,F_="https://cursor.com/api/dashboard/get-filtered-usage-events",xg=100;async function U_({sinceMs:t,cookie:e,fetchImpl:n=fetch}){let r=[];for(let o=1;o<=20;o++){let s=await n(F_,{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:xg})});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<xg)break}return r}function H_(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 W_=5*6e4,yn={at:0,rows:null,error:null},Xa=()=>mu.join(L_,"cursor-usage.json");function vg(){try{return JSON.parse(Ja.readFileSync(Xa(),"utf8"))}catch{return{sinceMs:0,seen:[]}}}function q_(t){try{Ja.mkdirSync(mu.dirname(Xa()),{recursive:!0});let e=Xa()+".tmp";Ja.writeFileSync(e,JSON.stringify(t)),Ja.renameSync(e,Xa())}catch{}}var K_=5e3;async function G_({fetchImpl:t,now:e=Date.now()}={}){if(yn.rows&&e-yn.at<W_)return yn.rows;let n=process.env.BOSTRAT_CURSOR_USAGE_COOKIE;if(!n)return null;let r=vg(),o=Math.max(r.sinceMs-6e4,e-24*36e5);try{let s=await U_({sinceMs:o,cookie:n,fetchImpl:t});yn={at:e,rows:s.map(H_).filter(Boolean),error:null}}catch(s){yn={at:e,rows:null,error:String(s?.message||s)}}return yn.rows}var hu=()=>({enabled:Sg(),lastPollAt:yn.at||null,error:yn.error}),pu=new Map;async function V_(t,e){let n=`${t.id}:${e.name}`;if(!pu.has(n)){let r=await L(t),o=new Set;for(let s of r)s.type==="cost.recorded"&&s.boxId===e.name&&s.payload?.cursorEventId&&o.add(s.payload.cursorEventId);pu.set(n,o)}return pu.get(n)}async function Tg(t,e,{fetchImpl:n}={}){try{if(e?.agent!=="cursor"||!e.cwd||!Sg())return;let r=await G_({fetchImpl:n});if(!r||!r.length)return;let o=await Uh(e.cwd,e.pinned?e.sessionId:null);if(!o?.chatId)return;let s=await V_(t,e),a=vg(),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}})}q_({sinceMs:c,seen:[...i].slice(-K_)})}catch{}}import{execFile as Z_}from"node:child_process";import{promisify as Q_}from"node:util";import Ag from"node:fs/promises";import Eg from"node:path";import z_ from"node:os";var _g=process.env.BOSTRAT_STATE_DIR||Eg.join(process.env.HOME||z_.tmpdir(),".bostrat"),J_=Eg.join(_g,"github-latency.ndjson"),X_=100,Ya=[],wu=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};Ya.push(l),Ya.length>X_&&Ya.shift();let u=wu.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)),wu.set(t,u),Y_(l),l}async function Y_(t){try{await Ag.mkdir(_g,{recursive:!0}),await Ag.appendFile(J_,JSON.stringify(t)+`
|
|
147
|
+
`)}catch{}}function Ig(){let t={};for(let[e,n]of wu)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:Ya.slice(-20)}}async function tI(t){let e=/workspaces[/\\]([0-9a-f]{8})(?:[/\\]|$)/.exec(t||"")?.[1];if(e)try{let[{stdout:n},{stdout:r}]=await Promise.all([Lr("git",["-C",t,"rev-parse","HEAD"]),Lr("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 Lr=Q_(Z_),eI={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 Lr("gh",e,{cwd:t,...eI,...o,env:{...process.env,...s,...o.env||{}}});return String(a||"")}catch(a){throw fn(a)&&pn(a),a}}var Qa=null;async function bu(){if(Qa&&Date.now()-Qa.at<6e4)return Qa.data;let t;try{Le("auth");let e=await Lr("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 uo();e.configured&&(t={installed:!0,authenticated:!0,login:e.username,via:"broker"})}catch{}return Qa={at:Date.now(),data:t},t}async function nI(t){try{let{stdout:e}=await Lr("git",["-C",t,"config","--get","remote.origin.url"],{timeout:5e3});return/github\.com/.test(e)}catch{return!1}}function $g(t){let e=t.name||t.context||"check",n=t.detailsUrl||t.targetUrl||null,r=t.completedAt||t.createdAt||null,o;return t.__typename==="StatusContext"?o=t.state==="SUCCESS"?"pass":t.state==="PENDING"||t.state==="EXPECTED"?"pending":"fail":t.status&&t.status!=="COMPLETED"?o="pending":["SUCCESS","NEUTRAL","SKIPPED"].includes(t.conclusion)?o="pass":o="fail",{name:e,state:o,url:n,completedAt:r}}function gu(t){let e=(t||[]).map($g),n=e.filter(s=>s.state==="pending").length,r=e.map(s=>Date.parse(s.completedAt||"")).filter(s=>s>0),o=e.length&&!n&&r.length?Math.max(...r):null;return{total:e.length,passed:e.filter(s=>s.state==="pass").length,failed:e.filter(s=>s.state==="fail").length,pending:n,failing:e.filter(s=>s.state==="fail").map(({name:s,url:a})=>({name:s,url:a})),settledAt:o}}var ku="number,title,url,headRefName,headRefOid,baseRefName,isDraft,author,reviewDecision,mergeable,mergeStateStatus,statusCheckRollup,autoMergeRequest,additions,deletions,createdAt,updatedAt",Og=ku.replace("statusCheckRollup,","");function xu(t){return/Resource not accessible by personal access token/.test(String(t?.stderr||t?.message||""))}async function Cg(t,e){if(!e)return null;let n=await et(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 Mg(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 Ng(t,e){let n=await Cg(t,e);return n===null?{...gu([]),unavailable:!0}:gu(Mg(n))}function ti(t,e=gu(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 rI=9e4,jr=new Map;function oI(t,e,n){let r=new Set;for(let o of e){let s=`${t}#${o.number}`;if(o.mergeable!=="UNKNOWN"&&o.mergeStateStatus!=="UNKNOWN"){jr.delete(s);continue}r.add(s),jr.has(s)||jr.set(s,n),o.mergeabilityPending=n-jr.get(s)<rI}for(let o of jr.keys())o.startsWith(`${t}#`)&&!r.has(o)&&jr.delete(o);return e}async function Fr(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",ku]);return ti(JSON.parse(o))}catch(o){if(!xu(o))throw o;let s=await Yt(n.path,["pr","view",String(r),"--json",Og]),a=JSON.parse(s);return ti(a,await Ng(n.path,a.headRefOid))}}var Rg=(t,e)=>t.number-e.number;async function ts(t,{caller:e="prs"}={}){try{let n=await Yt(t,["pr","list","--json",ku,"--limit","50"],{caller:e});return JSON.parse(n||"[]").map(r=>ti(r)).sort(Rg)}catch(n){if(!xu(n))throw n;let r=await Yt(t,["pr","list","--json",Og,"--limit","50"],{caller:e});return(await Promise.all(JSON.parse(r||"[]").map(async o=>ti(o,await Ng(t,o.headRefOid))))).sort(Rg)}}var sI=3e4,aI=1500,Qo=new Map;function ke(){Qo.clear()}async function iI(t,e){if(!t)return e;for(let n of e){let r=await Ot(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 Ra(t,n.number,{headSha:n.headSha}).catch(()=>{}):n.ciFix={delegatedAt:r.ciFix.delegatedAt,trackId:r.ciFix.trackId??null}),r.reconcile&&(o(r.reconcile)?await Oa(t,n.number,{headSha:n.headSha}).catch(()=>{}):n.reconcile={delegatedAt:r.reconcile.delegatedAt,trackId:r.reconcile.trackId??null})}return e}async function Pg(){let t=await bu();if(!t.authenticated)return{auth:t,repos:[]};let e=[];for(let a of await ve())await nI(a.path)&&e.push(a);let n=Date.now(),r=Ka(n)>0,o=await Promise.all(e.map(async a=>{let i=Qo.get(a.path),c=i?.pending?aI:sI;if(i&&(r||n-i.at<c))return i.entry;try{let l=await et(a.path).catch(()=>null),u=oI(a.path,await ts(a.path,{caller:"tab"}),n);if(await iI(l,u),i?.entry?.prs)for(let p of u)i.entry.prs.find(h=>h.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 Qo.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 Qo.keys())s.has(a)||Qo.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 Yn(t){return co(t)}var es=new Set(["squash","merge","rebase"]);async function Ur(t,e,{method:n="squash"}={}){let r=await Fe(t);if(!es.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 qi().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 ns(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 et(n.path).catch(()=>null);return o&&await fh(o,r).catch(()=>{}),{ok:!0,number:r}}async function Su(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 cI=2500,lI=9e4,uI=t=>new Promise(e=>setTimeout(e,t));function vu(t){return t.green&&!t.isDraft}function dI(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 Bg(t,{update:e=!0}={}){return t.isDraft?{action:"skip",reason:"draft"}:vu(t)?{action:"merge"}:e&&t.mergeStateStatus==="BEHIND"&&t.mergeable!=="CONFLICTING"&&t.checks.failed===0?{action:"update-arm"}:{action:"arm"}}function Dg(t){return[...t.filter(e=>e.plan.action!=="merge"),...t.filter(e=>e.plan.action==="merge")]}var yu=new Map;function jg(){let t={};for(let[e,n]of yu)t[e]={...n};return t}async function Lg(t,{method:e="merge",onMerged:n=null}={}){let r=await Fe(t);if(!es.has(e))throw new Error(`invalid merge method: ${e}`);if(yu.get(r.path)?.running)throw new Error("a merge-all is already running for this repo");let o=(await ts(r.path)).filter(vu).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 yu.set(r.path,s),pI(s,o,{listPrs:()=>ts(r.path),merge:a=>Ur(r.path,a,{method:e}),sleep:uI,onMerged:n}).catch(()=>{}),{ok:!0,repo:r.path,total:s.total,queue:o.map(a=>a.number)}}async function fI(t,e){let n=Date.now()+(e.settleMaxMs??lI),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(cI)}}async function pI(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 fI(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(!vu(a)){t.skipped.push({number:a.number,title:a.title,reason:dI(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 Tu(t,{title:e,body:n="",base:r=null}={}){if(typeof e!="string"||!e.trim())throw new Error("title required");try{await Lr("git",["-C",t,"push","-u","origin","HEAD"],{timeout:12e4,maxBuffer:4*1024*1024})}catch(l){throw new Error(`push failed: ${be(l)}`)}await tI(t);let o=n||"";if((await ae({cwd:t})).GH_TOKEN){let l=await qi().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 Fg(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(!xu(i))throw i;let c=await Yt(n.path,["pr","view",String(r),"--json","headRefOid"]),l=await Cg(n.path,JSON.parse(c).headRefOid);if(l===null)throw new Error(be(i));o=Mg(l).map($g).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=mI(i.link),l=null;c&&!s.has(c)&&(s.add(c),l=await Yt(n.path,["run","view",c,"--log-failed"]).then(u=>hI(u)).catch(()=>null)),a.push({name:i.name,link:i.link||null,log:l})}return a}function mI(t){return String(t||"").match(/\/actions\/runs\/(\d+)/)?.[1]??null}function hI(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
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 pI=6e3,Za=new Map;function xu(t){let e=null;for(let n of t||[]){let r=n.payload?.ts??n.ts;n.type==="pr.offer.answered"?e=n.payload?.action==="pr"?{ts:r,offerId:n.payload?.offerId??null,box:n.boxId??null}:null:n.type==="pr.state.changed"&&n.payload?.change==="opened"&&e!=null&&r>=e.ts&&(e=null)}return e}async function jg(t){let e=t?.workspace?.branch;if(!e)return;let n;try{n=await D(t)}catch{return}if(xu(n)==null)return;let r=(t.boxes||[]).find(c=>c.name===t.leadBox);if(await Wa(t.leadBox,r?.agent).catch(()=>!1))return;let o=Date.now();if(o-(Za.get(t.id)||0)<pI)return;Za.set(t.id,o);let s;try{s=await Yo(t.cwd,{caller:"reconcile"})}catch{return}let a=(s||[]).find(c=>c.headRefName===e);if(!a)return;let i=await tt(t.cwd).catch(()=>null);if(i){let{link:c,created:l}=await Wt({repo:i,number:a.number,trackId:t.id,url:a.url,headRef:e,title:a.title,source:"reconciled"});if(!l&&!(c?.trackId===t.id&&await Bo(i,a.number))){Za.delete(t.id);return}}await R(t,{type:"pr.state.changed",actor:"agent",source:"orchestrator",boxId:t.leadBox,payload:{ts:Date.now(),change:"opened",number:a.number,url:a.url,title:a.title,branch:e,...i?{repo:i}:{}}}).catch(()=>{}),Za.delete(t.id)}import mI from"node:fs/promises";import Wg from"node:path";import hI from"node:os";import{randomUUID as qg}from"node:crypto";var wI=process.env.BOSTRAT_STATE_DIR||Wg.join(process.env.HOME||hI.tmpdir(),".bostrat"),Kg=Wg.join(wI,"flows.json"),Lg=20,Fg=80,Ug=500,Hg=80,Ue=null,Su=null;async function He(){return Ue||(Su||=(async()=>{let t=!0;try{Ue=JSON.parse(await mI.readFile(Kg,"utf8"))}catch{Ue={flows:[]},t=!1}finally{Su=null}if(Ue.flows||=[],!t&&pt()){let{records:e,source:n}=await Tt("flows").catch(()=>({records:[],source:"error"}));if(n==="plane"){for(let r of e)!r.deletedAt&&r.data?.id&&Ue.flows.push(r.data);Ue.flows.length&&await Ur().catch(()=>{})}}return Ue})(),Su)}async function Ur(){await Mt(Kg,Ue)}var Qa=t=>Rt("flows",{...t},{trackId:t.trackId}).catch(()=>{});function vu(t,e,n={}){return R({id:t.trackId,cwd:t.cwd},{type:"flow.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box||null,provider:t.agent||null,payload:{ts:Date.now(),change:e,flowId:t.id,name:t.name,stepCount:t.steps.length,mockIds:[...new Set(t.steps.map(r=>r.mockId))],...n}}).catch(()=>{})}async function Gg({trackId:t,steps:e,prevSteps:n=[]}){if(!Array.isArray(e)||e.length<1||e.length>Lg)throw new Error(`steps must be an array of 1-${Lg}`);let r=await Nn({trackId:t}),o=[...n],s=[];for(let i=0;i<e.length;i++){let c=e[i]||{},l=`step ${i+1}`,u=typeof c.caption=="string"?c.caption.trim():"";if(!u)throw new Error(`${l}: caption (string) required`);if(u.length>Ug)throw new Error(`${l}: caption over ${Ug} chars`);let f=c.title==null?null:String(c.title).trim()||null;if(f&&f.length>Hg)throw new Error(`${l}: title over ${Hg} chars`);let d=String(c.mock??"").trim().replace(/^mock:/,"");if(!d)throw new Error(`${l}: mock (name or id) required`);let p=r.filter(S=>S.id===d);if(p.length||(p=r.filter(S=>S.name.toLowerCase()===d.toLowerCase())),!p.length)throw new Error(`${l}: no mock matching "${d}" in this Track \u2014 see \`bostrat-mock ls\``);if(p.length>1)throw new Error(`${l}: "${d}" is ambiguous (${p.map(S=>`mock:${S.id}`).join(", ")}) \u2014 use the id`);let m=p[0],g=null;if(c.v!=null&&(g=Number(c.v),!Number.isInteger(g)||!(m.versions||[]).some(S=>S.v===g)))throw new Error(`${l}: "${m.name}" has no captured v${c.v} to pin`);let y=o.findIndex(S=>S.mockId===m.id),b=y>=0?o.splice(y,1)[0].id:qg().slice(0,8);s.push({id:b,mockId:m.id,caption:u,title:f,v:g})}let a={};for(let i of s)i.mockId in a||(a[i.mockId]=r.find(c=>c.id===i.mockId)?.readyVersion??null);return{steps:s,resolved:a}}function gI(t){if(typeof t!="string"||!t.trim())throw new Error("name (string) required");if(t.trim().length>Fg)throw new Error(`name over ${Fg} chars`);return t.trim()}async function Vg({trackId:t}={}){let e=await He();return(t?e.flows.filter(r=>r.trackId===t):e.flows).map(r=>({...r}))}async function yI(t){let n=(await He()).flows.find(r=>r.id===t);return n?{...n}:null}async function zg(t){let e=await yI(t);if(!e)return null;let n=new Map((await Nn({trackId:e.trackId})).map(r=>[r.id,r]));return e.steps=e.steps.map(r=>{let o=n.get(r.mockId);if(!o)return{...r,missing:!0,mockName:null,mockState:null,servedV:null,width:null,height:null};let s=Ie(o,r.v);return{...r,missing:!1,mockName:o.name,mockState:o.state,servedV:s?.v??null,width:s?.width??null,height:s?.height??null}}),e}async function Jg({trackId:t,name:e,steps:n,cwd:r,box:o,agent:s}){if(!t)throw new Error("trackId required");if(!r)throw new Error("cwd required");let a=gI(e),i=await He(),c=i.flows.find(p=>p.trackId===t&&p.name.toLowerCase()===a.toLowerCase());if(c)throw new Error(`flow "${c.name}" already exists in this Track (flow:${c.id}) \u2014 revise it with \`bostrat-flow update\``);let{steps:l,resolved:u}=await Gg({trackId:t,steps:n}),f=Date.now(),d={id:qg().slice(0,8),trackId:t,box:o||null,agent:s||null,name:a,cwd:r,version:1,versions:[{v:1,steps:l,resolved:u,note:null,createdAt:f}],steps:l,createdAt:f,updatedAt:f};return i.flows.push(d),await Ur(),await vu(d,"created"),Qa(d),{...d}}async function Xg(t,{steps:e,note:n}={}){let o=(await He()).flows.find(i=>i.id===t);if(!o)throw new Error("flow not found");let{steps:s,resolved:a}=await Gg({trackId:o.trackId,steps:e,prevSteps:o.steps});return o.version+=1,o.versions.push({v:o.version,steps:s,resolved:a,note:n?String(n).slice(0,200):null,createdAt:Date.now()}),o.steps=s,o.updatedAt=Date.now(),await Ur(),await vu(o,"updated",{version:o.version,...n?{note:String(n).slice(0,200)}:{}}),Qa(o),{...o}}async function Yg(t){let e=await He(),n=e.flows.findIndex(o=>o.id===t);if(n===-1)throw new Error("flow not found");let r=e.flows[n];return e.flows.splice(n,1),await Ur(),await vu(r,"deleted"),Qt("flows",r.id).catch(()=>{}),{ok:!0,id:t,name:r.name}}async function Zg(t){return(await He()).flows.filter(n=>n.steps.some(r=>r.mockId===t)).map(n=>({...n}))}async function Qg(t){let n=(await He()).flows.filter(r=>r.trackId===t.id);for(let r of n)r.trackDeletedAt=Date.now(),r.trackName=t.name||t.id,r.updatedAt=Date.now(),Qa(r);return n.length&&await Ur(),n.length}async function ty(t){let e=await He(),n=e.flows.filter(r=>r.trackId===t);if(n.length){e.flows=e.flows.filter(r=>r.trackId!==t),await Ur();for(let r of n)Qt("flows",r.id).catch(()=>{})}return n.length}async function ey(){if(!pt())return{pushed:0,source:"unconfigured"};let{records:t,source:e}=await Tt("flows");if(e!=="plane"||t.length)return{pushed:0,source:e};let n=await He();for(let r of n.flows)await Qa(r);return{pushed:n.flows.length,source:"plane"}}import Tu from"node:fs/promises";import ts from"node:path";import{randomUUID as bI}from"node:crypto";var ry=".bostrat/uploads",Au=t=>ts.join(t,ry),ny=Number(process.env.BOSTRAT_MAX_UPLOAD_BYTES)||24*1024*1024,kI=new Set(["png","jpg","jpeg","gif","webp","svg","bmp","tif","tiff","heic","heif","avif","ico"]),xI=new Set(["pdf","doc","docx","xls","xlsx","ppt","pptx","odt","ods","odp","rtf","txt","md","markdown","rst","csv","tsv","json","jsonl","yaml","yml","xml","html","htm","log","ini","toml","conf","env","js","jsx","ts","tsx","py","rb","go","rs","java","kt","c","h","cpp","cc","cs","php","swift","sh","bash","zsh","sql","css","scss"]);function Eu(t){let e=String(t||"").toLowerCase().replace(/^\./,"");return kI.has(e)?"image":xI.has(e)?"doc":null}function SI(t){let r=ts.basename(String(t||"")).replace(/[\x00-\x1f]/g,"").replace(/[^A-Za-z0-9._-]+/g,"_").replace(/^_+|_+$/g,"").slice(-80)||"file";return r.startsWith(".")?`f${r}`:r}async function oy(t,{filename:e,buffer:n}){if(!t)throw new Error("cwd required");if(!Buffer.isBuffer(n)||n.length===0)throw new Error("empty upload");if(n.length>ny)throw new Error(`file too large \u2014 max ${Math.floor(ny/(1024*1024))} MB`);let r=SI(e),o=ts.extname(r).slice(1),s=Eu(o);if(!s)throw new Error(`unsupported file type "${o||"unknown"}" \u2014 attach an image or a document`);let a=bI().slice(0,8),i=Au(t);await Tu.mkdir(i,{recursive:!0});let c=`${a}-${r}`;return await Tu.writeFile(ts.join(i,c),n,{mode:384}),await Tu.chmod(ts.join(i,c),384).catch(()=>{}),{id:a,name:r,rel:`${ry}/${c}`,kind:s,size:n.length}}function sy(t){let e=t.map(r=>`\`${r}\``).join(", ");return`[Attached \u2014 please read ${t.length===1?"this file":`these ${t.length} files`}: ${e}]`}var vI=/\[Attached — please read (?:this file|these \d+ files): [^\]]*\]$/;function _u(t){return t.role==="user"&&vI.test((t.text||"").trim())}function Hr(t,e,n=Date.now()){return e?t.idleSince==null?(t.idleSince=n,!1):n-t.idleSince>=6500:(t.idleSince=null,!1)}var EI=AI(TI),We=process.env.BOSTRAT_STATE_DIR||F.join(process.env.HOME||"/tmp",".bostrat"),my=F.join(We,"tracks.json"),_I=F.join(We,"events.jsonl"),Nu=F.join(We,"workspaces"),II=process.env.BOSTRAT_DEFAULT_REPO||null,ri={claude:{envVar:"BOSTRAT_AGENT_CMD",base:"claude",skipFlag:"--dangerously-skip-permissions"},cursor:{envVar:"BOSTRAT_CURSOR_CMD",base:"cursor-agent",skipFlag:"--force"},codex:{envVar:"BOSTRAT_CODEX_CMD",base:"codex",skipFlag:"--dangerously-bypass-approvals-and-sandbox"}},oi=Object.keys(ri);function RI(t,{skipPermissions:e=!0}={}){let n=ri[t];if(!n)throw new Error(`unknown agent "${t}"`);let r=process.env[n.envVar];return r||(e&&n.skipFlag?`${n.base} ${n.skipFlag}`:n.base)}function hy(t,e,{skipPermissions:n=!0}={}){let r=ri[t];if(!r)throw new Error(`unknown agent "${t}"`);let o=process.env[r.envVar];if(o)return o;let s=n&&r.skipFlag?` ${r.skipFlag}`:"";switch(t){case"claude":return`${r.base} --resume ${e}${s}`;case"cursor":return`${r.base} --continue${s}`;case"codex":return`${r.base} resume --last${s}`;default:return`${r.base}${s}`}}function ls(t){let e=ri[t];if(e)return process.env[e.envVar]||e.base}var Xn={at:0,value:null};async function Bu({maxAgeMs:t=6e4}={}){if(Xn.value&&Date.now()-Xn.at<t)return Xn.value;let e=await Promise.all(oi.map(async n=>[n,await Hs(ls(n))]));return Xn.value=Object.fromEntries(e),Xn.at=Date.now(),Xn.value}async function si(t,e=!0){let n=RI(t,{skipPermissions:e});if(!await Hs(n)){Xn.at=0;let r=n.split(/\s+/)[0],o=process.env.FLY_APP_NAME?"This Fly node's image predates CLI parity \u2014 update the node image (Links page \u2192 Fly card \u2192 Update) or re-provision.":ac[r]?`Install it on this node: ${ac[r]}`:"Its CLI must be installed and on PATH first.";throw new Error(`agent "${t}" isn't available on this box (command not found: ${r}). ${o}`)}return n}var ai=".bostrat/handoff/latest.md";function $I(t,e=ai){return`${t.trim()}
|
|
154
|
+
`).find(n=>n.trim())||"gh failed"}var wI=6e3,ei=new Map;function Au(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 Ug(t){let e=t?.workspace?.branch;if(!e)return;let n;try{n=await L(t)}catch{return}if(Au(n)==null)return;let r=(t.boxes||[]).find(c=>c.name===t.leadBox);if(await Ga(t.leadBox,r?.agent).catch(()=>!1))return;let o=Date.now();if(o-(ei.get(t.id)||0)<wI)return;ei.set(t.id,o);let s;try{s=await ts(t.cwd,{caller:"reconcile"})}catch{return}let a=(s||[]).find(c=>c.headRefName===e);if(!a)return;let i=await et(t.cwd).catch(()=>null);if(i){let{link:c,created:l}=await qt({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 jo(i,a.number))){ei.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(()=>{}),ei.delete(t.id)}import gI from"node:fs/promises";import Gg from"node:path";import yI from"node:os";import{randomUUID as Vg}from"node:crypto";var bI=process.env.BOSTRAT_STATE_DIR||Gg.join(process.env.HOME||yI.tmpdir(),".bostrat"),zg=Gg.join(bI,"flows.json"),Hg=20,Wg=80,qg=500,Kg=80,Ue=null,Eu=null;async function He(){return Ue||(Eu||=(async()=>{let t=!0;try{Ue=JSON.parse(await gI.readFile(zg,"utf8"))}catch{Ue={flows:[]},t=!1}finally{Eu=null}if(Ue.flows||=[],!t&&pt()){let{records:e,source:n}=await At("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 Hr().catch(()=>{})}}return Ue})(),Eu)}async function Hr(){await Nt(zg,Ue)}var ni=t=>$t("flows",{...t},{trackId:t.trackId}).catch(()=>{});function _u(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 Jg({trackId:t,steps:e,prevSteps:n=[]}){if(!Array.isArray(e)||e.length<1||e.length>Hg)throw new Error(`steps must be an array of 1-${Hg}`);let r=await Pn({trackId:t}),o=[...n],s=[];for(let i=0;i<e.length;i++){let c=e[i]||{},l=`step ${i+1}`,u=typeof c.caption=="string"?c.caption.trim():"";if(!u)throw new Error(`${l}: caption (string) required`);if(u.length>qg)throw new Error(`${l}: caption over ${qg} chars`);let f=c.title==null?null:String(c.title).trim()||null;if(f&&f.length>Kg)throw new Error(`${l}: title over ${Kg} 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],h=null;if(c.v!=null&&(h=Number(c.v),!Number.isInteger(h)||!(m.versions||[]).some(S=>S.v===h)))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:Vg().slice(0,8);s.push({id:b,mockId:m.id,caption:u,title:f,v:h})}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 kI(t){if(typeof t!="string"||!t.trim())throw new Error("name (string) required");if(t.trim().length>Wg)throw new Error(`name over ${Wg} chars`);return t.trim()}async function Xg({trackId:t}={}){let e=await He();return(t?e.flows.filter(r=>r.trackId===t):e.flows).map(r=>({...r}))}async function xI(t){let n=(await He()).flows.find(r=>r.id===t);return n?{...n}:null}async function Yg(t){let e=await xI(t);if(!e)return null;let n=new Map((await Pn({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 Zg({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=kI(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 Jg({trackId:t,steps:n}),f=Date.now(),d={id:Vg().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 Hr(),await _u(d,"created"),ni(d),{...d}}async function Qg(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 Jg({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 Hr(),await _u(o,"updated",{version:o.version,...n?{note:String(n).slice(0,200)}:{}}),ni(o),{...o}}async function ty(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 Hr(),await _u(r,"deleted"),Qt("flows",r.id).catch(()=>{}),{ok:!0,id:t,name:r.name}}async function ey(t){return(await He()).flows.filter(n=>n.steps.some(r=>r.mockId===t)).map(n=>({...n}))}async function ny(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(),ni(r);return n.length&&await Hr(),n.length}async function ry(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 Hr();for(let r of n)Qt("flows",r.id).catch(()=>{})}return n.length}async function oy(){if(!pt())return{pushed:0,source:"unconfigured"};let{records:t,source:e}=await At("flows");if(e!=="plane"||t.length)return{pushed:0,source:e};let n=await He();for(let r of n.flows)await ni(r);return{pushed:n.flows.length,source:"plane"}}import Iu from"node:fs/promises";import rs from"node:path";import{randomUUID as SI}from"node:crypto";var ay=".bostrat/uploads",Ru=t=>rs.join(t,ay),sy=Number(process.env.BOSTRAT_MAX_UPLOAD_BYTES)||24*1024*1024,vI=new Set(["png","jpg","jpeg","gif","webp","svg","bmp","tif","tiff","heic","heif","avif","ico"]),TI=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 $u(t){let e=String(t||"").toLowerCase().replace(/^\./,"");return vI.has(e)?"image":TI.has(e)?"doc":null}function AI(t){let r=rs.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 iy(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>sy)throw new Error(`file too large \u2014 max ${Math.floor(sy/(1024*1024))} MB`);let r=AI(e),o=rs.extname(r).slice(1),s=$u(o);if(!s)throw new Error(`unsupported file type "${o||"unknown"}" \u2014 attach an image or a document`);let a=SI().slice(0,8),i=Ru(t);await Iu.mkdir(i,{recursive:!0});let c=`${a}-${r}`;return await Iu.writeFile(rs.join(i,c),n,{mode:384}),await Iu.chmod(rs.join(i,c),384).catch(()=>{}),{id:a,name:r,rel:`${ay}/${c}`,kind:s,size:n.length}}function cy(t){let e=t.map(r=>`\`${r}\``).join(", ");return`[Attached \u2014 please read ${t.length===1?"this file":`these ${t.length} files`}: ${e}]`}var EI=/\[Attached — please read (?:this file|these \d+ files): [^\]]*\]$/;function Ou(t){return t.role==="user"&&EI.test((t.text||"").trim())}function Wr(t,e,n=Date.now()){return e?t.idleSince==null?(t.idleSince=n,!1):n-t.idleSince>=6500:(t.idleSince=null,!1)}var RI=II(_I),We=process.env.BOSTRAT_STATE_DIR||H.join(process.env.HOME||"/tmp",".bostrat"),gy=H.join(We,"tracks.json"),$I=H.join(We,"events.jsonl"),ju=H.join(We,"workspaces"),OI=process.env.BOSTRAT_DEFAULT_REPO||null,si={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"}},ai=Object.keys(si);function CI(t,{skipPermissions:e=!0}={}){let n=si[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 yy(t,e,{skipPermissions:n=!0}={}){let r=si[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 ps(t){let e=si[t];if(e)return process.env[e.envVar]||e.base}var Zn={at:0,value:null};async function Fu({maxAgeMs:t=6e4}={}){if(Zn.value&&Date.now()-Zn.at<t)return Zn.value;let e=await Promise.all(ai.map(async n=>[n,await Gs(ps(n))]));return Zn.value=Object.fromEntries(e),Zn.at=Date.now(),Zn.value}async function ii(t,e=!0){let n=CI(t,{skipPermissions:e});if(!await Gs(n)){Zn.at=0;let r=n.split(/\s+/)[0],o=process.env.FLY_APP_NAME?"This Fly node's image predates CLI parity \u2014 update the node image (Links page \u2192 Fly card \u2192 Update) or re-provision.":lc[r]?`Install it on this node: ${lc[r]}`:"Its CLI must be installed and on PATH first.";throw new Error(`agent "${t}" isn't available on this box (command not found: ${r}). ${o}`)}return n}var ci=".bostrat/handoff/latest.md";function MI(t,e=ci){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 NI=/\(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 ly(t){return t.role==="user"&&NI.test((t.text||"").trim())}var ri=".bostrat/handoff/subtask-result.md",ss=".bostrat/handoff/lead-context.md",PI=(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 ${ss} \u2014 read that first for context. Your scoped task:
|
|
157
157
|
|
|
158
158
|
${t}
|
|
159
159
|
|
|
160
|
-
Work ONLY inside this directory. cd into the repo a change belongs to before running git commands, and commit per repo with a clear message (repos you don't touch need nothing). When you are finished: (1) make your code changes, (2) commit them in each changed repo, (3) LAST, write a short markdown digest to ${
|
|
160
|
+
Work ONLY inside this directory. cd into the repo a change belongs to before running git commands, and commit per repo with a clear message (repos you don't touch need nothing). When you are finished: (1) make your code changes, (2) commit them in each changed repo, (3) LAST, write a short markdown digest to ${ri} (at this directory's root) with sections \u2014 Result (what you did), Files (paths you changed, prefixed with the repo directory), Notes (anything the lead must know). Writing that file is your DONE signal: do it last, then stop and take no further action.`:`You are a SUBTASK agent working in an isolated git worktree, delegated by a lead agent on a shared workstream. The lead's full working tree is already checked out here, and the conversation so far is summarized in ${ss} \u2014 read that first for context. Your scoped task:
|
|
161
161
|
|
|
162
162
|
${t}
|
|
163
163
|
|
|
164
|
-
Work ONLY inside this directory. When you are finished: (1) make your code changes, (2) commit them with git and a clear message, (3) LAST, write a short markdown digest to ${
|
|
165
|
-
`;return Ru=Ru.then(()=>L.mkdir(We,{recursive:!0})).then(()=>L.appendFile(_I,n)).catch(()=>{}),Ru}var ct=(t,e,n={})=>{let r={at:Date.now(),type:e,...n};return t.events.push(r),NI(t.id,r),Ow(t,r),r};Uf(async({sessionName:t,from:e,to:n,version:r,status:o,error:s})=>{let i=(await W()).tracks.find(c=>c.leadBox===t||(c.boxes||[]).some(l=>l.name===t));i&&(ct(i,"codex-update",{box:t,from:e,to:r||n,status:o,...s?{error:s}:{}}),await z().catch(()=>{}))});var $u=(t,e,n)=>e!=null&&n.some(r=>t===r.box&&e>=r.from&&e<=r.to),PI=new Set(["stuck","wrong-approach","too-slow","want-different-model","context-refresh","cost","parallelize","other"]);function wy(t){if(!t)return null;let e=typeof t=="string"?[t]:Array.isArray(t?.codes)&&t.codes.length?t.codes:t?.code?[t.code]:[],n=[...new Set(e.filter(s=>PI.has(s)))];if(!n.length)return null;let r=typeof t=="object"?t?.note:null,o=r&&String(r).slice(0,500).trim()||null;return{code:n[0],...n.length>1?{codes:n}:{},note:o}}async function gy(t,e,n,{kind:r}={}){let o=await Z(e);if(!o)throw new Error(`box "${e}" is not running`);let s=ls(o.agent);await tn({cwd:o.cwd,sessionId:o.sessionId,sessionName:e,agent:o.agent,agentCmd:s}),await St(e,n,{..._e(o.agent),kind:r||"orchestration"}),await U({flow:"orchestration",phase:"sent",trackId:t,sessionName:e,agent:o.agent,kind:r||"orchestration",promptHead:n.slice(0,160)});let a=await W(),i=et(a,t);if(!i)return;let c=i.boxes.find(l=>l.name===e);ct(i,"orchestration-prompt",{box:e,agent:c?.agent||o.agent,boxRole:c?.role,prompt:n,kind:r||"orchestration"}),await z()}var Yn=new Set(["merged","conflict","canceled","failed","handed-off"]),iy=new Map;async function BI(t){let e=F.join(ea(t),`${t.id}.ndjson`),n;try{n=await L.stat(e)}catch{return null}let r=iy.get(t.id);if(r&&r.mtimeMs===n.mtimeMs)return r.at;let o=null;for(let s of await D(t)){if(s.type!=="turn.agent")continue;let a=s.payload?.ts??s.ts;a&&(!o||a>o)&&(o=a)}return iy.set(t.id,{mtimeMs:n.mtimeMs,at:o}),o}async function DI(t,e){let n=e.find(r=>r.name===t.leadBox);return n?.state!=="live"?{working:!1,prompted:!1}:tu(n.name,n.agent)}async function bn(t,e=null){let n=e??new Map((await Rn()).map(i=>[i.name,i])),r=t.boxes.map(i=>{let c=n.get(i.name),l=Yn.has(i.state)?i.state:c?i.role==="lead"?"live":i.state:"stopped";return{...i,state:l,cwd:c?.cwd||i.cwd,agent:c?.agent||i.agent,sessionId:c?.sessionId||i.sessionId}}),[o,{working:s,prompted:a}]=await Promise.all([BI(t),DI(t,r)]);return{...t,boxes:r,lastResponseAt:o,working:s,prompted:a}}var jI=5e3;function LI(t,e,n){let r=[...t].sort((l,u)=>l.createdAt-u.createdAt),o=[...e].sort((l,u)=>l.birthMs-u.birthMs),s=new Set,a=[];for(let l of r)l.sessionId&&n(l.sessionId)&&!s.has(l.sessionId)?s.add(l.sessionId):a.push(l);let i=new Map(r.map((l,u)=>[l,u])),c=[];for(let l of a){if(l.sessionId&&n(l.sessionId)){c.push({box:l.name,sessionId:null});continue}if(!l.sessionId||l.pinned)continue;let u=i.get(l),f=l.createdAt-jI,d=r[u+1]?.createdAt??1/0,p=o.find(m=>!s.has(m.id)&&m.birthMs>=f&&m.birthMs<d);p&&(s.add(p.id),c.push({box:l.name,sessionId:p.id}))}return c}async function yy(t){let e=new Map;for(let r of t.tracks)for(let o of r.boxes)o.agent==="claude"&&o.cwd&&(e.has(o.cwd)||e.set(o.cwd,[]),e.get(o.cwd).push({track:r,box:o}));let n=!1;for(let[r,o]of e){let s=o.map(d=>d.box.sessionId),a=Pf(r),i=new Set(a.map(d=>d.id)),c=s.every(d=>d&&i.has(d)),l=new Set(s).size===s.length;if(c&&l)continue;let u=o.map(d=>d.box),f=LI(u,a,d=>i.has(d));for(let d of f){let p=u.find(m=>m.name===d.box);p&&(p.sessionId=d.sessionId),await Ef(d.box,d.sessionId),n=!0}}return n}async function ot(){let t=await W();await yy(t)&&await z();let e=new Map((await Rn()).map(n=>[n.name,n]));return Promise.all(t.tracks.map(n=>bn(n,e)))}async function by(t,e){let n=await W();if(t)for(let r of n.tracks||[]){let o=r.boxes.find(s=>s.sessionId&&s.sessionId===t);if(o)return{track:r,box:o}}if(e)for(let r of n.tracks||[]){let o=r.boxes.find(s=>s.cwd===e);if(o)return{track:r,box:o}}return null}async function ky(t){let e=oo(t);if(!e)return null;let n=await W();for(let r of n.tracks||[])for(let o of r.boxes||[])if(o.hookTokenHash&&Md(o.hookTokenHash,e))return{track:r,box:o};return null}async function Q(t){let e=await W(),n=et(e,t);return n?(await yy(e)&&await z(),bn(n)):null}var FI={claude:[/^\[Request interrupted by user(?: for tool use)?\]$/i],codex:[/^\[?Request interrupted/i,/^Conversation interrupted/i],cursor:[/^\[?Request interrupted/i]};function cy(t){if(t.role!=="user")return!1;let e=(t.text||"").trim();return(FI[t.agent]||[]).some(n=>n.test(e))}var UI=[/^\/model(?:\s|$)/i,/<command-(?:name|message|args)>[^<]*\bmodel\b/i,/^Model changed to\b/i];function ly(t){let e=(t.text||"").trim();return e?UI.some(n=>n.test(e)):!1}var HI=10*6e4;async function Du(t,e={}){let n=await Q(t);if(!n)return null;let r=new Set([...n.events.filter(k=>k.type==="orchestration-prompt"&&k.prompt).map(k=>String(k.prompt).trim()),...n.events.filter(k=>k.type==="said"&&k.text).map(k=>String(k.text).trim())]),o=us(n.id),s=!!(o&&!o.finishedAt&&o.step!=="done"&&!o.error),a=s&&o?.startedAt?[...o.fromBox?[{box:o.fromBox,from:o.startedAt,to:Date.now()+1e12}]:[],...o.toBox?[{box:o.toBox,from:o.startedAt,to:Date.now()+1e12}]:[]]:[],i=Mc(n.id),c=!!(i&&!i.finishedAt),l=c&&i.startedAt&&i.box?[{box:i.box,from:i.startedAt,to:Date.now()+1e12}]:[],u=[...cm(n),...l],d={windows:[...vp(n),...Tp(n,oa),...a,...u]},m=(await Promise.all(n.boxes.map(k=>Rl(k,e)))).flat(),g=m.map(k=>{let V={kind:"message",...k};if(V.role==="assistant"&&(V.text||"").trim()&&V.tools?.length){let er=Wo(V.agent).cliTools,As=V.tools.filter(nr=>!er.has(nr));return{...V,tools:As.length?As:void 0}}return V}).map(k=>{if(k.role!=="user")return k;let V=Ka(k.text);return V?V.kind==="suppress"?null:{...k,note:V,text:mg(V)}:k}).filter(Boolean).filter(k=>!(k.role==="user"&&r.has((k.text||"").trim()))).filter(k=>!cy(k)).filter(k=>!ly(k)).filter(k=>!ay(k)).filter(k=>!_u(k)).filter(k=>k.role!=="assistant"||!!(k.text||"").trim()||!!k.images?.length).filter(k=>!Ap(k,d)),y=g.filter(k=>k.failed&&k.text===vl);if(y.length){let k=new Map;for(let V of y){if(!k.has(V.box)){let nr=[...(await ft(V.box,{lines:xt}).catch(()=>"")).matchAll(/^\s*■\s+(.+?)\s*$/gm)].map(Ni=>Ni[1]);k.set(V.box,nr.length?nr[nr.length-1]:null)}let er=k.get(V.box);er&&(V.text=`\u25A0 ${er}`)}}let b=await Promise.all(n.boxes.map(k=>zh(k,e)));await fg(n,b.flat()),await ug(n,g),await Cw(n),await hm(n,g).catch(()=>{});for(let k of n.boxes)k.agent==="codex"&&k.state==="live"&&await gg(n,k),k.agent==="cursor"&&k.state==="live"&&await xg(n,k);let S=await Promise.all(n.boxes.map(k=>Vh(k,e)));await dg(n,S.flat().filter(k=>!$u(k.box,k.ts,u)));let E=await D(n),M=c&&i.viewId?i.viewId:null,J=Hw(E),$=Jo(E).filter(k=>!(k.kind==="message"&&(cy(k)||ly(k)||ay(k)||_u(k)))&&!(k.kind==="view-card"&&M&&k.viewId===M)),j=Ww($,J),v=$.filter(k=>!(k.kind==="message"&&k.role==="assistant"&&!j.has(k)&&$u(k.box,k.ts,J))),C=iu(E,v).map(k=>({kind:"command-batch",id:k.id,ts:k.pending?k.ts:(k.anchorTs??k.ts)-1,anchorTs:k.anchorTs,anchorBox:k.anchorBox,agent:k.agent,boxRole:k.boxRole,commands:k.commands,pending:k.pending})).filter(k=>!$u(k.anchorBox,k.anchorTs??k.ts,u)),q=[...v,...C].filter(k=>k.ts!=null).sort((k,V)=>k.ts-V.ts),rt=WI(n),st=rt.size?q.filter(k=>k.kind==="event"||!rt.has(k.box??k.anchorBox)):q,w=n.boxes.find(k=>k.name===n.leadBox)||null,h=w?await bo(w.cwd,w.sessionId,{fallbackNewest:!1}):null;if(!h&&w){let k=[...E].reverse().find(V=>V.type==="context.usage"&&V.boxId===w.name);k?.payload?.window&&(h={pct:k.payload.pct??(k.payload.occupancy!=null?k.payload.occupancy/k.payload.window:null),occupancy:k.payload.occupancy??null,window:k.payload.window,windowKnown:!0,model:k.payload.model||null})}let T=[...m].reverse().find(k=>k.role==="assistant"&&k.model),_=T?.model||null,O=[...n.events||[]].reverse().find(k=>k.type==="model-change"),I=h?.model||_;O&&(!T?.ts||O.at>=T.ts)&&(I=O.to);let A=w?await ft(w.name,{lines:xt}).catch(()=>""):"",G=w&&Cr(w.agent,A)?await Ze(w.name).catch(()=>A):A,K=w?Vn(w,G):null;!K&&w&&!Ee(w.agent,A)&&!uy(n.id).length&&(K=Fl(st,{leadBoxId:w.name,provider:w.agent,isAnswered:k=>E.some(V=>V.type==="prompt.answered"&&V.payload?.promptId===k)})),K&&Mw(n,w,K);let dt=st.filter(k=>!(k.kind==="message"&&k.role==="assistant"&&k.ask&&!String(k.text||"").trim()&&!k.images?.length)),yt=!!w&&!K&&Ee(w.agent,A);w&&!w.readyAt&&(yt||K||A&&Xs(w.agent,A))&&(w.readyAt=Date.now(),await z());let Ot=!!w&&!w.readyAt&&Date.now()-(n.createdAt||0)<HI;if(n.kind==="setup"&&n.setup?.provider&&!n.setup.connectedAt&&w&&Xs(w.agent,A)&&!K){let k=await W(),V=et(k,n.id);V?.setup&&!V.setup.connectedAt&&(V.setup.connectedAt=Date.now(),n.setup.connectedAt=V.setup.connectedAt,await R(V,{type:"provider.connected",actor:"system",source:"pane",boxId:w.name,provider:w.agent,payload:{provider:w.agent,ts:V.setup.connectedAt}}).catch(()=>{}),await z())}let Vt=s?{running:!0,step:o.step,toAgent:o.toAgent||null}:null,Zt=null;if(c){let k=Date.now()-i.startedAt;if(i.viewId&&!yt){let V=da(n.id);V&&(ct(n,"view-spinup",{box:i.box,startedAt:i.startedAt,endedAt:V.finishedAt}),await z())}else if(K||k>am&&!yt&&!i.viewId||k>Cc){let V=K?"needs-input":k>Cc?"timeout":"no-view";da(n.id,{error:V})&&await R(n,{type:"view.spinup.failed",actor:"orchestrator",source:"orchestrator",boxId:i.box,provider:w?.agent||null,payload:{ts:i.startedAt,name:i.name,reason:V}}).catch(()=>{})}else Zt={running:!0,step:i.step,name:i.name}}return{trackId:n.id,leadBox:n.leadBox,items:dt,context:h?{pct:h.pct,occupancy:h.occupancy,window:h.window,windowKnown:h.windowKnown,model:h.model}:null,models:{current:I,options:Ew(w?.agent)},pendingPrompt:K,working:yt,stopping:JI(n.id),leadStarting:Ot,handoff:Vt,view:Zt,queued:uy(n.id).filter(k=>k.kind!=="ship-notify"&&k.kind!=="prompt-answer")}}function WI(t){let e=new Set;for(let n of t.boxes||[])n.role==="subtask"&&n.name&&e.add(n.name);for(let n of Object.values(t.subtasks||{}))n?.box&&e.add(n.box);return e}async function xy(t,e){let n=await Q(t);if(!n)return null;let r=zr(t).find(f=>f.id===e)||null;if(!r)return null;let o=r.box||null;if(!o)return{items:[],working:!1,box:null,agent:r.agent,task:r.task};let s=await D(n),a=Jo(s).filter(f=>f.box===o&&!(f.kind==="message"&&f.role==="assistant"&&f.ask&&!String(f.text||"").trim()&&!f.images?.length)),i=iu(s.filter(f=>f.boxId===o),a.filter(f=>f.kind==="message")).map(f=>({kind:"command-batch",id:f.id,ts:f.pending?f.ts:(f.anchorTs??f.ts)-1,anchorTs:f.anchorTs,anchorBox:f.anchorBox,agent:f.agent,boxRole:f.boxRole,commands:f.commands,pending:f.pending})),c=[...a,...i].filter(f=>f.ts!=null).sort((f,d)=>f.ts-d.ts),l=n.boxes.find(f=>f.name===o)||null,u=!1;if(l&&l.state==="live"){let f=await ft(o,{lines:xt}).catch(()=>"");u=Ee(l.agent||r.agent,f)}return{items:c,working:u,box:o,agent:r.agent,task:r.task}}async function ju(t,{promptId:e,answers:n,text:r}={}){let o=et(await W(),t);if(!o)throw new Error(`track "${t}" not found`);Uu(t,"prompt-answer");let s=o.boxes.find(d=>d.name===o.leadBox);if(!s)throw new Error(`track "${t}" has no lead box`);if(!await Z(o.leadBox))throw new Error(`lead box "${o.leadBox}" is not running`);let i=await Bl(s);if(i||(i=await qI(o,s)),!i)throw new Error("no pending prompt to answer");if(e&&e!==i.promptId)throw new Error("prompt changed since it was shown \u2014 reload and answer the current prompt");let c;if(i.answerMode==="text"){let d=typeof r=="string"?r.trim():"";if(!d)throw new Error("this prompt needs a code \u2014 paste it into the card");if(/\s/.test(d)||d.length>2048)throw new Error("that doesn't look like a sign-in code \u2014 paste the code exactly as shown");await St(o.leadBox,d,{enter:!0,kind:"answer-prompt"}),c={mode:"text-input",value:"\u2039code\u203A",count:1,redacted:!0}}else if(i.answerMode==="esc")await bt(o.leadBox,["Escape"],{kind:"answer-prompt"}),c={mode:"keys",value:"Escape",count:1};else if(i.answerMode==="say"){let d=fw(i,n||[],typeof r=="string"?r:"");if(!d)throw new Error("no answer selected \u2014 pick an option or reply in chat");await oe(o.id,d,{kind:"prompt-answer"}),c={mode:"say",value:d,count:1}}else if(i.provider==="claude"||i.answerMode==="keys"){let d=sw(i,n||[]);await bt(o.leadBox,d,{kind:"answer-prompt"}),c={mode:"keys",value:d.join(" "),count:d.length}}else{let d=zf(i,n||[]);await St(o.leadBox,d,{enter:!0,kind:"answer-prompt"}),c={mode:"text",value:d,count:1}}if(i.answerMode==="say"){let d=i.questions||[];for(let p=0;p<d.length;p++){let m=d[p],g=(Array.isArray(n?.[p])?n[p]:[]).map(y=>m.options?.[y]).filter(Boolean).map(y=>({label:y.label,description:y.description||""}));Zl(o,s,i,{...c,groupId:i.groupId||i.promptId,questionIndex:p,questionTotal:i.questionTotal??d.length,header:m.header||"",question:m.question||"",multiSelect:!!m.multiSelect,chosen:g})}return await U({flow:"prompt",phase:"answered",sessionName:o.leadBox,agent:s.agent,provider:i.provider,promptId:i.promptId,...c}).catch(()=>{}),{ok:!0,promptId:i.promptId,sent:c.count}}let l=i.questions?.[0]||{},u=(Array.isArray(n?.[0])?n[0]:[]).map(d=>l.options?.[d]).filter(Boolean).map(d=>({label:d.label,description:d.description||""})),f=i.review?"review":/^claude:model-confirm:/.test(i.promptId||"")?"model-confirm":/^claude:resume-confirm:/.test(i.promptId||"")?"resume-confirm":void 0;return c={...c,groupId:i.groupId||i.promptId,questionIndex:i.questionIndex??0,questionTotal:i.questionTotal??1,header:l.header||"",question:l.question||"",multiSelect:!!l.multiSelect,chosen:u,...f?{control:f}:{}},await U({flow:"prompt",phase:"answered",sessionName:o.leadBox,agent:s.agent,provider:i.provider,promptId:i.promptId,...c}).catch(()=>{}),Zl(o,s,i,c),{ok:!0,promptId:i.promptId,sent:c.count}}async function qI(t,e){if(!e)return null;let n=await D(t).catch(()=>[]);return Fl(Jo(n),{leadBoxId:e.name,provider:e.agent,isAnswered:r=>n.some(o=>o.type==="prompt.answered"&&o.payload?.promptId===r)})}async function Sy(t,{promptId:e,questionIndex:n}={}){let r=et(await W(),t);if(!r)throw new Error(`track "${t}" not found`);let o=r.boxes.find(l=>l.name===r.leadBox);if(!o)throw new Error(`track "${t}" has no lead box`);if(!await Z(r.leadBox))throw new Error(`lead box "${r.leadBox}" is not running`);let a=await Bl(o);if(!a)throw new Error("no pending prompt to navigate");if(a.provider!=="claude")throw new Error("prompt navigation not supported for this provider");if(a.review)throw new Error("cannot navigate on the review screen \u2014 answer or cancel");let i=a.questionTotal??1;if(i<2)throw new Error("not a multi-question prompt");if(!Number.isInteger(n)||n<0||n>=i)throw new Error("questionIndex out of range");if(e&&e!==a.promptId)throw new Error("prompt changed since it was shown \u2014 reload and try again");let c=aw(a,n);return c.length?(await bt(r.leadBox,c,{kind:"navigate-prompt"}),await U({flow:"prompt",phase:"navigated",sessionName:r.leadBox,agent:o.agent,provider:a.provider,promptId:a.promptId,fromIndex:a.questionIndex??0,toIndex:n,mode:"keys",value:c.join(" "),count:c.length}).catch(()=>{}),{ok:!0,promptId:a.promptId,sent:c.length}):{ok:!0,promptId:a.promptId,sent:0}}var KI=1500,rs=new Map;function Lu(t){let e=rs.get(t);return e||(e={items:[],timer:null,flushing:!1,idleSince:null},rs.set(t,e)),e}function uy(t){return(rs.get(t)?.items??[]).map(e=>({...e}))}function ii(t){let e=rs.get(t);if(!e)return[];let n=e.items.splice(0);return e.idleSince=null,e.timer&&(clearInterval(e.timer),e.timer=null),n}function vy(t){let e=Lu(t);e.timer||(e.timer=setInterval(()=>GI(t).catch(()=>{}),KI))}async function GI(t){let e=rs.get(t);if(!(!e||e.flushing)){if(!e.items.length){e.timer&&(clearInterval(e.timer),e.timer=null);return}e.flushing=!0;try{let n=et(await W(),t);if(!n||n.archivedAt){ii(t);return}let r=us(t);if(r&&!r.finishedAt&&r.step!=="done"&&!r.error){Hr(e,!1);return}let o=await Z(n.leadBox);if(!o){Hr(e,!1);return}let s=n.boxes.find(c=>c.name===n.leadBox),a=await ft(n.leadBox,{lines:xt}).catch(()=>"");if(Js(s?.agent,a)){Hr(e,!1);return}if(Cr(s?.agent,a)){let c=await Ze(n.leadBox).catch(()=>a);if(Vn(s,c)){Hr(e,!1);return}}if(!Hr(e,!0))return;let i=e.items.shift();e.idleSince=null,await Ty(n,s,o,i.text,i.kind??null,i.attachments??null),await z()}finally{e.flushing=!1}}}async function Ty(t,e,n,r,o=null,s=null){let a=ei.get(t.id)||0,i=r,c=!1;t.pendingHandoffHydrate&&(i=$I(r,t.pendingHandoffHydrate.artifact||ai),delete t.pendingHandoffHydrate,c=!0);let l=Array.isArray(s)?s.filter(f=>f&&f.rel):[];if(l.length){let f=sy(l.map(d=>d.rel));i=i.trim()?`${i.trim()}
|
|
164
|
+
Work ONLY inside this directory. When you are finished: (1) make your code changes, (2) commit them with git and a clear message, (3) LAST, write a short markdown digest to ${ri} with sections \u2014 Result (what you did), Files (paths you changed), Notes (anything the lead must know). Writing that file is your DONE signal: do it last, then stop and take no further action.`,BI=(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,Cu=null;async function W(){return xe||(Cu||=(async()=>{try{xe=JSON.parse(await U.readFile(gy,"utf8"))}catch{xe={tracks:[]}}finally{Cu=null}return xe})(),Cu)}async function V(){await Nt(gy,xe)}var Y=(t,e)=>t.tracks.find(n=>n.id===e)||null,Mu=Promise.resolve();function DI(t,e){let n=JSON.stringify({trackId:t,...e})+`
|
|
165
|
+
`;return Mu=Mu.then(()=>U.mkdir(We,{recursive:!0})).then(()=>U.appendFile($I,n)).catch(()=>{}),Mu}var lt=(t,e,n={})=>{let r={at:Date.now(),type:e,...n};return t.events.push(r),DI(t.id,r),Nw(t,r),r};qf(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&&(lt(i,"codex-update",{box:t,from:e,to:r||n,status:o,...s?{error:s}:{}}),await V().catch(()=>{}))});var Nu=(t,e,n)=>e!=null&&n.some(r=>t===r.box&&e>=r.from&&e<=r.to),jI=new Set(["stuck","wrong-approach","too-slow","want-different-model","context-refresh","cost","parallelize","other"]);function by(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=>jI.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 ky(t,e,n,{kind:r}={}){let o=await Q(e);if(!o)throw new Error(`box "${e}" is not running`);let s=ps(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 q({flow:"orchestration",phase:"sent",trackId:t,sessionName:e,agent:o.agent,kind:r||"orchestration",promptHead:n.slice(0,160)});let a=await W(),i=Y(a,t);if(!i)return;let c=i.boxes.find(l=>l.name===e);lt(i,"orchestration-prompt",{box:e,agent:c?.agent||o.agent,boxRole:c?.role,prompt:n,kind:r||"orchestration"}),await V()}var tr=new Set(["merged","conflict","canceled","failed","handed-off"]),uy=new Map;async function LI(t){let e=H.join(sa(t),`${t.id}.ndjson`),n;try{n=await U.stat(e)}catch{return null}let r=uy.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 uy.set(t.id,{mtimeMs:n.mtimeMs,at:o}),o}async function FI(t,e){let n=e.find(r=>r.name===t.leadBox);return n?.state!=="live"?{working:!1,prompted:!1}:ou(n.name,n.agent)}async function bn(t,e=null){let n=e??new Map((await $n()).map(i=>[i.name,i])),r=t.boxes.map(i=>{let c=n.get(i.name),l=tr.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([LI(t),FI(t,r)]);return{...t,boxes:r,lastResponseAt:o,working:s,prompted:a}}var UI=5e3;function HI(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-UI,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 xy(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=jf(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=HI(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 Rf(d.box,d.sessionId),n=!0}}return n}async function at(){let t=await W();await xy(t)&&await V();let e=new Map((await $n()).map(n=>[n.name,n]));return Promise.all(t.tracks.map(n=>bn(n,e)))}async function Sy(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 vy(t){let e=ao(t);if(!e)return null;let n=await W();for(let r of n.tracks||[])for(let o of r.boxes||[])if(o.hookTokenHash&&Bd(o.hookTokenHash,e))return{track:r,box:o};return null}async function tt(t){let e=await W(),n=Y(e,t);return n?(await xy(e)&&await V(),bn(n)):null}var WI={claude:[/^\[Request interrupted by user(?: for tool use)?\]$/i],codex:[/^\[?Request interrupted/i,/^Conversation interrupted/i],cursor:[/^\[?Request interrupted/i]};function dy(t){if(t.role!=="user")return!1;let e=(t.text||"").trim();return(WI[t.agent]||[]).some(n=>n.test(e))}var qI=[/^\/model(?:\s|$)/i,/<command-(?:name|message|args)>[^<]*\bmodel\b/i,/^Model changed to\b/i];function fy(t){let e=(t.text||"").trim();return e?qI.some(n=>n.test(e)):!1}var KI=10*6e4;async function Uu(t,e={}){let n=await tt(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=ms(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=Bc(n.id),c=!!(i&&!i.finishedAt),l=c&&i.startedAt&&i.box?[{box:i.box,from:i.startedAt,to:Date.now()+1e12}]:[],u=[...dm(n),...l],d={windows:[...Ep(n),..._p(n,ca),...a,...u]},m=(await Promise.all(n.boxes.map(k=>Cl(k,e)))).flat(),h=m.map(k=>{let G={kind:"message",...k};if(G.role==="assistant"&&(G.text||"").trim()&&G.tools?.length){let rr=Go(G.agent).cliTools,Rs=G.tools.filter(or=>!rr.has(or));return{...G,tools:Rs.length?Rs:void 0}}return G}).map(k=>{if(k.role!=="user")return k;let G=za(k.text);return G?G.kind==="suppress"?null:{...k,note:G,text:gg(G)}:k}).filter(Boolean).filter(k=>!(k.role==="user"&&r.has((k.text||"").trim()))).filter(k=>!dy(k)).filter(k=>!fy(k)).filter(k=>!ly(k)).filter(k=>!Ou(k)).filter(k=>k.role!=="assistant"||!!(k.text||"").trim()||!!k.images?.length).filter(k=>!Ip(k,d)),y=h.filter(k=>k.failed&&k.text===El);if(y.length){let k=new Map;for(let G of y){if(!k.has(G.box)){let or=[...(await ft(G.box,{lines:xt}).catch(()=>"")).matchAll(/^\s*■\s+(.+?)\s*$/gm)].map(Di=>Di[1]);k.set(G.box,or.length?or[or.length-1]:null)}let rr=k.get(G.box);rr&&(G.text=`\u25A0 ${rr}`)}}let b=await Promise.all(n.boxes.map(k=>Yh(k,e)));await hg(n,b.flat()),await pg(n,h),await Pw(n),await ym(n,h).catch(()=>{});for(let k of n.boxes)k.agent==="codex"&&k.state==="live"&&await kg(n,k),k.agent==="cursor"&&k.state==="live"&&await Tg(n,k);let S=await Promise.all(n.boxes.map(k=>Xh(k,e)));await mg(n,S.flat().filter(k=>!Nu(k.box,k.ts,u)));let I=await L(n),M=c&&i.viewId?i.viewId:null,z=Kw(I),$=Zo(I).filter(k=>!(k.kind==="message"&&(dy(k)||fy(k)||ly(k)||Ou(k)))&&!(k.kind==="view-card"&&M&&k.viewId===M)),F=Gw($,z),T=$.filter(k=>!(k.kind==="message"&&k.role==="assistant"&&!F.has(k)&&Nu(k.box,k.ts,z))),C=du(I,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=>!Nu(k.anchorBox,k.anchorTs??k.ts,u)),D=[...T,...C].filter(k=>k.ts!=null).sort((k,G)=>k.ts-G.ts),it=GI(n),ot=it.size?D.filter(k=>k.kind==="event"||!it.has(k.box??k.anchorBox)):D,A=n.boxes.find(k=>k.name===n.leadBox)||null,g=A?await xo(A.cwd,A.sessionId,{fallbackNewest:!1}):null;if(!g&&A){let k=[...I].reverse().find(G=>G.type==="context.usage"&&G.boxId===A.name);k?.payload?.window&&(g={pct:k.payload.pct??(k.payload.occupancy!=null?k.payload.occupancy/k.payload.window:null),occupancy:k.payload.occupancy??null,window:k.payload.window,windowKnown:!0,model:k.payload.model||null})}let w=[...m].reverse().find(k=>k.role==="assistant"&&k.model),_=w?.model||null,v=[...n.events||[]].reverse().find(k=>k.type==="model-change"),O=g?.model||_;v&&(!w?.ts||v.at>=w.ts)&&(O=v.to);let E=A?await ft(A.name,{lines:xt}).catch(()=>""):"",N=A&&Mr(A.agent,E)?await Ze(A.name).catch(()=>E):E,nt=A?Jn(A,N):null;!nt&&A&&!Ee(A.agent,E)&&!py(n.id).length&&(nt=ql(ot,{leadBoxId:A.name,provider:A.agent,isAnswered:k=>I.some(G=>G.type==="prompt.answered"&&G.payload?.promptId===k)})),nt&&Bw(n,A,nt);let Z=ot.filter(k=>!(k.kind==="message"&&k.role==="assistant"&&k.ask&&!String(k.text||"").trim()&&!k.images?.length)),wt=!!A&&!nt&&Ee(A.agent,E);A&&!A.readyAt&&(wt||nt||E&&ta(A.agent,E))&&(A.readyAt=Date.now(),await V());let Ct=!!A&&!A.readyAt&&Date.now()-(n.createdAt||0)<KI;if(n.kind==="setup"&&n.setup?.provider&&!n.setup.connectedAt&&A&&ta(A.agent,E)&&!nt){let k=await W(),G=Y(k,n.id);G?.setup&&!G.setup.connectedAt&&(G.setup.connectedAt=Date.now(),n.setup.connectedAt=G.setup.connectedAt,await R(G,{type:"provider.connected",actor:"system",source:"pane",boxId:A.name,provider:A.agent,payload:{provider:A.agent,ts:G.setup.connectedAt}}).catch(()=>{}),await V())}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&&!wt){let G=ha(n.id);G&&(lt(n,"view-spinup",{box:i.box,startedAt:i.startedAt,endedAt:G.finishedAt}),await V())}else if(nt||k>lm&&!wt&&!i.viewId||k>Pc){let G=nt?"needs-input":k>Pc?"timeout":"no-view";ha(n.id,{error:G})&&await R(n,{type:"view.spinup.failed",actor:"orchestrator",source:"orchestrator",boxId:i.box,provider:A?.agent||null,payload:{ts:i.startedAt,name:i.name,reason:G}}).catch(()=>{})}else Zt={running:!0,step:i.step,name:i.name}}return{trackId:n.id,leadBox:n.leadBox,items:Z,context:g?{pct:g.pct,occupancy:g.occupancy,window:g.window,windowKnown:g.windowKnown,model:g.model}:null,models:{current:O,options:Rw(A?.agent)},pendingPrompt:nt,working:wt,stopping:eR(n.id),leadStarting:Ct,handoff:Vt,view:Zt,queued:py(n.id).filter(k=>k.kind!=="ship-notify"&&k.kind!=="prompt-answer"),draft:n.draft||null,draftRev:n.draftRev||0}}function GI(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 Ty(t,e){let n=await tt(t);if(!n)return null;let r=Xr(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=Zo(s).filter(f=>f.box===o&&!(f.kind==="message"&&f.role==="assistant"&&f.ask&&!String(f.text||"").trim()&&!f.images?.length)),i=du(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 Hu(t,{promptId:e,answers:n,text:r}={}){let o=Y(await W(),t);if(!o)throw new Error(`track "${t}" not found`);ui(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 Q(o.leadBox))throw new Error(`lead box "${o.leadBox}" is not running`);let i=await Ll(s);if(i||(i=await VI(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=hw(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=cw(i,n||[]);await bt(o.leadBox,d,{kind:"answer-prompt"}),c={mode:"keys",value:d.join(" "),count:d.length}}else{let d=Yf(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],h=(Array.isArray(n?.[p])?n[p]:[]).map(y=>m.options?.[y]).filter(Boolean).map(y=>({label:y.label,description:y.description||""}));nu(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:h})}return await q({flow:"prompt",phase:"answered",sessionName:o.leadBox,agent:s.agent,provider:i.provider,promptId:i.promptId,...c}).catch(()=>{}),{ok:!0,promptId:i.promptId,sent:c.count}}let l=i.questions?.[0]||{},u=(Array.isArray(n?.[0])?n[0]:[]).map(d=>l.options?.[d]).filter(Boolean).map(d=>({label:d.label,description:d.description||""})),f=i.review?"review":/^claude:model-confirm:/.test(i.promptId||"")?"model-confirm":/^claude:resume-confirm:/.test(i.promptId||"")?"resume-confirm":void 0;return c={...c,groupId:i.groupId||i.promptId,questionIndex:i.questionIndex??0,questionTotal:i.questionTotal??1,header:l.header||"",question:l.question||"",multiSelect:!!l.multiSelect,chosen:u,...f?{control:f}:{}},await q({flow:"prompt",phase:"answered",sessionName:o.leadBox,agent:s.agent,provider:i.provider,promptId:i.promptId,...c}).catch(()=>{}),nu(o,s,i,c),{ok:!0,promptId:i.promptId,sent:c.count}}async function VI(t,e){if(!e)return null;let n=await L(t).catch(()=>[]);return ql(Zo(n),{leadBoxId:e.name,provider:e.agent,isAnswered:r=>n.some(o=>o.type==="prompt.answered"&&o.payload?.promptId===r)})}async function Ay(t,{promptId:e,questionIndex:n}={}){let r=Y(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 Q(r.leadBox))throw new Error(`lead box "${r.leadBox}" is not running`);let a=await Ll(o);if(!a)throw new Error("no pending prompt to navigate");if(a.provider!=="claude")throw new Error("prompt navigation not supported for this provider");if(a.review)throw new Error("cannot navigate on the review screen \u2014 answer or cancel");let i=a.questionTotal??1;if(i<2)throw new Error("not a multi-question prompt");if(!Number.isInteger(n)||n<0||n>=i)throw new Error("questionIndex out of range");if(e&&e!==a.promptId)throw new Error("prompt changed since it was shown \u2014 reload and try again");let c=lw(a,n);return c.length?(await bt(r.leadBox,c,{kind:"navigate-prompt"}),await q({flow:"prompt",phase:"navigated",sessionName:r.leadBox,agent:o.agent,provider:a.provider,promptId:a.promptId,fromIndex:a.questionIndex??0,toIndex:n,mode:"keys",value:c.join(" "),count:c.length}).catch(()=>{}),{ok:!0,promptId:a.promptId,sent:c.length}):{ok:!0,promptId:a.promptId,sent:0}}var zI=1500,as=new Map;function Wu(t){let e=as.get(t);return e||(e={items:[],timer:null,flushing:!1,idleSince:null},as.set(t,e)),e}function py(t){return(as.get(t)?.items??[]).map(e=>({...e}))}function li(t){let e=as.get(t);if(!e)return[];let n=e.items.splice(0);return e.idleSince=null,e.timer&&(clearInterval(e.timer),e.timer=null),n}async function Ey(t){let e=li(t),n=e.filter(o=>o.text&&!o.kind).map(o=>o.text),r=null;if(n.length){let o=Y(await W(),t);if(o)return r=cs(o,n),await V(),{removed:e,restored:r,draftRev:o.draftRev||0}}return{removed:e,restored:r,draftRev:null}}function _y(t){let e=Wu(t);e.timer||(e.timer=setInterval(()=>JI(t).catch(()=>{}),zI))}async function JI(t){let e=as.get(t);if(!(!e||e.flushing)){if(!e.items.length){e.timer&&(clearInterval(e.timer),e.timer=null);return}e.flushing=!0;try{let n=Y(await W(),t);if(!n||n.archivedAt){li(t);return}let r=ms(t);if(r&&!r.finishedAt&&r.step!=="done"&&!r.error){Wr(e,!1);return}let o=await Q(n.leadBox);if(!o){Wr(e,!1);return}let s=n.boxes.find(c=>c.name===n.leadBox),a=await ft(n.leadBox,{lines:xt}).catch(()=>"");if(Qs(s?.agent,a)){Wr(e,!1);return}if(Mr(s?.agent,a)){let c=await Ze(n.leadBox).catch(()=>a);if(Jn(s,c)){Wr(e,!1);return}}if(!Wr(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=is.get(t.id)||0,i=r,c=!1;t.pendingHandoffHydrate&&(i=MI(r,t.pendingHandoffHydrate.artifact||ci),delete t.pendingHandoffHydrate,c=!0);let l=Array.isArray(s)?s.filter(f=>f&&f.rel):[];if(l.length){let f=cy(l.map(d=>d.rel));i=i.trim()?`${i.trim()}
|
|
166
166
|
|
|
167
|
-
${f}`:f}let u=
|
|
167
|
+
${f}`:f}let u=lt(t,"said",{box:t.leadBox,agent:e?.agent,boxRole:e?.role,text:r,...o?{kind:o}:{},...l.length?{attachments:l}:{}});Qn(t,null);try{if(await tn({cwd:n.cwd,sessionId:n.sessionId,sessionName:t.leadBox,agent:n.agent,agentCmd:ps(n.agent)}),(is.get(t.id)||0)!==a){!o&&r.trim()&&(cs(t,[r]),await V());let f=new Error("delivery stopped \u2014 the message was handed back as the draft");throw f.interrupted=!0,f}await St(t.leadBox,i,{..._e(e?.agent),kind:c?"lead-handoff-first-prompt":"say"})}catch(f){throw lt(t,"unsaid",{box:t.leadBox,agent:e?.agent,boxRole:e?.role,saidTs:u.at,text:r,...f?.interrupted?{reason:"interrupted"}:{}}),f}await dR(t,e,i)||(await Cy(t.leadBox,e?.agent,i.length+8).catch(()=>{}),lt(t,"unsaid",{box:t.leadBox,agent:e?.agent,boxRole:e?.role,saidTs:u.at,text:r,reason:"unsubmitted"}),o||cs(t,[r])),c&&await q({flow:"lead-handoff",phase:"first-prompt",trackId:t.id,sessionName:t.leadBox,agent:e?.agent,userTextLen:r.length})}function Ry(t,{actions:e}={}){Wu(t).items.push({id:fs(),text:Vw(e),ts:Date.now(),kind:"ship-notify"}),_y(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"||!Nr(e))&&!o.length)throw new Error("text (string) or an attachment required");let s=Y(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`);ui(t,"say");let a=await Q(s.leadBox);if(!a&&await Vu(s)&&(await V(),a=await Q(s.leadBox)),!a)throw new Error(`lead box "${s.leadBox}" is not running`);let i=s.boxes.find(d=>d.name===s.leadBox),c=Nr(e),l=Wu(t),u=await ft(s.leadBox,{lines:xt}).catch(()=>""),f=l.items.length>0||Qs(i?.agent,u);if(!f&&Mr(i?.agent,u)){let d=await Ze(s.leadBox).catch(()=>u);f=!!Jn(i,d)}if(f){let d={id:fs(),text:c,ts:Date.now(),...n?{kind:n}:{},...o.length?{attachments:o}:{}};return l.items.push(d),_y(t),Qn(s,null),await V(),{ok:!0,leadBox:s.leadBox,queued:!0,id:d.id,position:l.items.length}}return await Iy(s,i,a,c,n,o),await V(),{ok:!0,leadBox:s.leadBox}}var my="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 $y(t,{hint:e}={}){let n=Y(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=Bc(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";um(n.id,{name:s,box:n.leadBox});let a=String(e||"").trim()?`${my}
|
|
168
168
|
|
|
169
|
-
|
|
169
|
+
What to preview: ${String(e).trim()}`:my;try{await oe(t,a,{kind:"spin-up-view"})}catch(i){throw ha(n.id,{error:"drive-failed"}),i}return{ok:!0,running:!0}}async function qu(t){let e=Y(await W(),t);if(!e)throw new Error(`track "${t}" not found`);if(e.archivedAt)throw new Error(`track "${t}" is archived`);is.set(t,(is.get(t)||0)+1);let n=li(t),r=n.filter(i=>i.text&&!i.kind).map(i=>i.text),o=Gr.get(t);if(o&&!o.canceled){o.clicks++;let i=null;return r.length&&(i=cs(e,r),await V()),{ok:!0,interrupted:!0,stopped:!1,stopping:!0,dropped:n.length,restored:i,draftRev:e.draftRev||0}}let s=nR(t,{droppedCount:n.length,droppedTexts:r});await Promise.race([s.settled,kn(XI)]);let a=Y(await W(),t)||e;return{ok:!0,interrupted:s.result!=="no-session",stopped:s.result==="stopped"||s.result==="stopped-late",stopping:!s.result,dropped:n.length,restored:s.restored,draftRev:a.draftRev||0}}var XI=4e3,YI=3,ZI=[300,350,400],QI=1e3,tR=12,Gr=new Map,is=new Map;function eR(t){let e=Gr.get(t);return!!e&&!e.canceled}function ui(t,e){let n=Gr.get(t);n&&!n.canceled&&(n.canceled=!0,n.cancelReason=e||null)}function Oy(t){ui(t,"track-ended"),is.delete(t),li(t)}function nR(t,{droppedCount:e,droppedTexts:n}){let r={startedAt:Date.now(),clicks:1,escSends:0,bgSends:0,queuedTexts:[...n],canceled:!1,cancelReason:null,result:null,restored:null},o;return r.settled=new Promise(s=>{o=s}),Gr.set(t,r),(async()=>{let s=Y(await W(),t),a=s?.boxes.find(y=>y.name===s.leadBox),i=s?await Q(s.leadBox):null,c=!1,l=!1;if(i){t:for(let y=0;y<YI&&!r.canceled;y++){await bt(s.leadBox,["Escape"],{kind:"interrupt"}).catch(()=>{}),r.escSends++;for(let b of ZI){await kn(b);let S=await ft(s.leadBox,{lines:xt}).catch(()=>"");if(!Ee(a?.agent,S)){c=!0;break t}}}if(!c){l=!0;for(let y=0;y<tR&&!r.canceled&&(await kn(QI),!r.canceled);y++){let b=await ft(s.leadBox,{lines:xt}).catch(()=>"");if(!Ee(a?.agent,b)){c=!0;break}await bt(s.leadBox,["Escape"],{kind:"interrupt"}).catch(()=>{}),r.bgSends++}}}let u=Y(await W(),t)||s,f=u?.boxes.find(y=>y.name===u.leadBox)||a,d=null,p=null;u&&f&&i&&c&&!r.canceled&&(d=await aR(u,f).catch(()=>null),d||(p=await lR(u,f).catch(()=>null)));let m=u&&i&&!r.canceled?await oR(u).catch(()=>null):null,h=[d||p,...r.queuedTexts].filter(Boolean);u&&h.length&&(r.restored=cs(u,h),await V()),r.result=i?r.canceled?"canceled":c?l?"stopped-late":"stopped":"gave-up":"no-session",await rR(u||s,f,{ts:r.startedAt,result:r.result,dropped:e,escSends:r.escSends,bgSends:r.bgSends,clicks:r.clicks,unsent:!!d,lostRestored:!!p,queuedRestored:r.queuedTexts.length,shipDismissed:!!m,durationMs:Date.now()-r.startedAt,...r.cancelReason?{cancelReason:r.cancelReason}:{}}),await q({flow:"interrupt",phase:r.result==="gave-up"?"failed":"done",trackId:t,sessionName:u?.leadBox,agent:f?.agent,result:r.result,escSends:r.escSends,bgSends:r.bgSends,clicks:r.clicks,unsent:!!d,lostRestored:!!p,queuedRestored:r.queuedTexts.length,shipDismissed:!!m}).catch(()=>{})})().catch(()=>{}).finally(()=>{r.result||(r.result=r.canceled?"canceled":"gave-up"),Gr.get(t)===r&&Gr.delete(t),o()}),r}async function rR(t,e,n){await R(t,{type:"turn.interrupted",actor:"user",source:"orchestrator",boxId:t.leadBox,provider:e?.agent||null,payload:n}).catch(()=>{})}async function oR(t){let e=Au(await L(t));return e?.offerId?(await R(t,{type:"pr.offer.answered",actor:"user",source:"orchestrator",boxId:e.box,payload:{ts:Date.now(),offerId:e.offerId,action:"dismiss",actions:null,reason:"interrupt"}}),e.offerId):null}var sR=[250,400,500,600];async function aR(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 sR){await kn(r);let o=await ft(t.leadBox,{lines:xt}).catch(()=>""),s=Vo(e.agent,o);if(s&&Fl(n.text,s))return await Cy(t.leadBox,e.agent,n.text.length+8),lt(t,"unsaid",{box:t.leadBox,agent:e.agent,boxRole:e.role,saidTs:n.at,text:n.text}),n.text}return null}var iR=3e4,cR=[350,650,900];async function lR(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>iR||n.some(s=>s.type==="unsaid"&&s.saidTs===r.at)||!Tt(r.text))return null;for(let s of cR){await kn(s);let a;try{a=await Cl(e,{})}catch{return null}if(dw(a,r))return null;let i=await ft(t.leadBox,{lines:xt}).catch(()=>"");if(Vo(e.agent,i))return null}return lt(t,"unsaid",{box:t.leadBox,agent:e.agent,boxRole:e.role,saidTs:r.at,text:r.text,reason:"lost"}),r.text}var uR=[250,450,700],Pu=2;async function dR(t,e,n){if(e?.agent!=="claude")return!0;for(let r=0;r<=Pu;r++){let o=!1;for(let s of uR){await kn(s);let a=await ft(t.leadBox,{lines:xt}).catch(()=>""),i=fw(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<Pu&&(await bt(t.leadBox,["Enter"],{kind:"submit-retry"}),q({flow:"say",phase:"submit-retry",trackId:t.id,sessionName:t.leadBox,agent:e.agent,attempt:r+1,textHead:n.slice(0,120)}).catch(()=>{}))}return q({flow:"say",phase:"submit-failed",trackId:t.id,sessionName:t.leadBox,agent:e.agent,retries:Pu,textHead:n.slice(0,120)}).catch(()=>{}),!1}async function Cy(t,e,n){for(let r=0;r<3;r++){await zs(t,n,{kind:"unsend-wipe"}).catch(()=>{}),await kn(200);let o=await ft(t,{lines:xt}).catch(()=>""),s=Vo(e,o);if(!s)return!0;n=s.length+8}return!1}function Qn(t,e,{source:n}={}){t.draftRev=(t.draftRev||0)+1;let r=typeof e=="string"?e:"";return r.trim()?t.draft={text:r,updatedAt:Date.now(),...n?{source:n}:{}}:delete t.draft,t.draft||null}function cs(t,e){let n=[],r=o=>n.some(s=>Tt(s).includes(Tt(o)));for(let o of[t.draft?.text,...e])o&&o.trim()&&!r(o)&&n.push(o);return n.length?Qn(t,n.join(`
|
|
170
170
|
|
|
171
|
-
|
|
171
|
+
`),{source:"restore"})?.text??null:null}async function My(t,e,{rev:n=null}={}){if(typeof e!="string")throw new Error("text (string) required");let r=Y(await W(),t);if(!r)throw new Error(`track "${t}" not found`);let o=Nr(e);if(typeof n=="number"&&n<(r.draftRev||0)&&r.draft?.source==="restore"){let a=r.draft.text;o?Tt(o).includes(Tt(a))?Qn(r,o):Qn(r,`${a}
|
|
172
172
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
`),e.draft={text:d,updatedAt:Date.now()},await z());let p={dropped:r.length,escSends:i.sends,unsent:!!c,lostRestored:!!l,queuedRestored:f.length,shipDismissed:!!u},m=!!a&&!i.stopped;return m?XI(e,n,{startedAt:s,syncSends:i.sends,outcome:p}):await _y(e,n,{ts:s,result:a?"stopped":"no-session",...p,clicks:1,bgSends:0,durationMs:Date.now()-s}),await U({flow:"interrupt",phase:m?"failed":"done",trackId:t,sessionName:e.leadBox,agent:n?.agent,stopped:i.stopped,stopping:m,...p}).catch(()=>{}),{ok:!0,interrupted:!!a,stopped:i.stopped,stopping:m,dropped:r.length,restored:d||null}}var VI=1e3,zI=12,os=new Map,ei=new Map;function JI(t){let e=os.get(t);return!!e&&!e.canceled}function Uu(t,e){let n=os.get(t);n&&!n.canceled&&(n.canceled=!0,n.cancelReason=e||null)}function XI(t,e,{startedAt:n,syncSends:r,outcome:o}){let s={startedAt:n,sends:0,clicks:1,canceled:!1,cancelReason:null};os.set(t.id,s),(async()=>{let a=!1;for(let l=0;l<zI&&!s.canceled&&(await Zn(VI),!s.canceled);l++){let u=await ft(t.leadBox,{lines:xt}).catch(()=>"");if(!Ee(e?.agent,u)){a=!0;break}await bt(t.leadBox,["Escape"],{kind:"interrupt"}).catch(()=>{}),s.sends++}let i=!1;if(a&&!s.canceled)try{let l=et(await W(),t.id),u=l?.boxes.find(f=>f.name===l.leadBox);l&&u&&(i=!!await Iy(l,u))}catch{}let c=s.canceled?"canceled":a?"stopped-late":"gave-up";await _y(t,e,{ts:n,result:c,...o,lostRestored:o.lostRestored||i,escSends:r,bgSends:s.sends,clicks:s.clicks,durationMs:Date.now()-n,...s.cancelReason?{cancelReason:s.cancelReason}:{}}),await U({flow:"interrupt",phase:c==="gave-up"?"failed":"done",trackId:t.id,sessionName:t.leadBox,agent:e?.agent,result:c,bgSends:s.sends,clicks:s.clicks}).catch(()=>{})})().catch(()=>{}).finally(()=>{os.delete(t.id)})}async function _y(t,e,n){await R(t,{type:"turn.interrupted",actor:"user",source:"orchestrator",boxId:t.leadBox,provider:e?.agent||null,payload:n}).catch(()=>{})}var YI=[300,350,400],ZI=3;async function QI(t,e){let n=0;for(let r=0;r<ZI;r++){await bt(t,["Escape"],{kind:"interrupt"}),n++;for(let o of YI){await Zn(o);let s=await ft(t,{lines:xt}).catch(()=>"");if(!Ee(e,s))return{stopped:!0,sends:n}}}return{stopped:!1,sends:n}}async function tR(t){let e=xu(await D(t));return e?.offerId?(await R(t,{type:"pr.offer.answered",actor:"user",source:"orchestrator",boxId:e.box,payload:{ts:Date.now(),offerId:e.offerId,action:"dismiss",actions:null,reason:"interrupt"}}),e.offerId):null}var eR=[250,400,500,600];async function nR(t,e){if(e?.agent!=="claude")return null;let n=[...t.events||[]].reverse().find(r=>r.type==="said"&&r.text);if(!n)return null;for(let r of eR){await Zn(r);let o=await ft(t.leadBox,{lines:xt}).catch(()=>""),s=qo(e.agent,o);if(s&&Dl(n.text,s))return await Ry(t.leadBox,e.agent,n.text.length+8),t.draft={text:n.text,updatedAt:Date.now()},ct(t,"unsaid",{box:t.leadBox,agent:e.agent,boxRole:e.role,saidTs:n.at,text:n.text}),await z(),n.text}return null}var rR=3e4,oR=[350,650,900];async function Iy(t,e){if(e?.agent!=="claude")return null;let n=t.events||[],r=[...n].reverse().find(s=>s.type==="said"&&s.text);if(!r||r.kind||Date.now()-r.at>rR||n.some(s=>s.type==="unsaid"&&s.saidTs===r.at)||!qt(r.text))return null;for(let s of oR){await Zn(s);let a;try{a=await Rl(e,{})}catch{return null}if(cw(a,r))return null;let i=await ft(t.leadBox,{lines:xt}).catch(()=>"");if(qo(e.agent,i))return null}return t.draft={text:r.text,updatedAt:Date.now()},ct(t,"unsaid",{box:t.leadBox,agent:e.agent,boxRole:e.role,saidTs:r.at,text:r.text,reason:"lost"}),await z(),r.text}var sR=[250,450,700],Ou=2;async function aR(t,e,n){if(e?.agent!=="claude")return!0;for(let r=0;r<=Ou;r++){let o=!1;for(let s of sR){await Zn(s);let a=await ft(t.leadBox,{lines:xt}).catch(()=>""),i=lw(e.agent,n,a,{busy:Ee(e.agent,a)});if(i==="stuck"){o=!0;break}if(i==="submitted")return!0}if(!o)return!0;r<Ou&&(await bt(t.leadBox,["Enter"],{kind:"submit-retry"}),U({flow:"say",phase:"submit-retry",trackId:t.id,sessionName:t.leadBox,agent:e.agent,attempt:r+1,textHead:n.slice(0,120)}).catch(()=>{}))}return U({flow:"say",phase:"submit-failed",trackId:t.id,sessionName:t.leadBox,agent:e.agent,retries:Ou,textHead:n.slice(0,120)}).catch(()=>{}),!1}async function Ry(t,e,n){for(let r=0;r<3;r++){await qs(t,n,{kind:"unsend-wipe"}).catch(()=>{}),await Zn(200);let o=await ft(t,{lines:xt}).catch(()=>""),s=qo(e,o);if(!s)return!0;n=s.length+8}return!1}async function $y(t,e){if(typeof e!="string")throw new Error("text (string) required");let n=et(await W(),t);if(!n)throw new Error(`track "${t}" not found`);let r=Mr(e);return r?n.draft={text:r,updatedAt:Date.now()}:delete n.draft,await z(),{ok:!0,draft:n.draft||null}}async function Oy(t,e){if(typeof e!="string"||!e.trim())throw new Error("model (string) required");let n=e.trim(),r=et(await W(),t);if(!r)throw new Error(`track "${t}" not found`);let o=await Z(r.leadBox);if(!o)throw new Error(`lead box "${r.leadBox}" is not running`);let a=r.boxes.find(f=>f.name===r.leadBox)?.agent||o.agent||"claude",i=_w(a,n),l=[...r.events||[]].reverse().find(f=>f.type==="model-change")?.to??null;l||(l=(await bo(o.cwd,o.sessionId,{fallbackNewest:!1}).catch(()=>null))?.model??null),await tn({cwd:o.cwd,sessionId:o.sessionId,sessionName:r.leadBox,agent:o.agent,agentCmd:ls(o.agent)});let u=await Iw(a,r.leadBox,i);return ct(r,"model-change",{to:n,from:l,box:r.leadBox,agent:a,verified:!!u.verified}),await z(),{ok:!0,requested:n,leadBox:r.leadBox,...u}}async function Cy(t){for(let e of t){if(!e||!await Na(e))continue;let n=await hw(e).catch(()=>null);if(!n)continue;if((await L.stat(F.join(n,".git")).catch(()=>null))?.isDirectory())return n}return null}function Dt(t){let e=t;return e?e.kind==="multi"?Array.isArray(e.repos)?e.repos.filter(Boolean):[]:e.base?[{name:F.basename(e.base),dir:e.dir??null,base:e.base,baseRef:e.baseRef??null,repo:e.repo??null}]:[]:[]}async function zo(t){let e=[];for(let n of Dt(t?.workspace)){if(!n?.dir)continue;let r=await ql(n.dir,{ignorePlumbing:!0}).catch(()=>!1),o=await Kl(n.dir,n.baseRef).catch(()=>0);(r||o>0)&&e.push({...n,dirty:r,ahead:o})}return e}async function My(t){let e=t;typeof e=="string"&&!F.isAbsolute(e)&&/^[^/\s]+\/[^/\s]+$/.test(e)&&(e=await ao(e)??await Wi(e));let n=await Cy([e]);if(!n)throw new Error(`tagged repo "${t}" is not a usable base (need a primary checkout or a synced owner/name)`);return n}async function iR(t,e,n,r){let o=`track/${Vu(e)}-${t}`,s=[];try{await L.mkdir(n,{recursive:!0});let a=new Set([".bostrat"]);for(let i of r){let c=await My(i);if(s.some(p=>p.base===c))throw new Error(`repo tagged twice: ${i}`);let l=await tt(c).catch(()=>null),u=F.basename(c);a.has(u)&&l&&(u=l.replace("/","-"));for(let p=2;a.has(u);p++)u=`${F.basename(c)}-${p}`;a.add(u);let f=F.join(n,u),d=await Pa(c);await Nr(c,f,o,d),s.push({name:u,dir:f,base:c,baseRef:d,repo:l})}return await ci(n,s,o),{dir:n,kind:"multi",branch:o,repos:s}}catch(a){for(let i of s)await Xt(i.base,i.dir).catch(()=>{});throw await L.rm(n,{recursive:!0,force:!0}).catch(()=>{}),a}}var Ny=t=>({branch:t.branch,kind:t.kind,repos:(t.repos??[]).filter(Boolean).map(({name:e,base:n,baseRef:r,repo:o})=>({name:e,base:n,baseRef:r,repo:o}))});async function Py(t,e){let n=await W(),r=et(n,t);if(!r)throw new Error(`track "${t}" not found`);if(r.archivedAt)throw new Error("track is archived \u2014 unarchive it first");let o=r.workspace;if(o?.kind!=="multi")throw new Error("live re-tagging needs a multi-repo Track \u2014 a single-repo Track's workspace IS its repo checkout; create the Track with multiple repos tagged instead");let s=await My(typeof e=="string"?e.trim():e);if((o.repos??[]).some(d=>d?.base===s))throw new Error(`repo already tagged: ${e}`);let a=await tt(s).catch(()=>null),i=new Set([".bostrat",...(o.repos??[]).map(d=>d?.name).filter(Boolean)]),c=F.basename(s);i.has(c)&&a&&(c=a.replace("/","-"));for(let d=2;i.has(c);d++)c=`${F.basename(s)}-${d}`;let l=F.join(o.dir,c),u=await Pa(s);await ww(s,o.branch)?await gw(s,l,o.branch):await Nr(s,l,o.branch,u);let f={name:c,dir:l,base:s,baseRef:u,repo:a};return o.repos=[...(o.repos??[]).filter(Boolean),f],await ci(o.dir,o.repos,o.branch),ct(r,"retagged",{action:"add",repo:a??s,name:c,workspace:Ny(o)}),await z(),f}async function By(t,e){let n=await W(),r=et(n,t);if(!r)throw new Error(`track "${t}" not found`);let o=r.workspace;if(o?.kind!=="multi")throw new Error("live re-tagging needs a multi-repo Track");let s=(o.repos??[]).find(c=>c&&(c.name===e||c.repo===e||c.base===e));if(!s)throw new Error(`repo "${e}" is not tagged on this Track`);if((o.repos??[]).filter(Boolean).length<=1)throw new Error("can't untag the last repo \u2014 archive or delete the Track instead");for(let c of re.values())if(c.trackId===t&&!c.finishedAt)throw new Error("a subtask is still running on this Track \u2014 wait for it to merge back before untagging");let a=await ql(s.dir,{ignorePlumbing:!0}).catch(()=>!1),i=await Kl(s.dir,s.baseRef).catch(()=>0);if(a||i>0)throw new Error(`"${s.name}" has work (${a?"uncommitted changes":`${i} commit${i===1?"":"s"} ahead of ${s.baseRef??"base"}`}) \u2014 ship or discard it first`);return await Xt(s.base,s.dir).catch(()=>{}),await L.rm(s.dir,{recursive:!0,force:!0}).catch(()=>{}),o.repos=(o.repos??[]).filter(c=>c&&c!==s),await ci(o.dir,o.repos,o.branch),ct(r,"retagged",{action:"remove",repo:s.repo??s.base,name:s.name,workspace:Ny(o)}),await z(),{removed:s.name}}async function cR(t,e,{repo:n,repos:r,cwd:o}={}){await L.mkdir(Nu,{recursive:!0});let s=F.join(Nu,t),a=Array.isArray(r)?r.map(c=>typeof c=="string"?c.trim():"").filter(Boolean):[];if(a.length>=2)return iR(t,e,s,a);a.length===1&&!n&&(n=a[0]),typeof n=="string"&&!F.isAbsolute(n)&&/^[^/\s]+\/[^/\s]+$/.test(n)&&(n=await ao(n)??await Wi(n));let i=await Cy([n,o,II]);if(i){let c=`track/${Vu(e)}-${t}`;try{let l=await Pa(i);await Nr(i,s,c,l),await Pu(s);let u=await tt(i).catch(()=>null);return{dir:s,base:i,branch:c,kind:"worktree",baseRef:l,repo:u}}catch{await L.rm(s,{recursive:!0,force:!0}).catch(()=>{})}}return await L.mkdir(s,{recursive:!0}),await yw(s),await Pu(s),{dir:s,base:null,branch:null,kind:"repo"}}var ss="# bostrat workspace",Dy=`${ss}
|
|
173
|
+
${o}`,{source:"restore"}):Qn(r,a,{source:"restore"})}else Qn(r,o);return await V(),{ok:!0,draft:r.draft||null,rev:r.draftRev||0}}async function Ny(t,e){if(typeof e!="string"||!e.trim())throw new Error("model (string) required");let n=e.trim(),r=Y(await W(),t);if(!r)throw new Error(`track "${t}" not found`);let o=await Q(r.leadBox);if(!o)throw new Error(`lead box "${r.leadBox}" is not running`);let a=r.boxes.find(f=>f.name===r.leadBox)?.agent||o.agent||"claude",i=$w(a,n),l=[...r.events||[]].reverse().find(f=>f.type==="model-change")?.to??null;l||(l=(await xo(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:ps(o.agent)});let u=await Ow(a,r.leadBox,i);return lt(r,"model-change",{to:n,from:l,box:r.leadBox,agent:a,verified:!!u.verified}),await V(),{ok:!0,requested:n,leadBox:r.leadBox,...u}}async function Py(t){for(let e of t){if(!e||!await Da(e))continue;let n=await yw(e).catch(()=>null);if(!n)continue;if((await U.stat(H.join(n,".git")).catch(()=>null))?.isDirectory())return n}return null}function jt(t){let e=t;return e?e.kind==="multi"?Array.isArray(e.repos)?e.repos.filter(Boolean):[]:e.base?[{name:H.basename(e.base),dir:e.dir??null,base:e.base,baseRef:e.baseRef??null,repo:e.repo??null}]:[]:[]}async function Yo(t){let e=[];for(let n of jt(t?.workspace)){if(!n?.dir)continue;let r=await zl(n.dir,{ignorePlumbing:!0}).catch(()=>!1),o=await Jl(n.dir,n.baseRef).catch(()=>0);(r||o>0)&&e.push({...n,dirty:r,ahead:o})}return e}async function By(t){let e=t;typeof e=="string"&&!H.isAbsolute(e)&&/^[^/\s]+\/[^/\s]+$/.test(e)&&(e=await co(e)??await Gi(e));let n=await Py([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 fR(t,e,n,r){let o=`track/${Xu(e)}-${t}`,s=[];try{await U.mkdir(n,{recursive:!0});let a=new Set([".bostrat"]);for(let i of r){let c=await By(i);if(s.some(p=>p.base===c))throw new Error(`repo tagged twice: ${i}`);let l=await et(c).catch(()=>null),u=H.basename(c);a.has(u)&&l&&(u=l.replace("/","-"));for(let p=2;a.has(u);p++)u=`${H.basename(c)}-${p}`;a.add(u);let f=H.join(n,u),d=await ja(c);await Pr(c,f,o,d),s.push({name:u,dir:f,base:c,baseRef:d,repo:l})}return await di(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 U.rm(n,{recursive:!0,force:!0}).catch(()=>{}),a}}var Dy=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 jy(t,e){let n=await W(),r=Y(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 By(typeof e=="string"?e.trim():e);if((o.repos??[]).some(d=>d?.base===s))throw new Error(`repo already tagged: ${e}`);let a=await et(s).catch(()=>null),i=new Set([".bostrat",...(o.repos??[]).map(d=>d?.name).filter(Boolean)]),c=H.basename(s);i.has(c)&&a&&(c=a.replace("/","-"));for(let d=2;i.has(c);d++)c=`${H.basename(s)}-${d}`;let l=H.join(o.dir,c),u=await ja(s);await bw(s,o.branch)?await kw(s,l,o.branch):await Pr(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 di(o.dir,o.repos,o.branch),lt(r,"retagged",{action:"add",repo:a??s,name:c,workspace:Dy(o)}),await V(),f}async function Ly(t,e){let n=await W(),r=Y(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 zl(s.dir,{ignorePlumbing:!0}).catch(()=>!1),i=await Jl(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 U.rm(s.dir,{recursive:!0,force:!0}).catch(()=>{}),o.repos=(o.repos??[]).filter(c=>c&&c!==s),await di(o.dir,o.repos,o.branch),lt(r,"retagged",{action:"remove",repo:s.repo??s.base,name:s.name,workspace:Dy(o)}),await V(),{removed:s.name}}async function pR(t,e,{repo:n,repos:r,cwd:o}={}){await U.mkdir(ju,{recursive:!0});let s=H.join(ju,t),a=Array.isArray(r)?r.map(c=>typeof c=="string"?c.trim():"").filter(Boolean):[];if(a.length>=2)return fR(t,e,s,a);a.length===1&&!n&&(n=a[0]),typeof n=="string"&&!H.isAbsolute(n)&&/^[^/\s]+\/[^/\s]+$/.test(n)&&(n=await co(n)??await Gi(n));let i=await Py([n,o,OI]);if(i){let c=`track/${Xu(e)}-${t}`;try{let l=await ja(i);await Pr(i,s,c,l),await Lu(s);let u=await et(i).catch(()=>null);return{dir:s,base:i,branch:c,kind:"worktree",baseRef:l,repo:u}}catch{await U.rm(s,{recursive:!0,force:!0}).catch(()=>{})}}return await U.mkdir(s,{recursive:!0}),await xw(s),await Lu(s),{dir:s,base:null,branch:null,kind:"repo"}}var ls="# bostrat workspace",Fy=`${ls}
|
|
176
174
|
|
|
177
175
|
TRACK MEMORY: if \`.bostrat/memory/MEMORY.md\` exists in your workspace, read it
|
|
178
176
|
before substantive work \u2014 it is the shared cross-provider memory for this repo
|
|
@@ -339,9 +337,9 @@ which steps to run (and which to skip). An offer buried mid-message, phrased as
|
|
|
339
337
|
statement, or omitted entirely renders no controls \u2014 so close EVERY completed
|
|
340
338
|
implementation turn this way, every time. When the operator has already asked you
|
|
341
339
|
to commit / push / open the PR, just do it; do not re-offer.
|
|
342
|
-
`,
|
|
343
|
-
`)),
|
|
344
|
-
`);return`${
|
|
340
|
+
`,mR=Fy,os=Fy+ww;async function Lu(t){try{await U.writeFile(H.join(t,"AGENTS.md"),os,{flag:"wx"})}catch{return}await gR(t,["/AGENTS.md"])}var Bu=t=>typeof t=="string"&&(t===ls||t.startsWith(`${ls}
|
|
341
|
+
`)),hR="# bostrat multi-repo workspace";function wR(t,e){let n=t.map(r=>`- \`${r.name}/\`${r.repo?` \u2014 github.com/${r.repo}`:""}${r.baseRef?` (forked from ${r.baseRef})`:""}`).join(`
|
|
342
|
+
`);return`${hR}
|
|
345
343
|
|
|
346
344
|
This directory is NOT a git repo. It is a bostrat Track workspace holding one
|
|
347
345
|
git worktree per tagged repo, ALL on the same branch \`${e}\`:
|
|
@@ -353,25 +351,25 @@ push / open PRs) in each repo separately, always on that shared branch. Work
|
|
|
353
351
|
only needs to land in the repos it actually touches \u2014 an untouched repo's
|
|
354
352
|
branch simply stays at its fork point and never gets pushed. When asked to run
|
|
355
353
|
or preview an app, run it from that app's own repo directory.
|
|
356
|
-
`}async function
|
|
357
|
-
`,`${
|
|
354
|
+
`}async function di(t,e,n){let r=wR(e,n);for(let o of["CLAUDE.md","AGENTS.md"])await U.writeFile(H.join(t,o),r).catch(()=>{})}var Du=os.replace(`${ls}
|
|
355
|
+
`,`${ls}
|
|
358
356
|
|
|
359
357
|
(Global copy managed by bostrat \u2014 rewritten on service start; edits will be lost.)
|
|
360
358
|
SCOPE: these instructions apply ONLY when your working directory is under
|
|
361
359
|
~/.bostrat/workspaces/ or ~/.bostrat/worktrees/ \u2014 a bostrat-orchestrated box.
|
|
362
360
|
When working anywhere else, ignore everything below.
|
|
363
|
-
`);async function
|
|
364
|
-
`).includes(s));o.length&&await
|
|
361
|
+
`);async function Uy(t){let{stdout:e}=await RI("git",["-C",t,"rev-parse","--git-path","info/exclude"]);return H.resolve(t,e.trim())}async function gR(t,e){try{let n=await Uy(t);await U.mkdir(H.dirname(n),{recursive:!0});let r=await U.readFile(n,"utf8").catch(()=>""),o=e.filter(s=>!r.split(`
|
|
362
|
+
`).includes(s));o.length&&await U.appendFile(n,(r.endsWith(`
|
|
365
363
|
`)||!r?"":`
|
|
366
364
|
`)+o.join(`
|
|
367
365
|
`)+`
|
|
368
|
-
`)}catch{}}async function
|
|
366
|
+
`)}catch{}}async function yR(t,e){try{let n=await Uy(t),r=await U.readFile(n,"utf8"),o=r.split(`
|
|
369
367
|
`).filter(s=>s!==e);o.length!==r.split(`
|
|
370
|
-
`).length&&await
|
|
371
|
-
`))}catch{}}async function
|
|
368
|
+
`).length&&await U.writeFile(n,o.join(`
|
|
369
|
+
`))}catch{}}async function Hy(){let t={refreshed:0,removed:0,codexGlobal:null};try{await U.mkdir(We,{recursive:!0}),await U.writeFile(H.join(We,"CLAUDE.md"),mR).catch(()=>{}),await U.writeFile(H.join(We,"AGENTS.md"),os).catch(()=>{});let e=process.env.CODEX_HOME||H.join(process.env.HOME||"/tmp",".codex");if((await U.stat(e).catch(()=>null))?.isDirectory()){let r=H.join(e,"AGENTS.md"),o=await U.readFile(r,"utf8").catch(()=>null);o===null?(await U.writeFile(r,Du,{flag:"wx"}).catch(()=>{}),t.codexGlobal="created"):Bu(o)&&o!==Du&&(await U.writeFile(r,Du).catch(()=>{}),t.codexGlobal="refreshed")}for(let r of[ju,Vr]){let o=await U.readdir(r,{withFileTypes:!0}).catch(()=>[]);for(let s of o){if(!s.isDirectory())continue;let a=H.join(r,s.name),i=H.join(a,"AGENTS.md"),c=await U.readFile(i,"utf8").catch(()=>null);c!==null&&Bu(c)&&c!==os&&(await U.writeFile(i,os).catch(()=>{}),t.refreshed++);let l=H.join(a,"CLAUDE.md"),u=await U.readFile(l,"utf8").catch(()=>null);u!==null&&Bu(u)&&(await U.rm(l,{force:!0}).catch(()=>{}),await yR(a,"/CLAUDE.md"),t.removed++)}}}catch{}return t}async function bR(t){let e=t.workspace;if(!(!e||!e.dir))try{if(e.kind==="multi"){for(let n of jt(e))n.base&&n.dir&&await Xt(n.base,n.dir).catch(()=>{});await U.rm(e.dir,{recursive:!0,force:!0})}else e.kind==="worktree"&&e.base?await Xt(e.base,e.dir):await U.rm(e.dir,{recursive:!0,force:!0})}catch{}}async function Ku({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 ii(o,s),i=await W(),c=fs().slice(0,8),l=await pR(c,t,{repo:n,repos:r,cwd:e}),u=new Set(i.tracks.flatMap(m=>(m.boxes||[]).map(h=>h.name))),f=`${t.trim()} lead`;u.has(uc(f))&&(f=`${t.trim().slice(0,50).trim()} ${c} lead`);let d=await On(f,l.dir,{agentCmd:a,agent:o});ea({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:[Zy(d,"lead","live")],events:[],createdAt:Date.now()};return lt(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:h,baseRef:y,repo:b})=>({name:m,base:h,baseRef:y,repo:b}))}:{}}}),i.tracks.push(p),await V(),bn(p)}var kR=new Set(["claude","codex","cursor"]);async function Wy({provider:t="claude",fresh:e=!1}={}){if(!kR.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 Jr(r.id).catch(()=>{})}let o=(await ii(t,!1)).replace(/\s+--(?:dangerously-skip-permissions|dangerously-bypass-approvals-and-sandbox)\b/g,""),s=fs().slice(0,8),a=H.join(We,"setup",s);await U.mkdir(a,{recursive:!0});let i=await On(`Connect ${t} ${s}`,a,{agentCmd:o,agent:t,firstRun:!0});t==="codex"&&ea({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:[Zy(i,"lead","live")],events:[],createdAt:Date.now()};return lt(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 V(),bn(c)}async function qy(t,e,n){await gm(t).catch(()=>{});let r=us.get(t);r&&!r.finishedAt&&(r.error=n,r.finishedAt=Date.now()),us.delete(t),e.handoff&&!e.handoff.finishedAt&&(e.handoff.step="error",e.handoff.error=n,e.handoff.finishedAt=Date.now());for(let a of Xr(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 Ky(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 Zp(s),Oy(t),await qy(t,s,"track deleted"),n?await Rm(t).catch(()=>{}):await Im(t).catch(()=>{}),await Mw(),await bR(s),e&&await Qp(t).catch(()=>{}),e?await ry(t).catch(()=>{}):await ny(s).catch(()=>{}),lt(s,"track-deleted",{name:s.name,boxes:s.boxes.length,ageMs:s.createdAt?Date.now()-s.createdAt:null}),r.tracks.splice(o,1),await V(),{ok:!0,id:t,name:s.name,boxes:s.boxes.length}}async function Jr(t){let e=await W(),n=Y(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(),Oy(t),lt(n,"archived",{boxes:n.boxes.length}),await V(),n.kind==="setup"?await Gy(t):SR(t).catch(()=>{}),bn(n)}var xR=9e4;async function SR(t){try{await vR(t)}catch{}await Gy(t).catch(()=>{})}async function Gy(t){let e=await W(),n=Y(e,t);if(!(!n||!n.archivedAt)){await qy(t,n,"track archived");for(let r of n.boxes)tr.has(r.state)||(r.state="stopped");await V()}}async function vR(t){let e=await W(),n=Y(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 Q(n.leadBox).catch(()=>null):null;if(i?.cwd&&a)try{({text:r,items:o}=await Tp(n.leadBox,i.cwd,{timeoutMs:xR})),s="lead-cli"}catch{}if(r||(r=fl(n,await L(n).catch(()=>[]))),!r)return;let c={text:r,source:s,at:Date.now()};o.length&&(c.items=o),n.beat=c,await R(n,{type:"track.summary.recorded",actor:"orchestrator",source:"orchestrator",boxId:n.leadBox||null,provider:i?.agent||null,payload:o.length?{text:r,items:o,source:s}:{text:r,source:s}}).catch(()=>{}),await V()}async function Vy(){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=fl(o,await L(o).catch(()=>[]));if(!s){r++;continue}o.beat={text:s,source:"derived",at:Date.now()},await R(o,{type:"track.summary.recorded",actor:"orchestrator",source:"orchestrator",payload:{text:s,source:"derived",backfill:!0}}).catch(()=>{}),e++}return await V(),{recorded:e,skipped:n,empty:r}}async function hn(t,e){let n=await Ot(t,e).catch(()=>null);if(!n?.archiveOnMerge||!n.trackId)return!1;let r=await zy(n);if(r.length){for(let o of r)await Hn(o.repo,o.number,!0).catch(()=>{});return!1}try{return await Jr(n.trackId),!0}catch(o){if(/already archived/i.test(String(o?.message||o)))return!1;throw o}}async function zy(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 Gu(t,e,"merged")}async function Gu(t,e,n="merged"){let r=await Ot(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 zy(r);if(s.length){let i=[...r.notifyPending||[],o];for(let c of s)await hh(c.repo,c.number,i).catch(()=>{});return!1}let a=[...r.notifyPending||[],o];try{return await oe(r.trackId,TR(a),{kind:"merge-notify"}),!0}catch{return!1}}var hy={merged:"merged",closed:"closed on GitHub WITHOUT merging",lost:"no longer found on GitHub (deleted, or its repo left the grant)"};function TR(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 ${hy[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 ${hy[i.outcome]??i.outcome}`).join(`
|
|
372
370
|
`),a=n.length?r.length?`This is an automated merge notification: pick up whatever follow-up work the merged PR${n.length===1?"":"s"} unblock${n.length===1?"s":""} \u2014 and note the unmerged one${r.length===1?"":"s"}: that work did NOT land, so check whether it still needs to ship. If nothing needs doing, say so briefly.`:"This is an automated merge notification: pick up whatever follow-up work these merges unblock (dependent tasks, cleanup, next steps that were waiting on them). If nothing needs doing, say so briefly.":"This is an automated notification: NONE of the PRs landed \u2014 the work did not ship. Check whether it still needs to go out another way. If not, say so briefly.";return`${o}
|
|
373
371
|
${s}
|
|
374
|
-
${a}`}async function qu(t){if(t.archivedAt)return!1;let e=t.boxes.find(r=>r.name===t.leadBox);if(!e||Yn.has(e.state)||await Z(e.name))return!1;let n=hy(e.agent,e.sessionId);if(!await Hs(n))return ct(t,"rehydrate-failed",{box:e.name,agent:e.agent,reason:`agent "${e.agent}" not available on this box`}),!1;try{let r=await $n(e.name,e.cwd,{agentCmd:n,agent:e.agent,resumeSessionId:e.sessionId,pinned:e.pinned===!0});return e.hookTokenHash=oo(r.hookToken),Ys({cwd:e.cwd,sessionName:e.name,agent:e.agent,agentCmd:n}),e.state="live",ct(t,"rehydrated",{box:e.name,agent:e.agent}),!0}catch(r){return ct(t,"rehydrate-failed",{box:e.name,agent:e.agent,reason:String(r?.message||r)}),!1}}async function Gy(t){let e=await W(),n=et(e,t);if(!n)throw new Error(`track "${t}" not found`);if(!n.archivedAt)throw new Error(`track "${t}" is not archived`);return delete n.archivedAt,ct(n,"unarchived",{}),await qu(n),await z(),bn(n)}async function Vy(){let t=await W(),e=new Set((await Rn()).map(s=>s.name)),n=new Set((t.tracks||[]).filter(s=>!s.archivedAt).flatMap(s=>(s.boxes||[]).map(a=>a.name))),r=0,o=!1;for(let s of t.tracks||[])if(s.archivedAt)for(let a of s.boxes)e.has(a.name)&&!n.has(a.name)&&(await ce(a.name).catch(()=>{}),ct(s,"orphan-reaped",{box:a.name,agent:a.agent}),r++,o=!0),!Yn.has(a.state)&&a.state!=="stopped"&&(a.state="stopped",o=!0);return o&&await z(),r}async function zy(t){let e=await W(),n=et(e,t);if(!n)throw new Error(`track "${t}" not found`);if(n.archivedAt)throw new Error(`track "${t}" is archived \u2014 restore it before restarting the lead`);let r=n.boxes.find(a=>a.name===n.leadBox);if(!r)throw new Error(`track "${t}" has no lead box`);if(Yn.has(r.state))throw new Error("lead box is terminal \u2014 nothing to restart");if(await Z(n.leadBox)){let a=await ft(n.leadBox,{lines:xt}).catch(()=>"");if(Js(r.agent,a))throw new Error("lead is mid-turn \u2014 retry when it goes idle");await ce(n.leadBox),r.state="stopped"}let s=await qu(n);if(await z(),!s)throw new Error("rehydrate failed \u2014 see the track's rehydrate-failed event");return bn(n)}var Jy=(t,e,n)=>({name:t.name,agent:t.agent,role:e,state:n,cwd:t.cwd,sessionId:t.sessionId,pinned:t.pinned===!0,hookTokenHash:oo(t.hookToken),createdAt:Date.now()});function Xy(t){return(!Array.isArray(t.epochs)||t.epochs.length===0)&&(t.epochs=[{agent:t.agent??null,sessionId:t.sessionId??null,pinned:t.pinned===!0,startedAt:t.createdAt??null,endedAt:null}]),t.epochs}var as=new Map,us=t=>as.get(t)||(xe?et(xe,t)?.handoff:null)||null;async function ni(t,e){let n=await W(),r=et(n,t);r&&(r.handoff={...r.handoff||{},...e},await z())}function kR(t,e=Date.now()){let n=t?.handoff;if(!n||n.finishedAt)return null;let r=n.step,o=!!n.toBox&&n.toBox===n.fromBox,s=t.boxes?.find?.(i=>i.name===t.leadBox)||null;return(o?t.leadBox===n.fromBox&&!!n.startedAt&&Array.isArray(s?.epochs)&&s.epochs.some(i=>i.startedAt!=null&&i.startedAt>=n.startedAt):!!n.toBox&&t.leadBox===n.toBox)?{...n,step:"done",finishedAt:e,recovered:`committed before restart (was "${r}")`}:{...n,step:"error",finishedAt:e,error:`interrupted by an agent restart during "${r}"; lead "${t.leadBox}" retained \u2014 retry the handoff`,recovered:"rolled-back"}}async function Yy(){let t=await W(),e=!1;for(let n of t.tracks||[]){let r=n.handoff,o=kR(n);if(!o)continue;e=!0;let s=!!r.toBox&&r.toBox===r.fromBox;if(s&&o.step==="error"){let c=await Z(r.fromBox).catch(()=>null),l=n.boxes.find(u=>u.name===r.fromBox);if(c&&l&&c.agent===r.toAgent&&r.toAgent!==l.agent){let u=Date.now(),f=Xy(l),d=f[f.length-1],p=d.agent,m=d.sessionId??null;d.endedAt=u,r.reason&&(d.endReason=r.reason);let g=c.agent==="claude"&&!!c.sessionId;f.push({agent:r.toAgent,sessionId:c.sessionId||null,pinned:g,startedAt:u,endedAt:null}),l.agent=r.toAgent,l.sessionId=c.sessionId||null,l.pinned=g,l.state="live",n.pendingHandoffHydrate={artifact:ai,at:u},ct(n,"lead-handoff",{from:r.fromBox,to:r.fromBox,fromAgent:p,toAgent:r.toAgent,fromSessionId:m,toSessionId:c.sessionId||null,epoch:f.length-1,singleBox:!0,skipPermissions:r.skipPermissions,reason:r.reason??null,handoffStartedAt:r.startedAt,recovered:!0}),n.handoff={...r,step:"done",finishedAt:u,recovered:"swap adopted from live tmux tags after restart"},await U({flow:"lead-handoff",phase:"reconciled",trackId:n.id,outcome:"done",recovered:n.handoff.recovered,fromBox:r.fromBox,toBox:r.toBox}).catch(()=>{});continue}}let a=!s&&o.recovered==="rolled-back"&&r.toBox&&r.toBox!==n.leadBox?r.toBox:null,i=!s&&o.step==="done"?r.fromBox:null;n.handoff=o,await U({flow:"lead-handoff",phase:"reconciled",trackId:n.id,outcome:o.step,recovered:o.recovered,fromBox:o.fromBox,toBox:o.toBox}).catch(()=>{}),i&&await ce(i).catch(()=>{}),a&&await ce(a).catch(()=>{})}return e&&await z(),e}async function Ku(t,e,n=!0,r=null){let o=as.get(t);if(o&&!o.finishedAt)throw new Error("a lead handoff is already running for this Track");let s=et(await W(),t);if(!s)throw new Error(`track "${t}" not found`);Uu(t,"handoff");let a=await si(e,n),i=await Z(s.leadBox);if(!i)throw new Error(`lead box "${s.leadBox}" is not running`);let c={trackId:t,step:"checkpoint",fromBox:s.leadBox,toAgent:e,skipPermissions:n,reason:wy(r),toBox:s.leadBox,startedAt:Date.now(),finishedAt:null,error:null};return as.set(t,c),await ni(t,{step:c.step,fromBox:c.fromBox,toAgent:e,toBox:c.toBox,skipPermissions:n,startedAt:c.startedAt,finishedAt:null,error:null}),xR(c,s,i,a,e).catch(async l=>{c.error=l?.message||String(l),c.finishedAt=Date.now(),console.error(`[lead-handoff ${t}] FAILED at step "${c.step}": ${c.error}`),await ni(t,{step:c.step,error:c.error,finishedAt:c.finishedAt}).catch(()=>{}),U({flow:"lead-handoff",phase:"failed",trackId:t,step:c.step,error:c.error,fromBox:c.fromBox,toAgent:c.toAgent,toBox:c.toBox}).catch(()=>{})}),U({flow:"lead-handoff",phase:"started",trackId:t,fromBox:c.fromBox,toAgent:e,cwd:i.cwd}).catch(()=>{}),c}async function xR(t,e,n,r,o){let s=async(E,M={})=>{t.step=E,console.log(`[lead-handoff ${t.trackId}] step \u2192 ${E}`),await ni(t.trackId,{step:E,toBox:t.toBox}),await U({flow:"lead-handoff",phase:E,trackId:t.trackId,fromBox:t.fromBox,toBox:t.toBox,toAgent:o,sessionName:M.sessionName,...M})};console.log(`[lead-handoff ${t.trackId}] ${t.fromBox}: ${n.agent} \u2192 ${o} IN PLACE`);let a=await W(),i=et(a,t.trackId)?.boxes.find(E=>E.name===t.fromBox)||null,c=i?.agent||n.agent,l=i?.sessionId??n.sessionId??null,u=i?.pinned===!0;await s("checkpoint",{sessionName:t.fromBox}),await Ip(t.fromBox),await s("swap",{cwd:n.cwd,agentCmd:r});let f=await dl(t.fromBox,{agentCmd:r,agent:o});await s("hydrate",{sessionName:t.fromBox}),await tn({cwd:n.cwd,sessionId:f.sessionId,sessionName:t.fromBox,agent:o,agentCmd:ls(o)});let d=await ft(t.fromBox,{lines:xt}).catch(()=>"");if(!Xs(o,d)){await s("rollback",{sessionName:t.fromBox,fromAgent:c});let E=hy(c,l,{skipPermissions:t.skipPermissions});await dl(t.fromBox,{agentCmd:E,agent:c,resumeSessionId:l,pinned:u});let M=await W(),J=et(M,t.trackId);if(J){let $=J.boxes.find(j=>j.name===t.fromBox);$&&($.state="live"),ct(J,"lead-handoff-failed",{box:t.fromBox,fromAgent:c,toAgent:o,reason:t.reason,error:`incoming ${o} CLI never drew its composer`,rolledBack:!0,handoffStartedAt:t.startedAt}),await z()}throw new Error(`incoming ${o} CLI never drew its composer \u2014 rolled back to ${c} (--resume, context intact)`)}await s("commit");let p=await W(),m=et(p,t.trackId);if(!m)throw new Error(`track "${t.trackId}" not found during handoff commit \u2014 was it deleted or archived mid-flight?`);let g=m.boxes.find(E=>E.name===t.fromBox);if(!g)throw new Error(`lead box row "${t.fromBox}" missing during handoff commit`);let y=Date.now(),b=Xy(g),S=b[b.length-1];S.endedAt=y,t.reason&&(S.endReason=t.reason),b.push({agent:o,sessionId:f.sessionId,pinned:f.pinned,startedAt:y,endedAt:null}),g.agent=o,g.sessionId=f.sessionId,g.pinned=f.pinned,g.state="live",g.role="lead",m.pendingHandoffHydrate={artifact:ai,at:y},ct(m,"lead-handoff",{from:t.fromBox,to:t.fromBox,fromAgent:c,toAgent:o,fromSessionId:l,toSessionId:f.sessionId,epoch:b.length-1,singleBox:!0,forced:f.forced||void 0,skipPermissions:t.skipPermissions,reason:t.reason,handoffStartedAt:t.startedAt}),await z(),await s("done",{sessionName:t.fromBox}),await ni(t.trackId,{finishedAt:Date.now(),error:null}),t.finishedAt=Date.now()}var SR=Number(process.env.BOSTRAT_SUBTASK_TIMEOUT_MS)||18e5,vR=Number(process.env.BOSTRAT_SUBTASK_POLL_MS)||3e3,Kr=F.join(We,"worktrees"),Wr=[ns,ti],re=new Map;var zr=t=>{let e=new Map([...re.values()].filter(r=>r.trackId===t).map(r=>[r.id,r])),n=xe?et(xe,t):null;if(n?.subtasks)for(let[r,o]of Object.entries(n.subtasks))e.has(r)||e.set(r,o);return[...e.values()].sort((r,o)=>r.startedAt-o.startedAt)},Gu=t=>({id:t.id,trackId:t.trackId,trackName:t.trackName,agent:t.agent,task:t.task,skipPermissions:t.skipPermissions,reason:t.reason||null,step:t.step,failedStep:t.failedStep||null,box:t.box,branch:t.branch,base:t.base,repos:t.repos||null,dir:t.dir,stat:t.stat,files:t.files||null,conflicts:t.conflicts||[],applied:t.applied,error:t.error,canceled:t.canceled,startedAt:t.startedAt,finishedAt:t.finishedAt});async function TR(t,e,n){let r=await W(),o=et(r,t);o&&(o.subtasks=o.subtasks||{},o.subtasks[e]={...o.subtasks[e]||{},...n},await z())}var Gr=t=>TR(t.trackId,t.id,Gu(t));async function qr(t,e){t.step=e,await Gr(t)}function AR(t,e=Date.now()){if(!t||t.finishedAt)return null;let n=t.step;return["snapshot","spawn","working"].includes(n)?{...t,step:"error",finishedAt:e,error:`interrupted by an agent restart during "${n}"; no changes were applied to the lead \u2014 retry the delegation`,recovered:"rolled-back"}:{...t,step:"error",finishedAt:e,error:`interrupted by an agent restart during "${n}"; the lead's working tree may hold a PARTIAL merge \u2014 review git status. The subtask's work is preserved on branch ${t.branch}`,recovered:"partial-merge"}}function ER(t){return!!t&&!t.finishedAt&&!t.canceled&&t.step==="working"&&!!t.box&&!!t.dir&&!!t.base&&!!t.branch}async function _R(t,e,n){if(!ER(n)||re.has(e)||!await Z(n.box).catch(()=>null))return!1;try{await L.access(n.dir)}catch{return!1}let o=await Z(t.leadBox).catch(()=>null);if(!o?.cwd)return!1;let s={...n,id:e,leadBox:t.leadBox,step:"working",canceled:!1,forceMerge:!1};return re.set(e,s),nb(s,o).catch(a=>tb(s,a)),!0}async function Zy(){let t=await W(),e=!1;for(let n of t.tracks||[]){for(let[r,o]of Object.entries(n.subtasks||{})){if(re.has(r))continue;if(await _R(n,r,o)){e=!0,ct(n,"subtask-resumed",{box:o.box,agent:o.agent,task:o.task,subtaskId:r,resumedStep:o.step}),await U({flow:"subtask",phase:"resumed",trackId:n.id,subtaskId:r,box:o.box,branch:o.branch}).catch(()=>{});continue}let s=o.step,a=AR(o);if(!a)continue;e=!0,n.subtasks[r]=a;let i=n.boxes.find(c=>c.subtaskId===r);i&&!Yn.has(i.state)&&(i.state="canceled"),ct(n,"subtask-failed",{from:o.box,fromAgent:o.agent,task:o.task,subtaskId:r,failedStep:s,error:a.error,recovered:a.recovered,durationMs:o.startedAt?a.finishedAt-o.startedAt:null}),await U({flow:"subtask",phase:"reconciled",trackId:n.id,subtaskId:r,outcome:a.step,recovered:a.recovered,box:o.box,branch:o.branch}).catch(()=>{}),await py(n,o.box,o.dir)}for(let r of n.boxes){if(r.role!=="subtask"||Yn.has(r.state)||re.has(r.subtaskId)||n.subtasks?.[r.subtaskId])continue;e=!0,r.state="canceled";let o=r.subtaskId?F.join(Kr,`${n.id}-${r.subtaskId}`):null;await U({flow:"subtask",phase:"reaped-legacy-orphan",trackId:n.id,subtaskId:r.subtaskId,box:r.name}).catch(()=>{}),await py(n,r.name,o)}}return e=await IR(t)||e,e&&await z(),e}async function py(t,e,n){if(e&&await ce(e).catch(()=>{}),!n)return;if(t.workspace?.kind==="multi"){for(let o of Dt(t.workspace)){let s=F.join(n,o.name);await Xt(o.base,s).then(()=>!0,()=>!1)||await Xt(o.dir,s).catch(()=>{})}await L.rm(n,{recursive:!0,force:!0}).catch(()=>{});return}let r=await Z(t.leadBox).catch(()=>null);r?.cwd&&await Xt(r.cwd,n).catch(()=>{})}async function IR(t){let e;try{e=await L.readdir(Kr)}catch{return!1}let n=new Set;for(let o of t.tracks||[]){for(let s of Object.keys(o.subtasks||{}))n.add(`${o.id}-${s}`);for(let s of o.boxes||[])s.subtaskId&&n.add(`${o.id}-${s.subtaskId}`)}for(let o of re.values())o.dir&&n.add(F.basename(o.dir));let r=!1;for(let o of e){if(n.has(o))continue;let s=F.join(Kr,o),a=(t.tracks||[]).find(i=>o.startsWith(`${i.id}-`));if(a?.workspace?.kind==="multi"){for(let i of Dt(a.workspace))await Xt(i.base,F.join(s,i.name)).catch(()=>{});await L.rm(s,{recursive:!0,force:!0}).catch(()=>{})}else{let i=a?await Z(a.leadBox).catch(()=>null):null;i?.cwd?await Xt(i.cwd,s).catch(()=>{}):await L.rm(s,{recursive:!0,force:!0}).catch(()=>{})}await U({flow:"subtask",phase:"swept-orphan-worktree",dir:s}).catch(()=>{}),r=!0}return r}var Vu=t=>t.trim().replace(/\s+/g,"-").replace(/[^A-Za-z0-9._-]/g,"").toLowerCase()||"track",is=class extends Error{};async function Qy(t,{agent:e,task:n,skipPermissions:r=!0,reason:o=null}={}){if(typeof n!="string"||!Mr(n))throw new Error("task (string) required");let s=await si(e,r),a=et(await W(),t);if(!a)throw new Error(`track "${t}" not found`);let i=await Z(a.leadBox);if(!i)throw new Error(`lead box "${a.leadBox}" is not running`);if(!i.cwd)throw new Error("can't resolve the lead's working directory");let c=a.workspace?.kind==="multi",l=null;if(c){let d=Dt(a.workspace);if(!d.length)throw new Error("this multi-repo Track has no tagged repos to delegate into");for(let p of d)if(!p?.dir||!await Na(p.dir))throw new Error(`tagged checkout "${p?.name??"?"}" is missing from the lead workspace (${p?.dir??"no dir"})`);l=d.map(p=>({name:p.name,leadDir:p.dir,base:null}))}else if(!await Na(i.cwd))throw new Error(`subtask delegation needs the lead's workspace to be a git repo (got ${i.cwd})`);let u=cs().slice(0,8),f={id:u,trackId:t,trackName:a.name,agent:e,task:Mr(n),skipPermissions:r,reason:wy(o),step:"snapshot",leadBox:a.leadBox,box:null,branch:`bostrat/${Vu(a.name)}-${u}`,base:null,repos:l,dir:F.join(Kr,`${t}-${u}`),stat:null,conflicts:[],applied:null,startedAt:Date.now(),finishedAt:null,error:null,canceled:!1,forceMerge:!1};return re.set(u,f),await Gr(f),RR(f,i,s).catch(d=>tb(f,d)),f}async function tb(t,e){t.failedStep=t.step,t.error=e instanceof is?"canceled":e?.message||String(e),t.step=e instanceof is?"canceled":"error",t.finishedAt=Date.now(),await Ju(t).catch(()=>{}),await Gr(t).catch(()=>{}),await MR(t).catch(()=>{})}function zu(t){let e=re.get(t);if(!e)throw new Error(`subtask "${t}" not found`);if(e.finishedAt)throw new Error("subtask already finished");return e.canceled=!0,e}function eb(t){let e=re.get(t);if(!e)throw new Error(`subtask "${t}" not found`);if(e.finishedAt)throw new Error("subtask already finished");if(e.step!=="working")throw new Error(`subtask is not waiting to merge (step: ${e.step})`);return e.forceMerge=!0,e}async function RR(t,e,n){if(await qr(t,"snapshot"),t.repos){for(let s of t.repos)s.base=await Go(s.leadDir,`bostrat: snapshot for subtask ${t.id}`);await Gr(t)}else t.base=await Go(e.cwd,`bostrat: snapshot for subtask ${t.id}`);if(await qr(t,"spawn"),await L.mkdir(Kr,{recursive:!0}),t.repos){await L.mkdir(t.dir,{recursive:!0});for(let a of t.repos)await Nr(a.leadDir,F.join(t.dir,a.name),t.branch,a.base);let s=Dt((et(await W(),t.trackId)||{}).workspace);await ci(t.dir,t.repos.map(a=>{let i=s.find(c=>c?.name===a.name);return{name:a.name,repo:i?.repo??null,baseRef:null}}),t.branch)}else await Nr(e.cwd,t.dir,t.branch,t.base),await Pu(t.dir);await $R(t,e);let r=t.trackName.slice(0,38).trim(),o=await $n(`${r} ${t.agent} sub ${t.id}`,t.dir,{agentCmd:n,agent:t.agent});t.box=o.name,await OR(t,o),await gy(t.trackId,o.name,CI(t.task,!!t.repos),{kind:"subtask"}),await nb(t,e)}async function nb(t,e){await qr(t,"working");let n=F.join(t.dir,ti);if(await PR(n,t),t.canceled)throw new is;if(await qr(t,"merge"),t.repos){let o={files:0,insertions:0,deletions:0},s=[],a=[],i=!0;for(let c of t.repos){let l=F.join(t.dir,c.name);await Wl(l,`bostrat: subtask ${t.id} result (auto-committed)`),c.stat=await Vl(c.leadDir,c.base,t.branch,Wr),c.files=await zl(c.leadDir,c.base,t.branch,Wr);let u=await Jl(c.leadDir,c.base,t.branch,Wr);c.applied=u.applied,c.conflicts=u.conflicts??[],o.files+=c.stat.files,o.insertions+=c.stat.insertions,o.deletions+=c.stat.deletions,s.push(...c.files.map(f=>F.join(c.name,f))),a.push(...c.conflicts.map(f=>f.startsWith("patch did not apply cleanly")?`${c.name}: ${f}`:F.join(c.name,f))),u.applied||(i=!1)}t.stat=o,t.files=s,t.conflicts=a,t.applied=i}else{await Wl(t.dir,`bostrat: subtask ${t.id} result (auto-committed)`),t.stat=await Vl(e.cwd,t.base,t.branch,Wr),t.files=await zl(e.cwd,t.base,t.branch,Wr);let o=await Jl(e.cwd,t.base,t.branch,Wr);t.applied=o.applied,t.conflicts=o.conflicts}await Gr(t);let r=await NR(t,e,n);await qr(t,"resume"),await gy(t.trackId,t.leadBox,MI(t.task,t.stat,t.conflicts,r),{kind:"subtask-resume"}),await qr(t,"reap"),await Ju(t),t.step="done",t.finishedAt=Date.now(),await CR(t),await Gr(t)}async function $R(t,e){let n=F.join(t.dir,F.dirname(ns));await L.mkdir(n,{recursive:!0});let r="";try{r=await Df(e.cwd,e.sessionId)}catch{}if(!r)try{r=await L.readFile(F.join(e.cwd,".bostrat/handoff/latest.md"),"utf8")}catch{r="_(no prior conversation digest available \u2014 rely on the working tree.)_"}let o=`# Lead context for subtask ${t.id}
|
|
372
|
+
${a}`}async function Vu(t){if(t.archivedAt)return!1;let e=t.boxes.find(r=>r.name===t.leadBox);if(!e||tr.has(e.state)||await Q(e.name))return!1;let n=yy(e.agent,e.sessionId);if(!await Gs(n))return lt(t,"rehydrate-failed",{box:e.name,agent:e.agent,reason:`agent "${e.agent}" not available on this box`}),!1;try{let r=await On(e.name,e.cwd,{agentCmd:n,agent:e.agent,resumeSessionId:e.sessionId,pinned:e.pinned===!0});return e.hookTokenHash=ao(r.hookToken),ea({cwd:e.cwd,sessionName:e.name,agent:e.agent,agentCmd:n}),e.state="live",lt(t,"rehydrated",{box:e.name,agent:e.agent}),!0}catch(r){return lt(t,"rehydrate-failed",{box:e.name,agent:e.agent,reason:String(r?.message||r)}),!1}}async function Jy(t){let e=await W(),n=Y(e,t);if(!n)throw new Error(`track "${t}" not found`);if(!n.archivedAt)throw new Error(`track "${t}" is not archived`);return delete n.archivedAt,lt(n,"unarchived",{}),await Vu(n),await V(),bn(n)}async function Xy(){let t=await W(),e=new Set((await $n()).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(()=>{}),lt(s,"orphan-reaped",{box:a.name,agent:a.agent}),r++,o=!0),!tr.has(a.state)&&a.state!=="stopped"&&(a.state="stopped",o=!0);return o&&await V(),r}async function Yy(t){let e=await W(),n=Y(e,t);if(!n)throw new Error(`track "${t}" not found`);if(n.archivedAt)throw new Error(`track "${t}" is archived \u2014 restore it before restarting the lead`);let r=n.boxes.find(a=>a.name===n.leadBox);if(!r)throw new Error(`track "${t}" has no lead box`);if(tr.has(r.state))throw new Error("lead box is terminal \u2014 nothing to restart");if(await Q(n.leadBox)){let a=await ft(n.leadBox,{lines:xt}).catch(()=>"");if(Qs(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 Vu(n);if(await V(),!s)throw new Error("rehydrate failed \u2014 see the track's rehydrate-failed event");return bn(n)}var Zy=(t,e,n)=>({name:t.name,agent:t.agent,role:e,state:n,cwd:t.cwd,sessionId:t.sessionId,pinned:t.pinned===!0,hookTokenHash:ao(t.hookToken),createdAt:Date.now()});function Qy(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 us=new Map,ms=t=>us.get(t)||(xe?Y(xe,t)?.handoff:null)||null;async function oi(t,e){let n=await W(),r=Y(n,t);r&&(r.handoff={...r.handoff||{},...e},await V())}function AR(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 tb(){let t=await W(),e=!1;for(let n of t.tracks||[]){let r=n.handoff,o=AR(n);if(!o)continue;e=!0;let s=!!r.toBox&&r.toBox===r.fromBox;if(s&&o.step==="error"){let c=await Q(r.fromBox).catch(()=>null),l=n.boxes.find(u=>u.name===r.fromBox);if(c&&l&&c.agent===r.toAgent&&r.toAgent!==l.agent){let u=Date.now(),f=Qy(l),d=f[f.length-1],p=d.agent,m=d.sessionId??null;d.endedAt=u,r.reason&&(d.endReason=r.reason);let h=c.agent==="claude"&&!!c.sessionId;f.push({agent:r.toAgent,sessionId:c.sessionId||null,pinned:h,startedAt:u,endedAt:null}),l.agent=r.toAgent,l.sessionId=c.sessionId||null,l.pinned=h,l.state="live",n.pendingHandoffHydrate={artifact:ci,at:u},lt(n,"lead-handoff",{from:r.fromBox,to:r.fromBox,fromAgent:p,toAgent:r.toAgent,fromSessionId:m,toSessionId:c.sessionId||null,epoch:f.length-1,singleBox:!0,skipPermissions:r.skipPermissions,reason:r.reason??null,handoffStartedAt:r.startedAt,recovered:!0}),n.handoff={...r,step:"done",finishedAt:u,recovered:"swap adopted from live tmux tags after restart"},await q({flow:"lead-handoff",phase:"reconciled",trackId:n.id,outcome:"done",recovered:n.handoff.recovered,fromBox:r.fromBox,toBox:r.toBox}).catch(()=>{});continue}}let a=!s&&o.recovered==="rolled-back"&&r.toBox&&r.toBox!==n.leadBox?r.toBox:null,i=!s&&o.step==="done"?r.fromBox:null;n.handoff=o,await q({flow:"lead-handoff",phase:"reconciled",trackId:n.id,outcome:o.step,recovered:o.recovered,fromBox:o.fromBox,toBox:o.toBox}).catch(()=>{}),i&&await ce(i).catch(()=>{}),a&&await ce(a).catch(()=>{})}return e&&await V(),e}async function zu(t,e,n=!0,r=null){let o=us.get(t);if(o&&!o.finishedAt)throw new Error("a lead handoff is already running for this Track");let s=Y(await W(),t);if(!s)throw new Error(`track "${t}" not found`);ui(t,"handoff");let a=await ii(e,n),i=await Q(s.leadBox);if(!i)throw new Error(`lead box "${s.leadBox}" is not running`);let c={trackId:t,step:"checkpoint",fromBox:s.leadBox,toAgent:e,skipPermissions:n,reason:by(r),toBox:s.leadBox,startedAt:Date.now(),finishedAt:null,error:null};return us.set(t,c),await oi(t,{step:c.step,fromBox:c.fromBox,toAgent:e,toBox:c.toBox,skipPermissions:n,startedAt:c.startedAt,finishedAt:null,error:null}),ER(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 oi(t,{step:c.step,error:c.error,finishedAt:c.finishedAt}).catch(()=>{}),q({flow:"lead-handoff",phase:"failed",trackId:t,step:c.step,error:c.error,fromBox:c.fromBox,toAgent:c.toAgent,toBox:c.toBox}).catch(()=>{})}),q({flow:"lead-handoff",phase:"started",trackId:t,fromBox:c.fromBox,toAgent:e,cwd:i.cwd}).catch(()=>{}),c}async function ER(t,e,n,r,o){let s=async(I,M={})=>{t.step=I,console.log(`[lead-handoff ${t.trackId}] step \u2192 ${I}`),await oi(t.trackId,{step:I,toBox:t.toBox}),await q({flow:"lead-handoff",phase:I,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=Y(a,t.trackId)?.boxes.find(I=>I.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 Op(t.fromBox),await s("swap",{cwd:n.cwd,agentCmd:r});let f=await ml(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:ps(o)});let d=await ft(t.fromBox,{lines:xt}).catch(()=>"");if(!ta(o,d)){await s("rollback",{sessionName:t.fromBox,fromAgent:c});let I=yy(c,l,{skipPermissions:t.skipPermissions});await ml(t.fromBox,{agentCmd:I,agent:c,resumeSessionId:l,pinned:u});let M=await W(),z=Y(M,t.trackId);if(z){let $=z.boxes.find(F=>F.name===t.fromBox);$&&($.state="live"),lt(z,"lead-handoff-failed",{box:t.fromBox,fromAgent:c,toAgent:o,reason:t.reason,error:`incoming ${o} CLI never drew its composer`,rolledBack:!0,handoffStartedAt:t.startedAt}),await V()}throw new Error(`incoming ${o} CLI never drew its composer \u2014 rolled back to ${c} (--resume, context intact)`)}await s("commit");let p=await W(),m=Y(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 h=m.boxes.find(I=>I.name===t.fromBox);if(!h)throw new Error(`lead box row "${t.fromBox}" missing during handoff commit`);let y=Date.now(),b=Qy(h),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}),h.agent=o,h.sessionId=f.sessionId,h.pinned=f.pinned,h.state="live",h.role="lead",m.pendingHandoffHydrate={artifact:ci,at:y},lt(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 V(),await s("done",{sessionName:t.fromBox}),await oi(t.trackId,{finishedAt:Date.now(),error:null}),t.finishedAt=Date.now()}var _R=Number(process.env.BOSTRAT_SUBTASK_TIMEOUT_MS)||18e5,IR=Number(process.env.BOSTRAT_SUBTASK_POLL_MS)||3e3,Vr=H.join(We,"worktrees"),qr=[ss,ri],re=new Map;var Xr=t=>{let e=new Map([...re.values()].filter(r=>r.trackId===t).map(r=>[r.id,r])),n=xe?Y(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)},Ju=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 RR(t,e,n){let r=await W(),o=Y(r,t);o&&(o.subtasks=o.subtasks||{},o.subtasks[e]={...o.subtasks[e]||{},...n},await V())}var zr=t=>RR(t.trackId,t.id,Ju(t));async function Kr(t,e){t.step=e,await zr(t)}function $R(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 OR(t){return!!t&&!t.finishedAt&&!t.canceled&&t.step==="working"&&!!t.box&&!!t.dir&&!!t.base&&!!t.branch}async function CR(t,e,n){if(!OR(n)||re.has(e)||!await Q(n.box).catch(()=>null))return!1;try{await U.access(n.dir)}catch{return!1}let o=await Q(t.leadBox).catch(()=>null);if(!o?.cwd)return!1;let s={...n,id:e,leadBox:t.leadBox,step:"working",canceled:!1,forceMerge:!1};return re.set(e,s),sb(s,o).catch(a=>rb(s,a)),!0}async function eb(){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 CR(n,r,o)){e=!0,lt(n,"subtask-resumed",{box:o.box,agent:o.agent,task:o.task,subtaskId:r,resumedStep:o.step}),await q({flow:"subtask",phase:"resumed",trackId:n.id,subtaskId:r,box:o.box,branch:o.branch}).catch(()=>{});continue}let s=o.step,a=$R(o);if(!a)continue;e=!0,n.subtasks[r]=a;let i=n.boxes.find(c=>c.subtaskId===r);i&&!tr.has(i.state)&&(i.state="canceled"),lt(n,"subtask-failed",{from:o.box,fromAgent:o.agent,task:o.task,subtaskId:r,failedStep:s,error:a.error,recovered:a.recovered,durationMs:o.startedAt?a.finishedAt-o.startedAt:null}),await q({flow:"subtask",phase:"reconciled",trackId:n.id,subtaskId:r,outcome:a.step,recovered:a.recovered,box:o.box,branch:o.branch}).catch(()=>{}),await wy(n,o.box,o.dir)}for(let r of n.boxes){if(r.role!=="subtask"||tr.has(r.state)||re.has(r.subtaskId)||n.subtasks?.[r.subtaskId])continue;e=!0,r.state="canceled";let o=r.subtaskId?H.join(Vr,`${n.id}-${r.subtaskId}`):null;await q({flow:"subtask",phase:"reaped-legacy-orphan",trackId:n.id,subtaskId:r.subtaskId,box:r.name}).catch(()=>{}),await wy(n,r.name,o)}}return e=await MR(t)||e,e&&await V(),e}async function wy(t,e,n){if(e&&await ce(e).catch(()=>{}),!n)return;if(t.workspace?.kind==="multi"){for(let o of jt(t.workspace)){let s=H.join(n,o.name);await Xt(o.base,s).then(()=>!0,()=>!1)||await Xt(o.dir,s).catch(()=>{})}await U.rm(n,{recursive:!0,force:!0}).catch(()=>{});return}let r=await Q(t.leadBox).catch(()=>null);r?.cwd&&await Xt(r.cwd,n).catch(()=>{})}async function MR(t){let e;try{e=await U.readdir(Vr)}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(Vr,o),a=(t.tracks||[]).find(i=>o.startsWith(`${i.id}-`));if(a?.workspace?.kind==="multi"){for(let i of jt(a.workspace))await Xt(i.base,H.join(s,i.name)).catch(()=>{});await U.rm(s,{recursive:!0,force:!0}).catch(()=>{})}else{let i=a?await Q(a.leadBox).catch(()=>null):null;i?.cwd?await Xt(i.cwd,s).catch(()=>{}):await U.rm(s,{recursive:!0,force:!0}).catch(()=>{})}await q({flow:"subtask",phase:"swept-orphan-worktree",dir:s}).catch(()=>{}),r=!0}return r}var Xu=t=>t.trim().replace(/\s+/g,"-").replace(/[^A-Za-z0-9._-]/g,"").toLowerCase()||"track",ds=class extends Error{};async function nb(t,{agent:e,task:n,skipPermissions:r=!0,reason:o=null}={}){if(typeof n!="string"||!Nr(n))throw new Error("task (string) required");let s=await ii(e,r),a=Y(await W(),t);if(!a)throw new Error(`track "${t}" not found`);let i=await Q(a.leadBox);if(!i)throw new Error(`lead box "${a.leadBox}" is not running`);if(!i.cwd)throw new Error("can't resolve the lead's working directory");let c=a.workspace?.kind==="multi",l=null;if(c){let d=jt(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 Da(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 Da(i.cwd))throw new Error(`subtask delegation needs the lead's workspace to be a git repo (got ${i.cwd})`);let u=fs().slice(0,8),f={id:u,trackId:t,trackName:a.name,agent:e,task:Nr(n),skipPermissions:r,reason:by(o),step:"snapshot",leadBox:a.leadBox,box:null,branch:`bostrat/${Xu(a.name)}-${u}`,base:null,repos:l,dir:H.join(Vr,`${t}-${u}`),stat:null,conflicts:[],applied:null,startedAt:Date.now(),finishedAt:null,error:null,canceled:!1,forceMerge:!1};return re.set(u,f),await zr(f),NR(f,i,s).catch(d=>rb(f,d)),f}async function rb(t,e){t.failedStep=t.step,t.error=e instanceof ds?"canceled":e?.message||String(e),t.step=e instanceof ds?"canceled":"error",t.finishedAt=Date.now(),await Zu(t).catch(()=>{}),await zr(t).catch(()=>{}),await jR(t).catch(()=>{})}function Yu(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 ob(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 NR(t,e,n){if(await Kr(t,"snapshot"),t.repos){for(let s of t.repos)s.base=await Jo(s.leadDir,`bostrat: snapshot for subtask ${t.id}`);await zr(t)}else t.base=await Jo(e.cwd,`bostrat: snapshot for subtask ${t.id}`);if(await Kr(t,"spawn"),await U.mkdir(Vr,{recursive:!0}),t.repos){await U.mkdir(t.dir,{recursive:!0});for(let a of t.repos)await Pr(a.leadDir,H.join(t.dir,a.name),t.branch,a.base);let s=jt((Y(await W(),t.trackId)||{}).workspace);await di(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 Pr(e.cwd,t.dir,t.branch,t.base),await Lu(t.dir);await PR(t,e);let r=t.trackName.slice(0,38).trim(),o=await On(`${r} ${t.agent} sub ${t.id}`,t.dir,{agentCmd:n,agent:t.agent});t.box=o.name,await BR(t,o),await ky(t.trackId,o.name,PI(t.task,!!t.repos),{kind:"subtask"}),await sb(t,e)}async function sb(t,e){await Kr(t,"working");let n=H.join(t.dir,ri);if(await FR(n,t),t.canceled)throw new ds;if(await Kr(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 Vl(l,`bostrat: subtask ${t.id} result (auto-committed)`),c.stat=await Yl(c.leadDir,c.base,t.branch,qr),c.files=await Zl(c.leadDir,c.base,t.branch,qr);let u=await Ql(c.leadDir,c.base,t.branch,qr);c.applied=u.applied,c.conflicts=u.conflicts??[],o.files+=c.stat.files,o.insertions+=c.stat.insertions,o.deletions+=c.stat.deletions,s.push(...c.files.map(f=>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 Vl(t.dir,`bostrat: subtask ${t.id} result (auto-committed)`),t.stat=await Yl(e.cwd,t.base,t.branch,qr),t.files=await Zl(e.cwd,t.base,t.branch,qr);let o=await Ql(e.cwd,t.base,t.branch,qr);t.applied=o.applied,t.conflicts=o.conflicts}await zr(t);let r=await LR(t,e,n);await Kr(t,"resume"),await ky(t.trackId,t.leadBox,BI(t.task,t.stat,t.conflicts,r),{kind:"subtask-resume"}),await Kr(t,"reap"),await Zu(t),t.step="done",t.finishedAt=Date.now(),await DR(t),await zr(t)}async function PR(t,e){let n=H.join(t.dir,H.dirname(ss));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}
|
|
375
373
|
|
|
376
374
|
**Delegated task:** ${t.task}
|
|
377
375
|
|
|
@@ -382,50 +380,50 @@ ${a}`}async function qu(t){if(t.archivedAt)return!1;let e=t.boxes.find(r=>r.name
|
|
|
382
380
|
## Conversation so far (lead \u2192 you)
|
|
383
381
|
|
|
384
382
|
${r}
|
|
385
|
-
`;await
|
|
386
|
-
${n}`}},{type:"section",text:{type:"mrkdwn",text:r}},{type:"context",elements:[{type:"mrkdwn",text:`<${
|
|
383
|
+
`;await U.writeFile(H.join(t.dir,ss),o)}async function BR(t,e){let n=await W(),r=Y(n,t.trackId);r&&(r.boxes.push({name:e.name,agent:e.agent,role:"subtask",state:"live",cwd:e.cwd,sessionId:e.sessionId,hookTokenHash:ao(e.hookToken),createdAt:Date.now(),subtaskId:t.id,branch:t.branch,task:t.task}),lt(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]||{},...Ju(t)},await V())}async function DR(t){let e=await W(),n=Y(e,t.trackId);if(!n)return;let r=n.boxes.find(o=>o.subtaskId===t.id);r&&(r.state=t.conflicts.length?"conflict":"merged",r.stat=t.stat),lt(n,"subtask-merged",{from:t.box,fromAgent:t.agent,task:t.task,subtaskId:t.id,stat:t.stat,conflicts:t.conflicts.length,files:t.files,durationMs:t.finishedAt?t.finishedAt-t.startedAt:null}),await V()}async function jR(t){let e=await W(),n=Y(e,t.trackId);if(!n)return;let r=t.canceled||t.error==="canceled",o=n.boxes.find(s=>s.subtaskId===t.id);o&&(o.state=r?"canceled":"failed"),lt(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]||{},...Ju(t)},await V()}async function LR(t,e,n){let r=`.bostrat/handoff/subtask-${t.id}.md`;try{let o=H.join(e.cwd,r);await U.mkdir(H.dirname(o),{recursive:!0}),await U.copyFile(n,o)}catch{}return r}async function Zu(t){if(t.box&&await ce(t.box).catch(()=>{}),t.repos&&t.dir){for(let e of t.repos)await Xt(e.leadDir,H.join(t.dir,e.name)).catch(()=>{});await U.rm(t.dir,{recursive:!0,force:!0}).catch(()=>{})}else if(t.dir){let n=(await Q(t.leadBox).catch(()=>null))?.cwd;n&&await Xt(n,t.dir).catch(()=>{})}}async function FR(t,e){let n=(e.startedAt||Date.now())+_R;for(;;){if(e.canceled||e.forceMerge)return;try{await U.access(t);return}catch{}if(Date.now()>n)throw new Error("subtask timed out without signaling completion (no result digest written)");await kn(IR)}}function kn(t){return new Promise(e=>setTimeout(e,t))}import hs from"node:fs/promises";import mi from"node:path";import KR from"node:os";import UR from"ws";var ab=()=>process.env.BOSTRAT_SLACK_API_BASE||"https://slack.com/api";async function fi(t,e,n={}){let r=await fetch(`${ab()}/${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 Qu=(t,e)=>fi("chat.postMessage",t,e),Yr=(t,e)=>fi("chat.update",t,e);async function Lt(t,e){await fetch(t,{method:"POST",headers:{"content-type":"application/json; charset=utf-8"},body:JSON.stringify(e)}).catch(()=>{})}async function HR(t,e,n={}){let r=await fetch(`${ab()}/${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 ib(t,{filename:e,buffer:n,title:r,channelId:o,threadTs:s,initialComment:a}){let{upload_url:i,file_id:c}=await HR("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 fi("files.completeUploadExternal",t,{files:[{id:c,title:r}],channel_id:o,thread_ts:s,...a?{initial_comment:a}:{}})}var WR=2200;function cb({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 fi("apps.connections.open",u))}catch(u){return n(`connections.open failed: ${u?.message||u}`),c()}o=new UR(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=h=>{try{o.send(JSON.stringify(h?{envelope_id:f.envelope_id,payload:h}:{envelope_id:f.envelope_id}))}catch{}},p=!1,m=setTimeout(()=>{p=!0,d()},WR);Promise.resolve().then(()=>e(f)).then(h=>{clearTimeout(m),!p&&(p=!0,d(f.accepts_response_payload?h:void 0))},h=>{clearTimeout(m),p||(p=!0,d()),n(`envelope handler failed: ${h?.message||h}`)})}),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 db=()=>process.env.BOSTRAT_STATE_DIR||mi.join(process.env.HOME||KR.tmpdir(),".bostrat"),od=()=>mi.join(db(),"slack.json"),pi=null,td=null,ed=Promise.resolve();async function ad(){return pi||(td??=hs.readFile(od(),"utf8").then(t=>JSON.parse(t)?.threads??{}).catch(()=>({})).then(t=>pi=t).finally(()=>td=null),td)}async function lb(){let t=JSON.stringify({threads:pi},null,2),e=`${od()}.${process.pid}.tmp`;await hs.mkdir(db(),{recursive:!0}),await hs.writeFile(e,t),await hs.rename(e,od())}async function qe(){ed=ed.then(lb,lb),await ed.catch(()=>{})}async function id(t){return(await ad())[t]??null}async function GR(t,e){let n=await ad();for(let[r,o]of Object.entries(n))if(o.channel===t&&o.ts===e)return{trackId:r,rec:o};return null}var X=t=>String(t??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");function ws(t,e){let n=String(t??"").trim();return n.length<=e?n:`${n.slice(0,e-1).trimEnd()}\u2026`}function VR(t,e=500){let n=String(t??"").trim().split(/\n\s*\n/)[0]??"";return ws(n.replace(/\n+/g," "),e)}function fb(t){let e=`*${X(t.name)}*${t.archivedAt?" \xB7 _archived_":""}`,n=`${X(t.repoLabel)} \xB7 \`${X(t.branch||"?")}\` \xB7 lead *${X(t.lead)}*`,r=t.kickoff?`> ${X(ws(t.kickoff,300))}`:"_no kickoff yet \u2014 reply in this thread to steer the lead_";return[{type:"section",text:{type:"mrkdwn",text:`${e}
|
|
384
|
+
${n}`}},{type:"section",text:{type:"mrkdwn",text:r}},{type:"context",elements:[{type:"mrkdwn",text:`<${Aa(t.trackId)}|open the Track> \xB7 reachable on the operator's private network only`}]}]}async function sd(t,e){await Yr(t.botToken,{channel:e.channel,ts:e.ts,text:`Track: ${e.name}`,blocks:fb(e)})}async function nr(t,e,n,r=void 0){return Qu(t.botToken,{channel:e.channel,thread_ts:e.ts,text:n,...r?{blocks:r}:{},unfurl_links:!1})}var ub=new Set;function er(t){ub.add(t),t.catch(()=>{}).finally(()=>ub.delete(t))}var dt=t=>({response_type:"ephemeral",text:t}),zR="*/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._",JR=["claude","cursor","codex"];function nd(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 rd=(t,e)=>dt(`No live Track matches \u201C${X(e)}\u201D. Live: ${t.map(n=>`\`${X(n.name)}\``).join(" \xB7 ")||"none"}`);async function pb(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 Lt(o,{...dt(`At capacity \u2014 ${a.length} live Tracks (limit ${e.maxConcurrent}). Archive or stop something first.`),replace_original:!0});return}if(!e.channel){await Lt(o,{...dt("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?mi.basename(c.base):"fresh workspace"),lead:"claude"},u=await Qu(e.botToken,{channel:e.channel,text:`Track started: ${l.name}`,blocks:fb(l),unfurl_links:!1});l.ts=u.ts,(await ad())[i.id]=l,await qe(),await Lt(o,{...dt(`\u2713 *${X(l.name)}* started \u2014 root message posted.`),replace_original:!0})}catch(a){await Lt(o,{...dt(`Start failed: ${a?.message||a}`),replace_original:!0})}}async function XR(t,e,{track:n,responseUrl:r}){let o=t.deps;try{await o.archiveTrack(n.id);let s=await id(n.id);s&&(s.archivedAt=Date.now(),await qe(),await sd(e,s).catch(()=>{})),await Lt(r,{...dt(`\u2713 *${X(n.name)}* archived.`),replace_original:!0})}catch(s){await Lt(r,{...dt(`Archive failed: ${s?.message||s}`),replace_original:!0})}}async function YR(t,e,{track:n,agent:r,responseUrl:o}){try{await t.deps.startLeadHandoff(n.id,r),await Lt(o,{...dt(`\u2713 Handing *${X(n.name)}* to ${X(r)} \u2014 the root message updates when the baton lands.`),replace_original:!0})}catch(s){await Lt(o,{...dt(`Handoff failed: ${s?.message||s}`),replace_original:!0})}}async function ZR(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 Lt(r,{...dt(`\u2713 Stopped *${X(n.name)}*${s.length?` (+ ${s.length} subtask${s.length>1?"s":""} canceled)`:""} \u2014 Track parked, resume from the Track page.`),replace_original:!0})}catch(s){await Lt(r,{...dt(`Stop failed: ${s?.message||s}`),replace_original:!0})}}function QR(t,e){let n=[...e.slice(0,99).map(r=>({text:{type:"plain_text",text:ws(`${r.name} (${r.branch})`,75)},value:r.path})),{text:{type:"plain_text",text:"fresh workspace"},value:"__fresh__"}];return[{type:"section",block_id:JSON.stringify({name:t}),text:{type:"mrkdwn",text:`Repo for *${X(t)}*:`},accessory:{type:"static_select",action_id:"bostrat-start-repo",placeholder:{type:"plain_text",text:"Choose a repo"},options:n}}]}async function t$(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?mi.basename(a.base):"fresh"),c=[...s.boxes||[]].reverse().find(u=>u.role==="lead")?.agent||"?",l=s.prompted?"\u26A0 needs an answer":s.working?"working":"idle";return`\u2022 *${X(s.name)}* \u2014 ${X(i)}${a.branch?` \`${X(a.branch)}\``:""} \xB7 lead ${X(c)} \xB7 ${l}`}),o=e.length-n.length;return r.join(`
|
|
387
385
|
`)+(o?`
|
|
388
|
-
_${o} archived_`:"")}async function
|
|
389
|
-
\u2713 *${
|
|
390
|
-
\u2713 _answered_`}}]})}async function
|
|
391
|
-
_runs on the Track's branch; reachable on the operator's private network only_`}}]);return}case"mock.state.changed":return
|
|
392
|
-
`)){let r=n.trim();if(!r||r[0]!=="{")continue;let o;try{o=JSON.parse(r)}catch{continue}let s=o?.message?.usage;if(!s)continue;let a={input:s.input_tokens||0,output:s.output_tokens||0,cacheWrite:s.cache_creation_input_tokens||0,cacheRead:s.cache_read_input_tokens||0};if(a.input+a.output+a.cacheWrite+a.cacheRead===0)continue;let i=o.message?.id||o.uuid||null;i&&e.set(i,{messageId:i,tokens:a,model:o.message?.model||o.model||null})}return[...e.values()]}function p$(t){let e=new Set;for(let n of t)if(n.type==="cost.recorded"){n.payload?.messageId&&e.add(n.payload.messageId);for(let r of n.payload?.messageIds||[])e.add(r)}return e}function m$(t,e){return t?.transcript_path?t.transcript_path:e?.cwd&&e?.sessionId?Ae(e.cwd,e.sessionId):e?.cwd?dc(e.cwd):null}async function fb(t,e){let n=await e(t?.session_id,t?.cwd);if(!n?.track)return[];let{track:r,box:o}=n,s={boxId:o?.name||null,provider:"claude",source:"hook"},a=[],i=async c=>{a.push(await R(r,{...s,...c}))};switch(t?.hook_event_name){case"SessionStart":await i({type:"session.ready",actor:"system",payload:{sessionId:t.session_id}});break;case"UserPromptSubmit":await i({type:"turn.user",actor:"user",payload:{promptHead:String(t.prompt??t.user_prompt??"").slice(0,160)||null}});break;case"PostToolUse":await i({type:"tool.called",actor:"agent",payload:{tool:t.tool_name??null}});break;case"Stop":await h$(r,o,t,i);break;default:break}return a}async function h$(t,e,n,r){let o=[],s=m$(n,e);if(s)try{o=f$(await d$.readFile(s,"utf8"))}catch{}let a=o.length?o[o.length-1]:null;if(await r({type:"turn.agent",actor:"agent",payload:{model:a?.model??e?.model??null,messageId:a?.messageId??null}}),!o.length)return;let i=p$(await D(t)),c=o.filter(u=>!i.has(u.messageId));if(c.length){let u=new Map;for(let f of c){let d=f.model||"unknown",p=u.get(d)||{tokens:{input:0,output:0,cacheWrite:0,cacheRead:0},messageIds:[]};for(let m of Object.keys(p.tokens))p.tokens[m]+=f.tokens[m];p.messageIds.push(f.messageId),u.set(d,p)}for(let[f,d]of u)await r({type:"cost.recorded",actor:"system",source:"transcript",payload:{model:f,usd:le(f,d.tokens),tokens:d.tokens,messageId:d.messageIds[d.messageIds.length-1],messageIds:d.messageIds,apiCalls:d.messageIds.length}})}let l=a.tokens.input+a.tokens.cacheWrite+a.tokens.cacheRead;await r({type:"context.usage",actor:"system",source:"transcript",payload:{model:a.model,occupancy:l,tokens:a.tokens}})}import y$ from"node:fs";import sd from"node:fs/promises";import b$ from"node:http";import Xr from"node:path";import pi from"node:os";import{createRequire as k$}from"node:module";import ad from"ws";import w$ from"node:path";import g$ from"node:os";var pb=process.env.BOSTRAT_STATE_DIR||w$.join(process.env.HOME||g$.tmpdir(),".bostrat");function mb(t=process.env){let e=(t.BOSTRAT_HOST||"").trim();if(e!=="all")return e||(t.BOSTRAT_PLANE_URL?"127.0.0.1":void 0)}function hb(t){let e=String(t||"").trim();if(!e)return"";let n=/^[a-z][a-z0-9+.-]*:\/\//i.test(e)?e:`wss://${e}`,r;try{r=new URL(n)}catch{return e}let o={"https:":"wss:","http:":"ws:","wss:":"wss:","ws:":"ws:"}[r.protocol];return o?(r.protocol=o,(r.pathname===""||r.pathname==="/")&&(r.pathname="/v1/node"),r.toString()):e}var fi=(()=>{try{return k$(import.meta.url)("../package.json").version||null}catch{return null}})(),x$=process.env.BOSTRAT_STATE_DIR||Xr.join(process.env.HOME||pi.tmpdir(),".bostrat");function S$(t,e){let n=String(t).split(".").map(Number),r=String(e).split(".").map(Number);if(n.some(Number.isNaN)||r.some(Number.isNaN))return!1;for(let o=0;o<Math.max(n.length,r.length);o++){let s=(n[o]||0)-(r[o]||0);if(s)return s<0}return!1}function v$({home:t=process.env.HOME||pi.homedir(),env:e=process.env}={}){let n=r=>{try{return y$.statSync(r),!0}catch{return!1}};return{claude:n(Xr.join(t,".claude",".credentials.json")),codex:n(Xr.join(e.CODEX_HOME||Xr.join(t,".codex"),"auth.json"))}}var _t=1,wb=1e3,T$=3e4,A$=6e4,E$=1500,_$=500,gb=100,I$=2e3,R$=25e3;function yb({url:t,token:e,nodeId:n=pi.hostname(),name:r=pi.hostname(),listTracks:o,detectAgents:s=null,selfUrl:a=`http://127.0.0.1:${process.env.PORT||3362}`,log:i=console}){if(!t||!e)throw new Error("plane-link: url and token are required");if(typeof o!="function")throw new Error("plane-link: listTracks is required");t=hb(t);let c=a.replace(/^http/,"ws"),l=new Map,u=new Map,f=Xr.join(x$,"plane-link","cursor.json"),d={},p=new Map,m=null,g=!1,y=!1,b=wb,S=null,E=null,M=null,J=null,$=!1;async function j(){try{d=JSON.parse(await sd.readFile(f,"utf8"))}catch{d={}}}function v(){$=!0,!M&&(M=setTimeout(async()=>{if(M=null,!!$){$=!1;try{await sd.mkdir(Xr.dirname(f),{recursive:!0}),await sd.writeFile(f,JSON.stringify(d))}catch(O){i.error("[plane-link] cursor flush failed:",O?.message||O)}}},I$),M.unref?.())}function C(O){return!m||m.readyState!==ad.OPEN?!1:(m.send(JSON.stringify(O)),!0)}function q(O){for(let{trackId:I,event:A}of O)p.set(A.id,{trackId:I,seq:A.seq});return C({v:_t,ch:"events",type:"append",events:O})}async function rt(){let O;try{O=await o()}catch(I){i.error("[plane-link] backfill listTracks failed:",I?.message||I);return}C({v:_t,ch:"ctl",type:"tracks",ids:O.map(I=>I.id).filter(I=>typeof I=="string")});for(let I of O){if(!g)return;let A;try{A=await D(I)}catch{continue}let G=d[I.id]??0,K=A.filter(dt=>dt.seq>G&&typeof dt.id=="string");for(let dt=0;dt<K.length;dt+=gb){let yt=K.slice(dt,dt+gb).map(Ot=>({trackId:I.id,event:Ot}));if(!q(yt))return}}}function st(O){!g||!O?.trackId||typeof O.id!="string"||q([{trackId:O.trackId,event:O}])}let w=new Map;async function h(O){let{reqId:I,method:A,path:G,headers:K={},body:dt}=O,yt=Object.fromEntries(Object.entries(K).filter(([,Vt])=>Vt!=null)),Ot=new AbortController;w.set(I,Ot);try{if(O.host){await T(O,yt,Ot);return}let Vt=await fetch(a+G,{method:A,headers:yt,body:dt?Buffer.from(dt,"base64"):void 0,signal:Ot.signal}),Zt=Object.fromEntries(Vt.headers);for(let k of["connection","keep-alive","transfer-encoding","upgrade","content-encoding","content-length"])delete Zt[k];if(Zt["content-type"]||(Zt["content-type"]="application/octet-stream"),C({v:_t,ch:"control",type:"http_head",reqId:I,status:Vt.status,headers:Zt}),Vt.body){for await(let k of Vt.body)if(!C({v:_t,ch:"control",type:"http_data",reqId:I,data:Buffer.from(k).toString("base64")}))break}C({v:_t,ch:"control",type:"http_end",reqId:I,error:null})}finally{w.delete(I)}}function T(O,I,A){let{reqId:G,method:K,path:dt,body:yt}=O,Ot=new URL(a);return new Promise(Vt=>{let Zt=b$.request({host:Ot.hostname,port:Ot.port,method:K,path:dt,headers:{...I,host:O.host},signal:A.signal},k=>{let{connection:V,"keep-alive":er,"transfer-encoding":As,upgrade:nr,...Ni}=k.headers;C({v:_t,ch:"control",type:"http_head",reqId:G,status:k.statusCode||502,headers:Ni}),k.on("data",nx=>{C({v:_t,ch:"control",type:"http_data",reqId:G,data:Buffer.from(nx).toString("base64")})||k.destroy()}),k.on("end",()=>{C({v:_t,ch:"control",type:"http_end",reqId:G,error:null}),Vt()}),k.on("error",()=>{C({v:_t,ch:"control",type:"http_end",reqId:G,error:"view upstream error"}),Vt()})});Zt.on("error",k=>{C({v:_t,ch:"control",type:"http_end",reqId:G,error:k?.message||"view request failed"}),Vt()}),yt&&Zt.write(Buffer.from(yt,"base64")),Zt.end()})}function _(){if(y)return;m=new ad(t,{headers:{authorization:`Bearer ${e}`}}),m.on("open",async()=>{let I=null;try{I=await s?.()||null}catch{I=null}C({v:_t,ch:"ctl",type:"hello",nodeId:n,name:r,platform:process.platform,version:fi,providers:v$(),...I?{agents:I}:{}})}),m.on("message",I=>{let A;try{A=JSON.parse(I)}catch{return}if(A.ch==="ctl"&&A.type==="hello_ok"){g=!0,b=wb,S=null,i.log(`[plane-link] connected to plane as ${n}`),A.minNodeVersion&&fi&&S$(fi,A.minNodeVersion)&&i.error(`[plane-link] this node is bostrat-node ${fi}; the plane asks for \u2265${A.minNodeVersion} \u2014 update with: npm install -g bostrat-node`),J&&clearInterval(J),J=setInterval(()=>C({v:_t,ch:"ctl",type:"ping"}),R$),J.unref?.(),rt().catch(G=>i.error("[plane-link] backfill failed:",G?.message||G));return}if(A.ch==="events"&&A.type==="ack"&&Array.isArray(A.ids)){for(let G of A.ids){let K=p.get(G);K&&(p.delete(G),(d[K.trackId]??0)<K.seq&&(d[K.trackId]=K.seq))}v();return}if(A.ch==="control"&&A.type==="http_open"){h(A).catch(G=>C({v:_t,ch:"control",type:"http_end",reqId:A.reqId,error:G?.message||"tunnel error"}));return}if(A.ch==="control"&&A.type==="http_cancel"){w.get(A.reqId)?.abort();return}if(A.ch==="attach"&&A.type==="open"){let G=new ad(c+A.path,Array.isArray(A.protocols)?A.protocols:[],A.host?{headers:{host:A.host}}:void 0);l.set(A.streamId,G),u.set(A.streamId,[]),G.on("open",()=>{let dt=u.get(A.streamId)||[];u.delete(A.streamId);for(let{buf:yt,bin:Ot}of dt)G.send(yt,{binary:Ot})}),G.on("message",(dt,yt)=>{let Ot=yt?dt:Buffer.from(String(dt));C({v:_t,ch:"attach",type:"data",streamId:A.streamId,data:Ot.toString("base64"),...yt?{bin:!0}:{}})});let K=()=>{u.delete(A.streamId),l.delete(A.streamId)&&C({v:_t,ch:"attach",type:"close",streamId:A.streamId})};G.on("close",K),G.on("error",K);return}if(A.ch==="attach"&&A.type==="data"){let G=l.get(A.streamId);if(!G)return;let K=Buffer.from(A.data,"base64"),dt=A.bin===!0,yt=u.get(A.streamId);yt?yt.push({buf:K,bin:dt}):G.send(K,{binary:dt});return}if(A.ch==="attach"&&A.type==="close"){let G=l.get(A.streamId);l.delete(A.streamId),u.delete(A.streamId),G?.close();return}});let O=()=>{g&&i.log("[plane-link] disconnected from plane"),g=!1,J&&(clearInterval(J),J=null),p.clear();for(let G of l.values())G.close();l.clear(),u.clear();for(let G of w.values())G.abort();if(w.clear(),y||E)return;S==null&&(S=Date.now());let I=Date.now()-S<A$,A=I?E$+Math.random()*_$:b;E=setTimeout(()=>{E=null,_()},A),E.unref?.(),I||(b=Math.min(b*2,T$))};m.on("close",O),m.on("error",O)}return Jt.on("event",st),j().then(_),{stop:()=>{y=!0,Jt.off("event",st),E&&clearTimeout(E),M&&clearTimeout(M),J&&clearInterval(J),C({v:_t,ch:"ctl",type:"bye",reason:"shutdown"}),m?.close()},status:()=>({connected:g,nodeId:n,cursor:{...d},inflight:p.size})}}function cd(t){let e={};for(let n of t.boxes||[]){let r=n.role&&n.role!=="lead"?` \xB7 ${n.role}`:"";e[n.name]=`${n.name} (${n.agent||"?"}${r})`}return e}var bb=()=>({input:0,output:0,cacheRead:0,cacheCreation:0}),kb=(t,e)=>{for(let n of Object.keys(t))t[n]+=e?.[n]||0},id=t=>Math.round(t*1e4)/1e4;async function xb(t){let e=[],n={},r={},o=0,s=0,a=0,i=0,c=new Set,l=bb(),u=0,f=[];for(let d of t||[]){let p=await D(d),m=ue(p,{boxLabels:cd(d)});if(m.turns===0){d.archivedAt?u++:f.push(d.name||d.id);continue}o+=m.totalUsd,s+=m.turns,a+=m.repricedTurns||0,i+=m.unpricedTurns||0;for(let g of m.unpricedModels||[])c.add(g);kb(l,m.totalTokens);for(let[g,y]of Object.entries(m.byModel))n[g]||={usd:0,events:0,tokens:bb()},n[g].usd=id(n[g].usd+y.usd),n[g].events+=y.events,kb(n[g].tokens,y.tokens);for(let[g,y]of Object.entries(m.byProvider))r[g]||={usd:0,events:0},r[g].usd=id(r[g].usd+y.usd),r[g].events+=y.events;e.push({id:d.id,name:d.name,usd:m.totalUsd,rollup:m})}return e.sort((d,p)=>p.usd-d.usd),{generatedAt:new Date().toISOString(),pricingBasis:"api-equivalent (subscription usage priced at public API rates)",fleet:{totalUsd:id(o),turns:s,repricedTurns:a,unpricedTurns:i,unpricedModels:[...c],totalTokens:l,byModel:n,byProvider:r,tracksWithCost:e.length,tracksSilent:u+f.length,tracksSilentArchived:u,tracksSilentLive:f},tracks:e}}var ps=t=>`$${t.toFixed(4)}`,Yr=(t,e)=>String(t).padEnd(e);function Sb(t){let{fleet:e,tracks:n}=t,r=[];r.push(`BOSTRAT COST READ-OUT \xB7 ${t.generatedAt}`),t.pricingBasis&&r.push(`basis: ${t.pricingBasis}`);let o=e.tracksSilentLive||[];r.push(`fleet total ${ps(e.totalUsd)} over ${e.turns} turn(s) \xB7 ${e.tracksWithCost} track(s) with cost, ${e.tracksSilentArchived??e.tracksSilent} silent (archived)`+(o.length?`, ${o.length} LIVE with no cost data: ${o.join(", ")}`:"")),e.repricedTurns&&r.push(`${e.repricedTurns} turn(s) imputed at current rates (recorded before their model was priced)`),e.unpricedTurns&&r.push(`\u26A0 ${e.unpricedTurns} turn(s) UNPRICED \u2014 total under-reports. Add PER_MTOK rate(s) for: ${(e.unpricedModels||[]).join(", ")}`),r.push(""),r.push("BY MODEL");let s=Object.entries(e.byModel).sort((a,i)=>i[1].usd-a[1].usd);s.length||r.push(" (no cost events yet \u2014 hooks emit on each turn of a newly started box)");for(let[a,i]of s){let c=i.tokens;r.push(` ${Yr(a,26)} ${Yr(ps(i.usd),10)} ${i.events} turn(s) \xB7 in ${c.input} out ${c.output} cacheR ${c.cacheRead} cacheW ${c.cacheCreation}`)}r.push(""),r.push("BY TRACK / TASK");for(let a of n){r.push(` ${Yr(a.name||a.id,30)} ${ps(a.usd)}`);let i=Object.values(a.rollup.byBox).sort((c,l)=>l.usd-c.usd);for(let c of i)if(r.push(` ${Yr(c.label,40)} ${Yr(ps(c.usd),10)} ${c.models.join(",")||"?"}`),c.epochs)for(let[l,u]of Object.entries(c.epochs).sort((f,d)=>Number(f[0])-Number(d[0])))r.push(` \u21B3 tenure ${l} (${u.provider||"?"})${" ".repeat(Math.max(1,24-String(u.provider||"?").length))}${Yr(ps(u.usd),10)} ${u.models.join(",")||"?"}`)}return r.join(`
|
|
393
|
-
`)}import N$ from"node:fs/promises";import P$ from"node:path";var mi=t=>t.payload?.ts??t.ts,$$=12e4,kn=t=>Math.round(t*1e4)/1e4,O$=()=>({usd:0,activeMs:0,turns:0,toolCalls:0,models:{},providers:{},byTrack:new Map});function C$(t,{since:e}){let{track:n,events:r,brief:o}=t,s=S=>mi(S)>=e,a=pr([xo(n?.name),xo(n?.workspace?.branch),o].filter(Boolean).join(" ")),i=new Map;for(let S of r)if(S.type==="subtask.delegated"&&S.boxId&&S.payload?.task){let E=pr(S.payload.task);E&&i.set(S.boxId,E)}let c=new Map,l=S=>{let E=S||"unattributed";return c.has(E)||c.set(E,{segments:[u(null)],lastTs:null,provider:null}),c.get(E)},u=S=>({textCat:S,tools:{},usd:0,tokens:{input:0,output:0,cacheRead:0,cacheWrite:0},models:{},provider:null,activeMs:0,turns:0,toolCalls:0}),f=[...r].sort((S,E)=>mi(S)-mi(E)),d=0,p=0,m=0,g={turns:0};for(let S of f){let E=l(S.boxId),M=E.segments[E.segments.length-1],J=mi(S);switch(E.lastTs!=null&&J>E.lastTs&&s(S)&&(M.activeMs+=Math.min(J-E.lastTs,$$)),E.lastTs=J,S.provider&&(E.provider=S.provider),S.type){case"turn.user":{let $=S.payload?.text;if(S.source==="hook"||!String($||"").trim())break;let j=u(pr($));E.segments.push(j),s(S)&&(j.turns+=1);break}case"tool.called":{if(S.source==="transcript")break;let $=S.payload?.tool;$&&(M.tools[$]=(M.tools[$]||0)+1),s(S)&&(M.toolCalls+=1);break}case"cost.recorded":{if(!s(S))break;let $=S.payload||{},j={input:$.tokens?.input||0,output:$.tokens?.output||0,cacheRead:$.tokens?.cacheRead||0,cacheWrite:$.tokens?.cacheWrite??$.tokens?.cacheCreation??0},v=Number($.usd)||0;!v&&$.tokens&&(v=le($.model,j)),!v&&$.tokens&&!sa($.model)&&(g.turns+=1),M.usd+=v,M.tokens.input+=j.input,M.tokens.output+=j.output,M.tokens.cacheRead+=j.cacheRead,M.tokens.cacheWrite+=j.cacheWrite;let C=$.model||"unknown";M.models[C]=(M.models[C]||0)+v,S.provider&&(M.provider=S.provider);break}case"handoff.done":s(S)&&(d+=1);break;case"subtask.merged":s(S)&&(p+=1);break;case"error":s(S)&&S.payload?.kind==="subtask-failed"&&(m+=1);break;default:break}}let y=new Map,b=!1;for(let[S,E]of c)for(let M of E.segments){if(!(M.usd>0||M.activeMs>0||M.turns>0||M.toolCalls>0))continue;let $=M.textCat?.category??i.get(S)?.category??Qf(M.tools)?.category??a?.category??"uncategorized";y.has($)||y.set($,{usd:0,activeMs:0,turns:0,toolCalls:0,models:{},providers:{}});let j=y.get($);j.usd+=M.usd,j.activeMs+=M.activeMs,j.turns+=M.turns,j.toolCalls+=M.toolCalls;for(let[C,q]of Object.entries(M.models))j.models[C]=(j.models[C]||0)+q;let v=M.provider||E.provider;v&&(j.providers[v]=(j.providers[v]||0)+M.usd),v==="cursor"&&M.usd===0&&(b=!0)}return{perCategory:y,handoffs:d,merged:p,failed:m,sawCursorActivity:b,unpricedTurns:g.turns}}function Tb(t,{windowMs:e=null,now:n=Date.now()}={}){let r=e==null?0:n-e,o=new Map,s=[],a=0,i=!1,c=0;for(let g of t||[]){let{track:y,events:b}=g;if(!b?.length){a+=1;continue}let{perCategory:S,handoffs:E,merged:M,failed:J,sawCursorActivity:$,unpricedTurns:j}=C$(g,{since:r});$&&(i=!0),c+=j;let v={trackId:y?.id,name:y?.name||y?.id,archived:!!y?.archivedAt,usd:0,activeMs:0,turns:0,categories:{},models:{},providers:{},handoffs:E,subtasksMerged:M,subtasksFailed:J};for(let[C,q]of S){v.usd+=q.usd,v.activeMs+=q.activeMs,v.turns+=q.turns,v.categories[C]={usd:kn(q.usd),activeMs:q.activeMs};for(let[w,h]of Object.entries(q.models))v.models[w]=kn((v.models[w]||0)+h);for(let[w,h]of Object.entries(q.providers))v.providers[w]=kn((v.providers[w]||0)+h);o.has(C)||o.set(C,O$());let rt=o.get(C);rt.usd+=q.usd,rt.activeMs+=q.activeMs,rt.turns+=q.turns,rt.toolCalls+=q.toolCalls;for(let[w,h]of Object.entries(q.models))rt.models[w]=(rt.models[w]||0)+h;for(let[w,h]of Object.entries(q.providers))rt.providers[w]=(rt.providers[w]||0)+h;let st=rt.byTrack.get(y?.id)||{usd:0,activeMs:0};st.usd+=q.usd,st.activeMs+=q.activeMs,rt.byTrack.set(y?.id,st)}(v.usd>0||v.activeMs>0||v.turns>0)&&(v.usd=kn(v.usd),v.dominantCategory=M$(v.categories),s.push(v))}let l=[...o.values()].reduce((g,y)=>g+y.usd,0),u=[...o.values()].reduce((g,y)=>g+y.activeMs,0),f=l>0?"usd":"time",d=f==="usd"?l:u,p=new Map((t||[]).map(g=>[g.track?.id,g.track?.name||g.track?.id])),m=[...o.entries()].map(([g,y])=>({category:g,sharePct:d>0?Math.round((f==="usd"?y.usd:y.activeMs)/d*100):0,shareBasis:f,usd:kn(y.usd),activeMs:y.activeMs,turns:y.turns,toolCalls:y.toolCalls,models:vb(y.models),providers:vb(y.providers),topWorkstreams:[...y.byTrack.entries()].sort((b,S)=>S[1].usd-b[1].usd||S[1].activeMs-b[1].activeMs).slice(0,2).map(([b,S])=>({trackId:b,name:p.get(b),usd:kn(S.usd),activeMs:S.activeMs}))})).sort((g,y)=>y.usd-g.usd||y.activeMs-g.activeMs);return s.sort((g,y)=>y.usd-g.usd||y.activeMs-g.activeMs),{since:r,capturedAt:n,totalUsd:kn(l),activeMs:u,turns:[...o.values()].reduce((g,y)=>g+y.turns,0),bullets:m,workstreams:s,gaps:{tracksWithoutEvents:a,cursorCostUnknown:i,unpricedTurns:c}}}var M$=t=>{let e=null;for(let[n,r]of Object.entries(t))(!e||r.usd>e.v.usd||r.usd===e.v.usd&&r.activeMs>e.v.activeMs)&&(e={cat:n,v:r});return e?.cat??null},vb=t=>Object.fromEntries(Object.entries(t).map(([e,n])=>[e,kn(n)]));var Zr={"24h":24*36e5,"7d":168*36e5,"30d":720*36e5,all:null};function B$(t){let e=String(t||"7d");return e in Zr?{key:e,ms:Zr[e]}:{key:"7d",ms:Zr["7d"]}}function D$(t,e){let n=new Set((e||[]).filter(o=>o.source==="orchestrator").map(o=>`${o.type}|${o.payload?.ts??"?"}`)),r=[...e||[]];for(let o of t?.events||[]){let s=ja(o);!s||n.has(`${s.type}|${s.payload?.ts??"?"}`)||r.push({trackId:t.id,boxId:s.boxId??null,ts:o.at,type:s.type,actor:s.actor,provider:s.provider??null,source:s.source,payload:s.payload})}return r}async function j$(t){if(!t?.cwd)return null;try{let n=(await N$.readFile(P$.join(t.cwd,".bostrat","handoff","latest.md"),"utf8")).match(/^##\s*Objective\s*\n([\s\S]*?)(?:\n##\s|\n*$)/m);return n?n[1].trim().slice(0,1e3):null}catch{return null}}async function Ab(t,{window:e="7d",from:n,to:r,now:o=Date.now()}={}){let s=Number(n),a=Number(r),i=Number.isFinite(s)&&s>0,c=i&&Number.isFinite(a)&&a>s?Math.min(a,o):o,{key:l,ms:u}=i?{key:"custom",ms:c-s}:B$(e),f=[];for(let d of t||[]){let p=await D(d),m=D$(d,p);i&&c<o&&(m=m.filter(g=>(g.ts??0)<=c)),f.push({track:d,events:m,brief:await j$(d)})}return{window:l,...Tb(f,{windowMs:u,now:c})}}var xn=36e5,Rb=24*xn,L$=400,F$=12,Qr=t=>Math.round(t*1e4)/1e4;function U$({window:t,from:e,to:n,now:r=Date.now()}={}){let o=Number(e),s=Number(n);if(Number.isFinite(o)&&o>0){let c=Number.isFinite(s)&&s>o?Math.min(s,r):r;return{key:"custom",since:o,until:c}}let a=String(t||"7d")in Zr?String(t||"7d"):"7d",i=Zr[a];return{key:a,since:i==null?null:r-i,until:r}}var H$=(t,e)=>t!=null&&e-t<=48*xn?xn:Rb;function Eb(t,e){if(e===xn)return Math.floor(t/xn)*xn;let n=new Date(t);return n.setHours(0,0,0,0),n.getTime()}function W$(t,e){if(e===xn)return t+xn;let n=new Date(t);return n.setDate(n.getDate()+1),n.setHours(0,0,0,0),n.getTime()}var _b=()=>({input:0,output:0,cacheRead:0,cacheCreation:0}),Ib=(t,e)=>{for(let n of Object.keys(t))t[n]+=e?.[n]||0},q$=t=>Object.fromEntries(Object.entries(t).map(([e,n])=>[e,Qr(n)]));async function $b(t,e={}){let n=e.now??Date.now(),r=U$({...e,now:n}),o=r.since==null?Rb:H$(r.since,r.until),s=v=>(r.since==null||v>=r.since)&&v<r.until,a=r.since!=null?r.until-r.since:null,i=v=>a!=null&&v>=r.since-a&&v<r.since,c={},l={},u=_b(),f=0,d=0,p=0,m=0,g=0,y=new Set,b=new Map,S=[],E=[],M=[],J=null;for(let v of t||[]){let q=(await D(v)).filter(w=>w.type==="cost.recorded");if(!q.length){v.archivedAt||M.push(v.name||v.id);continue}let rt=[];for(let w of q){let h=w.ts??0;s(h)?rt.push(w):i(h)&&(g+=ia(w).usd)}if(!rt.length)continue;let st=ue(rt,{boxLabels:cd(v)});f+=st.totalUsd,d+=st.turns,p+=st.repricedTurns||0,m+=st.unpricedTurns||0;for(let w of st.unpricedModels||[])y.add(w);Ib(u,st.totalTokens);for(let[w,h]of Object.entries(st.byModel))c[w]||={usd:0,events:0,tokens:_b()},c[w].usd=Qr(c[w].usd+h.usd),c[w].events+=h.events,Ib(c[w].tokens,h.tokens);for(let[w,h]of Object.entries(st.byProvider))l[w]||={usd:0,events:0},l[w].usd=Qr(l[w].usd+h.usd),l[w].events+=h.events;for(let w of rt){let{usd:h}=ia(w);if(!h)continue;let T=Eb(w.ts??0,o),_=b.get(T)??{t:T,total:0,byProvider:{}};_.total+=h;let O=w.provider||"unknown";_.byProvider[O]=(_.byProvider[O]||0)+h,b.set(T,_),(J==null||T<J)&&(J=T)}S.push({id:v.id,name:v.name||v.id,archived:!!v.archivedAt,usd:st.totalUsd,turns:st.turns,providers:Object.keys(st.byProvider)});for(let[w,h]of Object.entries(st.byBox))E.push({trackId:v.id,trackName:v.name||v.id,boxId:w,...h})}let $=r.since!=null?Eb(r.since,o):J,j=[];for(let v=$,C=0;v!=null&&v<r.until&&C<L$;v=W$(v,o),C++){let q=b.get(v);j.push(q?{t:v,total:Qr(q.total),byProvider:q$(q.byProvider)}:{t:v,total:0,byProvider:{}})}return S.sort((v,C)=>C.usd-v.usd),E.sort((v,C)=>C.usd-v.usd),{generatedAt:new Date().toISOString(),pricingBasis:"api-equivalent (subscription usage priced at public API rates)",window:r.key,since:r.since,until:r.until,bucketMs:o,series:j,fleet:{totalUsd:Qr(f),turns:d,repricedTurns:p,unpricedTurns:m,unpricedModels:[...y],totalTokens:u,byModel:c,byProvider:l,tracksWithCost:S.length,tracksSilentLive:M},prior:a!=null?{totalUsd:Qr(g),since:r.since-a,until:r.since}:null,tracks:S.slice(0,50),boxes:E.slice(0,F$)}}import to from"node:fs/promises";import K$ from"node:fs";import It from"node:path";import G$ from"node:os";import{execFile as V$}from"node:child_process";import{promisify as z$}from"node:util";var J$=z$(V$),Ob=process.env.BOSTRAT_STATE_DIR||It.join(process.env.HOME||G$.tmpdir(),".bostrat"),hi=!1,ld=null;async function ud(t,e){let n=await to.stat(t).catch(()=>null);if(!n?.isFile())return!1;let r=await to.stat(e).catch(()=>null);return r&&r.size===n.size&&r.mtimeMs>=n.mtimeMs?!1:(await to.mkdir(It.dirname(e),{recursive:!0}),await to.copyFile(t,e),!0)}var X$=new Set([".claude",".codex",".credentials.json","auth.json",".claude.json","integrations.json"]);async function wi(t,e,{skip:n=null}={}){let r=0,o=await to.readdir(t,{withFileTypes:!0}).catch(()=>[]);for(let s of o){if(X$.has(s.name)||n?.has(s.name))continue;let a=It.join(t,s.name);s.isDirectory()?r+=await wi(a,It.join(e,s.name)):s.isFile()&&(r+=await ud(a,It.join(e,s.name))?1:0)}return r}async function Cb({stateDir:t=Ob,dest:e=process.env.BOSTRAT_SYNC_DIR||`${Ob}-sync`,remote:n=process.env.BOSTRAT_STATE_REMOTE||null,tracks:r=null}={}){try{await to.mkdir(e,{recursive:!0});let o=0;o+=await ud(It.join(t,"tracks.json"),It.join(e,"tracks.json"))?1:0,o+=await ud(It.join(t,"events.jsonl"),It.join(e,"events.jsonl"))?1:0,o+=await wi(It.join(t,"events"),It.join(e,"events")),o+=await wi(It.join(t,"memory"),It.join(e,"memory"));let s=new Set(["mocks"]);for(let u of r||[])!u?.cwd||!u?.id||(o+=await wi(It.join(u.cwd,".bostrat"),It.join(e,"workspaces",u.id,".bostrat"),{skip:s}));let a=(...u)=>J$("git",["-C",e,...u]);K$.existsSync(It.join(e,".git"))||(await a("init","-q"),await a("config","user.email","agent@bostrat.local"),await a("config","user.name","bostrat state sync")),n&&/^https:\/\/github\.com\//i.test(n)&&(await a("config","--get","credential.useHttpPath").then(()=>!0).catch(()=>!1)||await Hi(e).catch(()=>{}));let i=!1,{stdout:c}=await a("status","--porcelain");c.trim()&&(await a("add","-A"),await a("commit","-q","-m",`state sync ${new Date().toISOString()}`),i=!0);let l=!1;if(n&&(i||hi))try{await a("push","-q",n,"HEAD:refs/heads/main"),l=!0,hi&&console.log("[state-sync] push recovered \u2014 off-box copy is current again"),hi=!1,ld=null}catch(u){hi=!0;let f=(u?.stderr||u?.message||String(u)).trim().split(`
|
|
394
|
-
`).slice(-3).join(" | ").slice(0,300);f!==
|
|
395
|
-
`)[0]}`),{pushed:!1,reason:"push-failed"}}}function
|
|
396
|
-
`).map(s=>s.replace(/\s+$/,"").trim()),n=s=>{let a=null,i=null;for(let c=s+1;c<Math.min(s+5,e.length)&&!/^Current\s/i.test(e[c]);c++){let l=e[c].match(/(\d+)%\s*used/i);l&&a===null&&(a=Number(l[1]));let u=e[c].match(/^Resets\s+(.+)$/i);u&&i===null&&(i=u[1].trim())}return a===null?null:{pct:a,resets:i||null}},r=s=>{let a=e.findIndex(i=>s.test(i));return a<0?null:n(a)},o=[];return e.forEach((s,a)=>{let i=s.match(/^Current week \(([^)]+)\)$/i);if(!i||/all models/i.test(i[1]))return;let c=n(a);c&&o.push({model:i[1].trim(),...c})}),{session:r(/^Current session\b/i),week:r(/^Current week \(all models\)/i)||r(/^Current week$/i),weekModels:o.length?o:null}}function
|
|
397
|
-
`).map(s=>s.replace(/\s+$/,"")),n=e.findIndex(s=>/What's contributing/i.test(s));if(n<0)return null;let r=/(Usage credits|Esc to cancel|d to day · w to week|^\s*d to day)/i,o=[];for(let s=n+1;s<e.length&&!r.test(e[s]);s++)e[s].trim()&&o.push(e[s].trim());return o.length?o:null}async function
|
|
398
|
-
`),n=!1;for(let r=e.length-1;r>=0;r--){let o=e[r].trim();if(!o||o[0]!=="{"||!o.includes("token_count"))continue;let s;try{s=JSON.parse(o)}catch{continue}let a=s?.payload;if(a?.type!=="token_count")continue;n=!0;let i=a.rate_limits;if(!i)return{session:null,week:null,sawTokenCount:n};if(i.primary||i.secondary)return{session:
|
|
399
|
-
`,{mode:384}),
|
|
386
|
+
_${o} archived_`:"")}async function e$(t,e){let n=await Ln();if(!n)return dt("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 dt("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?(er(pb(t,n,{name:u,repo:l.path,responseUrl:e.response_url})),dt(`Starting *${X(u)}* on ${X(l.name)}\u2026`)):{response_type:"ephemeral",text:`Pick a repo for \u201C${u}\u201D`,blocks:QR(u,i)}}case"archive":{if(!a.length)return dt("Usage: `/bostrat archive <track>`");let{match:i,live:c}=nd(await r.listTracks(),a.join(" "));return i?(er(XR(t,n,{track:i,responseUrl:e.response_url})),dt(`Archiving *${X(i.name)}*\u2026`)):rd(c,a.join(" "))}case"handoff":{let i=r.knownAgents??JR,c=a.at(-1)?.toLowerCase();if(a.length<2||!i.includes(c))return dt(`Usage: \`/bostrat handoff <track> <${i.join("|")}>\``);let l=a.slice(0,-1).join(" "),{match:u,live:f}=nd(await r.listTracks(),l);return u?(er(YR(t,n,{track:u,agent:c,responseUrl:e.response_url})),dt(`Handing *${X(u.name)}* to ${X(c)}\u2026`)):rd(f,l)}case"stop":{if(!a.length)return dt("Usage: `/bostrat stop <track>`");let{match:i,live:c}=nd(await r.listTracks(),a.join(" "));return i?(er(ZR(t,n,{track:i,responseUrl:e.response_url})),dt(`Stopping *${X(i.name)}*\u2026`)):rd(c,a.join(" "))}case"list":return dt(await t$(r));default:return dt(zR)}}async function n$(t,e,n,r){let{trackId:o,promptId:s,label:a}=r,i=await id(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 Yr(e.botToken,{channel:i.channel,ts:c,text:`\u2713 ${a}`,blocks:[{type:"section",text:{type:"mrkdwn",text:`~${X(l||"question")}~
|
|
387
|
+
\u2713 *${X(a)}*`}}]}).catch(()=>{})}}catch(c){await Lt(n.response_url,dt(`Couldn't answer: ${c?.message||c}`))}}async function r$(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,Ha(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 Lt(n.response_url,dt(`Couldn't act on the offer: ${c?.message||c}`))}}async function o$(t,e){let n=e.actions?.[0];if(!n?.action_id)return;let r=await Ln();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 Lt(e.response_url,{...dt(`Starting *${X(s)}*\u2026`),replace_original:!0}),er(pb(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"?er(n$(t,r,e,o)):n.action_id==="bostrat-ship"&&er(r$(t,r,e,o))}async function s$(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 GR(e.channel,e.thread_ts);if(!n)return;let r=await Ln();if(r)try{await t.deps.sayToLead(n.trackId,e.text.trim())}catch(o){await nr(r,n.rec,`Couldn't deliver that to the lead: ${o?.message||o}`).catch(()=>{})}}function a$(t){return async e=>{if(e.type==="slash_commands")return e$(t,e.payload);if(e.type==="interactive"){await o$(t,e.payload);return}if(e.type==="events_api"){await s$(t,e.payload?.event);return}}}var i$=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 c$(t,e){let n=[{type:"section",text:{type:"mrkdwn",text:`\u2753 ${X(e.question||"The lead needs an answer")}`}}],r=(e.options||[]).slice(0,12);return r.length?n.push({type:"actions",elements:r.map(o=>({type:"button",action_id:"bostrat-prompt",text:{type:"plain_text",text:ws(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 l$(t,e,n){let r=n.payload||{};if(!r.promptId||e.prompt?.promptId===r.promptId)return;e.prompt&&await Yr(t.botToken,{channel:e.channel,ts:e.prompt.ts,text:"superseded",blocks:[{type:"section",text:{type:"mrkdwn",text:`~${X(e.prompt.question||"question")}~ _superseded_`}}]}).catch(()=>{});let o=await nr(t,e,r.question||"The lead needs an answer",c$(n.trackId,r));e.prompt={promptId:r.promptId,ts:o.ts,question:r.question||null},await qe()}async function u$(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 Yr(t.botToken,{channel:e.channel,ts:o,text:"answered",blocks:[{type:"section",text:{type:"mrkdwn",text:`~${X(s||"question")}~
|
|
388
|
+
\u2713 _answered_`}}]})}async function d$(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 nr(t,e,r.excerpt||"Ship it?",[{type:"section",text:{type:"mrkdwn",text:`\u{1F6A2} ${X(ws(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 f$(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 Yr(t.botToken,{channel:e.channel,ts:o,text:s,blocks:[{type:"section",text:{type:"mrkdwn",text:`\u{1F6A2} ${s}`}}]})}async function p$(t,e,n,r){let o=n.payload||{};if(o.change==="ready"&&o.mockId){let s=await(r.readMockPng??m$)(o.mockId,o.version??null);if(!s)return;let a=(o.version??1)>1?` (v${o.version})`:"";await ib(t.botToken,{filename:s.filename,buffer:s.buffer,title:`${o.name}${a}`,channelId:e.channel,threadTs:e.ts,initialComment:`\u{1F3A8} mock *${X(o.name)}*${a}${o.group?` (group \`${X(o.group)}\`)`:""}`})}else if(o.change==="picked"){let s=(o.siblings??[]).map(a=>`*${X(a.name)}*`).join(", ");await nr(t,e,`Design decision: ${o.name} picked`,[{type:"section",text:{type:"mrkdwn",text:`\u{1F3A8} design decision: *${X(o.name)}* picked${s?` over ${s}`:""}`}}])}}async function m$(t,e){let n=await nn(t);if(!n)return null;let r=Ie(n,e);if(!r)return null;let o=await hs.readFile(Wt(n,r.v).png).catch(()=>null);return o?{filename:`${n.name.replace(/[^\w.-]+/g,"-")}.v${r.v}.png`,buffer:o}:null}async function h$(t,e){if(!i$.has(e.type))return;let n=await id(e.trackId);if(!n)return;let r=await Ln();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 sd(r,n);return;case"turn.agent":{if(!o.text)return;let s=VR(o.text);if(!s)return;await nr(r,n,s,[{type:"section",text:{type:"mrkdwn",text:X(s)}},{type:"context",elements:[{type:"mrkdwn",text:`<${Aa(e.trackId)}|full turn \u2192>`}]}]);return}case"prompt.raised":return l$(r,n,e);case"prompt.answered":return u$(r,n,e);case"pr.offered":return d$(r,n,e);case"pr.offer.answered":return f$(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 nr(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 sd(r,n);return;case"view.state.changed":{if(o.change!=="created"||o.restarted||!o.slug)return;await nr(r,n,`Live preview ${o.name}: ${_a(o.slug)}`,[{type:"section",text:{type:"mrkdwn",text:`\u{1F50D} live preview *${X(o.name)}*: ${_a(o.slug)}
|
|
389
|
+
_runs on the Track's branch; reachable on the operator's private network only_`}}]);return}case"mock.state.changed":return p$(r,n,e,t.deps)}}function mb(t){let e={deps:t};Jt.on("event",s=>{h$(e,s).catch(()=>{})});let n=null,r=null,o=async()=>{n||!await Ln()||(r&&clearInterval(r),r=null,n=cb({getToken:async()=>(await Ln())?.appToken,onEnvelope:a$(e),log:s=>console.log(`[slack] ${s}`)}))};return r=setInterval(()=>o().catch(()=>{}),3e4),r.unref?.(),o().catch(()=>{}),()=>{r&&clearInterval(r),n?.()}}import w$ from"node:fs/promises";function g$(t){let e=new Map;for(let n of String(t||"").split(`
|
|
390
|
+
`)){let r=n.trim();if(!r||r[0]!=="{")continue;let o;try{o=JSON.parse(r)}catch{continue}let s=o?.message?.usage;if(!s)continue;let a={input:s.input_tokens||0,output:s.output_tokens||0,cacheWrite:s.cache_creation_input_tokens||0,cacheRead:s.cache_read_input_tokens||0};if(a.input+a.output+a.cacheWrite+a.cacheRead===0)continue;let i=o.message?.id||o.uuid||null;i&&e.set(i,{messageId:i,tokens:a,model:o.message?.model||o.model||null})}return[...e.values()]}function y$(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 b$(t,e){return t?.transcript_path?t.transcript_path:e?.cwd&&e?.sessionId?Ae(e.cwd,e.sessionId):e?.cwd?mc(e.cwd):null}async function hb(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 k$(r,o,t,i);break;default:break}return a}async function k$(t,e,n,r){let o=[],s=b$(n,e);if(s)try{o=g$(await w$.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=y$(await L(t)),c=o.filter(u=>!i.has(u.messageId));if(c.length){let u=new Map;for(let f of c){let d=f.model||"unknown",p=u.get(d)||{tokens:{input:0,output:0,cacheWrite:0,cacheRead:0},messageIds:[]};for(let m of Object.keys(p.tokens))p.tokens[m]+=f.tokens[m];p.messageIds.push(f.messageId),u.set(d,p)}for(let[f,d]of u)await r({type:"cost.recorded",actor:"system",source:"transcript",payload:{model:f,usd: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 v$ from"node:fs";import cd from"node:fs/promises";import T$ from"node:http";import Zr from"node:path";import wi from"node:os";import{createRequire as A$}from"node:module";import ld from"ws";import x$ from"node:path";import S$ from"node:os";var wb=process.env.BOSTRAT_STATE_DIR||x$.join(process.env.HOME||S$.tmpdir(),".bostrat");function gb(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 yb(t){let e=String(t||"").trim();if(!e)return"";let n=/^[a-z][a-z0-9+.-]*:\/\//i.test(e)?e:`wss://${e}`,r;try{r=new URL(n)}catch{return e}let o={"https:":"wss:","http:":"ws:","wss:":"wss:","ws:":"ws:"}[r.protocol];return o?(r.protocol=o,(r.pathname===""||r.pathname==="/")&&(r.pathname="/v1/node"),r.toString()):e}var hi=(()=>{try{return A$(import.meta.url)("../package.json").version||null}catch{return null}})(),E$=process.env.BOSTRAT_STATE_DIR||Zr.join(process.env.HOME||wi.tmpdir(),".bostrat");function _$(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 I$({home:t=process.env.HOME||wi.homedir(),env:e=process.env}={}){let n=r=>{try{return v$.statSync(r),!0}catch{return!1}};return{claude:n(Zr.join(t,".claude",".credentials.json")),codex:n(Zr.join(e.CODEX_HOME||Zr.join(t,".codex"),"auth.json"))}}var It=1,bb=1e3,R$=3e4,$$=6e4,O$=1500,C$=500,kb=100,M$=2e3,N$=25e3;function xb({url:t,token:e,nodeId:n=wi.hostname(),name:r=wi.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=yb(t);let c=a.replace(/^http/,"ws"),l=new Map,u=new Map,f=Zr.join(E$,"plane-link","cursor.json"),d={},p=new Map,m=null,h=!1,y=!1,b=bb,S=null,I=null,M=null,z=null,$=!1;async function F(){try{d=JSON.parse(await cd.readFile(f,"utf8"))}catch{d={}}}function T(){$=!0,!M&&(M=setTimeout(async()=>{if(M=null,!!$){$=!1;try{await cd.mkdir(Zr.dirname(f),{recursive:!0}),await cd.writeFile(f,JSON.stringify(d))}catch(v){i.error("[plane-link] cursor flush failed:",v?.message||v)}}},M$),M.unref?.())}function C(v){return!m||m.readyState!==ld.OPEN?!1:(m.send(JSON.stringify(v)),!0)}function D(v){for(let{trackId:O,event:E}of v)p.set(E.id,{trackId:O,seq:E.seq});return C({v:It,ch:"events",type:"append",events:v})}async function it(){let v;try{v=await o()}catch(O){i.error("[plane-link] backfill listTracks failed:",O?.message||O);return}C({v:It,ch:"ctl",type:"tracks",ids:v.map(O=>O.id).filter(O=>typeof O=="string")});for(let O of v){if(!h)return;let E;try{E=await L(O)}catch{continue}let N=d[O.id]??0,nt=E.filter(Z=>Z.seq>N&&typeof Z.id=="string");for(let Z=0;Z<nt.length;Z+=kb){let wt=nt.slice(Z,Z+kb).map(Ct=>({trackId:O.id,event:Ct}));if(!D(wt))return}}}function ot(v){!h||!v?.trackId||typeof v.id!="string"||D([{trackId:v.trackId,event:v}])}let A=new Map;async function g(v){let{reqId:O,method:E,path:N,headers:nt={},body:Z}=v,wt=Object.fromEntries(Object.entries(nt).filter(([,Vt])=>Vt!=null)),Ct=new AbortController;A.set(O,Ct);try{if(v.host){await w(v,wt,Ct);return}let Vt=await fetch(a+N,{method:E,headers:wt,body:Z?Buffer.from(Z,"base64"):void 0,signal:Ct.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:It,ch:"control",type:"http_head",reqId:O,status:Vt.status,headers:Zt}),Vt.body){for await(let k of Vt.body)if(!C({v:It,ch:"control",type:"http_data",reqId:O,data:Buffer.from(k).toString("base64")}))break}C({v:It,ch:"control",type:"http_end",reqId:O,error:null})}finally{A.delete(O)}}function w(v,O,E){let{reqId:N,method:nt,path:Z,body:wt}=v,Ct=new URL(a);return new Promise(Vt=>{let Zt=T$.request({host:Ct.hostname,port:Ct.port,method:nt,path:Z,headers:{...O,host:v.host},signal:E.signal},k=>{let{connection:G,"keep-alive":rr,"transfer-encoding":Rs,upgrade:or,...Di}=k.headers;C({v:It,ch:"control",type:"http_head",reqId:N,status:k.statusCode||502,headers:Di}),k.on("data",sx=>{C({v:It,ch:"control",type:"http_data",reqId:N,data:Buffer.from(sx).toString("base64")})||k.destroy()}),k.on("end",()=>{C({v:It,ch:"control",type:"http_end",reqId:N,error:null}),Vt()}),k.on("error",()=>{C({v:It,ch:"control",type:"http_end",reqId:N,error:"view upstream error"}),Vt()})});Zt.on("error",k=>{C({v:It,ch:"control",type:"http_end",reqId:N,error:k?.message||"view request failed"}),Vt()}),wt&&Zt.write(Buffer.from(wt,"base64")),Zt.end()})}function _(){if(y)return;m=new ld(t,{headers:{authorization:`Bearer ${e}`}}),m.on("open",async()=>{let O=null;try{O=await s?.()||null}catch{O=null}C({v:It,ch:"ctl",type:"hello",nodeId:n,name:r,platform:process.platform,version:hi,providers:I$(),...O?{agents:O}:{}})}),m.on("message",O=>{let E;try{E=JSON.parse(O)}catch{return}if(E.ch==="ctl"&&E.type==="hello_ok"){h=!0,b=bb,S=null,i.log(`[plane-link] connected to plane as ${n}`),E.minNodeVersion&&hi&&_$(hi,E.minNodeVersion)&&i.error(`[plane-link] this node is bostrat-node ${hi}; the plane asks for \u2265${E.minNodeVersion} \u2014 update with: npm install -g bostrat-node`),z&&clearInterval(z),z=setInterval(()=>C({v:It,ch:"ctl",type:"ping"}),N$),z.unref?.(),it().catch(N=>i.error("[plane-link] backfill failed:",N?.message||N));return}if(E.ch==="events"&&E.type==="ack"&&Array.isArray(E.ids)){for(let N of E.ids){let nt=p.get(N);nt&&(p.delete(N),(d[nt.trackId]??0)<nt.seq&&(d[nt.trackId]=nt.seq))}T();return}if(E.ch==="control"&&E.type==="http_open"){g(E).catch(N=>C({v:It,ch:"control",type:"http_end",reqId:E.reqId,error:N?.message||"tunnel error"}));return}if(E.ch==="control"&&E.type==="http_cancel"){A.get(E.reqId)?.abort();return}if(E.ch==="attach"&&E.type==="open"){let N=new ld(c+E.path,Array.isArray(E.protocols)?E.protocols:[],E.host?{headers:{host:E.host}}:void 0);l.set(E.streamId,N),u.set(E.streamId,[]),N.on("open",()=>{let Z=u.get(E.streamId)||[];u.delete(E.streamId);for(let{buf:wt,bin:Ct}of Z)N.send(wt,{binary:Ct})}),N.on("message",(Z,wt)=>{let Ct=wt?Z:Buffer.from(String(Z));C({v:It,ch:"attach",type:"data",streamId:E.streamId,data:Ct.toString("base64"),...wt?{bin:!0}:{}})});let nt=()=>{u.delete(E.streamId),l.delete(E.streamId)&&C({v:It,ch:"attach",type:"close",streamId:E.streamId})};N.on("close",nt),N.on("error",nt);return}if(E.ch==="attach"&&E.type==="data"){let N=l.get(E.streamId);if(!N)return;let nt=Buffer.from(E.data,"base64"),Z=E.bin===!0,wt=u.get(E.streamId);wt?wt.push({buf:nt,bin:Z}):N.send(nt,{binary:Z});return}if(E.ch==="attach"&&E.type==="close"){let N=l.get(E.streamId);l.delete(E.streamId),u.delete(E.streamId),N?.close();return}});let v=()=>{h&&i.log("[plane-link] disconnected from plane"),h=!1,z&&(clearInterval(z),z=null),p.clear();for(let N of l.values())N.close();l.clear(),u.clear();for(let N of A.values())N.abort();if(A.clear(),y||I)return;S==null&&(S=Date.now());let O=Date.now()-S<$$,E=O?O$+Math.random()*C$:b;I=setTimeout(()=>{I=null,_()},E),I.unref?.(),O||(b=Math.min(b*2,R$))};m.on("close",v),m.on("error",v)}return Jt.on("event",ot),F().then(_),{stop:()=>{y=!0,Jt.off("event",ot),I&&clearTimeout(I),M&&clearTimeout(M),z&&clearInterval(z),C({v:It,ch:"ctl",type:"bye",reason:"shutdown"}),m?.close()},status:()=>({connected:h,nodeId:n,cursor:{...d},inflight:p.size})}}function dd(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 Sb=()=>({input:0,output:0,cacheRead:0,cacheCreation:0}),vb=(t,e)=>{for(let n of Object.keys(t))t[n]+=e?.[n]||0},ud=t=>Math.round(t*1e4)/1e4;async function Tb(t){let e=[],n={},r={},o=0,s=0,a=0,i=0,c=new Set,l=Sb(),u=0,f=[];for(let d of t||[]){let p=await L(d),m=ue(p,{boxLabels:dd(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 h of m.unpricedModels||[])c.add(h);vb(l,m.totalTokens);for(let[h,y]of Object.entries(m.byModel))n[h]||={usd:0,events:0,tokens:Sb()},n[h].usd=ud(n[h].usd+y.usd),n[h].events+=y.events,vb(n[h].tokens,y.tokens);for(let[h,y]of Object.entries(m.byProvider))r[h]||={usd:0,events:0},r[h].usd=ud(r[h].usd+y.usd),r[h].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:ud(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 gs=t=>`$${t.toFixed(4)}`,Qr=(t,e)=>String(t).padEnd(e);function Ab(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 ${gs(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(` ${Qr(a,26)} ${Qr(gs(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(` ${Qr(a.name||a.id,30)} ${gs(a.usd)}`);let i=Object.values(a.rollup.byBox).sort((c,l)=>l.usd-c.usd);for(let c of i)if(r.push(` ${Qr(c.label,40)} ${Qr(gs(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))}${Qr(gs(u.usd),10)} ${u.models.join(",")||"?"}`)}return r.join(`
|
|
391
|
+
`)}import L$ from"node:fs/promises";import F$ from"node:path";var gi=t=>t.payload?.ts??t.ts,P$=12e4,xn=t=>Math.round(t*1e4)/1e4,B$=()=>({usd:0,activeMs:0,turns:0,toolCalls:0,models:{},providers:{},byTrack:new Map});function D$(t,{since:e}){let{track:n,events:r,brief:o}=t,s=S=>gi(S)>=e,a=hr([vo(n?.name),vo(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 I=hr(S.payload.task);I&&i.set(S.boxId,I)}let c=new Map,l=S=>{let I=S||"unattributed";return c.has(I)||c.set(I,{segments:[u(null)],lastTs:null,provider:null}),c.get(I)},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,I)=>gi(S)-gi(I)),d=0,p=0,m=0,h={turns:0};for(let S of f){let I=l(S.boxId),M=I.segments[I.segments.length-1],z=gi(S);switch(I.lastTs!=null&&z>I.lastTs&&s(S)&&(M.activeMs+=Math.min(z-I.lastTs,P$)),I.lastTs=z,S.provider&&(I.provider=S.provider),S.type){case"turn.user":{let $=S.payload?.text;if(S.source==="hook"||!String($||"").trim())break;let F=u(hr($));I.segments.push(F),s(S)&&(F.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||{},F={input:$.tokens?.input||0,output:$.tokens?.output||0,cacheRead:$.tokens?.cacheRead||0,cacheWrite:$.tokens?.cacheWrite??$.tokens?.cacheCreation??0},T=Number($.usd)||0;!T&&$.tokens&&(T=le($.model,F)),!T&&$.tokens&&!la($.model)&&(h.turns+=1),M.usd+=T,M.tokens.input+=F.input,M.tokens.output+=F.output,M.tokens.cacheRead+=F.cacheRead,M.tokens.cacheWrite+=F.cacheWrite;let C=$.model||"unknown";M.models[C]=(M.models[C]||0)+T,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,I]of c)for(let M of I.segments){if(!(M.usd>0||M.activeMs>0||M.turns>0||M.toolCalls>0))continue;let $=M.textCat?.category??i.get(S)?.category??np(M.tools)?.category??a?.category??"uncategorized";y.has($)||y.set($,{usd:0,activeMs:0,turns:0,toolCalls:0,models:{},providers:{}});let F=y.get($);F.usd+=M.usd,F.activeMs+=M.activeMs,F.turns+=M.turns,F.toolCalls+=M.toolCalls;for(let[C,D]of Object.entries(M.models))F.models[C]=(F.models[C]||0)+D;let T=M.provider||I.provider;T&&(F.providers[T]=(F.providers[T]||0)+M.usd),T==="cursor"&&M.usd===0&&(b=!0)}return{perCategory:y,handoffs:d,merged:p,failed:m,sawCursorActivity:b,unpricedTurns:h.turns}}function _b(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 h of t||[]){let{track:y,events:b}=h;if(!b?.length){a+=1;continue}let{perCategory:S,handoffs:I,merged:M,failed:z,sawCursorActivity:$,unpricedTurns:F}=D$(h,{since:r});$&&(i=!0),c+=F;let T={trackId:y?.id,name:y?.name||y?.id,archived:!!y?.archivedAt,usd:0,activeMs:0,turns:0,categories:{},models:{},providers:{},handoffs:I,subtasksMerged:M,subtasksFailed:z};for(let[C,D]of S){T.usd+=D.usd,T.activeMs+=D.activeMs,T.turns+=D.turns,T.categories[C]={usd:xn(D.usd),activeMs:D.activeMs};for(let[A,g]of Object.entries(D.models))T.models[A]=xn((T.models[A]||0)+g);for(let[A,g]of Object.entries(D.providers))T.providers[A]=xn((T.providers[A]||0)+g);o.has(C)||o.set(C,B$());let it=o.get(C);it.usd+=D.usd,it.activeMs+=D.activeMs,it.turns+=D.turns,it.toolCalls+=D.toolCalls;for(let[A,g]of Object.entries(D.models))it.models[A]=(it.models[A]||0)+g;for(let[A,g]of Object.entries(D.providers))it.providers[A]=(it.providers[A]||0)+g;let ot=it.byTrack.get(y?.id)||{usd:0,activeMs:0};ot.usd+=D.usd,ot.activeMs+=D.activeMs,it.byTrack.set(y?.id,ot)}(T.usd>0||T.activeMs>0||T.turns>0)&&(T.usd=xn(T.usd),T.dominantCategory=j$(T.categories),s.push(T))}let l=[...o.values()].reduce((h,y)=>h+y.usd,0),u=[...o.values()].reduce((h,y)=>h+y.activeMs,0),f=l>0?"usd":"time",d=f==="usd"?l:u,p=new Map((t||[]).map(h=>[h.track?.id,h.track?.name||h.track?.id])),m=[...o.entries()].map(([h,y])=>({category:h,sharePct:d>0?Math.round((f==="usd"?y.usd:y.activeMs)/d*100):0,shareBasis:f,usd:xn(y.usd),activeMs:y.activeMs,turns:y.turns,toolCalls:y.toolCalls,models:Eb(y.models),providers:Eb(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:xn(S.usd),activeMs:S.activeMs}))})).sort((h,y)=>y.usd-h.usd||y.activeMs-h.activeMs);return s.sort((h,y)=>y.usd-h.usd||y.activeMs-h.activeMs),{since:r,capturedAt:n,totalUsd:xn(l),activeMs:u,turns:[...o.values()].reduce((h,y)=>h+y.turns,0),bullets:m,workstreams:s,gaps:{tracksWithoutEvents:a,cursorCostUnknown:i,unpricedTurns:c}}}var j$=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},Eb=t=>Object.fromEntries(Object.entries(t).map(([e,n])=>[e,xn(n)]));var to={"24h":24*36e5,"7d":168*36e5,"30d":720*36e5,all:null};function U$(t){let e=String(t||"7d");return e in to?{key:e,ms:to[e]}:{key:"7d",ms:to["7d"]}}function H$(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=Ua(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 W$(t){if(!t?.cwd)return null;try{let n=(await L$.readFile(F$.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 Ib(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}:U$(e),f=[];for(let d of t||[]){let p=await L(d),m=H$(d,p);i&&c<o&&(m=m.filter(h=>(h.ts??0)<=c)),f.push({track:d,events:m,brief:await W$(d)})}return{window:l,..._b(f,{windowMs:u,now:c})}}var Sn=36e5,Cb=24*Sn,q$=400,K$=12,eo=t=>Math.round(t*1e4)/1e4;function G$({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 to?String(t||"7d"):"7d",i=to[a];return{key:a,since:i==null?null:r-i,until:r}}var V$=(t,e)=>t!=null&&e-t<=48*Sn?Sn:Cb;function Rb(t,e){if(e===Sn)return Math.floor(t/Sn)*Sn;let n=new Date(t);return n.setHours(0,0,0,0),n.getTime()}function z$(t,e){if(e===Sn)return t+Sn;let n=new Date(t);return n.setDate(n.getDate()+1),n.setHours(0,0,0,0),n.getTime()}var $b=()=>({input:0,output:0,cacheRead:0,cacheCreation:0}),Ob=(t,e)=>{for(let n of Object.keys(t))t[n]+=e?.[n]||0},J$=t=>Object.fromEntries(Object.entries(t).map(([e,n])=>[e,eo(n)]));async function Mb(t,e={}){let n=e.now??Date.now(),r=G$({...e,now:n}),o=r.since==null?Cb:V$(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=$b(),f=0,d=0,p=0,m=0,h=0,y=new Set,b=new Map,S=[],I=[],M=[],z=null;for(let T of t||[]){let D=(await L(T)).filter(A=>A.type==="cost.recorded");if(!D.length){T.archivedAt||M.push(T.name||T.id);continue}let it=[];for(let A of D){let g=A.ts??0;s(g)?it.push(A):i(g)&&(h+=da(A).usd)}if(!it.length)continue;let ot=ue(it,{boxLabels:dd(T)});f+=ot.totalUsd,d+=ot.turns,p+=ot.repricedTurns||0,m+=ot.unpricedTurns||0;for(let A of ot.unpricedModels||[])y.add(A);Ob(u,ot.totalTokens);for(let[A,g]of Object.entries(ot.byModel))c[A]||={usd:0,events:0,tokens:$b()},c[A].usd=eo(c[A].usd+g.usd),c[A].events+=g.events,Ob(c[A].tokens,g.tokens);for(let[A,g]of Object.entries(ot.byProvider))l[A]||={usd:0,events:0},l[A].usd=eo(l[A].usd+g.usd),l[A].events+=g.events;for(let A of it){let{usd:g}=da(A);if(!g)continue;let w=Rb(A.ts??0,o),_=b.get(w)??{t:w,total:0,byProvider:{}};_.total+=g;let v=A.provider||"unknown";_.byProvider[v]=(_.byProvider[v]||0)+g,b.set(w,_),(z==null||w<z)&&(z=w)}S.push({id:T.id,name:T.name||T.id,archived:!!T.archivedAt,usd:ot.totalUsd,turns:ot.turns,providers:Object.keys(ot.byProvider)});for(let[A,g]of Object.entries(ot.byBox))I.push({trackId:T.id,trackName:T.name||T.id,boxId:A,...g})}let $=r.since!=null?Rb(r.since,o):z,F=[];for(let T=$,C=0;T!=null&&T<r.until&&C<q$;T=z$(T,o),C++){let D=b.get(T);F.push(D?{t:T,total:eo(D.total),byProvider:J$(D.byProvider)}:{t:T,total:0,byProvider:{}})}return S.sort((T,C)=>C.usd-T.usd),I.sort((T,C)=>C.usd-T.usd),{generatedAt:new Date().toISOString(),pricingBasis:"api-equivalent (subscription usage priced at public API rates)",window:r.key,since:r.since,until:r.until,bucketMs:o,series:F,fleet:{totalUsd:eo(f),turns:d,repricedTurns:p,unpricedTurns:m,unpricedModels:[...y],totalTokens:u,byModel:c,byProvider:l,tracksWithCost:S.length,tracksSilentLive:M},prior:a!=null?{totalUsd:eo(h),since:r.since-a,until:r.since}:null,tracks:S.slice(0,50),boxes:I.slice(0,K$)}}import no from"node:fs/promises";import X$ from"node:fs";import Rt from"node:path";import Y$ from"node:os";import{execFile as Z$}from"node:child_process";import{promisify as Q$}from"node:util";var tO=Q$(Z$),Nb=process.env.BOSTRAT_STATE_DIR||Rt.join(process.env.HOME||Y$.tmpdir(),".bostrat"),yi=!1,fd=null;async function pd(t,e){let n=await no.stat(t).catch(()=>null);if(!n?.isFile())return!1;let r=await no.stat(e).catch(()=>null);return r&&r.size===n.size&&r.mtimeMs>=n.mtimeMs?!1:(await no.mkdir(Rt.dirname(e),{recursive:!0}),await no.copyFile(t,e),!0)}var eO=new Set([".claude",".codex",".credentials.json","auth.json",".claude.json","integrations.json"]);async function bi(t,e,{skip:n=null}={}){let r=0,o=await no.readdir(t,{withFileTypes:!0}).catch(()=>[]);for(let s of o){if(eO.has(s.name)||n?.has(s.name))continue;let a=Rt.join(t,s.name);s.isDirectory()?r+=await bi(a,Rt.join(e,s.name)):s.isFile()&&(r+=await pd(a,Rt.join(e,s.name))?1:0)}return r}async function Pb({stateDir:t=Nb,dest:e=process.env.BOSTRAT_SYNC_DIR||`${Nb}-sync`,remote:n=process.env.BOSTRAT_STATE_REMOTE||null,tracks:r=null}={}){try{await no.mkdir(e,{recursive:!0});let o=0;o+=await pd(Rt.join(t,"tracks.json"),Rt.join(e,"tracks.json"))?1:0,o+=await pd(Rt.join(t,"events.jsonl"),Rt.join(e,"events.jsonl"))?1:0,o+=await bi(Rt.join(t,"events"),Rt.join(e,"events")),o+=await bi(Rt.join(t,"memory"),Rt.join(e,"memory"));let s=new Set(["mocks"]);for(let u of r||[])!u?.cwd||!u?.id||(o+=await bi(Rt.join(u.cwd,".bostrat"),Rt.join(e,"workspaces",u.id,".bostrat"),{skip:s}));let a=(...u)=>tO("git",["-C",e,...u]);X$.existsSync(Rt.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 Ki(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||yi))try{await a("push","-q",n,"HEAD:refs/heads/main"),l=!0,yi&&console.log("[state-sync] push recovered \u2014 off-box copy is current again"),yi=!1,fd=null}catch(u){yi=!0;let f=(u?.stderr||u?.message||String(u)).trim().split(`
|
|
392
|
+
`).slice(-3).join(" | ").slice(0,300);f!==fd&&(fd=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 nO}from"node:child_process";import{promisify as rO}from"node:util";var oO=rO(nO),sO=12e4,aO=6e4;function iO(t){return`refs/bostrat/wip/${t}`}function jb(t=process.env){let e=String(t.BOSTRAT_WIP_SYNC_MS??"").trim().toLowerCase();return e!=="0"&&e!=="off"&&e!=="false"}function cO(t=process.env){let e=Number(t.BOSTRAT_WIP_SYNC_MS);return Number.isFinite(e)&&e>0?e:sO}var ki=(t,e,n=15e3)=>oO("git",["-C",t,...e],{timeout:n,maxBuffer:4*1024*1024}),Bb=new Map,Db=new Set;async function lO(t,{log:e=console}={}){let{id:n,cwd:r}=t??{};if(!n||!r)return{pushed:!1,reason:"no-workspace"};try{await ki(r,["rev-parse","--git-dir"])}catch{return{pushed:!1,reason:"not-a-repo"}}try{await ki(r,["remote","get-url","origin"])}catch{return Db.has(r)||(Db.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 Jo(r,"bostrat: wip snapshot"),{stdout:s}=await ki(r,["rev-parse",`${o}^{tree}`]),a=s.trim();if(Bb.get(r)===a)return{pushed:!1,reason:"unchanged"};let i=iO(n);return await ki(r,["push","--force","origin",`${o}:${i}`],aO),Bb.set(r,a),await R({id:n,cwd:r},{type:"code.pushed",actor:"orchestrator",payload:{ref:i,sha:o,wip:!0,ts:Date.now()}}).catch(()=>{}),e.log?.(`[wip-sync] ${n}: pushed ${o.slice(0,7)} \u2192 ${i}`),{pushed:!0,sha:o,ref:i}}catch(o){return e.log?.(`[wip-sync] ${n}: push failed (will retry): ${String(o?.message||o).split(`
|
|
393
|
+
`)[0]}`),{pushed:!1,reason:"push-failed"}}}function Lb({listTracks:t,sweepMs:e=cO(),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 lO(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 uO}from"node:child_process";import{promisify as dO}from"node:util";import{randomUUID as fO}from"node:crypto";import pO from"node:fs";import mO from"node:os";import hO from"node:path";var Tn=dO(uO),xi=t=>new Promise(e=>setTimeout(e,t)),An=ac,md=process.env.BOSTRAT_USAGE_PROBE_CWD||hO.join(mO.homedir(),".bostrat","usage-probe"),wO=Number(process.env.BOSTRAT_USAGE_IDLE_REAP_MS)||5*6e4,gO=Number(process.env.BOSTRAT_USAGE_PANEL_TIMEOUT_MS)||15e3,yO=Number(process.env.BOSTRAT_USAGE_READY_TIMEOUT_MS)||45e3,Fb=Number(process.env.BOSTRAT_USAGE_CACHE_MS)||2e4,vn=null,ys=null,bs=null;async function bO(){try{return await Tn(P,B("has-session","-t",An)),!0}catch{return!1}}function kO(){vn&&clearTimeout(vn),vn=setTimeout(()=>xO().catch(()=>{}),wO),vn.unref&&vn.unref()}async function xO(){vn&&(clearTimeout(vn),vn=null);try{await Tn(P,B("kill-session","-t",An))}catch{}}async function SO(){let t=Date.now()+yO;for(;;){let e=await hd().catch(()=>"");if(/for agents|for shortcuts|bypass permissions on/i.test(e)){await xi(500);return}if(Date.now()>t)throw new Error("usage probe didn't reach the prompt (trust/onboarding may be blocking)");await xi(500)}}async function vO(){if(await bO())return;if(!await Ye())throw new Error("host tmux server not running \u2014 start globo-tmux.service");pO.mkdirSync(md,{recursive:!0}),await Ge(md);let t=fO();await Tn(P,B("new-session","-d","-s",An,"-c",md,"-x","200","-y","60",...sc()));let e=`${oc} --session-id ${t}`;await Tn(P,B("send-keys","-t",An,e,"Enter")),await SO()}async function hd(){let{stdout:t}=await Tn(P,B("capture-pane","-t",An,"-p"));return t}async function TO(){await vO(),await Tn(P,B("send-keys","-t",An,"-l","/usage")),await Tn(P,B("send-keys","-t",An,"Enter"));let t=Date.now()+gO,e="";for(;await xi(400),e=await hd(),!(/Current\s+(session|week)/i.test(e)||/\d+%\s*used/i.test(e)||Date.now()>t););return await xi(900),e=await hd(),await Tn(P,B("send-keys","-t",An,"Escape")).catch(()=>{}),kO(),e}function AO(t){let e=String(t||"").split(`
|
|
394
|
+
`).map(s=>s.replace(/\s+$/,"").trim()),n=s=>{let a=null,i=null;for(let c=s+1;c<Math.min(s+5,e.length)&&!/^Current\s/i.test(e[c]);c++){let l=e[c].match(/(\d+)%\s*used/i);l&&a===null&&(a=Number(l[1]));let u=e[c].match(/^Resets\s+(.+)$/i);u&&i===null&&(i=u[1].trim())}return a===null?null:{pct:a,resets:i||null}},r=s=>{let a=e.findIndex(i=>s.test(i));return a<0?null:n(a)},o=[];return e.forEach((s,a)=>{let i=s.match(/^Current week \(([^)]+)\)$/i);if(!i||/all models/i.test(i[1]))return;let c=n(a);c&&o.push({model:i[1].trim(),...c})}),{session:r(/^Current session\b/i),week:r(/^Current week \(all models\)/i)||r(/^Current week$/i),weekModels:o.length?o:null}}function EO(t){let e=String(t||"").split(`
|
|
395
|
+
`).map(s=>s.replace(/\s+$/,"")),n=e.findIndex(s=>/What's contributing/i.test(s));if(n<0)return null;let r=/(Usage credits|Esc to cancel|d to day · w to week|^\s*d to day)/i,o=[];for(let s=n+1;s<e.length&&!r.test(e[s]);s++)e[s].trim()&&o.push(e[s].trim());return o.length?o:null}async function Ub({fresh:t=!1}={}){return!t&&ys&&Date.now()-ys.at<Fb?ys.data:bs||(bs=(async()=>{try{let e=await TO(),n=AO(e),r={...n,contributors:EO(e),subscription:!!(n.session||n.week||n.weekModels),capturedAt:Date.now()};return ys={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 ys={at:Date.now()-(Fb-3e3),data:n},n}finally{bs=null}})(),bs)}var _O=262144;function IO(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 Hb(t){if(!t||typeof t!="object")return null;let e=Number(t.used_percent);return Number.isFinite(e)?{pct:Math.round(e),resets:IO(t.resets_in_seconds)}:null}function RO(t){let e=String(t||"").split(`
|
|
396
|
+
`),n=!1;for(let r=e.length-1;r>=0;r--){let o=e[r].trim();if(!o||o[0]!=="{"||!o.includes("token_count"))continue;let s;try{s=JSON.parse(o)}catch{continue}let a=s?.payload;if(a?.type!=="token_count")continue;n=!0;let i=a.rate_limits;if(!i)return{session:null,week:null,sawTokenCount:n};if(i.primary||i.secondary)return{session:Hb(i.primary),week:Hb(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 wd(t){let e={provider:"codex",supported:!0,contributors:null};try{let n=await qn(t?.cwd,t?.sessionId);if(!n)return{...e,session:null,week:null,subscription:!1,error:"no codex rollout found for this box yet",capturedAt:Date.now()};let r=await Pt(n.file,_O),o=RO(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 Wb(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 Si({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 wd(i);if(c.subscription){a=c;break}(!a||a.error&&!c.error)&&(a=c)}r.push(a||await wd(null))}return n.has("cursor")&&r.push(await Si({agent:"cursor"})),{providers:r,capturedAt:Date.now()}}async function Si(t,{fresh:e=!1}={}){let n=t?.agent||"claude";return n==="codex"?wd(t):n==="cursor"?{provider:"cursor",supported:!1,subscription:!1,session:null,week:null,contributors:null,capturedAt:Date.now()}:{provider:"claude",supported:!0,...await Ub({fresh:e})}}function $O(t=process.env){return!!(zt(t)&&t.BOSTRAT_PLANE_TOKEN)}var OO=t=>({authorization:`Bearer ${t.BOSTRAT_PLANE_TOKEN||""}`}),vi=(t,e)=>Object.assign(new Error(t),{status:e});async function qb(t,e){if(!$O(e))throw vi("node is not plane-linked \u2014 Gmail unavailable",503);let n;try{n=await fetch(`${zt(e)}${t}`,{headers:OO(e)})}catch(o){throw vi(`plane unreachable \u2014 Gmail unavailable (${o?.message||o})`,503)}let r=await n.json().catch(()=>({}));if(!n.ok)throw vi(r?.error||`plane replied ${n.status}`,n.status);return r}async function Kb(t,{max:e=10,env:n=process.env}={}){let r=new URLSearchParams({q:t||""});return e&&r.set("max",String(e)),(await qb(`/v1/gmail/messages?${r.toString()}`,n)).messages||[]}async function Gb(t,{env:e=process.env}={}){if(!t)throw vi("message id required",400);return(await qb(`/v1/gmail/messages/${encodeURIComponent(t)}`,e)).message}function gd(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 CO(t){let[e,n]=t.split("/"),r=gd(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 Vb(t){return(t||"").split(",").map(e=>e.trim()).filter(Boolean).map(CO).filter(Boolean)}function MO(t){let e=t.headers["fly-client-ip"];return typeof e=="string"&&e.trim()?e.trim():(t.socket&&t.socket.remoteAddress||"").replace(/^::ffff:/,"")}function zb(t){let e=Vb(t),n=String(t||"").trim().toLowerCase()==="open";return{open:n,clientIp:MO,allowed(r){if(n)return!0;let o=gd((r||"").replace(/^::ffff:/,""));return o==null?!1:e.some(s=>s(o))}}}function Jb(t,e){let n=Vb(t);return{active:n.length>0,allows(r){if(!n.length)return!1;let o=(r.socket&&r.socket.remoteAddress||"").replace(/^::ffff:/,""),s=gd(o);if(s==null||!n.some(i=>i(s)))return!1;let a=e(r);return!!(a&&a.view)}}}import Xb from"node:crypto";import Ti from"node:fs";import NO from"node:path";function Yb(t=process.env,{stateDir:e=wb,log:n=console}={}){let r=String(t.BOSTRAT_NODE_API_TOKEN||"").trim();if(r)return r;let o=NO.join(e,"api-token");try{let a=Ti.readFileSync(o,"utf8").trim();if(a)return a}catch{}let s=Xb.randomBytes(32).toString("base64url");try{Ti.mkdirSync(e,{recursive:!0}),Ti.writeFileSync(o,s+`
|
|
397
|
+
`,{mode:384}),Ti.chmodSync(o,384)}catch(a){n.error?.(`[bostrat-agent] could not persist the node API token: ${a?.message||a}`)}return s}function ks(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 Zb(t,e){if(!t||!e)return!1;let n=Buffer.from(String(t)),r=Buffer.from(String(e));return n.length!==r.length?!1:Xb.timingSafeEqual(n,r)}function Qb(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 PO=[/^\/api\/github\/token\b/],BO=t=>PO.some(e=>e.test(t));function tk({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=Qb(o);return Zb(c,t)?a():BO(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||ks(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 ek(t,e,{enforce:n=!0}={}){return!n||Zb(Qb(t),e)?!0:ks(t)}import DO from"node:http";import jO from"node:net";function nk(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 Ai(t){let e=nk(t.headers.host);return e?{slug:e,view:Lc(e)}:null}var LO=t=>`<!doctype html><meta charset="utf-8">
|
|
400
398
|
<meta http-equiv="refresh" content="3">
|
|
401
399
|
<title>${t.name} \u2014 starting</title>
|
|
402
400
|
<body style="font-family:system-ui;background:#0b0e14;color:#dce3f0;display:grid;place-items:center;height:100vh;margin:0">
|
|
403
401
|
<div style="text-align:center"><p style="font-size:2rem;margin:0">\u23F3</p>
|
|
404
|
-
<p><b>${t.name}</b> is waking up\u2026</p><p style="opacity:.6">auto-refreshing</p></div>`;function
|
|
402
|
+
<p><b>${t.name}</b> is waking up\u2026</p><p style="opacity:.6">auto-refreshing</p></div>`;function rk(t,e,n){let r=Ai(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(LO(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=DO.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 ok(t,e,n){let r=Ai(t);if(!r)return!1;let{view:o,slug:s}=r;if(!o||o.state!=="live"&&o.state!=="unhealthy")return e.write(`HTTP/1.1 404 Not Found\r
|
|
405
403
|
\r
|
|
406
404
|
bostrat: no live view "${s}"\r
|
|
407
|
-
`),e.destroy(),!0;let a=
|
|
405
|
+
`),e.destroy(),!0;let a=jO.connect(o.port,"127.0.0.1",()=>{let c=[`${t.method} ${t.url} HTTP/1.1`];for(let[l,u]of Object.entries(t.headers))l!=="host"&&c.push(`${l}: ${Array.isArray(u)?u.join(", "):u}`);c.push(`host: 127.0.0.1:${o.port}`),a.write(c.join(`\r
|
|
408
406
|
`)+`\r
|
|
409
407
|
\r
|
|
410
|
-
`),n?.length&&a.write(n),e.pipe(a),a.pipe(e)}),i=()=>{a.destroy(),e.destroy()};return a.on("error",i),e.on("error",i),!0}function
|
|
411
|
-
`)){let s=o.trim();if(!s)continue;if(s.startsWith("#")){n==null&&(n=s.replace(/^#+\s*/,"").trim().slice(0,BO)||null);continue}let a=/^[-*]\s+(.*)$/.exec(s);if(!a)continue;let i=a[1].trim(),c=null,l=new RegExp(`^\\[([^\\]]{1,${DO}})\\]\\s+(.*)$`).exec(i);if(l&&(c=l[1].trim(),i=l[2].trim()),!!i){if(i.length>ok)throw new Error(`bullet ${r.length+1} is ${i.length} chars \u2014 over the ${ok}-char cap; a changelog bullet is one line`);r.push({text:i,...c?{area:c}:{}})}}if(!r.length)throw new Error("no `- ` bullets found \u2014 an entry is a bullet list, optionally under a `# headline`");if(r.length>rk)throw new Error(`${r.length} bullets \u2014 over the ${rk}-bullet cap; group related changes into one line`);return{title:n||null,bullets:r}}async function ak(t,{repos:e=hd()}={}){let n=new Set(e);return(await ye()).filter(r=>r.state==="merged"&&(r.resolvedAt??0)>=t&&n.has(r.repo)).sort((r,o)=>(r.resolvedAt??0)-(o.resolvedAt??0)).map(r=>({repo:r.repo,number:r.number,title:r.title||null,url:r.url||null,mergedAt:r.resolvedAt??null}))}async function ik(t,{md:e,day:n=null}){let r=FO(e),o=n==null||n===""?LO():String(n);if(!jO.test(o)||Number.isNaN(Date.parse(o)))throw new Error(`invalid day "${n}" \u2014 expected YYYY-MM-DD`);let s=await R({id:t.id,cwd:t.cwd},{type:"changelog.published",actor:"agent",source:"orchestrator",boxId:t.leadBox||null,payload:{ts:Date.now(),day:o,title:r.title,bullets:r.bullets}});return{day:o,title:r.title,bullets:r.bullets,eventId:s.id}}import{execFile as UO}from"node:child_process";import{promisify as HO}from"node:util";var gd=HO(UO),ck=3e4,WO=30*6e4,qO=ck,KO=10*6e4,GO=10080*6e4,VO=60*6e4,zO=8,JO=1440*6e4,XO=3,lk="number,url,title,headRefName,headRefOid,state,mergedAt,closedAt",wd=60*6e4,YO=15*6e4,vi=new Map,Ti=new Map,ZO=/could not resolve|not found|no pull requests?/i,QO=t=>ZO.test(String(t?.message||t||""));function tC(t,e){return[t.ciFix?.delegatedAt,t.reconcile?.delegatedAt,t.ciFixAwait?.since,t.reconcileAwait?.since].some(n=>n&&e-n<wd)}var uk={fast:qO,slow:KO,decay:VO};function dk(t,e){return t.armedAt&&e-t.armedAt<WO||tC(t,e)?"fast":e-(t.firstSeen||0)>GO?"decay":"slow"}function eC(t,e){return uk[dk(t,e)]}function ks(t,e,n,r){let o=t.lanes?.get(H(e.repo,e.number))||{};return Ja({change:n,repo:e.repo,number:Number(e.number),githubAt:r||null,lane:o.lane||null,intervalMs:o.intervalMs||null,source:"watch"})}async function nC(t,e){Le("watch");try{let{stdout:n}=await gd("gh",["pr","view",String(e),"--repo",t,"--json",lk],{timeout:3e4,maxBuffer:4194304,env:{...process.env,...await ae({slug:t})}});return JSON.parse(n||"null")}catch(n){throw fn(n)&&pn(n),n}}async function rC(t){Le("watch");try{let{stdout:e}=await gd("gh",["pr","list","--repo",t,"--state","all","--limit","100","--json",lk],{timeout:3e4,maxBuffer:8388608,env:{...process.env,...await ae({slug:t})}}),n=new Map;for(let r of JSON.parse(e||"[]"))n.set(Number(r.number),r);return n}catch(e){throw fn(e)&&pn(e),e}}async function oC(){let t=new Map;for(let e of await ot().catch(()=>[]))t.set(e.id,e);return t}async function sC(t,e){await R({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"user",source:"orchestrator",boxId:null,payload:{ts:Date.now(),change:"merged",...e}})}async function aC(t,e){let n=await Jn(t);return n?Lr(n,e):null}async function iC(t,e,n){let r=await Jn(t);if(!r)throw new Error(`no local checkout for ${t}`);return Fr(r,e,{method:n})}async function cC(t,e){let n=await Jn(t);if(!n)throw new Error(`no local checkout for ${t}`);return Qo(n,e)}async function lC(t,e){await R({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"user",source:"orchestrator",boxId:null,payload:{ts:Date.now(),change:"auto-canceled",...e}})}async function eo(t,e,n,r,o){if(!await r.disarm(t.repo,t.number).catch(()=>null))return;let a=n.get(t.trackId);a&&await r.emitDisarmed(a,{number:Number(t.number),url:e?.url||t.url,title:e?.title||t.title,branch:e?.headRefName||t.headRef,repo:t.repo,reason:o}).catch(()=>{})}var uC=10*6e4;async function dC(t,e,n){let r=t.autoMerge?.armedAt||0;if(r&&(n.now??Date.now())-r>JO){await eo(t,null,e,n,"auto-merge expired \u2014 checks never settled within 24h");return}let o=await n.detail(t.repo,t.number).catch(()=>null);if(!o)return;if(o.checks.failed>0){let a=o.checks.failing.map(i=>i.name).join(", ");await eo(t,o,e,n,`check${o.checks.failed===1?"":"s"} failed: ${a}`);return}if(o.mergeable==="CONFLICTING"){await eo(t,o,e,n,`conflicts with ${o.baseRefName}`);return}if(!o.checks.total){let a=Math.max(t.autoMerge?.armedAt||0,Date.parse(o.updatedAt||"")||0);if((n.now??Date.now())-a<uC)return;await eo(t,o,e,n,o.checks.unavailable?"this token can't read the checks":"no CI checks appeared");return}if(o.checks.pending>0||o.mergeable==="UNKNOWN"||o.mergeStateStatus==="UNKNOWN"||o.isDraft)return;let s=await n.lookup(t.repo,t.number).catch(()=>null);if(s?.autoMerge){if(o.mergeStateStatus==="BEHIND"){try{await n.updateBranch(t.repo,t.number)}catch(a){let i=String(a?.message||a).trim();/no new commits|already up.to.date/i.test(i)||await eo(t,o,e,n,i||`couldn't update with ${o.baseRefName}`)}return}try{await n.merge(t.repo,t.number,s.autoMerge.method||"merge")}catch(a){if(/already merged|not open|is closed/i.test(String(a?.message||a)))return;await eo(t,o,e,n,String(a?.message||a).trim()||"merge failed");return}await fk(t,{...o,state:"MERGED"},e,n,{change:"auto-merged",githubAt:o.checks.settledAt||null})}}async function gs(t,e,n){await R({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"user",source:"orchestrator",boxId:null,payload:{ts:Date.now(),change:e,...n}})}async function fC(t,e){try{return await gd("git",["-C",t.cwd,"cat-file","-e",`${e}^{commit}`],{timeout:1e4}),!0}catch{return!1}}async function ys(t,e,n,r=()=>!0){return(await D(t).catch(()=>[])).some(s=>s.type==="pr.state.changed"&&s.payload?.change===e&&s.payload?.number===Number(n)&&r(s))}var bs=(t,e)=>({number:Number(t.number),url:e?.url||t.url,title:e?.title||t.title,branch:e?.headRefName||t.headRef,repo:t.repo});async function pC(t,e,n,r){let o=r.now??Date.now(),s=e.headRefOid||null,a=n.get(t.trackId)||null;if(s&&(t.ciFix?.headSha&&s!==t.ciFix.headSha&&await Aa(t.repo,t.number,{headSha:s}).catch(()=>{}),t.reconcile?.headSha&&s!==t.reconcile.headSha&&await _a(t.repo,t.number,{headSha:s}).catch(()=>{})),s&&s!==t.lastHeadSha){let i=!t.lastHeadSha,c=!!(t.ciFix||t.reconcile||t.ciFixAwait||t.reconcileAwait),l=!!t.baseFoldAt&&o-t.baseFoldAt<YO;await dh(t.repo,t.number,s).catch(()=>{}),t.baseFoldAt&&await uh(t.repo,t.number).catch(()=>{}),!i&&!c&&!l&&a&&!await r.localHasCommit(a,s).catch(()=>!1)&&!await ys(a,"pushed",t.number,f=>f.payload?.sha===s)&&await gs(a,"pushed",{...bs(t,e),sha:s}).catch(()=>{})}if(t.ciFixAwait)if(o-(t.ciFixAwait.since||0)>wd)await Ea(t.repo,t.number).catch(()=>{});else{let i=await r.detail(t.repo,t.number).catch(()=>null);if(i?.checks){if(i.checks.failed>0)await Ea(t.repo,t.number).catch(()=>{});else if(i.checks.total>0&&i.checks.pending===0){let c=ks(r,t,"ci-fixed",i.checks.settledAt||null),l=t.ciFixAwait.since||0;a&&!await ys(a,"ci-fixed",t.number,u=>(u.payload?.ts||0)>=l)&&await gs(a,"ci-fixed",{...bs(t,e),passed:i.checks.passed,githubAt:c.githubAt,latencyMs:c.latencyMs,lane:c.lane}).catch(()=>{}),await Ea(t.repo,t.number).catch(()=>{})}}}if(t.reconcileAwait)if(o-(t.reconcileAwait.since||0)>wd)await Ia(t.repo,t.number).catch(()=>{});else{let i=await r.detail(t.repo,t.number).catch(()=>null);if(i){if(i.mergeable==="CONFLICTING")await Ia(t.repo,t.number).catch(()=>{});else if(i.mergeable==="MERGEABLE"){ks(r,t,"reconciled",null);let c=t.reconcileAwait.since||0;a&&!await ys(a,"reconciled",t.number,l=>(l.payload?.ts||0)>=c)&&await gs(a,"reconciled",{...bs(t,e),base:i.baseRefName||null}).catch(()=>{}),await Ia(t.repo,t.number).catch(()=>{})}}}}async function mC(t,e,n,r){let o=ks(r,t,"closed",Date.parse(e?.closedAt||"")||null),s=n.get(t.trackId);s&&!await ys(s,"closed",t.number)&&await gs(s,"closed",{...bs(t,e),githubAt:o.githubAt,latencyMs:o.latencyMs,lane:o.lane}).catch(()=>{}),await r.notifyIfResolved(t.repo,t.number,"closed").catch(()=>{}),await r.mark(t.repo,t.number,"closed").catch(()=>{})}async function hC(t,e,n){ks(n,t,"lost",null);let r=e.get(t.trackId);r&&!await ys(r,"lost",t.number)&&await gs(r,"lost",bs(t,null)).catch(()=>{}),await n.notifyIfResolved(t.repo,t.number,"lost").catch(()=>{}),await n.mark(t.repo,t.number,"lost").catch(()=>{})}async function fk(t,e,n,r,o={}){let s=ks(r,t,o.change||"merged",o.githubAt??(Date.parse(e.mergedAt||"")||null)),a=n.get(t.trackId);a&&((await D(a).catch(()=>[])).some(l=>l.type==="pr.state.changed"&&l.payload?.change==="merged"&&l.payload?.number===Number(t.number))||await r.emitMerged(a,{number:Number(t.number),url:e.url||t.url,title:e.title||t.title,branch:e.headRefName||t.headRef,repo:t.repo,githubAt:s.githubAt,latencyMs:s.latencyMs,lane:s.lane}).catch(()=>{})),await r.notifyIfArmed(t.repo,t.number).catch(()=>{}),await r.archiveIfArmed(t.repo,t.number).catch(()=>{}),await r.mark(t.repo,t.number,"merged").catch(()=>{})}async function wC(t={}){let{now:e=Date.now(),auth:n=hu,view:r=nC,list:o=rC,links:s=ph,trackMap:a=oC,emitMerged:i=sC,mark:c=cn,archiveIfArmed:l=hn,notifyIfArmed:u=wn,notifyIfResolved:f=Wu,batchThreshold:d=XO,detail:p=aC,merge:m=iC,updateBranch:g=cC,lookup:y=$t,disarm:b=jo,emitDisarmed:S=lC,localHasCommit:E=fC}=t;if(!(await n().catch(()=>null))?.authenticated||Ha()>0)return 0;let $=(await s().catch(()=>[])).filter(w=>e-(vi.get(H(w.repo,w.number))||0)>=eC(w,e));if(!$.length)return 0;let j=Date.now(),v=new Map,C=new Map;for(let w of $){let h=dk(w,e);v.set(H(w.repo,w.number),{lane:h,intervalMs:uk[h]}),vi.set(H(w.repo,w.number),e),C.has(w.repo)||C.set(w.repo,[]),C.get(w.repo).push(w)}let q=await a().catch(()=>new Map),rt={now:e,lanes:v,emitMerged:i,mark:c,archiveIfArmed:l,notifyIfArmed:u,notifyIfResolved:f,detail:p,merge:m,updateBranch:g,lookup:y,disarm:b,emitDisarmed:S,localHasCommit:E};for(let[w,h]of C){let T,_=new Set;if(h.length>d)T=await o(w).catch(()=>null);else{T=new Map;for(let O of h)try{let I=await r(w,O.number);I?.number!=null&&T.set(Number(I.number),I)}catch(I){QO(I)&&_.add(H(O.repo,O.number))}}if(T)for(let O of h){let I=H(O.repo,O.number),A=T.get(Number(O.number));if(!A){if(_.has(I)){let G=(Ti.get(I)||0)+1;Ti.set(I,G),G>=zO&&(await hC(O,q,rt),Ti.delete(I),vi.delete(I))}continue}Ti.delete(I),A.state==="MERGED"||A.state==="CLOSED"?(A.state==="MERGED"?await fk(O,A,q,rt):await mC(O,A,q,rt),vi.delete(I)):(await pC(O,A,q,rt).catch(()=>{}),O.autoMerge&&await dC(O,q,rt).catch(()=>{}))}}let st={};for(let{lane:w}of v.values())st[w]=(st[w]||0)+1;return console.log(`[pr-watch] probed ${$.length} link(s) (${Object.entries(st).map(([w,h])=>`${w} ${h}`).join(", ")}) in ${Date.now()-j}ms`),$.length}function pk(){let t=!1;setInterval(async()=>{if(!t){t=!0;try{await wC()}catch{}finally{t=!1}}},ck)}import Ke from"node:fs/promises";import no from"node:path";import kk from"node:os";import mk from"node:fs/promises";import Ai from"node:path";import{execFile as gC}from"node:child_process";import{promisify as yC}from"node:util";import{fileURLToPath as bC}from"node:url";var kC=yC(gC),wk=Ai.dirname(bC(import.meta.url)),xC=Ai.resolve(wk,"..","..","client","dist"),SC="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",vC=new Map([...SC].map((t,e)=>[t,e]));function TC(t){let e=[],n=0,r=0;for(let o of t){let s=vC.get(o);if(s===void 0)return e;n+=(s&31)<<r,s&32?r+=5:(e.push(n&1?-(n>>>1):n>>>1),n=0,r=0)}return e}function AC(t){let e=[],n=0,r=0,o=0;for(let s of t.split(";")){let a=[],i=0;if(s)for(let c of s.split(",")){let l=TC(c);l.length!==0&&(i+=l[0],l.length>=4&&(n+=l[1],r+=l[2],o+=l[3],a.push([i,n,r,o])))}e.push(a)}return e}function EC(t,e){if(!t||t.length===0||t[0][0]>e)return null;let n=0,r=t.length-1;for(;n<r;){let o=n+r+1>>1;t[o][0]<=e?n=o:r=o-1}return t[n]}var hk=new Map;async function _C(t){let e;try{e=await mk.stat(t)}catch{return null}let n=hk.get(t);if(n&&n.mtimeMs===e.mtimeMs)return n;try{let r=JSON.parse(await mk.readFile(t,"utf8")),o={mtimeMs:e.mtimeMs,sources:r.sources||[],lines:AC(r.mappings||"")};return hk.set(t,o),o}catch{return null}}var yd=null;function IC(){return yd||(yd=process.env.BOSTRAT_RELEASE?Promise.resolve(process.env.BOSTRAT_RELEASE):kC("git",["rev-parse","--short","HEAD"],{cwd:Ai.resolve(wk,"..","..")}).then(t=>t.stdout.trim()).catch(()=>null)),yd}var RC=/(?:https?:\/\/[^\s()]*)?\/assets\/([^\s()/]+\.js):(\d+):(\d+)/g;async function $C(t,{distDir:e=xC}={}){let n=[],r=0,o=!1;for(let s of t.matchAll(RC)){let[a,i,c,l]=s,u=await _C(Ai.join(e,"assets",`${i}.map`)),f=u?EC(u.lines[Number(c)-1],Number(l)-1):null;if(n.push(t.slice(r,s.index)),f&&u.sources[f[1]]){let d=u.sources[f[1]].replace(/^(\.\.\/)+/,"");n.push(`${d}:${f[2]+1}:${f[3]+1}`),o=!0}else n.push(a);r=s.index+a.length}return n.push(t.slice(r)),{stack:n.join(""),symbolicated:o}}async function gk(t,e={}){try{if(!t?.error?.stack||!t.release||t.release==="unknown")return t;let n=e.release??await IC();if(!n||t.release!==n)return t;let{stack:r,symbolicated:o}=await $C(t.error.stack,e);o&&(t.error.stack=r,t.error.symbolicated=!0)}catch{}return t}var OC=()=>process.env.BOSTRAT_STATE_DIR||no.join(process.env.HOME||kk.tmpdir(),".bostrat"),An=()=>no.join(OC(),"telemetry"),xk=()=>no.join(An(),".relay-offsets.json"),CC=50,MC=14,NC=3e4,yk=500,Gt=(t,e)=>typeof t=="string"?t.slice(0,e):"";function PC(t,e){if(!t||typeof t!="object")return null;let n=Gt(t.name,64);if(!n)return null;let r=Number.isFinite(t.ts)?t.ts:e,o={name:n,ts:Math.abs(r-e)<168*3600*1e3?r:e,receivedAt:e,anonId:Gt(t.anonId,64),sessionId:Gt(t.sessionId,64),userId:Gt(t.userId,64),release:Gt(t.release,64),appVersion:Gt(t.appVersion,32),route:Gt(t.route,128),deviceClass:t.deviceClass==="phone"||t.deviceClass==="desktop"?t.deviceClass:""};return t.props&&typeof t.props=="object"&&!Array.isArray(t.props)&&(JSON.stringify(t.props).length<=8192?o.props=t.props:o.props={clamped:!0}),t.error&&typeof t.error=="object"&&(o.error={kind:Gt(t.error.kind,256),message:Gt(t.error.message,8192),stack:Gt(t.error.stack,32768),fingerprint:Gt(t.error.fingerprint,64)}),Array.isArray(t.breadcrumbs)&&(o.breadcrumbs=t.breadcrumbs.slice(-50).map(s=>({t:Number.isFinite(s?.t)?s.t:0,n:Gt(s?.n,64),r:Gt(s?.r,128)}))),o}var Sk=t=>new Date(t).toISOString().slice(0,10),bk=0;async function vk(t,e=Date.now()){let r=(Array.isArray(t?.events)?t.events.slice(0,CC):[]).map(s=>PC(s,e)).filter(Boolean);if(!r.length)return 0;for(let s of r)s.error?.stack&&await gk(s);await Ke.mkdir(An(),{recursive:!0});let o=no.join(An(),`${Sk(e)}.ndjson`);return await Ke.appendFile(o,r.map(s=>JSON.stringify(s)+`
|
|
412
|
-
`).join(""),"utf8"),e-
|
|
413
|
-
`).filter(Boolean),f=[];for(let d of u)try{f.push(JSON.parse(d))}catch{}for(let d=0;d<f.length;d+=yk){let p=f.slice(d,d+yk).map(g=>DC(g)),m=await t(`${e.url.replace(/\/$/,"")}/v1/logs`,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${e.ingestKey}`},body:JSON.stringify({events:p})}).catch(()=>null);if(!m||!m.ok)return{sent:o,disabled:!1};o+=p.length}r[s]=i+l+1,await Ek(r)}return{sent:o,disabled:!1}}var bd=null,kd=!1;function _k(){bd||(bd=setInterval(async()=>{if(!kd){kd=!0;try{await jC()}catch(t){console.error("[telemetry] relay failed:",t?.message||t)}finally{kd=!1}}},NC),bd.unref?.())}import Ii from"node:fs/promises";import Ck from"node:path";import LC from"node:os";var Mk=()=>process.env.BOSTRAT_STATE_DIR||Ck.join(process.env.HOME||LC.tmpdir(),".bostrat"),Nk=()=>Ck.join(Mk(),"dictation-glossary.json"),FC=100,UC=500,HC=["bostrat","globopi","codex","tmux","worktree","Deepgram","Linear","Firecracker","Tigris","WireGuard","misous","postyx","cedar","gerbil","Anthropic","Opus","Haiku","Sonnet","keyterm","monorepo","dogfood"],Ri=null,xd=null,Sd=Promise.resolve();function Ik(){let t={},e=Date.now();for(let n of HC)t[n.toLowerCase()]={term:n,count:1,lastUsed:e,source:"seed"};return t}async function Pk(){return Ri||(xd??=Ii.readFile(Nk(),"utf8").then(t=>JSON.parse(t)?.terms??Ik()).catch(()=>Ik()).then(t=>Ri=t).finally(()=>xd=null),xd)}async function Rk(){let t=JSON.stringify({terms:Ri},null,2),e=Nk(),n=`${e}.${process.pid}.tmp`;await Ii.mkdir(Mk(),{recursive:!0}),await Ii.writeFile(n,t),await Ii.rename(n,e)}function WC(){return Sd=Sd.then(Rk,Rk),Sd.catch(()=>{})}async function vd(){return Object.values(await Pk()).sort((e,n)=>n.count-e.count||n.lastUsed-e.lastUsed)}var qC=t=>Math.max(1,Math.ceil(t.length/4));async function KC(){let t=[],e=0;for(let{term:n}of await vd()){let r=qC(n);if(!(e+r>UC)&&(t.push(n),e+=r,t.length>=FC))break}return t}async function GC(t,e){let n=await Pk(),r=t.toLowerCase(),o=n[r];n[r]={term:t,count:(o?.count??0)+1,lastUsed:Date.now(),source:o?.source??e}}var $k=t=>(t||"").split(/\s+/).map(e=>e.replace(/^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu,"")).filter(Boolean),_i=t=>t.toLowerCase();function Ok(t){return!new RegExp("\\p{L}","u").test(t)||t.length<3?!1:/^[a-z]+$/.test(t)?t.length>=4:!0}async function Bk(t,e){let n=$k(t),r=$k(e);if(!n.length||!r.length)return[];let o=n.length,s=r.length,a=Array.from({length:o+1},()=>new Array(s+1).fill(0));for(let u=o-1;u>=0;u--)for(let f=s-1;f>=0;f--)a[u][f]=_i(n[u])===_i(r[f])?a[u+1][f+1]+1:Math.max(a[u+1][f],a[u][f+1]);let i=new Set,c=0,l=0;for(;c<o&&l<s;)_i(n[c])===_i(r[l])?(c++,l++):a[c+1][l]>=a[c][l+1]?c++:(Ok(r[l])&&i.add(r[l]),l++);for(;l<s;l++)Ok(r[l])&&i.add(r[l]);if(!i.size)return[];for(let u of i)await GC(u,"learned");return await WC(),[...i]}var xs=null;function VC(t,e){let n=null;try{n=JSON.parse(e)}catch{}let r=n?.err_code||n?.error_code||n?.category||"";return t===402||/PAYMENT_REQUIRED/i.test(r)?{status:402,code:"credits_exhausted",persistent:!0,message:"Deepgram credits are exhausted \u2014 dictation is down until you top up at console.deepgram.com or drop a new key into ~/.bostrat/integrations.json."}:t===401||t===403?{status:502,code:"key_invalid",persistent:!0,message:"Deepgram rejected the API key (revoked, or missing ASR scope) \u2014 replace it in ~/.bostrat/integrations.json."}:{status:502,code:"transcription_failed",persistent:!1,message:`Deepgram error ${t}: ${e.slice(0,300)}`}}async function Dk(){if(!xs)return null;let t=await Oo();if(!t||t.apiKey.slice(-6)!==xs.keyTail)return null;let{keyTail:e,...n}=xs;return n}async function jk(t,e){let n=await Oo();if(!n){let i=new Error("Deepgram is not configured \u2014 add deepgram.apiKey to ~/.bostrat/integrations.json");throw i.status=503,i.code="not_configured",i}let r=new URL("https://api.deepgram.com/v1/listen");r.searchParams.set("model","nova-3"),r.searchParams.set("smart_format","true");for(let i of await KC())r.searchParams.append("keyterm",i);let o=await fetch(r,{method:"POST",headers:{Authorization:`Token ${n.apiKey}`,"Content-Type":e||"application/octet-stream"},body:t});if(!o.ok){let i=await o.text().catch(()=>""),c=VC(o.status,i);c.persistent&&(xs={code:c.code,message:c.message,at:Date.now(),keyTail:n.apiKey.slice(-6)});let l=new Error(c.message);throw l.status=c.status,l.code=c.code,l}return xs=null,{transcript:(await o.json())?.results?.channels?.[0]?.alternatives?.[0]?.transcript??""}}var Td={"gpt-realtime-2.1":{textIn:4,textInCached:.4,textOut:24,audioIn:32,audioInCached:.4,audioOut:64},"gpt-realtime-2":{textIn:4,textInCached:.4,textOut:24,audioIn:32,audioInCached:.4,audioOut:64},"gpt-realtime":{textIn:4,textInCached:.4,textOut:16,audioIn:32,audioInCached:.4,audioOut:64},"gpt-realtime-2.1-mini":{textIn:.6,textInCached:.06,textOut:2.4,audioIn:10,audioInCached:.3,audioOut:20},"gpt-realtime-mini":{textIn:.6,textInCached:.06,textOut:2.4,audioIn:10,audioInCached:.3,audioOut:20}};function zC(t){if(!t)return null;let e=String(t).trim();if(Td[e])return e;let n=null;for(let r of Object.keys(Td))e.startsWith(r)&&(!n||r.length>n.length)&&(n=r);return n}function JC(t,e={}){let n=Td[zC(t)];if(!n)return 0;let r=0;for(let o of Object.keys(n))r+=(Number(e[o])||0)*n[o];return r/1e6}function XC(t,e){let n=null;try{n=JSON.parse(e)}catch{}let r=n?.error?.code||n?.error?.type||"";return t===429&&/insufficient_quota/i.test(r)?{status:402,code:"credits_exhausted",persistent:!0,message:"The OpenAI account is out of credit \u2014 voice chat is down until you top up at platform.openai.com or drop a new key into ~/.bostrat/integrations.json."}:t===401||t===403?{status:502,code:"key_invalid",persistent:!0,message:"OpenAI rejected the API key (revoked, or missing Realtime access) \u2014 replace it in ~/.bostrat/integrations.json."}:{status:502,code:"mint_failed",persistent:!1,message:`OpenAI realtime session mint failed (${t}): ${e.slice(0,300)}`}}var Ss=null;async function YC(){if(!Ss)return null;let t=await ka();if(!t||t.apiKey.slice(-6)!==Ss.keyTail)return null;let{keyTail:e,...n}=Ss;return n}async function ZC(t){let e=await fetch("https://api.openai.com/v1/realtime/client_secrets",{method:"POST",headers:{Authorization:`Bearer ${t.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify({session:{type:"realtime",model:t.model,audio:{output:{voice:t.voice}}}})});if(!e.ok){let o=await e.text().catch(()=>""),s=XC(e.status,o);s.persistent&&(Ss={code:s.code,message:s.message,at:Date.now(),keyTail:t.apiKey.slice(-6)});let a=new Error(s.message);throw a.status=s.status,a.code=s.code,a}Ss=null;let n=await e.json(),r=n?.value??n?.client_secret?.value;if(!r){let o=new Error("OpenAI returned no client secret");throw o.status=502,o.code="mint_failed",o}return{clientSecret:{value:r,expiresAt:n?.expires_at??n?.client_secret?.expires_at??null}}}var QC={openai:ZC};async function Lk(){let t=await ka();return t?{configured:!0,provider:t.provider,model:t.model,voice:t.voice,problem:await YC()}:{configured:!1}}async function Fk(){let t=await ka();if(!t){let n=new Error("Voice chat is not configured \u2014 add openai.apiKey to ~/.bostrat/integrations.json");throw n.status=503,n.code="not_configured",n}let e=await QC[t.provider](t);return{provider:t.provider,model:t.model,voice:t.voice,...e}}async function Uk(t,{model:e,usage:n={},seconds:r}={}){let o=c=>Math.max(0,Math.round(Number(c)||0)),s={textIn:o(n.textIn),textInCached:o(n.textInCached),textOut:o(n.textOut),audioIn:o(n.audioIn),audioInCached:o(n.audioInCached),audioOut:o(n.audioOut)},a=JC(e,s),i=await R(t,{type:"cost.recorded",actor:"user",provider:"openai-realtime",payload:{model:e||"unknown",usd:a,tokens:{input:s.textIn+s.audioIn,output:s.textOut+s.audioOut,cacheRead:s.textInCached+s.audioInCached,cacheWrite:0},voice:s,seconds:Math.max(0,Math.round(Number(r)||0)),source:"voice-chat"}});return{usd:a,seq:i.seq}}var Vk=lt.dirname(eM(import.meta.url)),Hk=Number(process.env.PORT)||3362,$i=mb(process.env),zk=Number(process.env.BOSTRAT_AUTO_TRIGGER_PCT)||80,ro=Kb(process.env.BOSTRAT_ALLOW_IPS),Rd=Gb(process.env.BOSTRAT_VIEW_FRONT_CIDRS,Si),Jk=zb(),Xk=process.env.BOSTRAT_ALLOW_LAN_TOKENLESS!=="1",x=Se();x.disable("x-powered-by");x.use((t,e,n)=>{if(ws(t)||ro.allowed(ro.clientIp(t))||Rd.allows(t))return n();e.status(403).type("text/plain").send("bostrat: access restricted to the allowlist")});x.use(tk);x.use(Yb({token:Jk,enforce:Xk}));x.use("/api/mocks",Se.json({limit:"1mb"}));x.use("/api/mocks",Se.raw({type:"image/png",limit:"24mb"}));x.use("/api/runbooks",Se.json({limit:"1mb"}));x.use("/api/telemetry",Se.json({limit:"256kb"}));x.use("/api/hook",Se.json({limit:"2mb"}));x.use("/api/dictation",Se.raw({type:["audio/*","video/mp4","application/octet-stream"],limit:"32mb"}));x.use("/api/tracks/:id/upload",Se.raw({type:()=>!0,limit:"25mb"}));x.use(Se.json({limit:"64kb"}));x.get("/api/health",async(t,e)=>{e.json({ok:!0,tmuxSocket:Ut,hostTmuxAlive:await Ye(),autoTriggerPct:zk})});x.get("/api/sessions",async(t,e)=>{e.json({sessions:await Rn()})});x.post("/api/sessions",async(t,e)=>{let{displayName:n,cwd:r}=t.body||{};if(typeof n!="string")return e.status(400).json({error:"displayName (string) required"});try{let o=await $n(n,r||void 0);e.json({ok:!0,...o})}catch(o){let s=o?.message||String(o),a=/already exists/i.test(s)?409:/must be|invalid/i.test(s)?400:500;e.status(a).json({error:s})}});x.delete("/api/sessions/:name",async(t,e)=>{let n=decodeURIComponent(t.params.name);if(!wt(n))return e.status(400).json({error:"invalid session name"});if(!await Z(n))return e.status(404).json({error:"session not found"});try{await ce(n),e.json({ok:!0})}catch(r){e.status(500).json({error:`failed to kill: ${r?.message||r}`})}});x.get("/api/context/:name",async(t,e)=>{let n=decodeURIComponent(t.params.name);if(!wt(n))return e.status(400).json({error:"invalid session name"});let r=await Z(n);if(!r)return e.status(404).json({error:"session not found"});let o=await bo(r.cwd,r.sessionId);if(!o)return e.json({available:!1,cwd:r.cwd,sessionId:r.sessionId||null});e.json({available:!0,...o})});x.post("/api/handoff/:name",async(t,e)=>{let n=decodeURIComponent(t.params.name);if(!wt(n))return e.status(400).json({error:"invalid session name"});try{let r=await Ep(n);e.json({ok:!0,step:r.step,file:r.file,startedAt:r.startedAt})}catch(r){e.status(409).json({error:r?.message||String(r)})}});x.get("/api/handoff/:name",(t,e)=>{let n=decodeURIComponent(t.params.name),r=oa(n);if(!r)return e.json({running:!1});e.json({running:!r.finishedAt,step:r.step,error:r.error,file:r.file,startedAt:r.startedAt,finishedAt:r.finishedAt})});var Wk=10;x.get("/api/tracks",async(t,e)=>{let n=await ot(),r=n.filter(s=>s.archivedAt),o=n;if(t.query.archived!=="all"&&r.length>Wk){let s=new Set([...r].sort((a,i)=>(i.archivedAt||0)-(a.archivedAt||0)).slice(0,Wk).map(a=>a.id));o=n.filter(a=>!a.archivedAt||s.has(a.id))}e.json({tracks:o,archivedTotal:r.length,agents:oi,agentAvailability:await Bu(),tickets:await Sa()})});x.get("/api/cost",async(t,e)=>{try{let n=await ot();t.query.track&&(n=n.filter(o=>o.id===t.query.track));let r=await xb(n);if(r.cursorUsage=du(),t.query.format==="text")return e.type("text/plain").send(Sb(r));e.json(r)}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/usage/roundup",async(t,e)=>{try{e.json(await Ab(await ot(),{window:t.query.window,from:t.query.from,to:t.query.to}))}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/usage/spend",async(t,e)=>{try{let n=await $b(await ot(),{window:t.query.window,from:t.query.from,to:t.query.to});n.cursorUsage=du(),n.subscriptionMonthlyUsd=Number(process.env.BOSTRAT_SUBSCRIPTION_MONTHLY_USD)||null,e.json(n)}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/usage/limits",async(t,e)=>{try{let n=t.query.fresh==="1"||t.query.fresh==="true";e.json(await Fb(await ot(),{fresh:n}))}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/arcs",async(t,e)=>{try{e.json(await bh(await ot()))}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/arcs/:key",async(t,e)=>{try{let n=await kh(await ot(),t.params.key);if(!n)return e.status(404).json({error:"unknown arc"});e.json(n)}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.post("/api/arcs/backfill-beats",async(t,e)=>{try{e.json({ok:!0,...await qy()})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.post("/api/hook",async(t,e)=>{try{let{mode:n,target:r}=await Nd(t.get(_s),{byToken:ky,legacy:()=>by(t.body?.session_id,t.body?.cwd)});if(n==="refused")return Is("refused",{reason:"unknown-token",hook:t.body?.hook_event_name}),e.json({ok:!1,error:"unknown hook token"});n==="token"&&t.body?.session_id&&r.box?.sessionId&&t.body.session_id!==r.box.sessionId&&Is("mismatch",{track:r.track.id,box:r.box.name}),Is(n,{track:r?.track?.id,hook:t.body?.hook_event_name});let o=await fb(t.body||{},()=>r);e.json({ok:!0,events:o.length})}catch(n){e.json({ok:!1,error:n?.message||String(n)})}});x.post("/api/telemetry",async(t,e)=>{try{if(!Tk(ro.clientIp(t)))return e.json({ok:!0,dropped:!0});let n=await vk(t.body||{});e.json({ok:!0,events:n})}catch(n){e.json({ok:!1,error:n?.message||String(n)})}});x.get("/api/repos",async(t,e)=>{try{let n=await ve(),r=new Set((await Promise.all(n.map(a=>tt(a.path)))).filter(Boolean).map(a=>a.toLowerCase())),s=((await co()).repos||[]).filter(a=>!r.has(String(a.slug).toLowerCase())).map(a=>({slug:a.slug,name:a.slug.split("/")[1],branch:a.defaultBranch||null,private:!!a.private,remote:!0}));e.json({repos:[...n,...s]})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/github/token",async(t,e)=>{try{e.json(await Cs(String(t.query.repo||"")))}catch(n){e.status(n?.status||502).json({error:n?.message||String(n)})}});x.get("/api/github/status",(t,e)=>{e.json({configured:Os(),calls:Qw(),latency:Ag()})});x.post("/api/github/refresh",(t,e)=>{qd(),e.json({ok:!0})});x.get("/api/gmail/search",async(t,e)=>{try{let n=await Hb(String(t.query.q||""),{max:Number(t.query.max)||10});e.json({messages:n})}catch(n){e.status(n?.status||502).json({error:n?.message||String(n)})}});x.get("/api/gmail/messages/:id",async(t,e)=>{try{e.json({message:await Wb(String(t.params.id))})}catch(n){e.status(n?.status||502).json({error:n?.message||String(n)})}});function $d(t,e,n){let r=t.workspace;return r?.kind==="multi"?(r.repos??[]).some(o=>e&&o?.base===e||n&&o?.repo===n):!r?.base||!e||r.base===e||n&&r.repo===n}async function Oi({repo:t=null,number:e=null,branch:n=null}){let r=t?await tt(t):null;if(r&&e!=null){let s=await $t(r,e),a=s&&await Q(s.trackId);if(a)return a}return n?(await ot()).find(s=>s.workspace?.branch===n&&$d(s,t,r))??null:null}async function Yk({repo:t=null,number:e=null,branch:n=null}){let r=await Oi({repo:t,number:e,branch:n}).catch(()=>null);if(r&&!r.archivedAt)return r;if(!n)return null;let o=t?await tt(t).catch(()=>null):null;return(await ot()).find(a=>!a.archivedAt&&a.workspace?.branch===n&&$d(a,t,o))??null}async function se(t,{number:e,url:n=null,title:r=null,branch:o=null,repo:s=null,track:a=null,extra:i={}}){let c=a||await Oi({repo:s,number:e,branch:o}).catch(()=>null);if(!c)return;let l=i.repo??(s?await tt(s).catch(()=>null):null);await R({id:c.id,cwd:c.cwd},{type:"pr.state.changed",actor:"user",source:"orchestrator",payload:{ts:Date.now(),change:t,number:e,url:n,title:r,branch:o??c.workspace?.branch??null,...l?{repo:l}:{},...i}}).catch(()=>{})}async function sM(t){let e=(await ot()).filter(n=>!n.archivedAt);for(let n of t??[]){if(!n.prs?.length)continue;let r=await tt(n.path);if(r)for(let o of n.prs){if(await $t(r,o.number))continue;let s=e.find(a=>a.workspace?.branch===o.headRefName&&$d(a,n.path,r));s&&await Wt({repo:r,number:o.number,trackId:s.id,url:o.url,headRef:o.headRefName,title:o.title,source:"matched"})}}}x.get("/api/git/prs",async(t,e)=>{try{let n=await Cg();await sM(n.repos).catch(()=>{}),e.json(n)}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.post("/api/git/prs/merge",async(t,e)=>{let{repo:n,number:r,method:o,auto:s,update:a,archive:i,notify:c,headRefName:l,title:u,url:f}=t.body||{};try{if(s){let g=o||"merge";if(!Zo.has(g))throw new Error(`invalid merge method: ${g}`);let y=Number(r);if(!Number.isInteger(y)||y<=0)throw new Error("invalid PR number");let b=await tt((await Fe(n)).path);if(!b)throw new Error("repo has no GitHub remote");let S=await Oi({repo:n,number:y,branch:l}).catch(()=>null);if(!S)throw new Error("auto-merge needs a Track to watch this PR \u2014 none owns it");if(a&&await Qo(n,y),await Wt({repo:b,number:y,trackId:S.id,url:f,headRef:l,title:u,source:"matched"}).catch(()=>{}),!await Do(b,y,{merge:{method:g}}))throw new Error("couldn't record the auto-merge arm");return await Tr(b,y,i===!0),await Ra(b,y,c===!0&&i!==!0),ke(),await se("auto-armed",{number:y,url:f,title:u,branch:l,repo:n,track:S,...a?{extra:{updated:!0}}:{}}),e.json({ok:!0,number:y,auto:!0})}let d=await Fr(n,r,{method:o||"squash"});await se("merged",{number:d.number,url:f,title:u,branch:l,repo:n});let p=await tt(n).catch(()=>null),m=!1;if(p&&(i===!0&&await Tr(p,d.number,!0).catch(()=>{}),c===!0&&i!==!0&&await Ra(p,d.number,!0).catch(()=>{}),await wn(p,d.number).catch(()=>{}),m=await hn(p,d.number).catch(()=>!1),await cn(p,d.number,"merged").catch(()=>{})),i===!0&&!m){let g=await Oi({repo:n,number:d.number,branch:l}).catch(()=>null);g&&!g.archivedAt&&await Vr(g.id).catch(()=>{})}e.json(d)}catch(d){let p=d?.message||String(d),m=/invalid|unknown repo|required|needs a Track|no GitHub remote/i.test(p)?400:502;e.status(m).json({error:p})}});x.post("/api/git/prs/disarm-auto",async(t,e)=>{let{repo:n,number:r,headRefName:o,title:s,url:a}=t.body||{};try{let i=Number(r);if(!Number.isInteger(i)||i<=0)throw new Error("invalid PR number");let c=await tt((await Fe(n)).path).catch(()=>null),l=c?await jo(c,i).catch(()=>null):null;try{await yu(n,i)}catch(u){if(!l)throw u}ke(),await se("auto-canceled",{number:i,url:a,title:s,branch:o,repo:n}),e.json({ok:!0,number:i})}catch(i){let c=i?.message||String(i),l=/invalid|unknown repo|required/i.test(c)?400:502;e.status(l).json({error:c})}});x.post("/api/git/prs/merge-all",async(t,e)=>{let{repo:n,method:r}=t.body||{};try{let o=await tt(n).catch(()=>null),s=await Bg(n,{method:r||"merge",onMerged:a=>{se("merged",{number:a.number,url:a.url,title:a.title,branch:a.headRefName,repo:n}),o&&wn(o,a.number).catch(()=>{}).then(()=>hn(o,a.number)).catch(()=>{}).then(()=>cn(o,a.number,"merged")).catch(()=>{})}});e.json(s)}catch(o){let s=o?.message||String(o),a=/already running/i.test(s)?409:/invalid|unknown repo|no PRs ready/i.test(s)?400:502;e.status(a).json({error:s})}});x.get("/api/git/prs/merge-all",(t,e)=>{e.json({jobs:Pg()})});x.post("/api/git/prs/merge-track",async(t,e)=>{let{trackId:n,update:r,archive:o,notify:s,method:a}=t.body||{};try{let i=a||"merge";if(!Zo.has(i))throw new Error(`invalid merge method: ${i}`);if(!n)return e.status(400).json({error:"trackId required"});let c=await Q(n);if(!c)return e.status(404).json({error:`Track ${n} isn't on this node`});let l=(await ye()).filter(d=>d.trackId===c.id&&d.state!=="merged"&&d.state!=="closed");if(!l.length)return e.status(409).json({error:"no open PRs on this Track"});for(let d of l)await Tr(d.repo,d.number,o===!0).catch(()=>{}),await Ra(d.repo,d.number,s===!0&&o!==!0).catch(()=>{});let u=[],f=[];for(let d of l){let p={repo:d.repo,number:d.number,ok:!0,action:"armed"};u.push(p);try{let m=await Jn(d.repo);if(!m)throw new Error(`no local checkout for ${d.repo}`);let g=await Lr(m,d.number);f.push({r:p,l:d,repoPath:m,pr:g,plan:Mg(g,{update:r!==!1})})}catch(m){p.ok=!1,p.action="failed",p.reason=m?.message||String(m)}}for(let{r:d,l:p,repoPath:m,pr:g,plan:y}of Ng(f))try{if(y.action==="skip")d.action="skipped",d.reason=y.reason;else if(y.action==="merge")await Fr(m,p.number,{method:i}),await se("merged",{number:p.number,url:g.url,title:g.title,branch:g.headRefName,repo:m,track:c}),await wn(p.repo,p.number).catch(()=>{}),await hn(p.repo,p.number).catch(()=>{}),await cn(p.repo,p.number,"merged").catch(()=>{}),d.action="merged";else{if(y.action==="update-arm"&&(await Qo(m,p.number).catch(()=>{}),d.action="updated-armed"),!await Do(p.repo,p.number,{merge:{method:i}}))throw new Error("couldn't record the auto-merge arm");await se("auto-armed",{number:p.number,url:g.url,title:g.title,branch:g.headRefName,repo:m,track:c,...d.action==="updated-armed"?{extra:{updated:!0}}:{}})}}catch(b){d.ok=!1,d.action="failed",d.reason=b?.message||String(b)}if(ke(),!u.some(d=>d.ok))return e.status(502).json({error:u.map(d=>`${d.repo}#${d.number}: ${d.reason}`).join("; "),results:u});e.json({ok:!0,trackId:c.id,results:u})}catch(i){let c=i?.message||String(i),l=/invalid|required/i.test(c)?400:502;e.status(l).json({error:c})}});x.post("/api/git/prs/disarm-track",async(t,e)=>{let{trackId:n}=t.body||{};try{if(!n)return e.status(400).json({error:"trackId required"});let r=await Q(n);if(!r)return e.status(404).json({error:`Track ${n} isn't on this node`});let o=(await ye()).filter(a=>a.trackId===r.id&&a.state!=="merged"&&a.state!=="closed"),s=[];for(let a of o){let i=await jo(a.repo,a.number).catch(()=>null),c=await Jn(a.repo).catch(()=>null);c&&await yu(c,a.number).catch(()=>{}),i&&(await se("auto-canceled",{number:a.number,url:a.url,title:a.title,branch:a.headRef,repo:c,track:r}),s.push({repo:a.repo,number:a.number}))}ke(),e.json({ok:!0,trackId:r.id,canceled:s})}catch(r){let o=r?.message||String(r),s=/invalid|required/i.test(o)?400:502;e.status(s).json({error:o})}});x.post("/api/git/prs/open",async(t,e)=>{let{trackId:n,title:r,body:o,base:s,repos:a}=t.body||{};try{if(!n)return e.status(400).json({error:"trackId required"});let i=await Q(n);if(!i)return e.status(404).json({error:`Track ${n} isn't on this node`});if(i.workspace?.kind==="multi"){let u=Dt(i.workspace),f,d=Array.isArray(a)?a.filter(Boolean):[];if(d.length)f=d.map(g=>{let y=u.find(b=>b.name===g||b.repo===g||b.base===g||b.dir===g);if(!y)throw new Error(`invalid repo "${g}" \u2014 not tagged on this Track`);return y});else if(f=await zo(i),!f.length)return e.status(409).json({error:"no tagged repo has work to ship"});let p=[];for(let g of f)try{let y=await ku(g.dir,{title:r,body:o,base:s}),b=g.repo||await tt(g.dir).catch(()=>null);b&&y.number&&await Wt({repo:b,number:y.number,trackId:i.id,url:y.url,headRef:i.workspace?.branch??null,title:r,source:"api"}),await se("opened",{number:y.number,url:y.url,title:r,track:i,branch:i.workspace?.branch,extra:{repo:b??g.name}}),p.push({repo:g.name,slug:b??null,ok:!0,url:y.url,number:y.number})}catch(y){p.push({repo:g.name,slug:g.repo??null,ok:!1,error:y?.message||String(y)})}let m=p.filter(g=>g.ok);return m.length?e.json({ok:!0,multi:!0,results:p,url:m[0].url,number:m[0].number}):e.status(502).json({error:p.map(g=>`${g.repo}: ${g.error}`).join("; "),results:p})}let c=await ku(i.cwd,{title:r,body:o,base:s}),l=await tt(i.cwd).catch(()=>null);l&&c.number&&await Wt({repo:l,number:c.number,trackId:i.id,url:c.url,headRef:i.workspace?.branch??null,title:r,source:"api"}),await se("opened",{number:c.number,url:c.url,title:r,track:i,branch:i.workspace?.branch,extra:{...l?{repo:l}:{}}}),e.json(c)}catch(i){let c=i?.message||String(i),l=/required|invalid/i.test(c)?400:502;e.status(l).json({error:c})}});x.get("/api/tracks/:id/repos",async(t,e)=>{try{let n=await Q(t.params.id);if(!n)return e.status(404).json({error:"track not found"});let r=await zo(n),o=new Map(r.map(a=>[a.base,a])),s=Dt(n.workspace).map(a=>{let i=o.get(a.base);return{name:a.name,base:a.base,baseRef:a.baseRef??null,repo:a.repo??null,dirty:i?.dirty??!1,ahead:i?.ahead??0,changed:!!i}});e.json({repos:s,branch:n.workspace?.branch??null,kind:n.workspace?.kind??null})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.post("/api/tracks/:id/repos",async(t,e)=>{let{repo:n}=t.body||{};try{if(!n||typeof n!="string")return e.status(400).json({error:"repo (path or owner/name) required"});let r=await Py(t.params.id,n);e.json({ok:!0,added:{name:r.name,base:r.base,baseRef:r.baseRef??null,repo:r.repo??null}})}catch(r){let o=r?.message||String(r),s=/not found/i.test(o)?404:/already tagged|needs a multi|not a usable|archived|required/i.test(o)?400:502;e.status(s).json({error:o})}});x.delete("/api/tracks/:id/repos/:name",async(t,e)=>{try{let n=await By(t.params.id,t.params.name);e.json({ok:!0,...n})}catch(n){let r=n?.message||String(n),o=/track .* not found/i.test(r)?404:/has work|still running|last repo/i.test(r)?409:/needs a multi|not tagged/i.test(r)?400:502;e.status(o).json({error:r})}});x.post("/api/git/prs/fix",async(t,e)=>{let{repo:n,number:r}=t.body||{};try{let o=await Lr(n,r),s=await tt(n).catch(()=>null),a=await Yk({repo:n,number:r,branch:o.headRefName});if(!a)return e.status(404).json({error:`no active Track owns PR #${o.number} (branch "${o.headRefName}")`});let i=Dt(a.workspace).find(b=>b.base===n||s&&b.repo===s)??null,c=a.workspace?.kind==="multi",l=await Dg(n,o.number);if(!l.length)return e.status(409).json({error:"no failing checks on this PR"});let u=c&&i?lt.join("ci",i.name):"ci",f=lt.join(a.cwd,".bostrat",u),d=lt.join(".bostrat",u,`pr-${o.number}-fail.log`);await Lt.promises.mkdir(f,{recursive:!0}),await Lt.promises.writeFile(lt.join(a.cwd,d),l.map(b=>`\u2550\u2550\u2550\u2550 ${b.name}${b.link?` \xB7 ${b.link}`:""} \u2550\u2550\u2550\u2550
|
|
408
|
+
`),n?.length&&a.write(n),e.pipe(a),a.pipe(e)}),i=()=>{a.destroy(),e.destroy()};return a.on("error",i),e.on("error",i),!0}function sk(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}.${FO(n.host)}/`}function FO(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 nk(e)&&Lc(n[0])?n.slice(1).join("."):e}var ak=10,ik=200,UO=120,HO=24,WO=/^\d{4}-\d{2}-\d{2}$/;function yd(t=process.env){return(t.CHANGELOG_REPOS||"globocodes/bostrat").split(",").map(e=>e.trim()).filter(Boolean)}var qO=(t=Date.now())=>new Date(t).toISOString().slice(0,10);function ck(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 KO(t){if(typeof t!="string"||!t.trim())throw new Error("md (markdown string) required");let e=Oo(t);if(e.length)throw new Error(`changelog entry appears to contain a secret VALUE (${e.join(", ")}) \u2014 entries ship off-box; never put a credential in one. Replace it and retry.`);let n=null,r=[];for(let o of t.split(`
|
|
409
|
+
`)){let s=o.trim();if(!s)continue;if(s.startsWith("#")){n==null&&(n=s.replace(/^#+\s*/,"").trim().slice(0,UO)||null);continue}let a=/^[-*]\s+(.*)$/.exec(s);if(!a)continue;let i=a[1].trim(),c=null,l=new RegExp(`^\\[([^\\]]{1,${HO}})\\]\\s+(.*)$`).exec(i);if(l&&(c=l[1].trim(),i=l[2].trim()),!!i){if(i.length>ik)throw new Error(`bullet ${r.length+1} is ${i.length} chars \u2014 over the ${ik}-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>ak)throw new Error(`${r.length} bullets \u2014 over the ${ak}-bullet cap; group related changes into one line`);return{title:n||null,bullets:r}}async function lk(t,{repos:e=yd()}={}){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 uk(t,{md:e,day:n=null}){let r=KO(e),o=n==null||n===""?qO():String(n);if(!WO.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 GO}from"node:child_process";import{promisify as VO}from"node:util";var kd=VO(GO),dk=3e4,zO=30*6e4,JO=dk,XO=10*6e4,YO=10080*6e4,ZO=60*6e4,QO=8,tC=1440*6e4,eC=3,fk="number,url,title,headRefName,headRefOid,state,mergedAt,closedAt",bd=60*6e4,nC=15*6e4,Ei=new Map,_i=new Map,rC=/could not resolve|not found|no pull requests?/i,oC=t=>rC.test(String(t?.message||t||""));function sC(t,e){return[t.ciFix?.delegatedAt,t.reconcile?.delegatedAt,t.ciFixAwait?.since,t.reconcileAwait?.since].some(n=>n&&e-n<bd)}var pk={fast:JO,slow:XO,decay:ZO};function mk(t,e){return t.armedAt&&e-t.armedAt<zO||sC(t,e)?"fast":e-(t.firstSeen||0)>YO?"decay":"slow"}function aC(t,e){return pk[mk(t,e)]}function Ts(t,e,n,r){let o=t.lanes?.get(K(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 iC(t,e){Le("watch");try{let{stdout:n}=await kd("gh",["pr","view",String(e),"--repo",t,"--json",fk],{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 cC(t){Le("watch");try{let{stdout:e}=await kd("gh",["pr","list","--repo",t,"--state","all","--limit","100","--json",fk],{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 lC(){let t=new Map;for(let e of await at().catch(()=>[]))t.set(e.id,e);return t}async function uC(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 dC(t,e){let n=await Yn(t);return n?Fr(n,e):null}async function fC(t,e,n){let r=await Yn(t);if(!r)throw new Error(`no local checkout for ${t}`);return Ur(r,e,{method:n})}async function pC(t,e){let n=await Yn(t);if(!n)throw new Error(`no local checkout for ${t}`);return ns(n,e)}async function mC(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 ro(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 hC=10*6e4;async function wC(t,e,n){let r=t.autoMerge?.armedAt||0;if(r&&(n.now??Date.now())-r>tC){await ro(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 ro(t,o,e,n,`check${o.checks.failed===1?"":"s"} failed: ${a}`);return}if(o.mergeable==="CONFLICTING"){await ro(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<hC)return;await ro(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 ro(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 ro(t,o,e,n,String(a?.message||a).trim()||"merge failed");return}await hk(t,{...o,state:"MERGED"},e,n,{change:"auto-merged",githubAt:o.checks.settledAt||null})}}async function xs(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 gC(t,e){try{return await kd("git",["-C",t.cwd,"cat-file","-e",`${e}^{commit}`],{timeout:1e4}),!0}catch{return!1}}async function Ss(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 vs=(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 yC(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 Ra(t.repo,t.number,{headSha:s}).catch(()=>{}),t.reconcile?.headSha&&s!==t.reconcile.headSha&&await Oa(t.repo,t.number,{headSha:s}).catch(()=>{})),s&&s!==t.lastHeadSha){let i=!t.lastHeadSha,c=!!(t.ciFix||t.reconcile||t.ciFixAwait||t.reconcileAwait),l=!!t.baseFoldAt&&o-t.baseFoldAt<nC;await mh(t.repo,t.number,s).catch(()=>{}),t.baseFoldAt&&await ph(t.repo,t.number).catch(()=>{}),!i&&!c&&!l&&a&&!await r.localHasCommit(a,s).catch(()=>!1)&&!await Ss(a,"pushed",t.number,f=>f.payload?.sha===s)&&await xs(a,"pushed",{...vs(t,e),sha:s}).catch(()=>{})}if(t.ciFixAwait)if(o-(t.ciFixAwait.since||0)>bd)await $a(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 $a(t.repo,t.number).catch(()=>{});else if(i.checks.total>0&&i.checks.pending===0){let c=Ts(r,t,"ci-fixed",i.checks.settledAt||null),l=t.ciFixAwait.since||0;a&&!await Ss(a,"ci-fixed",t.number,u=>(u.payload?.ts||0)>=l)&&await xs(a,"ci-fixed",{...vs(t,e),passed:i.checks.passed,githubAt:c.githubAt,latencyMs:c.latencyMs,lane:c.lane}).catch(()=>{}),await $a(t.repo,t.number).catch(()=>{})}}}if(t.reconcileAwait)if(o-(t.reconcileAwait.since||0)>bd)await Ca(t.repo,t.number).catch(()=>{});else{let i=await r.detail(t.repo,t.number).catch(()=>null);if(i){if(i.mergeable==="CONFLICTING")await Ca(t.repo,t.number).catch(()=>{});else if(i.mergeable==="MERGEABLE"){Ts(r,t,"reconciled",null);let c=t.reconcileAwait.since||0;a&&!await Ss(a,"reconciled",t.number,l=>(l.payload?.ts||0)>=c)&&await xs(a,"reconciled",{...vs(t,e),base:i.baseRefName||null}).catch(()=>{}),await Ca(t.repo,t.number).catch(()=>{})}}}}async function bC(t,e,n,r){let o=Ts(r,t,"closed",Date.parse(e?.closedAt||"")||null),s=n.get(t.trackId);s&&!await Ss(s,"closed",t.number)&&await xs(s,"closed",{...vs(t,e),githubAt:o.githubAt,latencyMs:o.latencyMs,lane:o.lane}).catch(()=>{}),await r.notifyIfResolved(t.repo,t.number,"closed").catch(()=>{}),await r.mark(t.repo,t.number,"closed").catch(()=>{})}async function kC(t,e,n){Ts(n,t,"lost",null);let r=e.get(t.trackId);r&&!await Ss(r,"lost",t.number)&&await xs(r,"lost",vs(t,null)).catch(()=>{}),await n.notifyIfResolved(t.repo,t.number,"lost").catch(()=>{}),await n.mark(t.repo,t.number,"lost").catch(()=>{})}async function hk(t,e,n,r,o={}){let s=Ts(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 xC(t={}){let{now:e=Date.now(),auth:n=bu,view:r=iC,list:o=cC,links:s=wh,trackMap:a=lC,emitMerged:i=uC,mark:c=cn,archiveIfArmed:l=hn,notifyIfArmed:u=wn,notifyIfResolved:f=Gu,batchThreshold:d=eC,detail:p=dC,merge:m=fC,updateBranch:h=pC,lookup:y=Ot,disarm:b=Uo,emitDisarmed:S=mC,localHasCommit:I=gC}=t;if(!(await n().catch(()=>null))?.authenticated||Ka()>0)return 0;let $=(await s().catch(()=>[])).filter(A=>e-(Ei.get(K(A.repo,A.number))||0)>=aC(A,e));if(!$.length)return 0;let F=Date.now(),T=new Map,C=new Map;for(let A of $){let g=mk(A,e);T.set(K(A.repo,A.number),{lane:g,intervalMs:pk[g]}),Ei.set(K(A.repo,A.number),e),C.has(A.repo)||C.set(A.repo,[]),C.get(A.repo).push(A)}let D=await a().catch(()=>new Map),it={now:e,lanes:T,emitMerged:i,mark:c,archiveIfArmed:l,notifyIfArmed:u,notifyIfResolved:f,detail:p,merge:m,updateBranch:h,lookup:y,disarm:b,emitDisarmed:S,localHasCommit:I};for(let[A,g]of C){let w,_=new Set;if(g.length>d)w=await o(A).catch(()=>null);else{w=new Map;for(let v of g)try{let O=await r(A,v.number);O?.number!=null&&w.set(Number(O.number),O)}catch(O){oC(O)&&_.add(K(v.repo,v.number))}}if(w)for(let v of g){let O=K(v.repo,v.number),E=w.get(Number(v.number));if(!E){if(_.has(O)){let N=(_i.get(O)||0)+1;_i.set(O,N),N>=QO&&(await kC(v,D,it),_i.delete(O),Ei.delete(O))}continue}_i.delete(O),E.state==="MERGED"||E.state==="CLOSED"?(E.state==="MERGED"?await hk(v,E,D,it):await bC(v,E,D,it),Ei.delete(O)):(await yC(v,E,D,it).catch(()=>{}),v.autoMerge&&await wC(v,D,it).catch(()=>{}))}}let ot={};for(let{lane:A}of T.values())ot[A]=(ot[A]||0)+1;return console.log(`[pr-watch] probed ${$.length} link(s) (${Object.entries(ot).map(([A,g])=>`${A} ${g}`).join(", ")}) in ${Date.now()-F}ms`),$.length}function wk(){let t=!1;setInterval(async()=>{if(!t){t=!0;try{await xC()}catch{}finally{t=!1}}},dk)}import Ke from"node:fs/promises";import oo from"node:path";import vk from"node:os";import gk from"node:fs/promises";import Ii from"node:path";import{execFile as SC}from"node:child_process";import{promisify as vC}from"node:util";import{fileURLToPath as TC}from"node:url";var AC=vC(SC),bk=Ii.dirname(TC(import.meta.url)),EC=Ii.resolve(bk,"..","..","client","dist"),_C="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",IC=new Map([..._C].map((t,e)=>[t,e]));function RC(t){let e=[],n=0,r=0;for(let o of t){let s=IC.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 $C(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=RC(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 OC(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 yk=new Map;async function CC(t){let e;try{e=await gk.stat(t)}catch{return null}let n=yk.get(t);if(n&&n.mtimeMs===e.mtimeMs)return n;try{let r=JSON.parse(await gk.readFile(t,"utf8")),o={mtimeMs:e.mtimeMs,sources:r.sources||[],lines:$C(r.mappings||"")};return yk.set(t,o),o}catch{return null}}var xd=null;function MC(){return xd||(xd=process.env.BOSTRAT_RELEASE?Promise.resolve(process.env.BOSTRAT_RELEASE):AC("git",["rev-parse","--short","HEAD"],{cwd:Ii.resolve(bk,"..","..")}).then(t=>t.stdout.trim()).catch(()=>null)),xd}var NC=/(?:https?:\/\/[^\s()]*)?\/assets\/([^\s()/]+\.js):(\d+):(\d+)/g;async function PC(t,{distDir:e=EC}={}){let n=[],r=0,o=!1;for(let s of t.matchAll(NC)){let[a,i,c,l]=s,u=await CC(Ii.join(e,"assets",`${i}.map`)),f=u?OC(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 kk(t,e={}){try{if(!t?.error?.stack||!t.release||t.release==="unknown")return t;let n=e.release??await MC();if(!n||t.release!==n)return t;let{stack:r,symbolicated:o}=await PC(t.error.stack,e);o&&(t.error.stack=r,t.error.symbolicated=!0)}catch{}return t}var BC=()=>process.env.BOSTRAT_STATE_DIR||oo.join(process.env.HOME||vk.tmpdir(),".bostrat"),En=()=>oo.join(BC(),"telemetry"),Tk=()=>oo.join(En(),".relay-offsets.json"),DC=50,jC=14,LC=3e4,xk=500,Gt=(t,e)=>typeof t=="string"?t.slice(0,e):"";function FC(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 Ak=t=>new Date(t).toISOString().slice(0,10),Sk=0;async function Ek(t,e=Date.now()){let r=(Array.isArray(t?.events)?t.events.slice(0,DC):[]).map(s=>FC(s,e)).filter(Boolean);if(!r.length)return 0;for(let s of r)s.error?.stack&&await kk(s);await Ke.mkdir(En(),{recursive:!0});let o=oo.join(En(),`${Ak(e)}.ndjson`);return await Ke.appendFile(o,r.map(s=>JSON.stringify(s)+`
|
|
410
|
+
`).join(""),"utf8"),e-Sk>6*3600*1e3&&(Sk=e,UC(e).catch(()=>{})),r.length}async function UC(t=Date.now()){let e=Ak(t-jC*24*3600*1e3),n=[];try{n=await Ke.readdir(En())}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(oo.join(En(),s),{force:!0});let o=await Ik();for(let s of r)delete o[s];await Rk(o)}var Ri=new Map;function _k(t,e=Date.now()){let n=t||"unknown",r=Ri.get(n);return r||(Ri.size>512&&Ri.clear(),r={tokens:40,at:e},Ri.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 Ik(){try{return JSON.parse(await Ke.readFile(Tk(),"utf8"))||{}}catch{return{}}}async function Rk(t){await Ke.mkdir(En(),{recursive:!0}),await Ke.writeFile(Tk(),JSON.stringify(t),"utf8")}function HC(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||vk.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 WC({fetchImpl:t=fetch}={}){let e=await Pm();if(!e)return{sent:0,disabled:!0};let n;try{n=(await Ke.readdir(En())).filter(s=>s.endsWith(".ndjson")).sort()}catch{return{sent:0,disabled:!1}}let r=await Ik(),o=0;for(let s of n){let a=oo.join(En(),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(`
|
|
411
|
+
`).filter(Boolean),f=[];for(let d of u)try{f.push(JSON.parse(d))}catch{}for(let d=0;d<f.length;d+=xk){let p=f.slice(d,d+xk).map(h=>HC(h)),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 Rk(r)}return{sent:o,disabled:!1}}var Sd=null,vd=!1;function $k(){Sd||(Sd=setInterval(async()=>{if(!vd){vd=!0;try{await WC()}catch(t){console.error("[telemetry] relay failed:",t?.message||t)}finally{vd=!1}}},LC),Sd.unref?.())}import Oi from"node:fs/promises";import Pk from"node:path";import qC from"node:os";var Bk=()=>process.env.BOSTRAT_STATE_DIR||Pk.join(process.env.HOME||qC.tmpdir(),".bostrat"),Dk=()=>Pk.join(Bk(),"dictation-glossary.json"),KC=100,GC=500,VC=["bostrat","globopi","codex","tmux","worktree","Deepgram","Linear","Firecracker","Tigris","WireGuard","misous","postyx","cedar","gerbil","Anthropic","Opus","Haiku","Sonnet","keyterm","monorepo","dogfood"],Ci=null,Td=null,Ad=Promise.resolve();function Ok(){let t={},e=Date.now();for(let n of VC)t[n.toLowerCase()]={term:n,count:1,lastUsed:e,source:"seed"};return t}async function jk(){return Ci||(Td??=Oi.readFile(Dk(),"utf8").then(t=>JSON.parse(t)?.terms??Ok()).catch(()=>Ok()).then(t=>Ci=t).finally(()=>Td=null),Td)}async function Ck(){let t=JSON.stringify({terms:Ci},null,2),e=Dk(),n=`${e}.${process.pid}.tmp`;await Oi.mkdir(Bk(),{recursive:!0}),await Oi.writeFile(n,t),await Oi.rename(n,e)}function zC(){return Ad=Ad.then(Ck,Ck),Ad.catch(()=>{})}async function Ed(){return Object.values(await jk()).sort((e,n)=>n.count-e.count||n.lastUsed-e.lastUsed)}var JC=t=>Math.max(1,Math.ceil(t.length/4));async function XC(){let t=[],e=0;for(let{term:n}of await Ed()){let r=JC(n);if(!(e+r>GC)&&(t.push(n),e+=r,t.length>=KC))break}return t}async function YC(t,e){let n=await jk(),r=t.toLowerCase(),o=n[r];n[r]={term:t,count:(o?.count??0)+1,lastUsed:Date.now(),source:o?.source??e}}var Mk=t=>(t||"").split(/\s+/).map(e=>e.replace(/^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu,"")).filter(Boolean),$i=t=>t.toLowerCase();function Nk(t){return!new RegExp("\\p{L}","u").test(t)||t.length<3?!1:/^[a-z]+$/.test(t)?t.length>=4:!0}async function Lk(t,e){let n=Mk(t),r=Mk(e);if(!n.length||!r.length)return[];let o=n.length,s=r.length,a=Array.from({length:o+1},()=>new Array(s+1).fill(0));for(let u=o-1;u>=0;u--)for(let f=s-1;f>=0;f--)a[u][f]=$i(n[u])===$i(r[f])?a[u+1][f+1]+1:Math.max(a[u+1][f],a[u][f+1]);let i=new Set,c=0,l=0;for(;c<o&&l<s;)$i(n[c])===$i(r[l])?(c++,l++):a[c+1][l]>=a[c][l+1]?c++:(Nk(r[l])&&i.add(r[l]),l++);for(;l<s;l++)Nk(r[l])&&i.add(r[l]);if(!i.size)return[];for(let u of i)await YC(u,"learned");return await zC(),[...i]}var As=null;function ZC(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 Fk(){if(!As)return null;let t=await Mo();if(!t||t.apiKey.slice(-6)!==As.keyTail)return null;let{keyTail:e,...n}=As;return n}async function Uk(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 XC())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=ZC(o.status,i);c.persistent&&(As={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 As=null,{transcript:(await o.json())?.results?.channels?.[0]?.alternatives?.[0]?.transcript??""}}var _d={"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 QC(t){if(!t)return null;let e=String(t).trim();if(_d[e])return e;let n=null;for(let r of Object.keys(_d))e.startsWith(r)&&(!n||r.length>n.length)&&(n=r);return n}function tM(t,e={}){let n=_d[QC(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 eM(t,e){let n=null;try{n=JSON.parse(e)}catch{}let r=n?.error?.code||n?.error?.type||"";return t===429&&/insufficient_quota/i.test(r)?{status:402,code:"credits_exhausted",persistent:!0,message:"The OpenAI account is out of credit \u2014 voice chat is down until you top up at platform.openai.com or drop a new key into ~/.bostrat/integrations.json."}:t===401||t===403?{status:502,code:"key_invalid",persistent:!0,message:"OpenAI rejected the API key (revoked, or missing Realtime access) \u2014 replace it in ~/.bostrat/integrations.json."}:{status:502,code:"mint_failed",persistent:!1,message:`OpenAI realtime session mint failed (${t}): ${e.slice(0,300)}`}}var Es=null;async function nM(){if(!Es)return null;let t=await Ta();if(!t||t.apiKey.slice(-6)!==Es.keyTail)return null;let{keyTail:e,...n}=Es;return n}async function rM(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=eM(e.status,o);s.persistent&&(Es={code:s.code,message:s.message,at:Date.now(),keyTail:t.apiKey.slice(-6)});let a=new Error(s.message);throw a.status=s.status,a.code=s.code,a}Es=null;let n=await e.json(),r=n?.value??n?.client_secret?.value;if(!r){let o=new Error("OpenAI returned no client secret");throw o.status=502,o.code="mint_failed",o}return{clientSecret:{value:r,expiresAt:n?.expires_at??n?.client_secret?.expires_at??null}}}var oM={openai:rM};async function Hk(){let t=await Ta();return t?{configured:!0,provider:t.provider,model:t.model,voice:t.voice,problem:await nM()}:{configured:!1}}async function Wk(){let t=await Ta();if(!t){let n=new Error("Voice chat is not configured \u2014 add openai.apiKey to ~/.bostrat/integrations.json");throw n.status=503,n.code="not_configured",n}let e=await oM[t.provider](t);return{provider:t.provider,model:t.model,voice:t.voice,...e}}async function qk(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=tM(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 Xk=ut.dirname(aM(import.meta.url)),Kk=Number(process.env.PORT)||3362,Mi=gb(process.env),Yk=Number(process.env.BOSTRAT_AUTO_TRIGGER_PCT)||80,so=zb(process.env.BOSTRAT_ALLOW_IPS),Cd=Jb(process.env.BOSTRAT_VIEW_FRONT_CIDRS,Ai),Zk=Yb(),Qk=process.env.BOSTRAT_ALLOW_LAN_TOKENLESS!=="1",x=Se();x.disable("x-powered-by");x.use((t,e,n)=>{if(ks(t)||so.allowed(so.clientIp(t))||Cd.allows(t))return n();e.status(403).type("text/plain").send("bostrat: access restricted to the allowlist")});x.use(rk);x.use(tk({token:Zk,enforce:Qk}));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:Ht,hostTmuxAlive:await Ye(),autoTriggerPct:Yk})});x.get("/api/sessions",async(t,e)=>{e.json({sessions:await $n()})});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 On(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(!gt(n))return e.status(400).json({error:"invalid session name"});if(!await Q(n))return e.status(404).json({error:"session not found"});try{await 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(!gt(n))return e.status(400).json({error:"invalid session name"});let r=await Q(n);if(!r)return e.status(404).json({error:"session not found"});let o=await xo(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(!gt(n))return e.status(400).json({error:"invalid session name"});try{let r=await Rp(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=ca(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 Gk=10;x.get("/api/tracks",async(t,e)=>{let n=await at(),r=n.filter(s=>s.archivedAt),o=n;if(t.query.archived!=="all"&&r.length>Gk){let s=new Set([...r].sort((a,i)=>(i.archivedAt||0)-(a.archivedAt||0)).slice(0,Gk).map(a=>a.id));o=n.filter(a=>!a.archivedAt||s.has(a.id))}e.json({tracks:o,archivedTotal:r.length,agents:ai,agentAvailability:await Fu(),tickets:await Ea()})});x.get("/api/cost",async(t,e)=>{try{let n=await at();t.query.track&&(n=n.filter(o=>o.id===t.query.track));let r=await Tb(n);if(r.cursorUsage=hu(),t.query.format==="text")return e.type("text/plain").send(Ab(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 Ib(await at(),{window:t.query.window,from:t.query.from,to:t.query.to}))}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/usage/spend",async(t,e)=>{try{let n=await Mb(await at(),{window:t.query.window,from:t.query.from,to:t.query.to});n.cursorUsage=hu(),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 Wb(await at(),{fresh:n}))}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/arcs",async(t,e)=>{try{e.json(await Sh(await at()))}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/arcs/:key",async(t,e)=>{try{let n=await vh(await at(),t.params.key);if(!n)return e.status(404).json({error:"unknown arc"});e.json(n)}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.post("/api/arcs/backfill-beats",async(t,e)=>{try{e.json({ok:!0,...await Vy()})}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 Dd(t.get(Os),{byToken:vy,legacy:()=>Sy(t.body?.session_id,t.body?.cwd)});if(n==="refused")return Cs("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&&Cs("mismatch",{track:r.track.id,box:r.box.name}),Cs(n,{track:r?.track?.id,hook:t.body?.hook_event_name});let o=await hb(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(!_k(so.clientIp(t)))return e.json({ok:!0,dropped:!0});let n=await Ek(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=>et(a.path)))).filter(Boolean).map(a=>a.toLowerCase())),s=((await uo()).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 Bs(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:Ps(),calls:ng(),latency:Ig()})});x.post("/api/github/refresh",(t,e)=>{Vd(),e.json({ok:!0})});x.get("/api/gmail/search",async(t,e)=>{try{let n=await Kb(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 Gb(String(t.params.id))})}catch(n){e.status(n?.status||502).json({error:n?.message||String(n)})}});function Md(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 Ni({repo:t=null,number:e=null,branch:n=null}){let r=t?await et(t):null;if(r&&e!=null){let s=await Ot(r,e),a=s&&await tt(s.trackId);if(a)return a}return n?(await at()).find(s=>s.workspace?.branch===n&&Md(s,t,r))??null:null}async function tx({repo:t=null,number:e=null,branch:n=null}){let r=await Ni({repo:t,number:e,branch:n}).catch(()=>null);if(r&&!r.archivedAt)return r;if(!n)return null;let o=t?await et(t).catch(()=>null):null;return(await at()).find(a=>!a.archivedAt&&a.workspace?.branch===n&&Md(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 Ni({repo:s,number:e,branch:o}).catch(()=>null);if(!c)return;let l=i.repo??(s?await et(s).catch(()=>null):null);await R({id:c.id,cwd:c.cwd},{type:"pr.state.changed",actor:"user",source:"orchestrator",payload:{ts:Date.now(),change:t,number:e,url:n,title:r,branch:o??c.workspace?.branch??null,...l?{repo:l}:{},...i}}).catch(()=>{})}async function uM(t){let e=(await at()).filter(n=>!n.archivedAt);for(let n of t??[]){if(!n.prs?.length)continue;let r=await et(n.path);if(r)for(let o of n.prs){if(await Ot(r,o.number))continue;let s=e.find(a=>a.workspace?.branch===o.headRefName&&Md(a,n.path,r));s&&await qt({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 Pg();await uM(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 h=o||"merge";if(!es.has(h))throw new Error(`invalid merge method: ${h}`);let y=Number(r);if(!Number.isInteger(y)||y<=0)throw new Error("invalid PR number");let b=await et((await Fe(n)).path);if(!b)throw new Error("repo has no GitHub remote");let S=await Ni({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 ns(n,y),await qt({repo:b,number:y,trackId:S.id,url:f,headRef:l,title:u,source:"matched"}).catch(()=>{}),!await Lo(b,y,{merge:{method:h}}))throw new Error("couldn't record the auto-merge arm");return await Hn(b,y,i===!0),await Fo(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 Ur(n,r,{method:o||"squash"});await se("merged",{number:d.number,url:f,title:u,branch:l,repo:n});let p=await et(n).catch(()=>null),m=!1;if(p&&(i===!0&&await Hn(p,d.number,!0).catch(()=>{}),c===!0&&i!==!0&&await Fo(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 h=await Ni({repo:n,number:d.number,branch:l}).catch(()=>null);h&&!h.archivedAt&&await Jr(h.id).catch(()=>{})}e.json(d)}catch(d){let p=d?.message||String(d),m=/invalid|unknown repo|required|needs a Track|no GitHub remote/i.test(p)?400:502;e.status(m).json({error:p})}});x.post("/api/git/prs/disarm-auto",async(t,e)=>{let{repo:n,number:r,headRefName:o,title:s,url:a}=t.body||{};try{let i=Number(r);if(!Number.isInteger(i)||i<=0)throw new Error("invalid PR number");let c=await et((await Fe(n)).path).catch(()=>null),l=c?await Uo(c,i).catch(()=>null):null;try{await Su(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 et(n).catch(()=>null),s=await Lg(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:jg()})});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(!es.has(i))throw new Error(`invalid merge method: ${i}`);if(!n)return e.status(400).json({error:"trackId required"});let c=await tt(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 Hn(d.repo,d.number,o===!0).catch(()=>{}),await Fo(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 Yn(d.repo);if(!m)throw new Error(`no local checkout for ${d.repo}`);let h=await Fr(m,d.number);f.push({r:p,l:d,repoPath:m,pr:h,plan:Bg(h,{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:h,plan:y}of Dg(f))try{if(y.action==="skip")d.action="skipped",d.reason=y.reason;else if(y.action==="merge")await Ur(m,p.number,{method:i}),await se("merged",{number:p.number,url:h.url,title:h.title,branch:h.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 ns(m,p.number).catch(()=>{}),d.action="updated-armed"),!await Lo(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:h.url,title:h.title,branch:h.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)){if(o===!0)for(let d of l)await Hn(d.repo,d.number,!1).catch(()=>{});if(s===!0&&o!==!0)for(let d of l)await Fo(d.repo,d.number,!1).catch(()=>{});return e.status(502).json({error:u.map(d=>`${d.repo}#${d.number}: ${d.reason}`).join("; "),results:u})}e.json({ok:!0,trackId:c.id,results:u})}catch(i){let c=i?.message||String(i),l=/invalid|required/i.test(c)?400:502;e.status(l).json({error:c})}});x.post("/api/git/prs/disarm-track",async(t,e)=>{let{trackId:n}=t.body||{};try{if(!n)return e.status(400).json({error:"trackId required"});let r=await tt(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 Uo(a.repo,a.number).catch(()=>null),c=await Yn(a.repo).catch(()=>null);c&&await Su(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 tt(n);if(!i)return e.status(404).json({error:`Track ${n} isn't on this node`});if(i.workspace?.kind==="multi"){let u=jt(i.workspace),f,d=Array.isArray(a)?a.filter(Boolean):[];if(d.length)f=d.map(h=>{let y=u.find(b=>b.name===h||b.repo===h||b.base===h||b.dir===h);if(!y)throw new Error(`invalid repo "${h}" \u2014 not tagged on this Track`);return y});else if(f=await Yo(i),!f.length)return e.status(409).json({error:"no tagged repo has work to ship"});let p=[];for(let h of f)try{let y=await Tu(h.dir,{title:r,body:o,base:s}),b=h.repo||await et(h.dir).catch(()=>null);b&&y.number&&await qt({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??h.name}}),p.push({repo:h.name,slug:b??null,ok:!0,url:y.url,number:y.number})}catch(y){p.push({repo:h.name,slug:h.repo??null,ok:!1,error:y?.message||String(y)})}let m=p.filter(h=>h.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(h=>`${h.repo}: ${h.error}`).join("; "),results:p})}let c=await Tu(i.cwd,{title:r,body:o,base:s}),l=await et(i.cwd).catch(()=>null);l&&c.number&&await qt({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 tt(t.params.id);if(!n)return e.status(404).json({error:"track not found"});let r=await Yo(n),o=new Map(r.map(a=>[a.base,a])),s=jt(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 jy(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 Ly(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 Fr(n,r),s=await et(n).catch(()=>null),a=await tx({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=jt(a.workspace).find(b=>b.base===n||s&&b.repo===s)??null,c=a.workspace?.kind==="multi",l=await Fg(n,o.number);if(!l.length)return e.status(409).json({error:"no failing checks on this PR"});let u=c&&i?ut.join("ci",i.name):"ci",f=ut.join(a.cwd,".bostrat",u),d=ut.join(".bostrat",u,`pr-${o.number}-fail.log`);await Ft.promises.mkdir(f,{recursive:!0}),await Ft.promises.writeFile(ut.join(a.cwd,d),l.map(b=>`\u2550\u2550\u2550\u2550 ${b.name}${b.link?` \xB7 ${b.link}`:""} \u2550\u2550\u2550\u2550
|
|
414
412
|
${b.log??"(no fetchable log \u2014 see link)"}
|
|
415
413
|
`).join(`
|
|
416
|
-
`));let p=l.map(b=>b.name).join(", "),m=c&&i?` in the ${i.name}/ checkout`:"",g=await oe(a.id,`CI is red on PR #${o.number} "${o.title}" (${o.url}) \u2014 branch ${o.headRefName}${m}. Failing check${l.length===1?"":"s"}: ${p}. The failure log is at ${d} in this ${c?"workspace":"worktree"} \u2014 read it, fix the root cause${c&&i?` in ${i.name}/`:""}, run the relevant tests locally, then commit and push to update the PR.`,{kind:"ci-fix"}),y=s;y&&(await Wt({repo:y,number:o.number,trackId:a.id,url:o.url,headRef:o.headRefName,title:o.title,source:"matched"}),await ih(y,o.number,{headSha:o.headSha,trackId:a.id}).catch(()=>{})),await se("fix-delegated",{number:o.number,url:o.url,title:o.title,branch:o.headRefName,repo:n,track:a,extra:{checks:l.map(b=>b.name)}}),e.json({ok:!0,leadBox:g.leadBox,queued:!!g.queued,checks:l.map(b=>b.name),log:d})}catch(o){let s=o?.message||String(o),a=/unknown repo|invalid|required/i.test(s)?400:/not found|no active Track/i.test(s)?404:502;e.status(a).json({error:s})}});x.post("/api/git/prs/reconcile",async(t,e)=>{let{repo:n,number:r}=t.body||{};try{let o=await Lr(n,r);if(o.mergeable!=="CONFLICTING")return e.status(409).json({error:"this PR does not conflict with its base branch"});let s=await tt(n).catch(()=>null),a=await Yk({repo:n,number:r,branch:o.headRefName});if(!a)return e.status(404).json({error:`no active Track owns PR #${o.number} (branch "${o.headRefName}")`});let i=Dt(a.workspace).find(f=>f.base===n||s&&f.repo===s)??null,c=a.workspace?.kind==="multi"&&i?` In the ${i.name}/ checkout:`:"",l=await oe(a.id,`PR #${o.number} "${o.title}" (${o.url}) \u2014 branch ${o.headRefName} \u2014 has merge conflicts with ${o.baseRefName}.${c} Bring ${o.baseRefName} into this branch (fetch origin, then merge origin/${o.baseRefName} \u2014 don't rebase this pushed PR branch), and resolve every conflict so that BOTH sides' intent is preserved. Run the relevant tests, then commit and push to update the PR. If any conflict is a genuine ambiguity \u2014 where you can't confidently tell which side's behavior should win, or reconciling would drop work from either side \u2014 STOP and ask me here rather than guessing, laying out the specific choice and your recommendation.`,{kind:"reconcile"}),u=s;u&&(await Wt({repo:u,number:o.number,trackId:a.id,url:o.url,headRef:o.headRefName,title:o.title,source:"matched"}),await ch(u,o.number,{headSha:o.headSha,trackId:a.id}).catch(()=>{})),await se("reconcile-delegated",{number:o.number,url:o.url,title:o.title,branch:o.headRefName,repo:n,track:a,extra:{base:o.baseRefName}}),e.json({ok:!0,leadBox:l.leadBox,queued:!!l.queued,base:o.baseRefName})}catch(o){let s=o?.message||String(o),a=/unknown repo|invalid|required/i.test(s)?400:/not found|no active Track/i.test(s)?404:502;e.status(a).json({error:s})}});x.post("/api/tracks/:id/pr-offer",async(t,e)=>{let{offerId:n,action:r}=t.body||{};try{let o=null;if(Array.isArray(t.body?.actions)){if(o=Fw.filter(f=>t.body.actions.includes(f)),!o.length)return e.status(400).json({error:"actions must include commit, rebase, push and/or pr"})}else if(r==="pr")o=["commit","push","pr"];else if(r==="push")o=["commit","push"];else if(r!=="dismiss")return e.status(400).json({error:'offerId and actions ["commit"|"rebase"|"push"|"pr"] (or action "dismiss") required'});if(!n)return e.status(400).json({error:"offerId required"});let s=o!=null&&t.body?.notify===!0,a=await Q(t.params.id);if(!a)return e.status(404).json({error:"track not found"});let i=await D(a),c=i.find(f=>f.type==="pr.offered"&&f.payload?.offerId===n);if(!c)return e.status(404).json({error:"unknown offer"});if(i.some(f=>f.type==="pr.offer.answered"&&f.payload?.offerId===n))return e.status(409).json({error:"offer already answered"});let l=null;o&&(l=await oe(a.id,La(o,{branch:c.payload.branch,base:c.payload.base,toPr:c.payload.toPr||null,repos:c.payload.repos||null,offeredRebase:Array.isArray(c.payload.offered)&&c.payload.offered.includes("rebase")}),{kind:o.includes("pr")?"pr-go":"push-go"}));let u=o?o.includes("pr")?"pr":o.includes("push")?"push":o.includes("commit")?"commit":"rebase":"dismiss";await R({id:a.id,cwd:a.cwd},{type:"pr.offer.answered",actor:"user",source:"orchestrator",boxId:c.boxId,payload:{ts:Date.now(),offerId:n,action:u,actions:o,...s?{notify:!0}:{}}}),s&&Ay(a.id,{actions:o}),e.json({ok:!0,offerId:n,action:u,actions:o,notify:s,queued:!!l?.queued})}catch(o){let s=o?.message||String(o),a=/required|invalid/i.test(s)?400:/not found|not running|archived/i.test(s)?409:502;e.status(a).json({error:s})}});var vs=t=>e=>({...e,url:nk(e,t.headers)});x.get("/api/views",async(t,e)=>{try{let n=await pa({trackId:t.query.track||void 0});e.json({views:n.map(vs(t)),maxLive:rn})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/views/:id",async(t,e)=>{let n=await um(t.params.id);if(!n)return e.status(404).json({error:"view not found"});e.json({view:vs(t)(n),session:br(n.slug)})});x.post("/api/views",async(t,e)=>{let{trackId:n,name:r,cmd:o,cwd:s,port:a}=t.body||{};try{let i=n?await Q(n):null;if(!i&&s&&(i=(await ot()).find(u=>u.cwd&&(s===u.cwd||s.startsWith(u.cwd+lt.sep)))||null),!i)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});let c=await Dc({trackId:i.id,name:r,cmd:o,cwd:i.cwd,serveCwd:s,port:a,box:i.leadBox,branch:i.workspace?.branch??null});e.json({ok:!0,view:vs(t)(c)})}catch(i){let c=i?.message||String(i),l=i?.status??(/cap reached/i.test(c)?409:/required|invalid|already owned|no free/i.test(c)?400:500);e.status(l).json({error:c})}});x.post("/api/views/:id/stop",async(t,e)=>{try{e.json({ok:!0,view:vs(t)(await dm(t.params.id))})}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});x.post("/api/views/:id/restart",async(t,e)=>{try{e.json({ok:!0,view:vs(t)(await fm(t.params.id))})}catch(n){let r=n?.message||String(n),o=/not found/i.test(r)?404:/cap reached|no known command/i.test(r)?409:500;e.status(o).json({error:r})}});x.delete("/api/views/:id",async(t,e)=>{try{e.json(await pm(t.params.id))}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});var aM=[["linear",Dn,qc],["jira",ba,Hc]],Ad=new Map;x.get("/api/integrations/ticketing",async(t,e)=>{try{let n=await Uc(),r={};for(let[o,s,a]of aM){let i=await s();if(!i){Ad.delete(o),r[o]={configured:!1};continue}let c=Ad.get(o);if(!c||Date.now()-c.at>300*1e3){let l;try{l={configured:!0,ok:!0,...await a(i)}}catch(u){l={configured:!0,ok:!1,error:u?.message||String(u)}}c={at:Date.now(),body:l},Ad.set(o,c)}r[o]={...c.body,flow:n},o==="jira"&&i.tokenExpiresAt&&(r[o].tokenExpiresAt=i.tokenExpiresAt)}e.json(r)}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/tickets/flow",async(t,e)=>{try{e.json({flow:await Uc()})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.post("/api/tickets/flow",async(t,e)=>{let{trackId:n,cwd:r,set:o,actor:s}=t.body||{};try{let a=await Om(o),i=n?await Q(n):null;!i&&r&&(i=(await ot()).find(l=>l.cwd&&(r===l.cwd||r.startsWith(l.cwd+lt.sep)))||null),i&&await R({id:i.id,cwd:i.cwd},{type:"ticket.flow.changed",actor:s==="user"?"user":"agent",source:"orchestrator",payload:{ts:Date.now(),set:{...o},flow:a}}).catch(()=>{}),e.json({ok:!0,flow:a})}catch(a){let i=a?.message||String(a);e.status(/unknown|nothing to set/i.test(i)?400:500).json({error:i})}});x.post("/api/tickets/link",async(t,e)=>{let{trackId:n,cwd:r,ref:o}=t.body||{};try{let s=n?await Q(n):null;if(!s&&r&&(s=(await ot()).find(c=>c.cwd&&(r===c.cwd||r.startsWith(c.cwd+lt.sep)))||null),!s)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});if(!o)return e.status(400).json({error:"ref (issue URL or key) required"});let a=await vr(s,o,{source:n?"api":"cli"});e.json({ok:!0,trackId:s.id,ticket:a})}catch(s){let a=s?.message||String(s);e.status(/not a Linear|not found|no ticketing|required/i.test(a)?400:502).json({error:a})}});x.get("/api/tickets/status",async(t,e)=>{let{cwd:n,track:r}=t.query;try{let o=r?await Q(r):null;if(!o&&n&&(o=(await ot()).find(a=>a.cwd&&(n===a.cwd||String(n).startsWith(a.cwd+lt.sep)))||null),!o)return e.status(400).json({error:"track (or a cwd inside a Track workspace) required"});e.json({trackId:o.id,configured:await Gm(),ticket:await ge(o.id)})}catch(o){e.status(500).json({error:o?.message||String(o)})}});x.get("/api/tracks/:id/ticket",async(t,e)=>{e.json({ticket:await ge(t.params.id)})});x.post("/api/tracks/:id/ticket",async(t,e)=>{try{let n=await Q(t.params.id);if(!n)return e.status(404).json({error:"track not found"});let r=await vr(n,(t.body||{}).ref,{source:"api"});e.json({ok:!0,ticket:r})}catch(n){let r=n?.message||String(n);e.status(/not a Linear|not found|no ticketing|required/i.test(r)?400:502).json({error:r})}});x.delete("/api/tracks/:id/ticket",async(t,e)=>{e.json({ok:!0,removed:await nl(t.params.id)})});var Mi=t=>/no ticket linked|unknown offer|unknown state/i.test(t)?404:/already answered/i.test(t)?409:/required|no ticketing/i.test(t)?400:502;x.get("/api/tracks/:id/ticket/states",async(t,e)=>{try{e.json(await Xm(t.params.id))}catch(n){let r=n?.message||String(n);e.status(Mi(r)).json({error:r})}});x.post("/api/tracks/:id/ticket/state",async(t,e)=>{try{e.json({ok:!0,...await rl(t.params.id,(t.body||{}).stateId)})}catch(n){let r=n?.message||String(n);e.status(Mi(r)).json({error:r})}});x.post("/api/tracks/:id/ticket/comment",async(t,e)=>{try{e.json({ok:!0,...await Ym(t.params.id,(t.body||{}).body)})}catch(n){let r=n?.message||String(n);e.status(Mi(r)).json({error:r})}});x.post("/api/tracks/:id/ticket-offer",async(t,e)=>{let{offerId:n,accept:r}=t.body||{};try{let o=await Q(t.params.id);if(!o)return e.status(404).json({error:"track not found"});e.json({ok:!0,...await Zm(o,n,r===!0)})}catch(o){let s=o?.message||String(o);e.status(Mi(s)).json({error:s})}});x.post("/api/tickets/propose",async(t,e)=>{let{trackId:n,cwd:r,plan:o}=t.body||{};try{let s=n?await Q(n):null;if(!s&&r&&(s=(await ot()).find(i=>i.cwd&&(r===i.cwd||r.startsWith(i.cwd+lt.sep)))||null),!s)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});e.json({ok:!0,trackId:s.id,proposal:await th(s,o)})}catch(s){let a=s?.message||String(s);e.status(/no ticketing|are off|needs an epic|too many|required/i.test(a)?400:502).json({error:a})}});x.get("/api/tickets/teams",async(t,e)=>{try{e.json({teams:await Qm()})}catch(n){let r=n?.message||String(n);e.status(/no ticketing/i.test(r)?400:502).json({error:r})}});x.post("/api/tracks/:id/ticket-create-offer",async(t,e)=>{let{proposalId:n,accept:r,teamId:o}=t.body||{};try{let s=await Q(t.params.id);if(!s)return e.status(404).json({error:"track not found"});e.json({ok:!0,...await eh(s,n,r===!0,{teamId:o||null})})}catch(s){let a=s?.message||String(s);e.status(/unknown proposal|unknown team/i.test(a)?404:/already answered/i.test(a)?409:/required|no ticketing|pick one/i.test(a)?400:502).json({error:a})}});x.get("/api/mocks",async(t,e)=>{try{e.json({mocks:await Nn({trackId:t.query.track||void 0,group:t.query.group||void 0})})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/mocks/:id",async(t,e)=>{let n=await nn(t.params.id);if(!n)return e.status(404).json({error:"mock not found"});e.json({mock:n})});x.get("/api/mocks/:id/png",async(t,e)=>{let n=await nn(t.params.id);if(!n)return e.status(404).json({error:"mock not found"});let r=Ie(n,t.query.v??null);if(!r)return e.status(409).json({error:`mock is ${n.state} \u2014 no captured version yet`});e.set("cache-control","private, max-age=31536000, immutable"),e.sendFile(Ht(n,r.v).png,o=>{o&&!e.headersSent&&e.status(404).json({error:"rendered artifact missing"})})});x.get("/api/mocks/:id/html",async(t,e)=>{let n=await nn(t.params.id);if(!n)return e.status(404).json({error:"mock not found"});let r=Ie(n,t.query.v??null);if(!r?.htmlRel)return e.status(404).json({error:"this version has no HTML source (image-sourced)"});e.set("content-security-policy","sandbox"),e.sendFile(Ht(n,r.v).html,o=>{o&&!e.headersSent&&e.status(404).json({error:"source artifact missing"})})});x.post("/api/mocks",async(t,e)=>{let n=Buffer.isBuffer(t.body)?t.body:null,r=n?t.query:t.body||{},{trackId:o,name:s,cwd:a,width:i,group:c}=r;try{let l=o?await Q(o):null;if(!l&&a&&(l=(await ot()).find(p=>p.cwd&&(a===p.cwd||a.startsWith(p.cwd+lt.sep)))||null),!l)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});let u=(l.boxes||[]).find(d=>d.name===l.leadBox),f=await qp({trackId:l.id,name:s,html:n?null:r.html,image:n,cwd:l.cwd,width:i,group:c,box:l.leadBox,agent:u?.agent||null});e.json({ok:!0,mock:f})}catch(l){let u=l?.message||String(l);e.status(/required|invalid|must be/i.test(u)?400:500).json({error:u})}});x.post("/api/mocks/:id/update",async(t,e)=>{let n=Buffer.isBuffer(t.body)?t.body:null;try{let r=await Kp(t.params.id,{html:n?null:(t.body||{}).html,image:n,width:n?t.query.width:(t.body||{}).width});e.json({ok:!0,mock:r})}catch(r){let o=r?.message||String(r),s=/not found/i.test(o)?404:/required|invalid|must be/i.test(o)?400:500;e.status(s).json({error:o})}});x.post("/api/mocks/:id/pick",async(t,e)=>{try{let{mock:n,siblings:r}=await Vp(t.params.id),o=r.length?` over ${r.map(s=>`"${s.name}"`).join(", ")}`:"";await oe(n.trackId,`Design decision: go with "${n.name}" (mock:${n.id})${o} \u2014 proceed with this design.`,{kind:"mock-picked"}).catch(()=>{}),e.json({ok:!0,mock:n,siblings:r})}catch(n){let r=n?.message||String(n),o=/not found/i.test(r)?404:/nothing to pick/i.test(r)?409:500;e.status(o).json({error:r})}});x.delete("/api/mocks/:id",async(t,e)=>{try{let n=(await Zg(t.params.id)).map(r=>({id:r.id,name:r.name}));e.json({...await zp(t.params.id),...n.length?{danglingFlows:n}:{}})}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});x.get("/api/flows",async(t,e)=>{try{e.json({flows:await Vg({trackId:t.query.track||void 0})})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/flows/:id",async(t,e)=>{let n=await zg(t.params.id);if(!n)return e.status(404).json({error:"flow not found"});e.json({flow:n})});x.post("/api/flows",async(t,e)=>{let{trackId:n,name:r,steps:o,cwd:s}=t.body||{};try{let a=n?await Q(n):null;if(!a&&s&&(a=(await ot()).find(u=>u.cwd&&(s===u.cwd||s.startsWith(u.cwd+lt.sep)))||null),!a)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});let i=(a.boxes||[]).find(l=>l.name===a.leadBox),c=await Jg({trackId:a.id,name:r,steps:o,cwd:a.cwd,box:a.leadBox,agent:i?.agent||null});e.json({ok:!0,flow:c})}catch(a){let i=a?.message||String(a),c=/already exists/i.test(i)?409:/required|invalid|no mock|ambiguous|over \d+ chars|must be|no captured/i.test(i)?400:500;e.status(c).json({error:i})}});x.post("/api/flows/:id/update",async(t,e)=>{try{let{steps:n,note:r}=t.body||{};e.json({ok:!0,flow:await Xg(t.params.id,{steps:n,note:r})})}catch(n){let r=n?.message||String(n),o=/not found/i.test(r)?404:/required|invalid|no mock|ambiguous|over \d+ chars|must be|no captured/i.test(r)?400:500;e.status(o).json({error:r})}});x.delete("/api/flows/:id",async(t,e)=>{try{e.json(await Yg(t.params.id))}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});x.get("/api/runbooks",async(t,e)=>{try{let n=t.query.track||void 0;if(!n&&t.query.cwd){let r=await ot(),o=String(t.query.cwd);if(n=r.find(s=>s.cwd&&(o===s.cwd||o.startsWith(s.cwd+lt.sep)))?.id,!n)return e.json({runbooks:[]})}e.json({runbooks:await wa({trackId:n,status:t.query.status||void 0})})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/runbooks/:id",async(t,e)=>{let n=await Lc(t.params.id);if(!n)return e.status(404).json({error:"runbook not found"});e.json({runbook:n})});x.get("/api/runbooks/:id/md",async(t,e)=>{let n=await Lc(t.params.id);if(!n)return e.status(404).json({error:"runbook not found"});let r=ym(n,t.query.v??null);if(!r)return e.status(404).json({error:"no such version"});e.set("content-security-policy","sandbox"),e.set("cache-control","private, max-age=31536000, immutable"),e.type("text/markdown; charset=utf-8"),e.sendFile(Me(n,r.v),o=>{o&&!e.headersSent&&e.status(404).json({error:"runbook artifact missing"})})});x.post("/api/runbooks",async(t,e)=>{let{trackId:n,name:r,md:o,cwd:s}=t.body||{};try{let a=n?await Q(n):null;if(!a&&s&&(a=(await ot()).find(u=>u.cwd&&(s===u.cwd||s.startsWith(u.cwd+lt.sep)))||null),!a)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});let i=(a.boxes||[]).find(l=>l.name===a.leadBox),c=await xm({trackId:a.id,trackName:a.name,name:r,md:o,cwd:a.cwd,box:a.leadBox,agent:i?.agent||null});e.json({ok:!0,runbook:c})}catch(a){let i=a?.message||String(a);e.status(/required|invalid|over the|secret VALUE/i.test(i)?400:500).json({error:i})}});x.post("/api/runbooks/:id/update",async(t,e)=>{try{let{md:n,note:r}=t.body||{};e.json({ok:!0,runbook:await Sm(t.params.id,{md:n,note:r})})}catch(n){let r=n?.message||String(n),o=/not found/i.test(r)?404:/orphaned/i.test(r)?410:/required|invalid|over the|secret VALUE/i.test(r)?400:500;e.status(o).json({error:r})}});x.post("/api/runbooks/:id/status",async(t,e)=>{try{e.json({ok:!0,runbook:await vm(t.params.id,(t.body||{}).status)})}catch(n){let r=n?.message||String(n),o=/not found/i.test(r)?404:/orphaned/i.test(r)?410:/must be/i.test(r)?400:500;e.status(o).json({error:r})}});x.delete("/api/runbooks/:id",async(t,e)=>{try{e.json(await Tm(t.params.id))}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});x.get("/api/changelog/draft",async(t,e)=>{try{let n=sk(t.query.since||null);e.json({since:n,repos:hd(),prs:await ak(n)})}catch(n){let r=n?.message||String(n);e.status(/unparseable/i.test(r)?400:500).json({error:r})}});x.post("/api/changelog/publish",async(t,e)=>{let{md:n,day:r,trackId:o,cwd:s}=t.body||{};try{let a=o?await Q(o):null;if(!a&&s&&(a=(await ot()).find(c=>c.cwd&&(s===c.cwd||String(s).startsWith(c.cwd+lt.sep)))||null),!a)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});e.json({ok:!0,entry:await ik(a,{md:n,day:r})})}catch(a){let i=a?.message||String(a);e.status(/required|invalid|no `- `|over the|secret VALUE/i.test(i)?400:500).json({error:i})}});var iM=120,Ed=400,Ci=null;x.post("/api/notify",async(t,e)=>{let{title:n,body:r,urgency:o,cwd:s,trackId:a}=t.body||{};try{let i=typeof r=="string"?r.trim():"";if(!i)return e.status(400).json({error:"body (the message) required"});if(i.length>Ed)return e.status(400).json({error:`message is ${i.length} chars \u2014 over the ${Ed} cap; a notification is a nudge, put the detail in chat`});let c=Ro(`${n||""}
|
|
417
|
-
${i}`);if(c.length)return e.status(400).json({error:`notification appears to contain a secret VALUE (${c.join(", ")}) \u2014 notifications ship off-box; never put a credential in one`});let l=a?await
|
|
414
|
+
`));let p=l.map(b=>b.name).join(", "),m=c&&i?` in the ${i.name}/ checkout`:"",h=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 qt({repo:y,number:o.number,trackId:a.id,url:o.url,headRef:o.headRefName,title:o.title,source:"matched"}),await uh(y,o.number,{headSha:o.headSha,trackId:a.id}).catch(()=>{})),await 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:h.leadBox,queued:!!h.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 Fr(n,r);if(o.mergeable!=="CONFLICTING")return e.status(409).json({error:"this PR does not conflict with its base branch"});let s=await et(n).catch(()=>null),a=await tx({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=jt(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 qt({repo:u,number:o.number,trackId:a.id,url:o.url,headRef:o.headRefName,title:o.title,source:"matched"}),await dh(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=Ww.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 tt(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,Ha(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&&Ry(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 _s=t=>e=>({...e,url:sk(e,t.headers)});x.get("/api/views",async(t,e)=>{try{let n=await ga({trackId:t.query.track||void 0});e.json({views:n.map(_s(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 pm(t.params.id);if(!n)return e.status(404).json({error:"view not found"});e.json({view:_s(t)(n),session:xr(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 tt(n):null;if(!i&&s&&(i=(await at()).find(u=>u.cwd&&(s===u.cwd||s.startsWith(u.cwd+ut.sep)))||null),!i)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});let c=await Fc({trackId:i.id,name:r,cmd:o,cwd:i.cwd,serveCwd:s,port:a,box:i.leadBox,branch:i.workspace?.branch??null});e.json({ok:!0,view:_s(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:_s(t)(await mm(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:_s(t)(await hm(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 wm(t.params.id))}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});var dM=[["linear",jn,Vc],["jira",va,Kc]],Id=new Map;x.get("/api/integrations/ticketing",async(t,e)=>{try{let n=await qc(),r={};for(let[o,s,a]of dM){let i=await s();if(!i){Id.delete(o),r[o]={configured:!1};continue}let c=Id.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},Id.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 qc()})}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 Nm(o),i=n?await tt(n):null;!i&&r&&(i=(await at()).find(l=>l.cwd&&(r===l.cwd||r.startsWith(l.cwd+ut.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 tt(n):null;if(!s&&r&&(s=(await at()).find(c=>c.cwd&&(r===c.cwd||r.startsWith(c.cwd+ut.sep)))||null),!s)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});if(!o)return e.status(400).json({error:"ref (issue URL or key) required"});let a=await Ar(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 tt(r):null;if(!o&&n&&(o=(await at()).find(a=>a.cwd&&(n===a.cwd||String(n).startsWith(a.cwd+ut.sep)))||null),!o)return e.status(400).json({error:"track (or a cwd inside a Track workspace) required"});e.json({trackId:o.id,configured:await Jm(),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 tt(t.params.id);if(!n)return e.status(404).json({error:"track not found"});let r=await Ar(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 sl(t.params.id)})});var Bi=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 Qm(t.params.id))}catch(n){let r=n?.message||String(n);e.status(Bi(r)).json({error:r})}});x.post("/api/tracks/:id/ticket/state",async(t,e)=>{try{e.json({ok:!0,...await al(t.params.id,(t.body||{}).stateId)})}catch(n){let r=n?.message||String(n);e.status(Bi(r)).json({error:r})}});x.post("/api/tracks/:id/ticket/comment",async(t,e)=>{try{e.json({ok:!0,...await th(t.params.id,(t.body||{}).body)})}catch(n){let r=n?.message||String(n);e.status(Bi(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 tt(t.params.id);if(!o)return e.status(404).json({error:"track not found"});e.json({ok:!0,...await eh(o,n,r===!0)})}catch(o){let s=o?.message||String(o);e.status(Bi(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 tt(n):null;if(!s&&r&&(s=(await at()).find(i=>i.cwd&&(r===i.cwd||r.startsWith(i.cwd+ut.sep)))||null),!s)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});e.json({ok:!0,trackId:s.id,proposal:await rh(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 nh()})}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 tt(t.params.id);if(!s)return e.status(404).json({error:"track not found"});e.json({ok:!0,...await oh(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 Pn({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(Wt(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(Wt(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 tt(o):null;if(!l&&a&&(l=(await at()).find(p=>p.cwd&&(a===p.cwd||a.startsWith(p.cwd+ut.sep)))||null),!l)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});let u=(l.boxes||[]).find(d=>d.name===l.leadBox),f=await Vp({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 zp(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 Xp(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 ey(t.params.id)).map(r=>({id:r.id,name:r.name}));e.json({...await Yp(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 Xg({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 Yg(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 tt(n):null;if(!a&&s&&(a=(await at()).find(u=>u.cwd&&(s===u.cwd||s.startsWith(u.cwd+ut.sep)))||null),!a)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});let i=(a.boxes||[]).find(l=>l.name===a.leadBox),c=await Zg({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 Qg(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 ty(t.params.id))}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});x.get("/api/runbooks",async(t,e)=>{try{let n=t.query.track||void 0;if(!n&&t.query.cwd){let r=await at(),o=String(t.query.cwd);if(n=r.find(s=>s.cwd&&(o===s.cwd||o.startsWith(s.cwd+ut.sep)))?.id,!n)return e.json({runbooks:[]})}e.json({runbooks:await ka({trackId:n,status:t.query.status||void 0})})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/runbooks/:id",async(t,e)=>{let n=await Hc(t.params.id);if(!n)return e.status(404).json({error:"runbook not found"});e.json({runbook:n})});x.get("/api/runbooks/:id/md",async(t,e)=>{let n=await Hc(t.params.id);if(!n)return e.status(404).json({error:"runbook not found"});let r=xm(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 tt(n):null;if(!a&&s&&(a=(await at()).find(u=>u.cwd&&(s===u.cwd||s.startsWith(u.cwd+ut.sep)))||null),!a)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});let i=(a.boxes||[]).find(l=>l.name===a.leadBox),c=await Tm({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 Em(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 _m(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=ck(t.query.since||null);e.json({since:n,repos:yd(),prs:await lk(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 tt(o):null;if(!a&&s&&(a=(await at()).find(c=>c.cwd&&(s===c.cwd||String(s).startsWith(c.cwd+ut.sep)))||null),!a)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});e.json({ok:!0,entry:await uk(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 fM=120,Rd=400,Pi=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>Rd)return e.status(400).json({error:`message is ${i.length} chars \u2014 over the ${Rd} cap; a notification is a nudge, put the detail in chat`});let c=Oo(`${n||""}
|
|
415
|
+
${i}`);if(c.length)return e.status(400).json({error:`notification appears to contain a secret VALUE (${c.join(", ")}) \u2014 notifications ship off-box; never put a credential in one`});let l=a?await tt(a):null;if(!l&&s&&(l=(await at()).find(m=>m.cwd&&(s===m.cwd||s.startsWith(m.cwd+ut.sep)))||null),!l)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});let u=(l.boxes||[]).find(p=>p.name===l.leadBox),f=(p,m)=>String(p).replace(/\s+/g," ").trim().slice(0,m),d={title:n?f(n,fM):l.name,body:f(i,Rd),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:Pi?Pi.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 Uk(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 Mo(),terms:await Ed(),problem:await Fk()})});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 Lk(n,r)})});x.get("/api/tracks/:id/voice-chat",async(t,e)=>{if(!await tt(t.params.id))return e.status(404).json({error:"track not found"});e.json(await Hk())});x.post("/api/tracks/:id/voice-chat/session",async(t,e)=>{try{let n=await tt(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 Wk())}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 tt(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 qk(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 Ku({name:n,cwd:r,repo:o,repos:s,agent:a,skipPermissions:i!==!1}),u=null;if(c)try{await Ar(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 tt(t.params.id);if(!n)return e.status(404).json({error:"track not found"});e.json({track:n,handoff:ex(ms(t.params.id)),subtasks:Xr(t.params.id).map(Is)})});x.delete("/api/tracks/:id",async(t,e)=>{try{let n=await Ky(t.params.id,{cascadeMocks:t.query.mocks!=="0",cascadeRunbooks:t.query.runbooks==="1"});await sl(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 Jr(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 Jy(t.params.id)})}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:/not archived/i.test(r)?409:500).json({error:r})}});x.get("/api/tracks/:id/messages",async(t,e)=>{let n=await Uu(t.params.id);if(!n)return e.status(404).json({error:"track not found"});e.json(n)});x.get("/api/tracks/:id/events",async(t,e)=>{let n=await tt(t.params.id);if(!n)return e.status(404).json({error:"track not found"});e.set({"content-type":"text/event-stream","cache-control":"no-cache",connection:"keep-alive","x-accel-buffering":"no"}),e.flushHeaders();let r=i=>e.write(`id: ${i.seq}
|
|
418
416
|
data: ${JSON.stringify(i)}
|
|
419
417
|
|
|
420
|
-
`),o=Number(t.query.since??t.headers["last-event-id"])||0;for(let i of await
|
|
418
|
+
`),o=Number(t.query.since??t.headers["last-event-id"])||0;for(let i of await L(n))i.seq>o&&r(i);let s=dp(n.id,r),a=setInterval(()=>e.write(`: hb
|
|
421
419
|
|
|
422
|
-
`),25e3);t.on("close",()=>{clearInterval(a),s()})});x.get("/api/tracks/:id/search",async(t,e)=>{let n=await
|
|
423
|
-
`);x.post("/api/tracks/:id/memory/consolidate",async(t,e)=>{try{e.json({ok:!0,...await oe(t.params.id,
|
|
420
|
+
`),25e3);t.on("close",()=>{clearInterval(a),s()})});x.get("/api/tracks/:id/search",async(t,e)=>{let n=await tt(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=To(await L(n)),a=na(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 cf()})});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 lf(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 pM=["Run a memory consolidation pass on this workspace's `.bostrat/memory/` directory:","1. Read `MEMORY.md` and every fact file.","2. Merge duplicates and facts that describe the same thing (keep the best-written one; fold unique details in; give the survivor a `supersedes:` entry naming what it absorbed, delete the absorbed file, and remove its index line).","3. Resolve contradictions in favor of the newest evidence \u2014 check the code where feasible before deciding.","4. Mark clearly-stale facts: correct them in place or delete them (file + index line).","5. Bump `metadata.modified` (ISO-8601, now) on every fact you touch.","6. Keep `MEMORY.md` one line per fact, under 200 lines.","When done, summarize what you merged, corrected, and deleted \u2014 that summary is the review record."].join(`
|
|
421
|
+
`);x.post("/api/tracks/:id/memory/consolidate",async(t,e)=>{try{e.json({ok:!0,...await oe(t.params.id,pM)})}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 Yy(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 tt(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 iy(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 tt(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=ut.basename(String(t.params.file||""));if(!r||!$u(ut.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(ut.join(Ru(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 mM=/^[0-9a-fA-F-]{8,64}$/;x.get("/api/tracks/:id/chat-images/:box/:uuid/:idx",async(t,e)=>{try{let n=await tt(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(!mM.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=sM.createInterface({input:Ft.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?hc(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 qu(t.params.id))}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:/archived/i.test(r)?409:500).json({error:r})}});x.delete("/api/tracks/:id/queue",async(t,e)=>{try{let{removed:n,restored:r,draftRev:o}=await Ey(t.params.id);e.json({ok:!0,removed:n.filter(s=>s.kind!=="ship-notify"),restored:r,draftRev:o})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.post("/api/tracks/:id/answer-prompt",async(t,e)=>{let{promptId:n,answers:r,text:o}=t.body||{};try{e.json(await Hu(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 Wy({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 Ay(t.params.id,{promptId:n,questionIndex:r}))}catch(o){let s=o?.message||String(o),a=/not found/i.test(s)?404:/no pending prompt|changed since|not running|not supported|out of range|review screen/i.test(s)?409:500;e.status(a).json({error:s})}});x.put("/api/tracks/:id/draft",async(t,e)=>{let{text:n,rev:r}=t.body||{};try{e.json(await My(t.params.id,typeof n=="string"?n:"",{rev:typeof r=="number"?r:null}))}catch(o){let s=o?.message||String(o),a=/not found/i.test(s)?404:/required/i.test(s)?400:500;e.status(a).json({error:s})}});x.get("/api/tracks/:id/usage",async(t,e)=>{let n=await tt(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 Si(r,{fresh:o}))});x.post("/api/tracks/:id/model",async(t,e)=>{let{model:n}=t.body||{};try{e.json(await Ny(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 nb(t.params.id,{agent:n,task:r,skipPermissions:o!==!1,reason:s});e.json({ok:!0,subtask:Is(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:Xr(t.params.id).map(Is)})});x.get("/api/tracks/:id/subtask/:sid/messages",async(t,e)=>{let n=await Ty(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:Is(Yu(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:Is(ob(t.params.sid))})}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:409).json({error:r})}});function Is(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 zu(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 $y(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(ex(ms(t.params.id))??{running:!1})});function ex(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:Ws(),paths:Af,trace:await Tf({limit:n})})});var $d=ut.resolve(Xk,"..","..","client","dist");Ft.existsSync($d)&&(x.use(Se.static($d)),x.get(/^(?!\/api\/).*/,(t,e)=>e.sendFile(ut.join($d,"index.html"))));var nx=iM.createServer(x),hM=new lM({noServer:!0}),rx=(t,e,n)=>{if(!(ks(t)||so.allowed(so.clientIp(t))||Cd.allows(t))){e.write(`HTTP/1.1 403 Forbidden\r
|
|
424
422
|
\r
|
|
425
|
-
`),e.destroy();return}if(
|
|
423
|
+
`),e.destroy();return}if(ok(t,e,n))return;if(!ek(t,Zk,{enforce:Qk})){e.write(`HTTP/1.1 401 Unauthorized\r
|
|
426
424
|
\r
|
|
427
|
-
`),e.destroy();return}let r=
|
|
425
|
+
`),e.destroy();return}let r=Bf(t.url);if(!r.ok){e.write(`HTTP/1.1 ${r.status} ${r.status===400?"Bad Request":"Not Found"}\r
|
|
428
426
|
\r
|
|
429
|
-
`),e.destroy();return}let o=r.sessionName;if(!
|
|
427
|
+
`),e.destroy();return}let o=r.sessionName;if(!gt(o)){e.write(`HTTP/1.1 400 Bad Request\r
|
|
430
428
|
\r
|
|
431
|
-
`),e.destroy();return}let{cols:s,rows:a}=r;
|
|
429
|
+
`),e.destroy();return}let{cols:s,rows:a}=r;hM.handleUpgrade(t,e,n,i=>Mf(i,o,{cols:s,rows:a}))};nx.on("upgrade",rx);var ox=process.env.BOSTRAT_CERT_DIR||ut.join(process.env.HOME||Od.tmpdir(),".bostrat","certs"),Vk=Number(process.env.BOSTRAT_TLS_PORT)||3443,zk=ut.join(ox,"bostrat.globopi.key"),Jk=ut.join(ox,"bostrat.globopi.crt");if(Ft.existsSync(zk)&&Ft.existsSync(Jk)){let t=cM.createServer({key:Ft.readFileSync(zk),cert:Ft.readFileSync(Jk)},x);t.on("upgrade",rx),t.on("error",e=>{console.error(`[bostrat-agent] https listener failed (${e?.code||e?.message}) \u2014 continuing HTTP-only`)}),t.listen(Vk,Mi,()=>{console.log(`[bostrat-agent] https listening on ${Mi||"0.0.0.0"}:${Vk}`)})}nx.listen(Kk,Mi,()=>{console.log(`[bostrat-agent] listening on ${Mi||"0.0.0.0"}:${Kk}`),console.log(`[bostrat-agent] tmux socket: ${Ht}`),console.log(`[bostrat-agent] auto-trigger default: ${Yk}%`),console.log(`[bostrat-agent] ip allowlist: ${so.open?"OPEN":process.env.BOSTRAT_ALLOW_IPS||"CLOSED (BOSTRAT_ALLOW_IPS unset)"}`+(Cd.active?` \xB7 view front-door: ${process.env.BOSTRAT_VIEW_FRONT_CIDRS} (registered views only)`:"")),tb().then(t=>t&&console.log("[bostrat-agent] reconciled interrupted handoff(s)")).catch(t=>console.error("[bostrat-agent] handoff reconcile failed:",t?.message||t)),eb().then(t=>t&&console.log("[bostrat-agent] reconciled interrupted subtask(s)")).catch(t=>console.error("[bostrat-agent] subtask reconcile failed:",t?.message||t)),Xy().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)),Hy().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)),bM(),yM(),jb()&&Lb({listTracks:at}),wk(),sh({resolveTrack:tt}),mb({listTracks:at,createTrack:Ku,archiveTrack:Jr,listRepos:ve,resolveTrack:tt,sayToLead:oe,answerPrompt:Hu,startLeadHandoff:zu,interruptLead:qu,listSubtaskJobs:Xr,cancelSubtask:Yu,readEvents:L,appendEvent:R,knownAgents:ai}),wM(),bo().then(t=>{t||console.warn(`[boot] no host tmux server on ${Ht} and auto-start failed \u2014 boxes cannot spawn until one runs`)}),$k(),process.env.BOSTRAT_PLANE_URL&&process.env.BOSTRAT_PLANE_TOKEN&&(Pi=xb({url:process.env.BOSTRAT_PLANE_URL,token:process.env.BOSTRAT_PLANE_TOKEN,nodeId:process.env.BOSTRAT_NODE_ID||Od.hostname(),name:process.env.BOSTRAT_NODE_NAME||Od.hostname(),listTracks:at,detectAgents:Fu}),console.log(`[bostrat-agent] plane link \u2192 ${process.env.BOSTRAT_PLANE_URL}`),lr().catch(()=>{}),$m().then(t=>t.pushed&&console.log(`[content-plane] seeded ${t.pushed} runbooks to the plane`),()=>{}),ah().then(t=>t.pushed&&console.log(`[content-plane] seeded ${t.pushed} ticket links to the plane`),()=>{}),tm().then(t=>t.pushed&&console.log(`[content-plane] seeded ${t.pushed} mocks to the plane`),()=>{}),oy().then(t=>t.pushed&&console.log(`[content-plane] seeded ${t.pushed} flows to the plane`),()=>{}),gh().then(t=>t.pushed&&console.log(`[content-plane] seeded ${t.pushed} PR links to the plane`),()=>{}))});process.on("SIGTERM",()=>{try{Pi?.stop()}catch{}setTimeout(()=>process.exit(0),150)});function wM(){gM(),wa().catch(()=>{}),setInterval(()=>wa().catch(()=>{}),1e4)}function gM(){for(let t of["bostrat-view","bostrat-mock","bostrat-flow","bostrat-ticket","bostrat-runbook","bostrat-notify","bostrat-gmail","bostrat-changelog","gh"])try{let e=ut.resolve(Xk,"..","bin",t),n=ut.join(process.env.HOME||"",".local","bin");if(!process.env.HOME||!Ft.existsSync(e))continue;Ft.mkdirSync(n,{recursive:!0});let r=ut.join(n,t);(Ft.existsSync(r)?Ft.readlinkSync(r):null)!==e&&(Ft.rmSync(r,{force:!0}),Ft.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 yM(){let t=!1,e=async()=>{if(!t){t=!0;try{let n=await at(),r=await Pb({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 bM(){let t=!1;setInterval(async()=>{if(!t){t=!0;try{let e=await at();for(let n of e)n.archivedAt||!n.boxes?.some(r=>r.state==="live")||(await Uu(n.id).catch(()=>{}),await Ug(n).catch(()=>{}))}catch{}finally{t=!1}}},4e3)}
|