bostrat-node 0.2.6 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +44 -44
- package/dist/index.js +186 -116
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,111 +1,180 @@
|
|
|
1
|
-
var
|
|
1
|
+
var Nb=Object.defineProperty;var zu=(t,e)=>{for(var n in e)Nb(t,n,{get:e[n],enumerable:!0})};import ut from"node:path";import Bt from"node:fs";import G$ from"node:readline";import{fileURLToPath as z$}from"node:url";import ge from"express";import V$ from"node:http";import J$ from"node:https";import qu from"node:os";import{WebSocketServer as X$}from"ws";import{execFile as Fk}from"node:child_process";import{promisify as Uk}from"node:util";import Si from"node:fs";import{randomUUID as Hk}from"node:crypto";import Dt from"node:fs/promises";import Yu from"node:os";import $t from"node:path";import{execFile as Lb}from"node:child_process";import{promisify as Fb}from"node:util";import{createHash as Pb,randomBytes as Bb,timingSafeEqual as Db}from"node:crypto";var os="BOSTRAT_HOOK_TOKEN",ss="x-bostrat-hook";function Vu(){return Bb(32).toString("hex")}function Hr(t){let e=typeof t=="string"?t.trim():"";return e?Pb("sha256").update(e).digest("hex"):null}function Ju(t,e){return typeof t!="string"||typeof e!="string"||t.length!==e.length||!t?!1:Db(Buffer.from(t),Buffer.from(e))}var si=new Map,ai=new Map,jb=6e4;function as(t,e={},{now:n=Date.now(),log:r=console.warn}={}){if(si.set(t,(si.get(t)||0)+1),ai.has(t)&&n-ai.get(t)<jb)return!1;ai.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=${si.get(t)}${o?` ${o}`:""}`),!0}async function Xu(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 is(){return/^(1|true|yes|on)$/i.test(process.env.BOSTRAT_CLAUDE_USE_API_KEY||"")&&process.env.ANTHROPIC_API_KEY?.trim()||null}function Ub(){return process.env.CLAUDE_CONFIG_DIR?$t.join(process.env.CLAUDE_CONFIG_DIR,".claude.json"):$t.join(Yu.homedir(),".claude.json")}function Hb(){return process.env.CLAUDE_CONFIG_DIR?$t.join(process.env.CLAUDE_CONFIG_DIR,"settings.json"):$t.join(Yu.homedir(),".claude","settings.json")}async function Ne(t){if(!t||typeof t!="string")return;let e=$t.resolve(t),n=Ub(),r=Hb(),o={};try{o=JSON.parse(await Dt.readFile(n,"utf8"))}catch{}o.hasCompletedOnboarding=!0,o.theme||(o.theme="dark"),o.projects=o.projects||{},o.projects[e]=o.projects[e]||{};let s=o.projects[e];Array.isArray(s.allowedTools)||(s.allowedTools=[]),s.hasTrustDialogAccepted=!0,s.hasCompletedProjectOnboarding=!0;let a=process.env.ANTHROPIC_API_KEY;if(a){let c=a.slice(-20);o.customApiKeyResponses=o.customApiKeyResponses||{},o.customApiKeyResponses.approved=o.customApiKeyResponses.approved||[],o.customApiKeyResponses.rejected=o.customApiKeyResponses.rejected||[],o.customApiKeyResponses.approved.includes(c)||o.customApiKeyResponses.approved.push(c)}await Dt.mkdir($t.dirname(n),{recursive:!0}),await Dt.writeFile(n,JSON.stringify(o,null,2));let i={};try{i=JSON.parse(await Dt.readFile(r,"utf8"))}catch{}if(i.skipDangerousModePermissionPrompt=!0,process.env.BOSTRAT_HOOKS!=="off"){let l=`curl -sS -m 3 -X POST ${process.env.BOSTRAT_HOOK_URL||`http://127.0.0.1:${process.env.PORT||3362}/api/hook`} -H 'content-type: application/json' -H "${ss}: $${os}" --data-binary @- >/dev/null 2>&1 || true`,u=[{hooks:[{type:"command",command:l}]}];i.hooks=i.hooks||{};for(let f of["SessionStart","UserPromptSubmit","Stop"])i.hooks[f]=u;i.hooks.PostToolUse=[{matcher:"*",hooks:[{type:"command",command:l}]}]}await Dt.mkdir($t.dirname(r),{recursive:!0}),await Dt.writeFile(r,JSON.stringify(i,null,2)),await Wb(e)}async function Wb(t){try{let e=$t.resolve(t),n=$t.join(e,".bostrat","memory","claude"),r=$t.join(e,".claude","settings.local.json"),o={};try{o=JSON.parse(await Dt.readFile(r,"utf8"))}catch{}let s=o.autoMemoryDirectory,a=typeof s=="string"&&s.includes($t.join(".bostrat","memory"));if(s&&!a||s===n)return;o.autoMemoryDirectory=n,await Dt.mkdir(n,{recursive:!0}),await Dt.mkdir($t.dirname(r),{recursive:!0}),await Dt.writeFile(r,JSON.stringify(o,null,2)),await qb(e,["/.claude/settings.local.json"])}catch{}}async function qb(t,e){try{let{stdout:n}=await Fb(Lb)("git",["-C",t,"rev-parse","--git-path","info/exclude"]),r=$t.resolve(t,n.trim()),o=await Dt.readFile(r,"utf8").catch(()=>""),s=e.filter(a=>!o.split(`
|
|
2
|
+
`).includes(a));if(!s.length)return;await Dt.mkdir($t.dirname(r),{recursive:!0}),await Dt.appendFile(r,(o&&!o.endsWith(`
|
|
3
|
+
`)?`
|
|
4
|
+
`:"")+s.join(`
|
|
5
|
+
`)+`
|
|
6
|
+
`)}catch{}}import ii from"node:fs/promises";import Kb from"node:os";import cs from"node:path";function Gb(){let t=process.env.CODEX_HOME||cs.join(Kb.homedir(),".codex");return cs.join(t,"config.toml")}function Zu(t){return`[projects."${t}"]`}function zb(t,e){let n=Zu(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 Un(t){if(!t||typeof t!="string")return;let e=cs.resolve(t),n=Gb(),r="";try{r=await ii.readFile(n,"utf8")}catch{}if(zb(r,e))return;let o=Zu(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*$/,"")}
|
|
2
7
|
trust_level = "trusted"
|
|
3
8
|
`;r=r.slice(0,a)+f+r.slice(l)}else{let s=`
|
|
4
9
|
${o}
|
|
5
10
|
trust_level = "trusted"
|
|
6
11
|
`;r=r.trimEnd()+s+(r.length&&!r.endsWith(`
|
|
7
12
|
`)?`
|
|
8
|
-
`:"")}try{await
|
|
9
|
-
`,"utf8")}import
|
|
10
|
-
|
|
11
|
-
`).
|
|
13
|
+
`:"")}try{await ii.mkdir(cs.dirname(n),{recursive:!0}),await ii.writeFile(n,r)}catch(s){console.warn(`[codex-config] could not seed folder trust for ${e}: ${s?.message||s}`)}}import Qu from"node:fs/promises";import Vb from"node:os";import Wr from"node:path";function Jb(t){let n=Wr.resolve(t).slice(1).replace(/\//g,"-").replace(/\./g,""),r=process.env.CURSOR_CONFIG_DIR||Wr.join(Vb.homedir(),".cursor");return Wr.join(r,"projects",n)}async function Hn(t){if(!t||typeof t!="string")return;let e=Wr.resolve(t),n=Jb(e),r=Wr.join(n,".workspace-trusted");await Qu.mkdir(n,{recursive:!0}),await Qu.writeFile(r,`${JSON.stringify({trustedAt:new Date().toISOString(),workspacePath:e},null,2)}
|
|
14
|
+
`,"utf8")}import j from"node:fs/promises";import Sk from"node:os";import J from"node:path";import{execFile as vk}from"node:child_process";import{promisify as Tk}from"node:util";import ak from"node:fs";import ed from"node:fs/promises";import Kr from"node:path";import ik from"node:os";import{fileURLToPath as ck}from"node:url";import{execFile as lk}from"node:child_process";import{promisify as uk}from"node:util";import li from"node:fs/promises";import Pe from"node:path";import{execFile as Xb}from"node:child_process";import{promisify as Yb}from"node:util";var Zb=Yb(Xb),td=process.env.HOME||"/home/globo",Qb=Pe.join(process.env.BOSTRAT_STATE_DIR||Pe.join(td,".bostrat"),"repos"),tk=[...(process.env.BOSTRAT_REPOS_ROOT||Pe.join(td,"Documents")).split(","),Qb].map(t=>t.trim()).filter(Boolean),ek=Number(process.env.BOSTRAT_REPOS_DEPTH)||3,nk=new Set(["node_modules",".git",".cache","dist","build",".next","vendor","__pycache__","venv",".venv","target"]);async function rk(t){try{return(await li.stat(Pe.join(t,".git"))).isDirectory()?"repo":"worktree"}catch{return null}}async function ok(t){let e=i=>li.readFile(Pe.join(t,".git",i),"utf8").then(c=>c.trim()).catch(()=>null),r=(await e("refs/remotes/origin/HEAD"))?.match(/^ref:\s*refs\/remotes\/origin\/(.+)/);if(r)return r[1].trim();let o;for(let i of["main","master"])if(await e(`refs/heads/${i}`)||(o??=await e("packed-refs")||"",new RegExp(`^[0-9a-f]{40} refs/heads/${i}$`,"m").test(o)))return i;let a=(await e("HEAD"))?.match(/^ref:\s*refs\/heads\/(.+)/);return a?a[1].trim():null}async function ye(){let t=new Map;async function e(o,s){if(t.has(o))return;let a=await rk(o);if(a==="worktree")return;if(a==="repo"){t.set(o,{path:o,name:Pe.basename(o),branch:await ok(o)});return}if(s>=ek)return;let i;try{i=await li.readdir(o,{withFileTypes:!0})}catch{return}for(let c of i)!c.isDirectory()||c.name.startsWith(".")||nk.has(c.name)||await e(Pe.join(o,c.name),s+1)}for(let o of tk)await e(Pe.resolve(o),0);let n=new Set,r=[];for(let o of t.values()){let s=(await tt(o.path))?.toLowerCase();if(s){if(n.has(s))continue;n.add(s)}r.push(o)}return r.sort((o,s)=>o.name.localeCompare(s.name))}function sk(t){let e=String(t||"").trim().match(/github\.com[:/]+([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/i);return e?`${e[1]}/${e[2]}`:null}var ci=new Map;async function tt(t){if(!t)return null;if(ci.has(t))return ci.get(t);let e=null;try{let{stdout:n}=await Zb("git",["-C",t,"config","--get","remote.origin.url"],{timeout:5e3});e=sk(n)}catch{e=null}return ci.set(t,e),e}async function qr(t){let e=String(t||"").toLowerCase();if(!e)return null;for(let n of await ye())if((await tt(n.path))?.toLowerCase()===e)return n.path;return null}var nd=uk(lk),dk=process.env.BOSTRAT_STATE_DIR||Kr.join(process.env.HOME||ik.tmpdir(),".bostrat"),fk=Kr.join(dk,"repos"),rd=/^[^/\s]+\/[^/\s]+$/,pk=5*6e4,mk=6e4,hk=10*6e4,od=fetch,ui=new Map,fn=null;function be(t=process.env){let e=t.BOSTRAT_PLANE_URL||"";if(!e)return null;try{let n=new URL(e);return n.protocol=n.protocol==="ws:"?"http:":"https:",n.origin}catch{return null}}function ls(t=process.env){return!!(be(t)&&t.BOSTRAT_PLANE_TOKEN)}var sd=t=>({authorization:`Bearer ${t.BOSTRAT_PLANE_TOKEN||""}`}),Wn=(t,e)=>Object.assign(new Error(t),{status:e});async function us(t,{env:e=process.env}={}){let n=String(t||"").toLowerCase();if(!rd.test(n))throw Wn("repo must be owner/name",400);let r=ui.get(n);if(r&&r.expiresAt-Date.now()>pk)return r;if(!ls(e))throw Wn("node is not plane-linked \u2014 GitHub auth unavailable",503);let o;try{o=await od(`${be(e)}/v1/github/token?repo=${encodeURIComponent(t)}`,{headers:sd(e)})}catch(i){if(r&&r.expiresAt>Date.now())return r;throw Wn(`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 Wn(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 ui.set(n,a),a}async function Gr({env:t=process.env,force:e=!1}={}){if(!ls(t))return{configured:!1,repos:[],username:null};if(!e&&fn&&Date.now()-fn.at<mk)return fn.data;try{let n=await od(`${be(t)}/v1/github/repos`,{headers:sd(t)});if(!n.ok)throw new Error(`plane replied ${n.status}`);let r=await n.json();return fn={at:Date.now(),data:r},r}catch(n){return fn?fn.data:{configured:!0,repos:[],username:null,error:n?.message||String(n)}}}function ad(){ui.clear(),fn=null}async function ee({cwd:t=null,slug:e=null,env:n=process.env}={}){try{let r=e||(t?await tt(t):null);if(!r)return{};let{token:o}=await us(r,{env:n});return o?{GH_TOKEN:o}:{}}catch{return{}}}async function di({env:t=process.env}={}){return(await Gr({env:t})).username??null}var wk=()=>ck(new URL("../bin/bostrat-git-credential",import.meta.url)),gk=(t=process.env)=>`http://127.0.0.1:${Number(t.PORT)||3362}`;function yk(t){let[e,n]=String(t).split("/");return Kr.join(fk,e,n)}async function fi(t,{env:e=process.env}={}){let n=(...r)=>nd("git",["-C",t,...r],{timeout:1e4});await n("config","--replace-all","credential.helper",""),await n("config","--add","credential.helper",`!"${wk()}" ${gk(e)}`),await n("config","credential.useHttpPath","true")}async function pi(t,{env:e=process.env,baseUrl:n="https://github.com"}={}){if(!rd.test(String(t||"")))throw Wn(`bad repo slug "${t}"`,400);let r=yk(t);if(ak.existsSync(Kr.join(r,".git")))return r;let{token:o}=await us(t,{env:e});await ed.mkdir(Kr.dirname(r),{recursive:!0});let s="!f() { echo username=x-access-token; echo password=$BOSTRAT_GIT_TOKEN; }; f";try{await nd("git",["-c","credential.helper=","-c",`credential.helper=${s}`,"clone","--no-tags","-q",`${n}/${t}.git`,r],{timeout:hk,env:{...e,BOSTRAT_GIT_TOKEN:o,GIT_TERMINAL_PROMPT:"0"}})}catch(a){throw await ed.rm(r,{recursive:!0,force:!0}).catch(()=>{}),Wn(`clone of ${t} failed: ${a?.stderr?.trim?.()||a?.message||a}`,502)}return await fi(r,{env:e}),r}var bk=6e4,kk=5e3,mi=new Map,xk=(...t)=>fetch(...t);function id(t=process.env){return!!(be(t)&&t.BOSTRAT_PLANE_TOKEN)}var cd=t=>({authorization:`Bearer ${t.BOSTRAT_PLANE_TOKEN||""}`});async function ld(t,e,n){let r=be(n),o=new AbortController,s=setTimeout(()=>o.abort(),kk);try{return await xk(`${r}${t}`,{...e,signal:o.signal})}finally{clearTimeout(s)}}async function ud(t,{env:e=process.env,force:n=!1}={}){if(!id(e))return{facts:[],source:"unconfigured"};let r=mi.get(t);if(!n&&r&&Date.now()-r.at<bk)return{facts:r.facts,source:"cache"};try{let o=await ld(`/v1/memory/${encodeURIComponent(t)}`,{headers:cd(e)},e);if(!o.ok)throw new Error(`plane ${o.status}`);let s=await o.json(),a=Array.isArray(s?.facts)?s.facts:[];return mi.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 hi(t,e,{env:n=process.env}={}){if(!id(n))return{pushed:0,source:"unconfigured"};if(!e?.length)return{pushed:0,source:"empty"};try{let r=await ld(`/v1/memory/${encodeURIComponent(t)}`,{method:"POST",headers:{...cd(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 mi.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 Ak=Tk(vk),wi=process.env.BOSTRAT_STATE_DIR||J.join(process.env.HOME||Sk.tmpdir(),".bostrat"),dd=200,_k=25*1024;function ds(){return J.join(wi,"memory","repos")}var ke="operator";function pn(t){return t===ke?J.join(wi,"memory",ke):J.join(ds(),t)}var hd=".tombstones.json";async function fs(t){try{return JSON.parse(await j.readFile(J.join(t,hd),"utf8"))}catch{return{}}}async function wd(t,e){let n=await fs(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,hd),JSON.stringify(n))}function fd(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 pd(t){try{let{stdout:e}=await Ak("git",["-C",t,"config","--get","remote.origin.url"]);return e.trim()||null}catch{return null}}async function gi(t){if(!t)return[];let e=fd(await pd(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=fd(await pd(J.join(t,o.name)));s&&!n.includes(s)&&n.push(s)}return n}function ps(t){let e=/^---\n([\s\S]*?)\n---\n?/.exec(t);return e?{fm:e[1],body:t.slice(e[0].length)}:{fm:null,body:t}}function Be(t,e){let n=new RegExp(`^\\s*${e}:\\s*(.+?)\\s*$`,"m").exec(t||"");return n?n[1].replace(/^"|"$/g,""):null}async function ne(t,e=null){let n=e??await j.readFile(t,"utf8").catch(()=>""),{fm:r}=ps(n),o=Be(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 gd(t,e){let n=Be(t,"scope"),r=/^repo:([a-z0-9][a-z0-9._-]*)$/i.exec(n||"");if(r)return r[1].toLowerCase();if((n||"").toLowerCase()===ke){let o=Be(t,"trust");if(!o||/^operator/i.test(o))return ke}return e}var qn=t=>/\]\(([^)]+\.md)\)/.exec(t)?.[1]||null;function yi(t,e,n,r=new Map){let o=(t||"").split(`
|
|
15
|
+
`),s=new Map;for(let c of(e||"").split(`
|
|
16
|
+
`)){let l=qn(c);l&&c.trim().startsWith("-")&&s.set(l,c)}let a=new Set,i=o.map(c=>{let l=qn(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
|
+
`).replace(/\n{3,}/g,`
|
|
18
|
+
|
|
19
|
+
`)}function yd(t,e){let n=Be(e,"name")||t.replace(/\.md$/,""),r=Be(e,"description")||"";return`- [${n}](${t})${r?` \u2014 ${r}`:""}`}async function zr(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 bd(t,e){let n=J.join(t,".bostrat","memory"),r=await zr(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}=ps(f),m=gd(d,e);if(!m)continue;let h=pn(m),w=J.join(h,l),g=await ne(u,f);if(i.has(h)||i.set(h,await fs(h)),(i.get(h)[l]||0)>=g)continue;let b=await ne(w).catch(()=>0);if(await j.access(w).then(()=>!0).catch(()=>!1)&&b>=g)continue;await j.mkdir(h,{recursive:!0}),await j.writeFile(w,f),a++;let A=o.get(h);A||o.set(h,A={won:new Set,generated:new Map}),A.won.add(l),A.generated.set(l,yd(l,d)),s.has(m)||s.set(m,new Set),s.get(m).add(l)}let c=await j.readFile(J.join(n,"MEMORY.md"),"utf8").catch(()=>"");for(let[l,u]of o){let f=J.join(l,"MEMORY.md"),d=await j.readFile(f,"utf8").catch(()=>"");await j.writeFile(f,yi(d,c,u.won,u.generated))}return{harvested:a,changedByKey:s}}async function Ek(){let t=J.join(wi,"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 gi(s),c=await bd(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 Ik=/^[A-Za-z0-9][A-Za-z0-9._-]*\.md$/,Rk=t=>{let e=new Map;for(let n of(t||"").split(`
|
|
20
|
+
`)){let r=qn(n);r&&n.trim().startsWith("-")&&e.set(r,n)}return e};async function $k(t,e){let n=pn(t),r=new Set,o=new Map,s=[],a=0;for(let i of e||[]){let c=String(i?.file||"");if(!Ik.test(c)||c==="MEMORY.md")continue;let l=J.join(n,c),u=await j.access(l).then(()=>!0).catch(()=>!1),f=u?await ne(l):0,d=Number(i.modified)||0;if(i.deleted){d>f&&(u&&await j.rm(l,{force:!0}),await wd(n,[[c,d]]),u&&(s.push(c),a++));continue}u&&f>=d||(await j.mkdir(n,{recursive:!0}),await j.writeFile(l,String(i.content??"")),r.add(c),typeof i.indexLine=="string"&&i.indexLine.trim()&&o.set(c,i.indexLine),a++)}if(r.size||s.length){let i=J.join(n,"MEMORY.md"),c=await j.readFile(i,"utf8").catch(()=>""),l=yi(c,[...o.values()].join(`
|
|
21
|
+
`),r);if(s.length){let u=new Set(s);l=l.split(`
|
|
22
|
+
`).filter(f=>!u.has(qn(f))).join(`
|
|
23
|
+
`)}await j.mkdir(n,{recursive:!0}),await j.writeFile(i,l)}return{changed:a,tombstoned:s.length}}async function bi(t,e=null){let n=pn(t),r=e?.length?e:await zr(n);if(!r.length)return{pushed:0,source:"empty"};let o=Rk(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 ne(i,c),indexLine:o.get(a)??null})}return hi(t,s)}async function Ok(t){let{facts:e,source:n}=await ud(t);e.length?await $k(t,e):n==="plane"&&await bi(t)}function Mk(t){let e=t.split(`
|
|
24
|
+
`);e.length>dd&&(e=e.slice(0,dd));let n=e.join(`
|
|
25
|
+
`);for(;Buffer.byteLength(n,"utf8")>_k&&e.length;)e.pop(),n=e.join(`
|
|
26
|
+
`);return n}var Ck=`# Track memory
|
|
27
|
+
|
|
28
|
+
Shared Track memory (all providers) lives one level up: read \`../MEMORY.md\` for the
|
|
29
|
+
index and the fact files beside it. Facts you save here stay Claude-local but ride the
|
|
30
|
+
same sync; prefer adding durable cross-provider facts to \`../\` following its format.
|
|
31
|
+
`;async function Nk(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=pn(u),d=await fs(f);for(let[g,b]of Object.entries(d)){let v=J.join(n,g);await j.access(v).then(()=>!0).catch(()=>!1)&&await ne(v)<=b&&(await j.rm(v,{force:!0}),o++)}let m=await zr(f);if(!m.length)continue;await j.mkdir(n,{recursive:!0});for(let g of m){let b=J.join(f,g),v=J.join(n,g),A=await ne(b),M=await ne(v).catch(()=>0);await j.access(v).then(()=>!0).catch(()=>!1)&&M>=A||(await j.copyFile(b,v),r++)}let h=await j.readFile(J.join(f,"MEMORY.md"),"utf8").catch(()=>""),w=u===ke?"## operator memory":`## repo: ${u}`;h.trim()&&s.push(e.length>1?`${w}
|
|
32
|
+
|
|
33
|
+
${h.trim()}`:h.trim())}if(!s.length)return o?{materialized:!1,reason:"empty-store",cleaned:o}:{materialized:!1,reason:"empty-store"};let i="_Shared Track memory (point-in-time facts). Verify file/line citations against the current code before acting on them; correct a wrong fact in place (bump `modified`, use `supersedes`) rather than working around it._\n\n"+s.join(`
|
|
34
|
+
|
|
35
|
+
`)+`
|
|
36
|
+
`,c=Mk(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"),Ck,{flag:"wx"}).catch(()=>{}),{materialized:!0,facts:r,capped:c!==i}}var md=0,Pk=3e4;async function kd(t){try{let e=await gi(t);if(!e.length)return{materialized:!1,reason:"no-repo-key"};let n=[...e,ke];if(Date.now()-md>Pk){md=Date.now(),await Bk();let{changedByKey:r}=await Ek();for(let[o,s]of r)await bi(o,[...s])}for(let r of n)await Ok(r);return await Nk(t,n)}catch(e){return{materialized:!1,reason:"error",error:e?.message||String(e)}}}async function Bk(){let t=0,e=[];try{e=await j.readdir(ds(),{withFileTypes:!0})}catch{return{moved:t}}for(let n of e){if(!n.isDirectory())continue;let r=J.join(ds(),n.name),o=pn(ke),s=new Set,a=new Map;for(let d of await zr(r)){let m=J.join(r,d),h;try{h=await j.readFile(m,"utf8")}catch{continue}let{fm:w}=ps(h);if(gd(w,n.name)!==ke)continue;let g=J.join(o,d),b=await ne(g).catch(()=>0),v=await ne(m,h);await j.mkdir(o,{recursive:!0}),v>b&&await j.writeFile(g,h),await j.rm(m,{force:!0}),s.add(d),a.set(d,yd(d,w)),t++}if(!s.size)continue;let i=J.join(r,"MEMORY.md"),c=await j.readFile(i,"utf8").catch(()=>""),l=c.split(`
|
|
37
|
+
`).filter(d=>!s.has(qn(d))).join(`
|
|
38
|
+
`);await j.writeFile(i,l).catch(()=>{});let u=J.join(o,"MEMORY.md"),f=await j.readFile(u,"utf8").catch(()=>"");await j.writeFile(u,yi(f,c,s,a))}return{moved:t}}async function xd(){let t=[],e=[ke];try{for(let n of await j.readdir(ds(),{withFileTypes:!0}))n.isDirectory()&&e.push(n.name)}catch{}for(let n of e){let r=pn(n),o=await zr(r),s=await fs(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}=ps(c);a.push({file:i,name:Be(l,"name")||i.replace(/\.md$/,""),description:Be(l,"description")||"",scope:Be(l,"scope")||null,modified:await ne(J.join(r,i),c)})}t.push({key:n,facts:a,tombstones:Object.keys(s).length})}return t}async function Sd(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=pn(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 wd(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=>qn(l)!==e).join(`
|
|
40
|
+
`)).catch(()=>{});let c=await hi(t,[{file:e,deleted:!0,modified:s}]);return{deleted:e,key:t,plane:c.source}}async function vd(t){try{let e=await gi(t),{harvested:n,changedByKey:r}=await bd(t,e[0]||null);for(let[o,s]of r)await bi(o,[...s]);return{harvested:n}}catch(e){return{harvested:0,error:e?.message||String(e)}}}import Vr from"node:fs/promises";import Dk from"node:os";import Ad from"node:path";var jk=[/[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 ki(t){let e=String(t??"");for(let n of jk)e=e.replace(n,"\u2039redacted\u203A");return e}var xi=process.env.BOSTRAT_DEBUG_DIR||Ad.join(Dk.homedir(),".bostrat","debug"),Kn=Ad.join(xi,"handoff.jsonl"),Td=Number(process.env.BOSTRAT_HANDOFF_DEBUG_MAX)||500;function ms(){let t=process.env.BOSTRAT_HANDOFF_DEBUG;return t==="1"||t==="true"}async function Lk(t){try{await Vr.mkdir(xi,{recursive:!0}),await Vr.appendFile(Kn,`${JSON.stringify(t)}
|
|
41
|
+
`,"utf8");let n=(await Vr.readFile(Kn,"utf8")).split(`
|
|
42
|
+
`).filter(Boolean);n.length>Td&&await Vr.writeFile(Kn,`${n.slice(-Td).join(`
|
|
12
43
|
`)}
|
|
13
|
-
`,"utf8")}catch(e){console.warn(`[handoff-debug] failed to write ${
|
|
14
|
-
`).filter(Boolean).slice(-t).map(r=>{try{let o=JSON.parse(r);return typeof o?.paneTail=="string"&&(o.paneTail=
|
|
15
|
-
`).filter(Boolean).map(r=>{let[o,s,a,i,c,l,u,f,d]=r.split("|"),m=Number(i);return{name:o,title:u||null,windows:Number(s),attached:Number(a),lastActivityMs:Number.isFinite(m)?n-m*1e3:null,cwd:c||null,isBostrat:l==="1",sessionId:f||null,agent:d||(l==="1"?"claude":null)}}).filter(r=>!
|
|
16
|
-
`).map(n=>n.trim()).includes(t)}catch{return!1}}async function
|
|
17
|
-
`).filter(Boolean).length}catch{return 0}}async function
|
|
18
|
-
`);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
|
|
19
|
-
`).trim():""}function
|
|
20
|
-
`)){let i=a.trim();if(!i||i[0]!=="{")continue;let c;try{c=JSON.parse(i)}catch{continue}if(c.type==="attachment"){let h=c.attachment;h?.type==="queued_command"&&h.commandMode==="task-notification"&&typeof h.prompt=="string"&&h.prompt.trim()&&o.push({role:"user",text:h.prompt.trim(),ts:c.timestamp?Date.parse(c.timestamp):null,model:null});continue}let l=c.message;if(!l)continue;let u=l.role||c.type;if(u!=="user"&&u!=="assistant"||u==="assistant"&&l.model==="<synthetic>"&&!c.isApiErrorMessage)continue;if(u==="assistant"&&Array.isArray(l.content))for(let h of l.content){let w=h?.type==="tool_use"?h.input?.file_path||h.input?.path:null;typeof w=="string"&&
|
|
21
|
-
|
|
22
|
-
`):""}import
|
|
23
|
-
`);if(!
|
|
24
|
-
`);for(let r=n.length-1;r>=0;r--)if(
|
|
25
|
-
`);return e}function
|
|
26
|
-
`),o=[],s=-1;for(let l=0;l<r.length;l++){let u=r[l].match(
|
|
27
|
-
`),o=r.findIndex(i=>
|
|
28
|
-
`).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
|
|
29
|
-
`),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
|
|
30
|
-
`))Wb.test(n)&&(e=Math.max(e,n.trimEnd().length));return e}function li(t,e){let n=St[t];if(!n?.busy)return"idle";if(ui(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=qb(e);return t==="claude"&&r>0&&r<Hb?"unknown":"idle"}var Gb=/\bWaiting for \d+\b[^\n]*\bto finish\b|\/workflows to (?:stop|monitor)\b/;function nd(t,e){return t==="claude"&&Gb.test(String(e||""))}function Ln(t,e){return li(t,e)==="busy"}function rs(t,e){return li(t,e)!=="idle"}function os(t,e){let n=St[t];return n?.up?n.up.test(e):!0}function Kb(t,e){let n=St[t];return n?ui(t,e)?!1:n.up.test(e)&&li(t,e)==="idle":!0}function ui(t,e){let n=St[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 ed(t,e,n){let r=Date.now()+n,o=0;for(await on(t).catch(()=>{});;){let s=await wt(t);if(Kb(e,s)){await Ie(jr);return}if(e==="cursor"&&jb.test(s)&&!St.cursor.up.test(s)&&o<3){if(!td(t,"cursor-trust")){await Ie(ci);continue}o+=1,await V({flow:"agent-ready",phase:"gate-accept",sessionName:t,agent:e,attempt:o}).catch(()=>{}),await xt(t,"a",{enter:!1,kind:"cursor-trust-accept"}),await Ie(jr);continue}if(e==="codex"&&o<3){let a=Fb(s);if(a){if(!td(t,`codex-update:${a.from}->${a.to}`)){await Ie(ci);continue}o+=1,await V({flow:"agent-ready",phase:"gate-accept",sessionName:t,agent:e,attempt:o,update:`${a.from} -> ${a.to}`}).catch(()=>{}),await xt(t,"3",{enter:!1,kind:"codex-update-skip"}),Vu({sessionName:t,from:a.from,to:a.to}),await Ie(jr);continue}}if(Date.now()>r){if(ui(e,s)){let a=`${e} is blocked on an interactive prompt`;throw e==="codex"?a=St.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"&&St.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"&&St.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"&&St.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'),V({flow:"agent-ready",phase:"blocked",sessionName:t,agent:e,timeoutMs:n,gateAccepts:o,error:a}).catch(()=>{}),new Error(a)}console.warn(`[agent-ready] ${e} readiness unconfirmed in ${t} after ${n}ms \u2014 driving the box anyway.`),V({flow:"agent-ready",phase:"timeout",sessionName:t,agent:e,timeoutMs:n,gateAccepts:o,paneBlocked:!!St[e]?.block?.test(s),paneUp:!!St[e]?.up?.test(s),paneBusy:!!St[e]?.busy?.test(s)}).catch(()=>{}),await Ie(jr);return}await Ie(ci)}}function rd({agent:t,agentCmd:e}={}){let n=e?Bn(e):Bn(t||"");return{label:n,realClaude:e?Pn(e):n==="claude",realCodex:e?/codex/.test(e)&&!/fake-codex|fake-lead/.test(e):n==="codex",realCursor:e?/cursor/.test(e)&&!/fake-cursor|fake-box/.test(e):n==="cursor"}}function ss({cwd:t,sessionName:e,agent:n,agentCmd:r}={}){let{realCodex:o,realCursor:s}=rd({agent:n,agentCmd:r});return!e||!o&&!s?null:Re({cwd:t,sessionName:e,agent:n,agentCmd:r}).catch(()=>{})}async function Re({cwd:t,sessionId:e,sessionName:n,agent:r,agentCmd:o,timeoutMs:s=Db()}={}){let{label:a,realClaude:i,realCodex:c,realCursor:l}=rd({agent:r,agentCmd:o});if(i&&t&&await Te(t),c&&t&&await Cn(t),l&&t&&await Mn(t),(i||c||l)&&n){await ed(n,a,s);return}let u=St[r]?r:St[a]?a:null;if(process.env.BOSTRAT_FAKE_POLL_READY==="1"&&n&&u){await ed(n,u,s);return}await Ie(jr)}var Vb=(()=>{let t=Number(process.env.BOSTRAT_CODEX_SUBMIT_SETTLE_MS??120);return Number.isFinite(t)&&t>=0?t:120})();function fe(t){return t==="codex"?{enter:!0,submitDelayMs:Vb}:{enter:!0}}var ad=".bostrat/handoff",id="latest.md",cd="Checkpoint: write a work-in-progress handoff",ld="HANDOFF WRITTEN",ud="Resume from .bostrat/handoff/latest.md",Jb="/clear",Xb=t=>`${cd} 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 ${ld} and take no further action.`,Yb=`${ud}: read it, restate the Objective and the single next step in one line, then continue the work from 'Next steps'.`,dd="Beat: write a",fd="BEAT WRITTEN",Zb=".bostrat/arc",Qb="beat.txt",od=200,pd=4,md=5,tk=36,ek=t=>`${dd} 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 ${pd} further lines, each starting with "- " and naming ONE piece in at most ${md} 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 ${fd} and take no further action.`,sd=/^(?:[-–—*•·]|\d+[.)])\s+/,nk=(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 rk(t){let e=String(t||"").split(/\r?\n/).map(s=>s.trim()).filter(Boolean),n=[],r=[];for(let s of e)if(sd.test(s)){if(r.length>=pd)continue;let a=nk(s.replace(sd,"").replace(/[.;,:]+$/,""),md,tk);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>od&&(o=`${o.slice(0,od-1).trimEnd()}\u2026`),{text:o,items:r.length>1?r:[]}}async function hd(t,e,{timeoutMs:n=9e4}={}){let r=Fn.join(e,Zb),o=Fn.join(r,Qb);await Lr.mkdir(r,{recursive:!0});let s=await as(o),a=await X(t);if(!a)throw new Error(`session "${t}" not found`);return await is(t,a,ek(o)),await Sd(o,s,n),rk(await Lr.readFile(o,"utf8"))}function ok(t,e){let n=(e||"").trim();return n?t==="assistant"?n===ld||n===fd:n.startsWith(cd)||n===Jb||n.startsWith(ud)||n.startsWith(dd):!1}var sk=6e5;function wd(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-sk;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 gd(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 yd(t,{windows:e=[]}={}){if(ok(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 ak=3500,ik=3e5,di=new Map;function cs(t){return di.get(t)||null}async function bd(t){let e=di.get(t);if(e&&!e.finishedAt)throw new Error("a handoff is already running for this session");let n=await X(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:Fn.join(n.cwd,ad,id)};return di.set(t,r),ck(r,n.cwd).catch(o=>{r.error=o?.message||String(o),r.finishedAt=Date.now()}),r}async function ck(t,e){t.step="summarize",await kd(t.name,e),t.step="clear";let n=await X(t.name);await is(t.name,n,"/clear"),await vd(ak),t.step="resume",await is(t.name,n,Yb),t.step="done",t.finishedAt=Date.now()}async function kd(t,e){let n=Fn.join(e,ad),r=Fn.join(n,id);await Lr.mkdir(n,{recursive:!0});let o=await as(r),s=await X(t);return await V({flow:"checkpoint",phase:"summarize-start",sessionName:t,cwd:e,file:r,beforeMtime:o}),await is(t,s,Xb(r)),await Sd(r,o,ik),await V({flow:"checkpoint",phase:"summarize-done",sessionName:t,file:r,afterMtime:await as(r)}),await lk(n,r),r}async function xd(t){let e=await X(t);if(!e)throw new Error(`session "${t}" not found`);if(!e.cwd)throw new Error(`can't resolve cwd for "${t}"`);return kd(t,e.cwd)}async function as(t){try{return(await Lr.stat(t)).mtimeMs}catch{return 0}}async function Sd(t,e,n){let r=Date.now()+n;for(;;){if(await as(t)>e)return;if(Date.now()>r)throw new Error(`timed out waiting for the handoff file to be written (${t})`);await vd(2e3)}}async function lk(t,e){try{if(!zb.existsSync(e))return;let n=new Date().toISOString().replace(/[:.]/g,"-");await Lr.copyFile(e,Fn.join(t,`${n}.md`))}catch{}}function vd(t){return new Promise(e=>setTimeout(e,t))}async function is(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 Re({cwd:e.cwd,sessionId:e.sessionId,sessionName:t,agent:e.agent,agentCmd:o})}let r=await X(t);if(!r)throw new Error(`session "${t}" disappeared before the orchestration prompt could be sent`);await xt(t,n,{kind:"orchestration-drive",...fe(r.agent)})}import U from"node:fs/promises";import H from"node:path";import{randomUUID as So}from"node:crypto";import{execFile as LA}from"node:child_process";import{promisify as FA}from"node:util";import ip from"node:path";import fi from"node:fs/promises";import Un from"node:path";import uk from"node:os";import{EventEmitter as dk}from"node:events";import{randomUUID as fk}from"node:crypto";var pk=new Set(["session.ready","turn.user","turn.agent","turn.retracted","tool.called","tool.result","prompt.raised","prompt.answered","context.usage","cost.recorded","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","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"]),mk=new Set(["user","agent","orchestrator","system"]),hk=new Set(["hook","otel","statusline","transcript","pane","orchestrator","vendor"]),Lt=new dk;Lt.setMaxListeners(0);var pi=process.env.BOSTRAT_STATE_DIR||Un.join(process.env.HOME||uk.tmpdir(),".bostrat");function us(t){return t?.cwd?Un.join(t.cwd,".bostrat","events"):Un.join(pi,"events")}var Ad=(t,e)=>Un.join(t,`${e}.ndjson`),ls=new Map;async function wk(t,e){if(!ls.has(e)){let o=(await F(t,e)).reduce((s,a)=>a.seq>s?a.seq:s,0);ls.set(e,o)}let n=ls.get(e)+1;return ls.set(e,n),n}function gk(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 O(t,e){let n=typeof t=="string"?t:t?.id;if(!n)throw new Error("appendEvent: a track (or trackId) is required");if(!e||!pk.has(e.type))throw new Error(`appendEvent: unknown event type "${e?.type}"`);if(e.actor&&!mk.has(e.actor))throw new Error(`appendEvent: bad actor "${e.actor}"`);if(e.source&&!hk.has(e.source))throw new Error(`appendEvent: bad source "${e.source}"`);let r=typeof t=="string"?Un.join(pi,"events"):us(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=gk(c,Number(e.payload?.ts??e.ts)||Date.now())}let s=await wk(r,n),a=e.userId??process.env.BOSTRAT_OWNER??null,i={id:e.id||fk(),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 fi.mkdir(r,{recursive:!0}),await fi.appendFile(Ad(r,n),JSON.stringify(i)+`
|
|
31
|
-
`)
|
|
32
|
-
|
|
33
|
-
|
|
44
|
+
`,"utf8")}catch(e){console.warn(`[handoff-debug] failed to write ${Kn}: ${e?.message||e}`)}}async function G(t={}){let e={at:new Date().toISOString(),ts:Date.now(),...t},n=[e.flow||"handoff",e.phase||e.step||"?",e.trackId?`track=${e.trackId}`:"",e.sessionName?`box=${e.sessionName}`:"",e.error?`ERR=${e.error}`:""].filter(Boolean).join(" \xB7 ");if(console.log(`[handoff-debug] ${n}`),e.sessionName&&ms())try{e.paneTail=ki((await gt(e.sessionName,{lines:35})).slice(-4e3))}catch{e.paneTail=null}(ms()||e.error||/^(checkpoint|spawn|hydrate|commit|done|summarize|clear|resume|ready|send|failed)/.test(String(e.phase||e.step||"")))&&await Lk(e)}async function _d({limit:t=80}={}){try{return(await Vr.readFile(Kn,"utf8")).split(`
|
|
45
|
+
`).filter(Boolean).slice(-t).map(r=>{try{let o=JSON.parse(r);return typeof o?.paneTail=="string"&&(o.paneTail=ki(o.paneTail)),o}catch{return{raw:ki(r)}}})}catch{return[]}}var Ed={DEBUG_DIR:xi,LOG_FILE:Kn};var lt=Uk(Fk);function Jr(t){return new Promise(e=>setTimeout(e,t))}function hs(t,e){if(!e)return t;let n=o=>typeof o=="string"?o.split(e).join("<redacted>"):o,r=new Error(n(t?.message)||"command failed");return t?.code!==void 0&&(r.code=t.code),t?.stderr!==void 0&&(r.stderr=n(t.stderr)),r}var N=process.env.BOSTRAT_TMUX_BIN||"/usr/bin/tmux",jt=process.env.BOSTRAT_TMUX_SOCKET||"/tmp/tmux-1000/default",Wk=process.env.BOSTRAT_DEFAULT_CWD||process.env.HOME||"/",vi=process.env.BOSTRAT_AGENT_CMD||"claude --dangerously-skip-permissions",Ti="bostrat-usage-probe",qk=new Set([Ti]);function Ai(t,e){let n=Gn(t),r=/(^|\s)(--session-id|--resume|--continue|-r|-c)(\s|=|$)/.test(t);return!n||r?t:`${t} --session-id ${e}`}function Gn(t){return/(^|\/|\s)claude(\s|$)/.test(String(t||""))}function zn(t){return/(^|\/|\s)claude(\s|$)/.test(t)?"claude":/cursor/.test(t)?"cursor":/codex/.test(t)?"codex":/gemini/.test(t)?"gemini":"agent"}async function _i(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 lt(r,[o,'command -v "$1" >/dev/null 2>&1',"_",t],{timeout:5e3}),!0}catch(s){return s?.code===1?!1:n}}async function ws(t){return _i(String(t||"").trim().split(/\s+/)[0])}var Ei={claude:"npm install -g @anthropic-ai/claude-code",codex:"npm install -g @openai/codex","cursor-agent":"curl https://cursor.com/install -fsS | bash"};function B(...t){return["-S",jt,...t]}async function De(){if(!Si.existsSync(jt))return!1;try{return await lt(N,B("list-sessions")),!0}catch(t){let e=t&&t.stderr?String(t.stderr):"";return!/no server running/i.test(e)}}async function Xr(){if(await De())return!0;try{Si.mkdirSync(jt.replace(/\/[^/]+$/,""),{recursive:!0}),await lt(N,B("new-session","-d","-s","keeper","sleep 2147483647"))}catch{}return await De()}var Kk=/^[A-Za-z0-9_.-]{1,64}$/;function yt(t){return typeof t=="string"&&Kk.test(t)}async function mn(){if(!Si.existsSync(jt))return[];try{let t="#{session_name}|#{session_windows}|#{session_attached}|#{session_activity}|#{pane_current_path}|#{@bostrat-session}|#{@bostrat-title}|#{@bostrat-sid}|#{@bostrat-agent}",{stdout:e}=await lt(N,B("list-sessions","-F",t)),n=Date.now();return e.trim().split(`
|
|
46
|
+
`).filter(Boolean).map(r=>{let[o,s,a,i,c,l,u,f,d]=r.split("|"),m=Number(i);return{name:o,title:u||null,windows:Number(s),attached:Number(a),lastActivityMs:Number.isFinite(m)?n-m*1e3:null,cwd:c||null,isBostrat:l==="1",sessionId:f||null,agent:d||(l==="1"?"claude":null)}}).filter(r=>!qk.has(r.name)&&!r.name.startsWith("bostrat-view-"))}catch{return[]}}async function Y(t){return(await mn()).find(n=>n.name===t)||null}var Gk=/^[A-Za-z0-9 ._↻-]+$/,zk=64;function Vk(t){if(typeof t!="string")return!1;let e=t.trim();return e.length>=1&&e.length<=zk&&Gk.test(e)}function Ii(t){return t.trim().replace(/\s+/g,"-").toLowerCase().replace(/[^A-Za-z0-9_.-]/g,"").replace(/^-+|-+$/g,"")}async function Jk(t){try{let{stdout:e}=await lt(N,B("list-sessions","-F","#{session_name}"));return e.split(`
|
|
47
|
+
`).map(n=>n.trim()).includes(t)}catch{return!1}}async function hn(t,e=Wk,n={}){if(!Vk(t))throw new Error("displayName must be 1\u201364 chars (letters, digits, space . _ -)");let r=t.trim(),o=Ii(r);if(!yt(o))throw new Error("resulting tmux session name is invalid");if(!await Xr())throw new Error(`host tmux server not running on ${jt} and auto-start failed \u2014 start one: ${N} -S ${jt} new-session -d -s keeper "sleep 2147483647"`);if(await Jk(o))throw new Error(`tmux session "${o}" already exists`);let s=n.agentCmd||vi,a=n.agent||zn(s),i=!!n.resumeSessionId,c=n.resumeSessionId||Hk(),l=i?s:Ai(s,c),u=i?n.pinned===!0:/(^|\s)--session-id(\s|=)/.test(l),f=a==="claude"&&Gn(s),d=n.firstRun===!0;d||(f&&await Ne(e),a==="codex"&&await Un(e),a==="cursor"&&await Hn(e),await kd(e));let m=f&&!d?is():null,h=Vu(),w=["-e",`${os}=${h}`];m&&w.push("-e",`ANTHROPIC_API_KEY=${m}`);try{await lt(N,B("new-session","-d","-s",o,"-c",e,...w))}catch(g){throw hs(m?hs(g,m):g,h)}return await wn(o),await lt(N,B("set-option","-t",o,"@bostrat-session","1")),await lt(N,B("set-option","-t",o,"@bostrat-title",r)),await lt(N,B("set-option","-t",o,"@bostrat-sid",c)),await lt(N,B("set-option","-t",o,"@bostrat-agent",a)),await lt(N,B("send-keys","-t",o,l,"Enter")),{name:o,title:r,cwd:e,sessionId:c,agent:a,pinned:u,hookToken:h}}async function re(t){if(!yt(t))throw new Error("invalid session name");await lt(N,B("kill-session","-t",t))}async function Id(t,e){if(yt(t))try{let n=e?B("set-option","-t",t,"@bostrat-sid",e):B("set-option","-u","-t",t,"@bostrat-sid");await lt(N,n)}catch{}}async function gt(t,{lines:e=40}={}){if(!yt(t))return"";try{let n=B("capture-pane","-t",t,"-p");Number.isFinite(e)&&e>0&&n.push("-S",`-${Math.floor(e)}`);let{stdout:r}=await lt(N,n);return String(r||"")}catch{return""}}var Xk=Number(process.env.BOSTRAT_PROMPT_CAPTURE_COLS)||100,Yk=Number(process.env.BOSTRAT_PROMPT_CAPTURE_ROWS)||40,Tt=Number(process.env.BOSTRAT_PROMPT_CAPTURE_LINES)||80,Zk=Number(process.env.BOSTRAT_BOX_COLS)||120,Qk=Number(process.env.BOSTRAT_BOX_ROWS)||40;async function Rd(t){if(!yt(t))return 0;try{let{stdout:e}=await lt(N,B("list-clients","-t",t,"-F","1"));return String(e||"").trim().split(`
|
|
48
|
+
`).filter(Boolean).length}catch{return 0}}async function wn(t,{cols:e=Zk,rows:n=Qk}={}){if(yt(t)&&!await Rd(t))try{let r=await Od(t);if(r.cols===e&&r.rows===n)return;await lt(N,B("resize-window","-t",t,"-x",String(e),"-y",String(n))),await lt(N,B("set-window-option","-t",t,"window-size","latest")),await Jr(80)}catch{}}async function $d(t){if(!yt(t))return 0;try{let{stdout:e}=await lt(N,B("display-message","-t",t,"-p","#{pane_pid}"));return Number(String(e||"").trim())||0}catch{return 0}}async function Od(t){if(!yt(t))return{cols:0,rows:0};try{let{stdout:e}=await lt(N,B("display-message","-t",t,"-p","#{window_width} #{window_height}")),[n,r]=String(e||"").trim().split(/\s+/).map(o=>Number(o)||0);return{cols:n,rows:r}}catch{return{cols:0,rows:0}}}async function gs(t,{cols:e=Xk,rows:n=Yk}={}){if(yt(t)&&!await Rd(t))try{let r=await Od(t),o=Math.max(r.cols,e),s=Math.max(r.rows,n);(o>r.cols||s>r.rows)&&(await lt(N,B("resize-window","-t",t,"-x",String(o),"-y",String(s))),await lt(N,B("set-window-option","-t",t,"window-size","latest")),await Jr(80))}catch{}}async function je(t){return await gs(t),gt(t,{lines:Tt})}var tx=new Set(["Up","Down","Left","Right","Space","Enter","Tab","BTab","Escape","BSpace"]);async function xt(t,e,{settleMs:n=40,kind:r}={}){if(!yt(t))throw new Error("invalid session name");let o=(Array.isArray(e)?e:[e]).filter(s=>tx.has(s));for(let s=0;s<o.length;s++)await lt(N,B("send-keys","-t",t,o[s])),n>0&&s<o.length-1&&await Jr(n);return G({flow:"tmux",phase:"send-keys",sessionName:t,kind:r||"keys",keys:o.join(" ")}).catch(()=>{}),o.length}async function ys(t,e,{kind:n}={}){if(!yt(t))throw new Error("invalid session name");let r=Math.max(0,Math.floor(Number(e)||0));return r>0&&await lt(N,B("send-keys","-t",t,"-N",String(r),"BSpace")),G({flow:"tmux",phase:"send-keys",sessionName:t,kind:n||"wipe",keys:`BSpace x${r}`}).catch(()=>{}),r}async function bs(t,e,{perCharDelayMs:n=120,kind:r}={}){if(!yt(t))throw new Error("invalid session name");let o=[...String(e)];for(let s of o)await lt(N,B("send-keys","-t",t,"-l",s)),n>0&&await Jr(n);G({flow:"tmux",phase:"type",sessionName:t,kind:r||"type",textLen:o.length,textHead:String(e).slice(0,120)}).catch(()=>{})}async function vt(t,e,{enter:n=!0,submitDelayMs:r=0,kind:o}={}){if(!yt(t))throw new Error("invalid session name");if(typeof e=="string"&&e.length>0&&await lt(N,B("send-keys","-t",t,"-l",e)),n){let s=Number(r);Number.isFinite(s)&&s>0&&await Jr(s),await lt(N,B("send-keys","-t",t,"Enter"))}G({flow:"tmux",phase:"send",sessionName:t,kind:o||"text",textLen:typeof e=="string"?e.length:0,textHead:typeof e=="string"?e.slice(0,120):"",enter:n,submitDelayMs:r}).catch(()=>{})}import ex from"node-pty";async function Cd(t,e,n={}){if(!yt(e)){ks(t,`invalid session name: ${e}`),t.close(1008,"invalid session name");return}if(!await De()){ks(t,`host tmux server not running on ${jt}`),t.close(1011,"tmux server unavailable");return}if(!await Y(e)){ks(t,`session "${e}" not found`),t.close(1008,"session not found");return}let r=Md(n.cols,120,Nd),o=Md(n.rows,32,Pd),s;try{s=ex.spawn(N,["-S",jt,"attach-session","-d","-t",e],{name:"xterm-256color",cols:r,rows:o,cwd:process.env.HOME||"/",env:{...process.env,TERM:"xterm-256color"}})}catch(a){ks(t,`failed to spawn tmux attach: ${a.message}`),t.close(1011,"spawn failed");return}s.onData(a=>{t.readyState===t.OPEN&&Ri(t,{type:"data",data:a})}),s.onExit(({exitCode:a,signal:i})=>{t.readyState===t.OPEN&&(Ri(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"&&nx(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{}wn(e).catch(()=>{})})}var Nd=20,Pd=6;function nx(t,e){return Number.isFinite(Number(t))&&Number.isFinite(Number(e))&&Number(t)>=Nd&&Number(e)>=Pd}function Md(t,e,n){let r=Number(t);return!Number.isFinite(r)||r<n?e:Math.min(500,r|0)}function Ri(t,e){try{t.send(JSON.stringify(e))}catch{}}function ks(t,e){Ri(t,{type:"error",message:e})}function Bd(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 Yr from"node:fs";import rx from"node:fs/promises";import Vn from"node:path";import ox from"node:os";var Dd={"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},sx=2e5,$i=process.env.BOSTRAT_CLAUDE_PROJECTS||Vn.join(ox.homedir(),".claude","projects");function Oi(t){return t.replace(/[^A-Za-z0-9]/g,"-")}function Mi(t){let e=Vn.join($i,Oi(t)),n;try{n=Yr.readdirSync(e).filter(o=>o.endsWith(".jsonl"))}catch{return null}let r=null;for(let o of n){let s=Vn.join(e,o);try{let{mtimeMs:a}=Yr.statSync(s);(!r||a>r.mtimeMs)&&(r={full:s,mtimeMs:a})}catch{}}return r?r.full:null}function xe(t,e){if(!e)return null;let n=Vn.join($i,Oi(t),`${e}.jsonl`);return Yr.existsSync(n)?n:null}function jd(t){if(!t)return[];let e=Vn.join($i,Oi(t)),n;try{n=Yr.readdirSync(e).filter(o=>o.endsWith(".jsonl"))}catch{return[]}let r=[];for(let o of n)try{let s=Yr.statSync(Vn.join(e,o));r.push({id:o.slice(0,-6),birthMs:s.birthtimeMs||s.ctimeMs||s.mtimeMs})}catch{}return r}async function Ot(t,e=131072){let n=await rx.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 ax(t){let e=t.split(`
|
|
49
|
+
`);for(let n=e.length-1;n>=0;n--){let r=e[n].trim();if(!r||r[0]!=="{")continue;let o;try{o=JSON.parse(r)}catch{continue}let s=o?.message?.usage;if(!s)continue;let a=(s.input_tokens||0)+(s.cache_creation_input_tokens||0)+(s.cache_read_input_tokens||0);if(a>0)return{occupancy:a,model:o.message.model||o.model||null}}return null}async function Zr(t,e=null,{fallbackNewest:n=!0}={}){if(!t)return null;let r,o;if(e){if(r=xe(t,e),o=!0,!r)return null}else if(n){if(r=Mi(t),o=!1,!r)return null}else return null;let s;try{s=ax(await Ot(r))}catch{return null}if(!s)return null;let a=Object.prototype.hasOwnProperty.call(Dd,s.model),i=Dd[s.model]||sx;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 ix(t,e){return!t||!e?null:xe(t,e)}function cx(t){return typeof t=="string"?t.trim():Array.isArray(t)?t.filter(e=>e&&e.type==="text"&&typeof e.text=="string").map(e=>e.text).join(`
|
|
50
|
+
`).trim():""}function lx(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 ux(t){return Array.isArray(t)&&t.length>0&&t.every(e=>e&&e.type==="tool_result")}function Ci(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 Ld(t,e){if(!t.uuid)return null;let n=Ci(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 dx=/\.(png|jpe?g|gif|webp|bmp|svg|tiff?|avif|heic|heif|ico)$/i;async function Ni(t,{maxBytes:e=524288,limit:n=80}={}){let r;try{r=await Ot(t,e)}catch{return[]}let o=[],s=new Map;for(let a of r.split(`
|
|
51
|
+
`)){let i=a.trim();if(!i||i[0]!=="{")continue;let c;try{c=JSON.parse(i)}catch{continue}if(c.type==="attachment"){let h=c.attachment;h?.type==="queued_command"&&h.commandMode==="task-notification"&&typeof h.prompt=="string"&&h.prompt.trim()&&o.push({role:"user",text:h.prompt.trim(),ts:c.timestamp?Date.parse(c.timestamp):null,model:null});continue}let l=c.message;if(!l)continue;let u=l.role||c.type;if(u!=="user"&&u!=="assistant"||u==="assistant"&&l.model==="<synthetic>"&&!c.isApiErrorMessage)continue;if(u==="assistant"&&Array.isArray(l.content))for(let h of l.content){let w=h?.type==="tool_use"?h.input?.file_path||h.input?.path:null;typeof w=="string"&&dx.test(w)&&s.set(h.id,w.split("/").pop())}if(ux(l.content)){let h=Ld(c,s);h&&o.push({role:"assistant",text:"",images:h,ts:c.timestamp?Date.parse(c.timestamp):null,model:null});continue}let f=cx(l.content),d=u==="assistant"?lx(l.content):null,m=u==="user"?Ld(c,s):null;!f&&!d&&!m||o.push({role:u,text:f,tools:d||void 0,images:m||void 0,ts:c.timestamp?Date.parse(c.timestamp):null,model:l.model||null})}return o.slice(-n)}var fx=(t,e)=>t.length>e?`${t.slice(0,e).trimEnd()}\u2026`:t;async function Fd(t,e,{turns:n=16}={}){let r=ix(t,e);if(!r)return"";let o=await Ni(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}:** ${fx(s.text||c,700)} ${i}`.trim()}).join(`
|
|
52
|
+
|
|
53
|
+
`):""}import i0 from"node:fs";import no from"node:fs/promises";import Zn from"node:path";import px from"node:fs/promises";import{execFile as mx}from"node:child_process";import{promisify as hx}from"node:util";var Pi=hx(mx),wx=Number(process.env.BOSTRAT_CODEX_UPDATE_TIMEOUT_MS)||5*6e4,Ud=new Set,Hd=Promise.resolve(),Bi=null;function qd(t){Bi=typeof t=="function"?t:null}function gx(t){let e=String(t||""),n=e.indexOf("/lib/node_modules/");return n>0?e.slice(0,n):null}function Kd({sessionName:t,from:e,to:n}={}){let r=String(n||"").trim();!r||Ud.has(r)||(Ud.add(r),Hd=Hd.then(()=>yx({sessionName:t,from:e,to:r})).catch(()=>{}))}async function yx({sessionName:t,from:e,to:n}){await G({flow:"codex-update",phase:"start",sessionName:t,from:e,to:n}).catch(()=>{});try{let r=(await Pi("/bin/sh",["-c","command -v codex"])).stdout.trim();if(!r)throw new Error("codex not on PATH");let o=await px.realpath(r),s=gx(o),a=s?{...process.env,PATH:`${s}/bin:${process.env.PATH||""}`}:process.env;await Pi("npm",["install","-g","@openai/codex"],{env:a,timeout:wx});let i=(await Pi(r,["--version"])).stdout.trim().replace(/^codex-cli\s*/,"")||n;await G({flow:"codex-update",phase:"done",sessionName:t,from:e,to:n,version:i}).catch(()=>{}),await Wd({sessionName:t,from:e,to:n,version:i,status:"updated"})}catch(r){let o=String(r?.message||r).slice(0,300);await G({flow:"codex-update",phase:"failed",sessionName:t,from:e,to:n,error:o}).catch(()=>{}),await Wd({sessionName:t,from:e,to:n,status:"failed",error:o})}}async function Wd(t){if(Bi)try{await Bi(t)}catch{}}var bx=/^\s*[›>*\-❯]?\s*(\d+)[.)]\s+(.+?)\s*$/,Gd=/\b(allow|approve|apply (this )?patch|run (this )?command|do you want to run|grant)\b/i;function kx(t,{provider:e,box:n}){let r=String(t).replace(/\r/g,"").split(`
|
|
54
|
+
`);if(!Gd.test(t))return null;let o=[],s=!1;for(let l=r.length-1;l>=0;l--){let u=r[l].match(bx);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"&&Gd.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 xs=/(^|\n)\s*›/,Jd=/^\s*Welcome to Codex\b/;function Jn(t){let e=String(t??"").replace(/\r/g,""),n=e.split(`
|
|
55
|
+
`);for(let r=n.length-1;r>=0;r--)if(Jd.test(n[r]))return n.slice(r).join(`
|
|
56
|
+
`);return e}function xx(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 Sx(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 zd=/^\s*(>)?\s*(\d+)\.\s+(.+?)\s*$/;function vx(t,e=""){let n=Jn(t);if(xs.test(n)||!/Sign in with ChatGPT/.test(n)||!/Press enter to continue/.test(n))return null;let r=n.split(`
|
|
57
|
+
`),o=[],s=-1;for(let l=0;l<r.length;l++){let u=r[l].match(zd);if(!u)continue;s<0&&(s=l);let f="";for(let d=l+1;d<Math.min(l+3,r.length);d++){let m=r[d].trim();if(m){if(zd.test(r[d])||/^Press enter/.test(m))break;f=m;break}}o.push({n:Number(u[2]),cursor:!!u[1],label:u[3].trim(),desc:f})}let a=o.sort((l,u)=>l.n-u.n);if(a.length<2||a.some((l,u)=>l.n!==u+1)||!a.some(l=>/Sign in with ChatGPT/.test(l.label)))return null;let i=[];for(let l=0;l<s;l++){let u=r[l].trim();u&&!Jd.test(r[l])&&xx(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=Sx(l.label);return{label:l.label,description:[l.desc,u].filter(Boolean).join(" \xB7 ")}})}]}}var Vd=/Enter this one-time code/,Tx=/^\s*([A-Z0-9]{2,10}-[A-Z0-9]{2,10})\s*$/;function Ax(t,e=""){let n=Jn(t);if(xs.test(n)||!Vd.test(n))return null;let r=n.split(`
|
|
58
|
+
`),o=r.findIndex(i=>Vd.test(i)),s="";for(let i=o+1;i<r.length;i++){let c=r[i].match(Tx);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 _x(t,e=""){let n=Jn(t);return xs.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 Ex(t,e=""){let n=Jn(t);return xs.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 Di=/Run Everything|Add a follow-up|Plan, search, build anything/;function Ix(t,e=""){let n=String(t);if(!/Press any key to log in/.test(n)||Di.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
59
|
+
`).map(a=>a.trim()),o=r.findIndex(a=>/Press any key to log in/.test(a)),s=r.slice(0,o<0?r.length:o).filter(a=>a&&!/^Cursor Agent$/i.test(a)&&!/^v?\d{4}\.\d{2}\.\d{2}/.test(a)).join(" ");return{provider:"cursor",box:e,multiSelect:!1,cursorIndex:0,answerMode:"keys",promptId:"cursor:signin-start",questions:[{question:(s?`${s} `:"")+"Cursor isn't signed in on this box \u2014 start the sign-in to get a login link you can open from any device.",header:"Sign in",multiSelect:!1,options:[{label:"Start sign-in",description:"Shows a login link (and a QR option) \u2014 your login goes to cursor.com, never through bostrat"}]}]}}var Rx=/click this link to log in|Signing in with the browser/;function $x(t,e=""){let n=String(t);if(!Rx.test(n)||Di.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
60
|
+
`),o="",s=r.findIndex(a=>/https:\/\//.test(a));if(s>=0){o=r[s].slice(r[s].indexOf("https://")).trim();for(let a=s+1;a<r.length;a++){let i=r[a].trim();if(!i||!/^[A-Za-z0-9~%&=?/_.-]+$/.test(i))break;o+=i}}return{provider:"cursor",box:e,multiSelect:!1,cursorIndex:0,promptId:`cursor:signin-link:${(o.match(/[?&]challenge=([\w-]+)/)||[])[1]?.slice(0,16)||o.slice(-24)||"pending"}`,questions:[{question:o?`Cursor is waiting for you to finish signing in: open ${o} on any device and log in. Your login goes straight to cursor.com \u2014 bostrat never sees it. This card clears once sign-in completes.`:"Cursor is waiting for you to finish signing in \u2014 open the login link shown in the Terminal tab. This card clears once sign-in completes.",header:"Sign in",multiSelect:!1,options:[]}]}}function Ox(t,e=""){let n=String(t);return!/Scan this QR code to log in/.test(n)||Di.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 Xd(t,e,n=""){if(t!=="cursor"&&t!=="codex")return null;if(t==="codex"){let r=Ax(e,n)||_x(e,n)||Ex(e,n)||vx(e,n);if(r)return r}if(t==="cursor"){let r=$x(e,n)||Ox(e,n)||Ix(e,n);if(r)return r}return kx(e,{provider:t,box:n})}function Yd(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 Mx=()=>Number(process.env.BOSTRAT_AGENT_READY_TIMEOUT_MS)||9e4,ji=Number(process.env.BOSTRAT_AGENT_READY_POLL_MS)||500,Qr=Number(process.env.BOSTRAT_AGENT_READY_SETTLE_MS)||800,At={claude:{up:/shift\+tab to cycle|⏵⏵ bypass permissions on|⏸ manual mode on/,busy:/esc to interrupt/,busyAlt:/^[·✢✳✶✻✽✺✹✸✷]\s+[A-Z][\w '’-]{1,40}…/m,auth:/Select login method:|Paste code here if prompted|OAuth error:|Choose the text style that looks best|Quick safety check: Is this a project you created/,block:/Press\s+Enter\s+to\s+continue\s*[.…]{0,3}\s*$/m},codex:{up:/(^|\n)\s*›/,busy:/esc to interrupt|\bWorking \(/,block:/Update available![\s\S]*\b(?:Update now|Skip until next version)\b[\s\S]*Press enter to continue/,auth:/Sign in with ChatGPT[\s\S]*Press enter to continue|Finish signing in via your browser|Paste or type your API key/},cursor:{up:/Run Everything|Add a follow-up|Plan, search, build anything/,busy:/ctrl\+c to stop|^\s*[⠁-⣿]+\s+\S/m,block:/Workspace Trust Required|Trust this workspace|\[a\]\s*Trust|Trusting workspace/,auth:/Press any key to log in|click this link to log in|Scan this QR code to log in|Signing in with the browser/}},Cx=/Trust this workspace|\[a\]\s*Trust/,Nx=/Update available!\s*([\d][\w.-]*)\s*->\s*([\d][\w.-]*)/;function Px(t){let e=String(t||"");if(!At.codex.block.test(e))return null;let n=Jn(e);if(!At.codex.block.test(n))return null;let r=n.match(Nx);return{from:r?.[1]||null,to:r?.[2]||null}}function Le(t){return new Promise(e=>setTimeout(e,t))}var Bx=()=>Number(process.env.BOSTRAT_GATE_RESEND_MS)||3e3,Ss=new Map;function Zd(t,e){let n=Date.now(),r=Bx();for(let[s,a]of Ss)n-a>=r&&Ss.delete(s);let o=`${t}::${e}`;return Ss.has(o)?!1:(Ss.set(o,n),!0)}var Dx=68,jx=/^\s*[─━]{10,}\s*$/;function Lx(t){let e=0;for(let n of String(t).split(`
|
|
61
|
+
`))jx.test(n)&&(e=Math.max(e,n.trimEnd().length));return e}function Li(t,e){let n=At[t];if(!n?.busy)return"idle";if(Fi(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=Lx(e);return t==="claude"&&r>0&&r<Dx?"unknown":"idle"}var Fx=/\bWaiting for \d+\b[^\n]*\bto finish\b|\/workflows to (?:stop|monitor)\b/;function tf(t,e){return t==="claude"&&Fx.test(String(e||""))}function Fe(t,e){return Li(t,e)==="busy"}function vs(t,e){return Li(t,e)!=="idle"}function Ts(t,e){let n=At[t];return n?.up?n.up.test(e):!0}function Ux(t,e){let n=At[t];return n?Fi(t,e)?!1:n.up.test(e)&&Li(t,e)==="idle":!0}function Fi(t,e){let n=At[t];return n?n.auth?.test(e)&&!n.up.test(e)?!0:n.block?.test(e)?t==="codex"?!0:!n.up.test(e):!1:!1}async function Qd(t,e,n){let r=Date.now()+n,o=0;for(await wn(t).catch(()=>{});;){let s=await gt(t);if(Ux(e,s)){await Le(Qr);return}if(e==="cursor"&&Cx.test(s)&&!At.cursor.up.test(s)&&o<3){if(!Zd(t,"cursor-trust")){await Le(ji);continue}o+=1,await G({flow:"agent-ready",phase:"gate-accept",sessionName:t,agent:e,attempt:o}).catch(()=>{}),await vt(t,"a",{enter:!1,kind:"cursor-trust-accept"}),await Le(Qr);continue}if(e==="codex"&&o<3){let a=Px(s);if(a){if(!Zd(t,`codex-update:${a.from}->${a.to}`)){await Le(ji);continue}o+=1,await G({flow:"agent-ready",phase:"gate-accept",sessionName:t,agent:e,attempt:o,update:`${a.from} -> ${a.to}`}).catch(()=>{}),await vt(t,"3",{enter:!1,kind:"codex-update-skip"}),Kd({sessionName:t,from:a.from,to:a.to}),await Le(Qr);continue}}if(Date.now()>r){if(Fi(e,s)){let a=`${e} is blocked on an interactive prompt`;throw e==="codex"?a=At.codex.auth.test(s)?"codex isn't signed in on this box \u2014 answer the Sign in card in chat (Device Code works from any device) or finish signing in from the Terminal tab, then send again":"codex is stuck on its update prompt (auto-skip didn't clear it) \u2014 open the terminal and choose Skip, then send again":e==="cursor"&&At.cursor.auth.test(s)?a="cursor isn't signed in on this box \u2014 answer the Sign in card in chat (the login link works from any device) or finish signing in from the Terminal tab, then send again":e==="claude"&&At.claude.auth.test(s)?a="claude isn't signed in on this box \u2014 answer the setup cards in chat (or finish signing in from the Terminal tab), then send again":e==="claude"&&At.claude.block.test(s)&&(a='claude is waiting on its "Press Enter to continue" screen \u2014 answer the Continue card in chat (or press Enter in the Terminal tab), then send again'),G({flow:"agent-ready",phase:"blocked",sessionName:t,agent:e,timeoutMs:n,gateAccepts:o,error:a}).catch(()=>{}),new Error(a)}console.warn(`[agent-ready] ${e} readiness unconfirmed in ${t} after ${n}ms \u2014 driving the box anyway.`),G({flow:"agent-ready",phase:"timeout",sessionName:t,agent:e,timeoutMs:n,gateAccepts:o,paneBlocked:!!At[e]?.block?.test(s),paneUp:!!At[e]?.up?.test(s),paneBusy:!!At[e]?.busy?.test(s)}).catch(()=>{}),await Le(Qr);return}await Le(ji)}}function ef({agent:t,agentCmd:e}={}){let n=e?zn(e):zn(t||"");return{label:n,realClaude:e?Gn(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 As({cwd:t,sessionName:e,agent:n,agentCmd:r}={}){let{realCodex:o,realCursor:s}=ef({agent:n,agentCmd:r});return!e||!o&&!s?null:Ue({cwd:t,sessionName:e,agent:n,agentCmd:r}).catch(()=>{})}async function Ue({cwd:t,sessionId:e,sessionName:n,agent:r,agentCmd:o,timeoutMs:s=Mx()}={}){let{label:a,realClaude:i,realCodex:c,realCursor:l}=ef({agent:r,agentCmd:o});if(i&&t&&await Ne(t),c&&t&&await Un(t),l&&t&&await Hn(t),(i||c||l)&&n){await Qd(n,a,s);return}let u=At[r]?r:At[a]?a:null;if(process.env.BOSTRAT_FAKE_POLL_READY==="1"&&n&&u){await Qd(n,u,s);return}await Le(Qr)}var Hx=(()=>{let t=Number(process.env.BOSTRAT_CODEX_SUBMIT_SETTLE_MS??120);return Number.isFinite(t)&&t>=0?t:120})(),Wx=(()=>{let t=Number(process.env.BOSTRAT_CLAUDE_SUBMIT_SETTLE_MS??120);return Number.isFinite(t)&&t>=0?t:120})();function Se(t){return t==="codex"?{enter:!0,submitDelayMs:Hx}:t==="claude"?{enter:!0,submitDelayMs:Wx}:{enter:!0}}import Es from"node:fs/promises";import cf from"node:path";var qx=["bug-fix","orchestration","design","ops-infra","docs-writing","research","feature-build"],Kx={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 Xn(t){let e=String(t||"").toLowerCase();if(!e.trim())return null;let n=null;for(let r of qx){let o=0;for(let s of Kx[r])s.test(e)&&o++;o>0&&(!n||o>n.score)&&(n={category:r,score:o})}return n}var Gx=new Set(["Read","Grep","Glob","LS","WebSearch","WebFetch","NotebookRead"]),zx=new Set(["Edit","Write","MultiEdit","NotebookEdit"]);function nf(t){let e=0,n=0;for(let[r,o]of Object.entries(t||{}))Gx.has(r)&&(e+=o||0),zx.has(r)&&(n+=o||0);return e>=5&&n===0?{category:"research",score:1}:n>=3?{category:"feature-build",score:1}:null}function to(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 rf=1.5,of=.75,Vx=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 af(t){let e=to(String(t||"")).toLowerCase(),n=[];for(let r of e.split(/[^a-z0-9]+/))r.length<2||Vx.has(r)||n.push(r);return n}function Jx(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(sf(n,"user",o))}else if(n.type==="turn.agent"){if(!o)continue;e.push(sf(n,"assistant",o))}}return e}function sf(t,e,n){let r=Xn(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 eo(t){let e=Jx(t),n=new Map,r=new Array(e.length).fill(0),o=0;e.forEach((a,i)=>{let c=af(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 _s(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(af(e))];if(!i.length)return[];let c=new Map;for(let u of i){let f=t.postings.get(u);if(!f)continue;let d=f.size,m=Math.log(1+(t.N-d+.5)/(d+.5));for(let[h,w]of f){let g=t.lengths[h],b=w+rf*(1-of+of*(g/(t.avgdl||1))),v=m*(w*(rf+1)/b);c.set(h,(c.get(h)||0)+v)}}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 lf="Relevant prior discussion (verbatim, retrieved";async function Xx(t){let e=cf.join(t,".bostrat","events"),n;try{n=await Es.readdir(e)}catch{return eo([])}let r=[];for(let o of n)if(o.endsWith(".ndjson"))try{let s=await Es.readFile(cf.join(e,o),"utf8");for(let a of s.split(`
|
|
62
|
+
`))if(a.trim())try{r.push(JSON.parse(a))}catch{}}catch{}return eo(r)}function Yx(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}
|
|
63
|
+
${o}`.trim()||e.slice(0,2e3)}function Zx(t,e,{k:n=6,maxChars:r=4e3,exclude:o=null}={}){let s=_s(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?["",`## ${lf}, 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(`
|
|
64
|
+
`):""}async function uf(t,e,n={}){let{k:r=6,maxChars:o=4e3,exclude:s=null}=n;try{let a=await Es.readFile(e,"utf8");if(a.includes(lf))return{appended:!1,reason:"already-enriched"};let i=await Xx(t);if(!i.N)return{appended:!1,reason:"empty-index"};let c=Zx(i,Yx(a),{k:r,maxChars:o,exclude:s});return c?(await Es.appendFile(e,`
|
|
65
|
+
`+c),{appended:!0,chars:c.length,chunks:c.split(`
|
|
66
|
+
- `).length-1,indexed:i.N}):{appended:!1,reason:"no-hits",indexed:i.N}}catch(a){return{appended:!1,reason:"error",error:a?.message||String(a)}}}import Ui from"node:fs/promises";import Yn from"node:path";import Qx from"node:os";import{EventEmitter as t0}from"node:events";import{randomUUID as e0}from"node:crypto";var n0=new Set(["session.ready","turn.user","turn.agent","turn.retracted","tool.called","tool.result","prompt.raised","prompt.answered","context.usage","cost.recorded","memory.seed","handoff.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","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"]),r0=new Set(["user","agent","orchestrator","system"]),o0=new Set(["hook","otel","statusline","transcript","pane","orchestrator","vendor"]),Wt=new t0;Wt.setMaxListeners(0);var Hi=process.env.BOSTRAT_STATE_DIR||Yn.join(process.env.HOME||Qx.tmpdir(),".bostrat");function Rs(t){return t?.cwd?Yn.join(t.cwd,".bostrat","events"):Yn.join(Hi,"events")}var df=(t,e)=>Yn.join(t,`${e}.ndjson`),Is=new Map;async function s0(t,e){if(!Is.has(e)){let o=(await L(t,e)).reduce((s,a)=>a.seq>s?a.seq:s,0);Is.set(e,o)}let n=Is.get(e)+1;return Is.set(e,n),n}function a0(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 O(t,e){let n=typeof t=="string"?t:t?.id;if(!n)throw new Error("appendEvent: a track (or trackId) is required");if(!e||!n0.has(e.type))throw new Error(`appendEvent: unknown event type "${e?.type}"`);if(e.actor&&!r0.has(e.actor))throw new Error(`appendEvent: bad actor "${e.actor}"`);if(e.source&&!o0.has(e.source))throw new Error(`appendEvent: bad source "${e.source}"`);let r=typeof t=="string"?Yn.join(Hi,"events"):Rs(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=a0(c,Number(e.payload?.ts??e.ts)||Date.now())}let s=await s0(r,n),a=e.userId??process.env.BOSTRAT_OWNER??null,i={id:e.id||e0(),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 Ui.mkdir(r,{recursive:!0}),await Ui.appendFile(df(r,n),JSON.stringify(i)+`
|
|
67
|
+
`),Wt.emit("event",i),Wt.emit(`track:${n}`,i),i}async function L(t,e){let n,r;typeof t=="string"&&e?(n=t,r=e):(r=typeof t=="string"?t:t?.id,n=typeof t=="string"?Yn.join(Hi,"events"):Rs(t));let o;try{o=await Ui.readFile(df(n,r),"utf8")}catch{return[]}let s=[];for(let a of o.split(`
|
|
68
|
+
`))if(a.trim())try{s.push(JSON.parse(a))}catch{}return s}function ff(t,e){let n=`track:${t}`;return Wt.on(n,e),()=>Wt.off(n,e)}var hf=".bostrat/handoff",wf="latest.md",gf="Checkpoint: write a work-in-progress handoff",yf="HANDOFF WRITTEN",bf="Resume from .bostrat/handoff/latest.md",c0="/clear",l0=t=>`${gf} 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 ${yf} and take no further action.`,u0=`${bf}: read it, restate the Objective and the single next step in one line, then continue the work from 'Next steps'.`,kf="Beat: write a",xf="BEAT WRITTEN",d0=".bostrat/arc",f0="beat.txt",pf=200,Sf=4,vf=5,p0=36,m0=t=>`${kf} 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 ${Sf} further lines, each starting with "- " and naming ONE piece in at most ${vf} 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 ${xf} and take no further action.`,mf=/^(?:[-–—*•·]|\d+[.)])\s+/,h0=(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 w0(t){let e=String(t||"").split(/\r?\n/).map(s=>s.trim()).filter(Boolean),n=[],r=[];for(let s of e)if(mf.test(s)){if(r.length>=Sf)continue;let a=h0(s.replace(mf,"").replace(/[.;,:]+$/,""),vf,p0);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>pf&&(o=`${o.slice(0,pf-1).trimEnd()}\u2026`),{text:o,items:r.length>1?r:[]}}async function Tf(t,e,{timeoutMs:n=9e4}={}){let r=Zn.join(e,d0),o=Zn.join(r,f0);await no.mkdir(r,{recursive:!0});let s=await $s(o),a=await Y(t);if(!a)throw new Error(`session "${t}" not found`);return await Os(t,a,m0(o)),await Mf(o,s,n),w0(await no.readFile(o,"utf8"))}function Af(t,e){let n=(e||"").trim();return n?t==="assistant"?n===yf||n===xf:n.startsWith(gf)||n===c0||n.startsWith(bf)||n.startsWith(kf):!1}var g0=6e5;function _f(t){let e=[];for(let r of t?.events||[]){if(r.type!=="lead-handoff"||!r.from)continue;let o=r.at??Date.now(),s=r.handoffStartedAt??o-g0;e.push({box:r.from,from:s,to:o}),r.to&&r.to!==r.from&&e.push({box:r.to,from:s,to:o})}let n=t?.handoff;return n?.fromBox&&n.startedAt&&n.finishedAt&&e.push({box:n.fromBox,from:n.startedAt,to:n.finishedAt}),n?.toBox&&n.startedAt&&n.finishedAt&&e.push({box:n.toBox,from:n.startedAt,to:n.finishedAt}),e}function Ef(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 If(t,{windows:e=[]}={}){if(Af(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 y0=3500,b0=3e5,Wi=new Map;function Ms(t){return Wi.get(t)||null}async function Rf(t){let e=Wi.get(t);if(e&&!e.finishedAt)throw new Error("a handoff is already running for this session");let n=await Y(t);if(!n)throw new Error(`session "${t}" not found`);if(!n.cwd)throw new Error(`can't resolve cwd for "${t}"`);let r={name:t,step:"summarize",startedAt:Date.now(),finishedAt:null,error:null,file:Zn.join(n.cwd,hf,wf)};return Wi.set(t,r),k0(r,n.cwd).catch(o=>{r.error=o?.message||String(o),r.finishedAt=Date.now()}),r}async function k0(t,e){t.step="summarize",await $f(t.name,e),t.step="clear";let n=await Y(t.name);await Os(t.name,n,"/clear"),await Cf(y0),t.step="resume",await Os(t.name,n,u0),t.step="done",t.finishedAt=Date.now()}async function $f(t,e){let n=Zn.join(e,hf),r=Zn.join(n,wf);await no.mkdir(n,{recursive:!0});let o=await $s(r),s=await Y(t);await G({flow:"checkpoint",phase:"summarize-start",sessionName:t,cwd:e,file:r,beforeMtime:o}),await Os(t,s,l0(r)),await Mf(r,o,b0),await G({flow:"checkpoint",phase:"summarize-done",sessionName:t,file:r,afterMtime:await $s(r)});let a=await uf(e,r,{exclude:c=>Af(c.role,c.text)});await G({flow:"checkpoint",phase:"seed-enriched",sessionName:t,file:r,...a});let i=/workspaces[/\\]([0-9a-f]{8})$/.exec(e||"")?.[1];return i&&await O({id:i,cwd:e},{type:"memory.seed",actor:"orchestrator",payload:{...a,tokensEst:a.chars?Math.round(a.chars/4):0,ts:Date.now()}}).catch(()=>{}),await vd(e),await x0(n,r),r}async function Of(t){let e=await Y(t);if(!e)throw new Error(`session "${t}" not found`);if(!e.cwd)throw new Error(`can't resolve cwd for "${t}"`);return $f(t,e.cwd)}async function $s(t){try{return(await no.stat(t)).mtimeMs}catch{return 0}}async function Mf(t,e,n){let r=Date.now()+n;for(;;){if(await $s(t)>e)return;if(Date.now()>r)throw new Error(`timed out waiting for the handoff file to be written (${t})`);await Cf(2e3)}}async function x0(t,e){try{if(!i0.existsSync(e))return;let n=new Date().toISOString().replace(/[:.]/g,"-");await no.copyFile(e,Zn.join(t,`${n}.md`))}catch{}}function Cf(t){return new Promise(e=>setTimeout(e,t))}async function Os(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 Ue({cwd:e.cwd,sessionId:e.sessionId,sessionName:t,agent:e.agent,agentCmd:o})}let r=await Y(t);if(!r)throw new Error(`session "${t}" disappeared before the orchestration prompt could be sent`);await vt(t,n,{kind:"orchestration-drive",...Se(r.agent)})}import U from"node:fs/promises";import H from"node:path";import{randomUUID as Lo}from"node:crypto";import{execFile as __}from"node:child_process";import{promisify as E_}from"node:util";import mm from"node:path";var qi={"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}},Nf={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 Pf(t){if(!t)return null;let e=String(t).trim();return qi[e]?e:Nf[e]?Nf[e]:Object.keys(qi).find(r=>e.startsWith(r))||null}var Cs=t=>Pf(t)!==null;function oe(t,e={}){let n=qi[Pf(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 Bf=()=>({input:0,output:0,cacheRead:0,cacheCreation:0});function S0(t){return t?{input:t.input||0,output:t.output||0,cacheRead:t.cacheRead||0,cacheWrite:t.cacheWrite??t.cacheCreation??0}:null}function Df(t,e){e&&(t.input+=e.input,t.output+=e.output,t.cacheRead+=e.cacheRead,t.cacheCreation+=e.cacheWrite)}function Ps(t){let e=t.payload?.model||"unknown",n=S0(t.payload?.tokens),r=Number(t.payload?.usd)||0,o=!1;!r&&n&&(r=oe(e,n),r>0&&(o=!0));let s=n?n.input+n.output+n.cacheRead+n.cacheWrite:0,a=!r&&s>0&&!Cs(e);return{usd:r,model:e,tokens:n,repriced:o,unpriced:a}}function se(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=Bf();for(let f of n){let{usd:d,model:m,tokens:h,repriced:w,unpriced:g}=Ps(f),b=f.provider||"unknown",v=f.boxId||"unattributed";if(w&&i++,g&&(c++,l.add(m)),a+=d,Df(u,h),r[m]||={usd:0,tokens:Bf(),events:0},r[m].usd+=d,r[m].events+=1,Df(r[m].tokens,h),o[b]||={usd:0,events:0},o[b].usd+=d,o[b].events+=1,s[v]||={usd:0,events:0,provider:b,models:new Set,label:e[v]||v},s[v].usd+=d,s[v].events+=1,m!=="unknown"&&s[v].models.add(m),f.epoch!=null){let A=(s[v].epochs||={})[f.epoch]||={usd:0,events:0,provider:b,models:new Set};A.usd+=d,A.events+=1,A.provider=b,m!=="unknown"&&A.models.add(m)}}return{totalUsd:ro(a),totalTokens:u,turns:n.length,repricedTurns:i,unpricedTurns:c,unpricedModels:[...l],byModel:Ns(r,f=>({...f,usd:ro(f.usd)})),byProvider:Ns(o,f=>({...f,usd:ro(f.usd)})),byBox:Ns(s,f=>({usd:ro(f.usd),events:f.events,provider:f.provider,label:f.label,models:[...f.models],...f.epochs?{epochs:Ns(f.epochs,d=>({usd:ro(d.usd),events:d.events,provider:d.provider,models:[...d.models]}))}:{}}))}}var ro=t=>Math.round(t*1e4)/1e4,Ns=(t,e)=>Object.fromEntries(Object.entries(t).map(([n,r])=>[n,e(r)]));import St from"node:fs/promises";import Mt from"node:path";import oo from"node:os";import{randomUUID as A0}from"node:crypto";import{execFile as _0}from"node:child_process";import{promisify as E0}from"node:util";import{chromium as I0}from"playwright-core";import Ki from"node:fs/promises";import v0 from"node:path";var T0=0;async function ae(t,e,{mode:n}={}){await Ki.mkdir(v0.dirname(t),{recursive:!0});let r=`${t}.${process.pid}.${++T0}.tmp`;await Ki.writeFile(r,JSON.stringify(e,null,2)+`
|
|
69
|
+
`,n!=null?{mode:n}:void 0),await Ki.rename(r,t)}var Lf=E0(_0),Ff=process.env.BOSTRAT_STATE_DIR||Mt.join(process.env.HOME||oo.tmpdir(),".bostrat"),Uf=Mt.join(Ff,"mocks.json"),R0=process.env.BOSTRAT_MOCK_ENGINE||"chromium",$0=process.env.BOSTRAT_CHROMIUM_BIN||"/usr/bin/chromium",O0=process.env.BOSTRAT_FIREFOX_BIN||"firefox",M0=1300,Qn=2,C0=32,Hf=1500,tr=Number(process.env.BOSTRAT_MOCK_TIMEOUT_MS)||75e3,N0=700*1024*1024;async function P0(){if(process.platform!=="darwin")return oo.freemem();try{let{stdout:t}=await Lf("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:oo.freemem()}catch{return oo.freemem()}}var Vt=class extends Error{constructor(e,n=""){super(n?`${e}
|
|
34
70
|
${n.trim().split(`
|
|
35
71
|
`).slice(-6).join(`
|
|
36
|
-
`)}`:e),this.name="MockRenderError"}},sn=null,wi=null;async function pe(){return sn||(wi||=(async()=>{try{sn=JSON.parse(await yt.readFile(Md,"utf8"))}catch{sn={mocks:[]}}finally{wi=null}return sn.mocks||=[],sn})(),wi)}async function Qt(){await Zt(Md,sn)}function qt(t,e,n={}){return O({id:t.trackId,cwd:t.cwd},{type:"mock.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box||null,provider:t.agent||null,payload:{ts:Date.now(),change:e,mockId:t.id,name:t.name,...t.group?{group:t.group}:{},...n}}).catch(()=>{})}var ms=t=>Rt.join(t,".bostrat","mocks");function Ft(t,e){let n=ms(t.cwd);return{html:Rt.join(n,`${t.id}.v${e}.html`),png:Rt.join(n,`${t.id}.v${e}.png`)}}function an(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 yi(t){return t.length<24||t.readUInt32BE(12)!==1229472850?null:{width:t.readUInt32BE(16),height:t.readUInt32BE(20)}}var Ck=Buffer.from([137,80,78,71,13,10,26,10]);function Pd(t){return Buffer.isBuffer(t)&&t.length>24&&t.subarray(0,8).equals(Ck)}async function Mk(t){let e=Buffer.alloc(24),n=await yt.open(t,"r");try{await n.read(e,0,24,0)}finally{await n.close()}return e}function Nk({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 Pk(t,e){return t.evaluate(async n=>{let r=new Image;await new Promise((h,w)=>{r.onload=h,r.onerror=()=>w(new Error("decode failed")),r.src=`data:image/png;base64,${n}`});let o=r.naturalWidth,s=r.naturalHeight;if(!o||!s||o*s>24e6)return null;let a=document.createElement("canvas");a.width=o,a.height=s;let i=a.getContext("2d",{willReadFrequently:!0});i.drawImage(r,0,0);let c=i.getImageData(0,0,o,s).data,l=[c[0],c[1],c[2]],u=o,f=s,d=-1,m=-1;for(let h=0;h<s;h++)for(let w=h*o*4,y=0;y<o;y++,w+=4)Math.abs(c[w]-l[0])+Math.abs(c[w+1]-l[1])+Math.abs(c[w+2]-l[2])>24&&(y<u&&(u=y),y>d&&(d=y),h<f&&(f=h),m=h);return d<0?null:{imgW:o,imgH:s,bg:l,bbox:{left:u,top:f,right:d,bottom:m}}},e.toString("base64"))}async function Bk(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 Dk({htmlPath:t,outPath:e,width:n}){let r=Date.now(),o;try{o=await Ak.launch({executablePath:_k,args:["--disable-gpu"],timeout:3e4})}catch(s){throw new Wt("chromium failed to launch",s?.message||"")}try{let a=await(await o.newContext({viewport:{width:n,height:800},deviceScaleFactor:Hn})).newPage();a.setDefaultTimeout(Wn),await a.goto(`file://${t}`,{waitUntil:"load",timeout:Wn}),await a.waitForTimeout(250);let i;for(let l=1;;l++)try{i=await a.screenshot({fullPage:!0,timeout:Wn});break}catch(u){if(l>=3||!/captureScreenshot|Unable to capture/i.test(u?.message||""))throw u;await a.waitForTimeout(500)}try{let l=await Pk(a,i),u=l&&Nk({...l,pad:Rk*Hn,align:Hn});u&&(i=await Bk(a,i,{bg:l.bg,crop:u}))}catch{}if(i.length<Nd)throw new Wt(`capture produced a ${i.length}-byte image`);let c=yi(i);if(!c)throw new Wt("output is not a PNG");return await yt.writeFile(e,i),{width:Math.round(c.width/Hn),height:Math.round(c.height/Hn),dpr:Hn,renderMs:Date.now()-r}}catch(s){throw s instanceof Wt?s:new Wt(/timeout/i.test(s?.message||"")?`render timed out after ${Wn}ms`:"chromium render failed",s?.message||"")}finally{await o.close().catch(()=>{})}}async function $d({htmlPath:t,outPath:e,width:n}){let r=await yt.mkdtemp(Rt.join(Ur.tmpdir(),"bostrat-mock-profile-")),o=Date.now(),s="";try{try{s=(await Od(Ek,["--headless","--no-remote","--profile",r,`--window-size=${n}`,"--screenshot",e,`file://${t}`],{env:{...process.env,MOZ_HEADLESS:"1"},timeout:Wn,killSignal:"SIGKILL",maxBuffer:4194304})).stderr||""}catch(c){throw s=c?.stderr||"",new Wt(c?.killed?`render timed out after ${Wn}ms`:"firefox exited non-zero",s)}let a=await yt.stat(e).catch(()=>null);if(!a||a.size<Nd)throw new Wt(`render produced ${a?`a ${a.size}-byte file`:"no file"} (silent no-op \u2014 another firefox holding the profile?)`,s);let i=yi(await Mk(e));if(!i)throw new Wt("output is not a PNG",s);return{width:i.width,height:i.height,dpr:1,renderMs:Date.now()-o}}finally{await yt.rm(r,{recursive:!0,force:!0}).catch(()=>{})}}async function jk(t){if(Tk==="firefox")return $d(t);try{return await Dk(t)}catch(e){if(/failed to launch/i.test(e?.message||""))return $d(t);throw e}}var Lk=jk;var gi=Promise.resolve();function Bd(t){return gi=gi.then(t,t),gi}async function Dd(t,e){let n=await pe(),r=n.mocks.find(o=>o.id===t);if(r){r.state="rendering",await Qt(),await qt(r,"rendering",{version:e});try{let o=await Ok();if(o<$k)throw new Wt(`box is low on memory (${Math.round(o/1e6)} MB available) \u2014 not spawning a browser`);let s=Ft(r,e),a=await Lk({htmlPath:s.html,outPath:s.png,width:r.width});if(!n.mocks.some(i=>i.id===t)){await yt.rm(s.png,{force:!0}).catch(()=>{});return}jd(r,{v:e,source:"html",htmlRel:Rt.relative(r.cwd,s.html),pngRel:Rt.relative(r.cwd,s.png),width:a.width,height:a.height,dpr:a.dpr,renderMs:a.renderMs,createdAt:Date.now()}),await Qt(),await qt(r,"ready",{version:e,width:a.width,height:a.height,dpr:a.dpr})}catch(o){if(!n.mocks.some(s=>s.id===t))return;r.state="error",r.error=o?.message||String(o),await Qt(),await qt(r,"error",{version:e,error:r.error})}}}function jd(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 Fk({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?Ik:Number(r);if(!Number.isInteger(o)||o<320||o>4e3)throw new Error("invalid width (320-4000)");return o}async function hs({trackId:t,group:e}={}){let n=await pe(),r=t?n.mocks.filter(o=>o.trackId===t):n.mocks;return e&&(r=r.filter(o=>o.group===e)),r.map(o=>({...o}))}async function $e(t){let n=(await pe()).mocks.find(r=>r.id===t);return n?{...n}:null}async function Ld({trackId:t,name:e,html:n,image:r,cwd:o,width:s,group:a,box:i,agent:c,prompt:l}){let u=Fk({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&&!Pd(r))throw new Error("image must be a PNG");let d=await pe(),m={id:xk().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 yt.mkdir(ms(o),{recursive:!0}),d.mocks.push(m),f?(await yt.writeFile(Ft(m,1).html,n),await Qt(),await qt(m,"created"),Bd(()=>Dd(m.id,1))):(await Ud(m,1,r),await Qt(),await qt(m,"created"),await qt(m,"ready",{version:1,width:m.width,height:m.height,dpr:m.dpr})),{...m}}async function Fd(t,{html:e,image:n,width:r}={}){let s=(await pe()).mocks.find(c=>c.id===t);if(!s)throw new Error("mock not found");let a=typeof e=="string"&&e.trim();if(!a&&!n)throw new Error("html (string) or image (png) required");if(n&&!Pd(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 yt.mkdir(ms(s.cwd),{recursive:!0}),a?(s.state="queued",await yt.writeFile(Ft(s,i).html,e),await Qt(),await qt(s,"updated",{version:i}),Bd(()=>Dd(s.id,i))):(await Ud(s,i,n),await Qt(),await qt(s,"updated",{version:i}),await qt(s,"ready",{version:i,width:s.width,height:s.height,dpr:s.dpr})),{...s}}async function Ud(t,e,n){let r=yi(n);if(!r)throw new Error("image must be a PNG");let o=Ft(t,e);await yt.writeFile(o.png,n),jd(t,{v:e,source:"image",htmlRel:null,pngRel:Rt.relative(t.cwd,o.png),width:r.width,height:r.height,dpr:1,renderMs:null,createdAt:Date.now()}),t.width=r.width}async function Hd(t){let e=await pe(),n=e.mocks.find(o=>o.id===t);if(!n)throw new Error("mock not found");if(n.readyVersion==null)throw new Error(`mock is ${n.state} \u2014 nothing to pick yet`);let r=n.group?e.mocks.filter(o=>o.trackId===n.trackId&&o.group===n.group&&o.id!==t&&o.readyVersion!=null).map(o=>({mockId:o.id,name:o.name})):[];return await qt(n,"picked",{version:n.readyVersion,siblings:r}),{mock:{...n},siblings:r}}async function Wd(t){let e=await pe(),n=e.mocks.findIndex(o=>o.id===t);if(n===-1)throw new Error("mock not found");let r=e.mocks[n];e.mocks.splice(n,1);for(let o=1;o<=r.version;o++){let s=Ft(r,o);await yt.rm(s.html,{force:!0}).catch(()=>{}),await yt.rm(s.png,{force:!0}).catch(()=>{})}return await Qt(),await qt(r,"deleted"),{ok:!0,id:t,name:r.name}}async function qd(t){let n=(await pe()).mocks.filter(i=>i.trackId===t.id);if(!n.length)return 0;let r=Rt.join(Cd,"mock-archive",t.id),o=ms(t.cwd),s=Rt.join(r,".bostrat","mocks");await yt.mkdir(s,{recursive:!0});let a=await yt.readdir(o).catch(()=>[]);for(let i of a)await yt.copyFile(Rt.join(o,i),Rt.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 yt.stat(Rt.join(r,l)).catch(()=>null))?.isFile())throw new Error(`mock evacuation incomplete: ${l} missing from the archive`)}i.cwd=r,i.trackDeletedAt=Date.now(),i.trackName=t.name||t.id}return await Qt(),n.length}async function Gd(t){let e=await pe(),n=e.mocks.filter(r=>r.trackId===t);return n.length&&(e.mocks=e.mocks.filter(r=>r.trackId!==t),await Qt()),n.length}import Wr from"node:fs/promises";import $t from"node:path";import Uk from"node:os";import Hk from"node:net";import{randomUUID as Wk}from"node:crypto";import{execFile as qk}from"node:child_process";import{promisify as Gk}from"node:util";var ee=Gk(qk),Kk=process.env.BOSTRAT_STATE_DIR||$t.join(process.env.HOME||Uk.tmpdir(),".bostrat"),Xd=$t.join(Kk,"views.json"),[Kd,Vd]=(process.env.BOSTRAT_VIEW_PORTS||"4300-4349").split("-").map(t=>Number(t)||0),Oe=Number(process.env.BOSTRAT_VIEW_MAX)||3,Vk="bostrat-view-",qn=t=>`${Vk}${t}`,zd=new Set(["bostrat","www","api","views","app"]),ws=t=>Object.assign(new Error(t),{status:400}),zk=new Set(["npm","pnpm","yarn","bun"]),xi="package.json",Jk=/[|;&`]|\$\(/,Xk=new Set(["sh","bash","zsh","dash","env","exec","nohup","time"]),Yk=/(^|\s)(-C|--cwd|--manifest-path|--dir)(=|\s)/,Yd=t=>String(t).trim().split(/\s+/).filter(Boolean),Zk=/^[A-Za-z_][A-Za-z0-9_]*=/;function Qk(t){for(let e of Yd(t))if(!Zk.test(e))return e;return null}function Zd(t){let e=Yd(t),n=e.indexOf("--");return n===-1?e:e.slice(0,n)}function tx(t,e){let n=Zd(e);for(let r=0;r<n.length;r++){let o=/^--prefix=(.+)$/.exec(n[r]);if(o)return $t.resolve(t,o[1]);if(n[r]==="--prefix"&&n[r+1])return $t.resolve(t,n[r+1])}return t}var ex=t=>Wr.stat(t).then(e=>e.isDirectory(),()=>!1),Qd=t=>Wr.stat(t).then(e=>e.isFile(),()=>!1),gs=t=>Wr.realpath(t).catch(()=>$t.resolve(t));async function nx(t,e){if($t.resolve(t)===$t.resolve(e))return!0;let[n,r]=await Promise.all([gs(t),gs(e)]);return n===r}async function rx(t){let n=(await Wr.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 Qd($t.join(t,o.name,xi))?`./${o.name}`:null))).filter(Boolean)}async function ox(t,e){let[n,r]=await Promise.all([gs(t),gs(e)]),o=$t.relative(n,r);if(o&&(o.startsWith("..")||$t.isAbsolute(o)))throw ws(`serve directory escapes the Track workspace: ${e}`)}async function sx(t,e){if(!await ex(t))throw ws(`serve directory does not exist: ${t}`);let n=Qk(e);if(!n)return;if(!n.includes("/")&&!await Xa(n,{loginShell:!0,failOpen:!0}))throw ws(`\`${n}\` is not on the box's PATH \u2014 install it in the box first`);if(Jk.test(e)||Xk.has(n)||!zk.has(n)||!Zd(e).includes("run")||Yk.test(e))return;let r=tx(t,e);if(await Qd($t.join(r,xi)))return;let o=await rx(r),s=o.length?` \u2014 found one in ${o.join(", ")}; run bostrat-view from there`:"";throw ws(`no ${xi} in ${r}${s}`)}var te=null,bi=null;async function we(){return te||(bi||=(async()=>{try{te=JSON.parse(await Wr.readFile(Xd,"utf8"))}catch{te={views:[],adoptWatermarks:{}}}finally{bi=null}return te.views||=[],te.adoptWatermarks||={},te})(),bi)}async function ne(){await Zt(Xd,te)}function he(t,e,n={}){return O({id:t.trackId,cwd:t.cwd},{type:"view.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box||null,payload:{ts:Date.now(),change:e,viewId:t.id,name:t.name,slug:t.slug,port:t.port,...n}}).catch(()=>{})}var Hr=new Map,Ai=Number(process.env.BOSTRAT_VIEW_JOB_TIMEOUT)||3e5,tf=12e3;function Ti(t){return Hr.get(t)||null}function ef(t,{name:e,box:n}={}){let r=Hr.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 Hr.set(t,o),o}function Si(t,e){let n=Hr.get(t);n&&!n.finishedAt&&!n.viewId&&(n.viewId=e,n.step="booting")}function ys(t,{error:e=null}={}){let n=Hr.get(t);return!n||n.finishedAt?null:(n.finishedAt=Date.now(),e&&(n.error=e),n)}function nf(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 vi(t){return String(t||"").trim().toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,40).replace(/-+$/g,"")||"view"}function ax(t,e){let n=vi(e),r=new Set(t.views.map(o=>o.slug));if(!r.has(n)&&!zd.has(n))return n;for(let o=2;;o++){let s=`${n}-${o}`;if(!r.has(s)&&!zd.has(s))return s}}function qr(t,{timeoutMs:e=1200}={}){return new Promise(n=>{let r=Hk.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 _i(t){if(process.platform!=="darwin"){await ee("fuser",["-k",`${t}/tcp`]).catch(()=>{});return}let e="";try{({stdout:e}=await ee("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 rf(t=process.env){return new Set([Number(t.PORT)||3362,Number(t.BOSTRAT_TLS_PORT)||3443])}async function ix(t,e){let n=new Set(t.views.map(o=>o.port)),r=rf();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=Kd;o<=Vd;o++)if(!(n.has(o)||r.has(o))&&!await qr(o,{timeoutMs:250}))return o;throw new Error(`no free view port in ${Kd}-${Vd}`)}async function cx(t){try{let{stdout:e}=await ee(N,B("list-sessions","-F","#{session_name}"));return e.split(`
|
|
37
|
-
`).some(n=>n.trim()===
|
|
72
|
+
`)}`:e),this.name="MockRenderError"}},gn=null,Gi=null;async function ve(){return gn||(Gi||=(async()=>{try{gn=JSON.parse(await St.readFile(Uf,"utf8"))}catch{gn={mocks:[]}}finally{Gi=null}return gn.mocks||=[],gn})(),Gi)}async function ie(){await ae(Uf,gn)}function Jt(t,e,n={}){return O({id:t.trackId,cwd:t.cwd},{type:"mock.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box||null,provider:t.agent||null,payload:{ts:Date.now(),change:e,mockId:t.id,name:t.name,...t.group?{group:t.group}:{},...n}}).catch(()=>{})}var Bs=t=>Mt.join(t,".bostrat","mocks");function qt(t,e){let n=Bs(t.cwd);return{html:Mt.join(n,`${t.id}.v${e}.html`),png:Mt.join(n,`${t.id}.v${e}.png`)}}function yn(t,e=null){let n=t.versions||[];return e!=null?n.find(r=>r.v===Number(e))||null:n.length?n[n.length-1]:null}function Vi(t){return t.length<24||t.readUInt32BE(12)!==1229472850?null:{width:t.readUInt32BE(16),height:t.readUInt32BE(20)}}var B0=Buffer.from([137,80,78,71,13,10,26,10]);function Wf(t){return Buffer.isBuffer(t)&&t.length>24&&t.subarray(0,8).equals(B0)}async function D0(t){let e=Buffer.alloc(24),n=await St.open(t,"r");try{await n.read(e,0,24,0)}finally{await n.close()}return e}function j0({imgW:t,imgH:e,bbox:n,pad:r,align:o=1}){if(!n||!(t>0)||!(e>0))return null;let s=n.right-n.left+1,a=n.bottom-n.top+1;if(s<=0||a<=0||s>=t*.9&&a>=e*.9)return null;let i=s+2*r,c=a+2*r;return i+=(o-i%o)%o,c+=(o-c%o)%o,{sx:n.left,sy:n.top,sw:s,sh:a,outW:i,outH:c,dx:Math.floor((i-s)/2),dy:Math.floor((c-a)/2)}}async function L0(t,e){return t.evaluate(async n=>{let r=new Image;await new Promise((h,w)=>{r.onload=h,r.onerror=()=>w(new Error("decode failed")),r.src=`data:image/png;base64,${n}`});let o=r.naturalWidth,s=r.naturalHeight;if(!o||!s||o*s>24e6)return null;let a=document.createElement("canvas");a.width=o,a.height=s;let i=a.getContext("2d",{willReadFrequently:!0});i.drawImage(r,0,0);let c=i.getImageData(0,0,o,s).data,l=[c[0],c[1],c[2]],u=o,f=s,d=-1,m=-1;for(let h=0;h<s;h++)for(let w=h*o*4,g=0;g<o;g++,w+=4)Math.abs(c[w]-l[0])+Math.abs(c[w+1]-l[1])+Math.abs(c[w+2]-l[2])>24&&(g<u&&(u=g),g>d&&(d=g),h<f&&(f=h),m=h);return d<0?null:{imgW:o,imgH:s,bg:l,bbox:{left:u,top:f,right:d,bottom:m}}},e.toString("base64"))}async function F0(t,e,{bg:n,crop:r}){let o=await t.evaluate(async({b64:s,bg:a,crop:i})=>{let c=new Image;await new Promise((f,d)=>{c.onload=f,c.onerror=()=>d(new Error("decode failed")),c.src=`data:image/png;base64,${s}`});let l=document.createElement("canvas");l.width=i.outW,l.height=i.outH;let u=l.getContext("2d");return u.fillStyle=`rgb(${a[0]},${a[1]},${a[2]})`,u.fillRect(0,0,i.outW,i.outH),u.drawImage(c,i.sx,i.sy,i.sw,i.sh,i.dx,i.dy,i.sw,i.sh),l.toDataURL("image/png").split(",")[1]},{b64:e.toString("base64"),bg:n,crop:r});return Buffer.from(o,"base64")}async function U0({htmlPath:t,outPath:e,width:n}){let r=Date.now(),o;try{o=await I0.launch({executablePath:$0,args:["--disable-gpu"],timeout:3e4})}catch(s){throw new Vt("chromium failed to launch",s?.message||"")}try{let a=await(await o.newContext({viewport:{width:n,height:800},deviceScaleFactor:Qn})).newPage();a.setDefaultTimeout(tr),await a.goto(`file://${t}`,{waitUntil:"load",timeout:tr}),await a.waitForTimeout(250);let i;for(let l=1;;l++)try{i=await a.screenshot({fullPage:!0,timeout:tr});break}catch(u){if(l>=3||!/captureScreenshot|Unable to capture/i.test(u?.message||""))throw u;await a.waitForTimeout(500)}try{let l=await L0(a,i),u=l&&j0({...l,pad:C0*Qn,align:Qn});u&&(i=await F0(a,i,{bg:l.bg,crop:u}))}catch{}if(i.length<Hf)throw new Vt(`capture produced a ${i.length}-byte image`);let c=Vi(i);if(!c)throw new Vt("output is not a PNG");return await St.writeFile(e,i),{width:Math.round(c.width/Qn),height:Math.round(c.height/Qn),dpr:Qn,renderMs:Date.now()-r}}catch(s){throw s instanceof Vt?s:new Vt(/timeout/i.test(s?.message||"")?`render timed out after ${tr}ms`:"chromium render failed",s?.message||"")}finally{await o.close().catch(()=>{})}}async function jf({htmlPath:t,outPath:e,width:n}){let r=await St.mkdtemp(Mt.join(oo.tmpdir(),"bostrat-mock-profile-")),o=Date.now(),s="";try{try{s=(await Lf(O0,["--headless","--no-remote","--profile",r,`--window-size=${n}`,"--screenshot",e,`file://${t}`],{env:{...process.env,MOZ_HEADLESS:"1"},timeout:tr,killSignal:"SIGKILL",maxBuffer:4194304})).stderr||""}catch(c){throw s=c?.stderr||"",new Vt(c?.killed?`render timed out after ${tr}ms`:"firefox exited non-zero",s)}let a=await St.stat(e).catch(()=>null);if(!a||a.size<Hf)throw new Vt(`render produced ${a?`a ${a.size}-byte file`:"no file"} (silent no-op \u2014 another firefox holding the profile?)`,s);let i=Vi(await D0(e));if(!i)throw new Vt("output is not a PNG",s);return{width:i.width,height:i.height,dpr:1,renderMs:Date.now()-o}}finally{await St.rm(r,{recursive:!0,force:!0}).catch(()=>{})}}async function H0(t){if(R0==="firefox")return jf(t);try{return await U0(t)}catch(e){if(/failed to launch/i.test(e?.message||""))return jf(t);throw e}}var W0=H0;var zi=Promise.resolve();function qf(t){return zi=zi.then(t,t),zi}async function Kf(t,e){let n=await ve(),r=n.mocks.find(o=>o.id===t);if(r){r.state="rendering",await ie(),await Jt(r,"rendering",{version:e});try{let o=await P0();if(o<N0)throw new Vt(`box is low on memory (${Math.round(o/1e6)} MB available) \u2014 not spawning a browser`);let s=qt(r,e),a=await W0({htmlPath:s.html,outPath:s.png,width:r.width});if(!n.mocks.some(i=>i.id===t)){await St.rm(s.png,{force:!0}).catch(()=>{});return}Gf(r,{v:e,source:"html",htmlRel:Mt.relative(r.cwd,s.html),pngRel:Mt.relative(r.cwd,s.png),width:a.width,height:a.height,dpr:a.dpr,renderMs:a.renderMs,createdAt:Date.now()}),await ie(),await Jt(r,"ready",{version:e,width:a.width,height:a.height,dpr:a.dpr})}catch(o){if(!n.mocks.some(s=>s.id===t))return;r.state="error",r.error=o?.message||String(o),await ie(),await Jt(r,"error",{version:e,error:r.error})}}}function Gf(t,e){t.versions=(t.versions||[]).filter(r=>r.v!==e.v).concat(e),t.versions.sort((r,o)=>r.v-o.v);let n=t.versions[t.versions.length-1];t.readyVersion=n.v,t.height=n.height,t.dpr=n.dpr,t.renderMs=n.renderMs??null,n.v===t.version&&(t.state="ready",t.error=null)}function q0({trackId:t,name:e,cwd:n,width:r}){if(!t)throw new Error("trackId required");if(typeof e!="string"||!e.trim())throw new Error("name (string) required");if(!n)throw new Error("cwd required");let o=r==null?M0:Number(r);if(!Number.isInteger(o)||o<320||o>4e3)throw new Error("invalid width (320-4000)");return o}async function Ds({trackId:t,group:e}={}){let n=await ve(),r=t?n.mocks.filter(o=>o.trackId===t):n.mocks;return e&&(r=r.filter(o=>o.group===e)),r.map(o=>({...o}))}async function He(t){let n=(await ve()).mocks.find(r=>r.id===t);return n?{...n}:null}async function zf({trackId:t,name:e,html:n,image:r,cwd:o,width:s,group:a,box:i,agent:c,prompt:l}){let u=q0({trackId:t,name:e,cwd:o,width:s}),f=typeof n=="string"&&n.trim();if(!f&&!r)throw new Error("html (string) or image (png) required");if(r&&!Wf(r))throw new Error("image must be a PNG");let d=await ve(),m={id:A0().slice(0,8),trackId:t,box:i||null,agent:c||null,name:e.trim(),prompt:l?String(l).slice(0,500):null,group:a?String(a).trim():null,cwd:o,width:u,height:null,dpr:null,state:"queued",source:f?"html":"image",version:1,readyVersion:null,versions:[],error:null,renderMs:null,createdAt:Date.now(),updatedAt:Date.now()};return await St.mkdir(Bs(o),{recursive:!0}),d.mocks.push(m),f?(await St.writeFile(qt(m,1).html,n),await ie(),await Jt(m,"created"),qf(()=>Kf(m.id,1))):(await Jf(m,1,r),await ie(),await Jt(m,"created"),await Jt(m,"ready",{version:1,width:m.width,height:m.height,dpr:m.dpr})),{...m}}async function Vf(t,{html:e,image:n,width:r}={}){let s=(await ve()).mocks.find(c=>c.id===t);if(!s)throw new Error("mock not found");let a=typeof e=="string"&&e.trim();if(!a&&!n)throw new Error("html (string) or image (png) required");if(n&&!Wf(n))throw new Error("image must be a PNG");if(r!=null){let c=Number(r);if(!Number.isInteger(c)||c<320||c>4e3)throw new Error("invalid width (320-4000)");s.width=c}let i=s.version+1;return s.version=i,s.updatedAt=Date.now(),s.source=a?"html":"image",await St.mkdir(Bs(s.cwd),{recursive:!0}),a?(s.state="queued",await St.writeFile(qt(s,i).html,e),await ie(),await Jt(s,"updated",{version:i}),qf(()=>Kf(s.id,i))):(await Jf(s,i,n),await ie(),await Jt(s,"updated",{version:i}),await Jt(s,"ready",{version:i,width:s.width,height:s.height,dpr:s.dpr})),{...s}}async function Jf(t,e,n){let r=Vi(n);if(!r)throw new Error("image must be a PNG");let o=qt(t,e);await St.writeFile(o.png,n),Gf(t,{v:e,source:"image",htmlRel:null,pngRel:Mt.relative(t.cwd,o.png),width:r.width,height:r.height,dpr:1,renderMs:null,createdAt:Date.now()}),t.width=r.width}async function Xf(t){let e=await ve(),n=e.mocks.find(o=>o.id===t);if(!n)throw new Error("mock not found");if(n.readyVersion==null)throw new Error(`mock is ${n.state} \u2014 nothing to pick yet`);let r=n.group?e.mocks.filter(o=>o.trackId===n.trackId&&o.group===n.group&&o.id!==t&&o.readyVersion!=null).map(o=>({mockId:o.id,name:o.name})):[];return await Jt(n,"picked",{version:n.readyVersion,siblings:r}),{mock:{...n},siblings:r}}async function Yf(t){let e=await ve(),n=e.mocks.findIndex(o=>o.id===t);if(n===-1)throw new Error("mock not found");let r=e.mocks[n];e.mocks.splice(n,1);for(let o=1;o<=r.version;o++){let s=qt(r,o);await St.rm(s.html,{force:!0}).catch(()=>{}),await St.rm(s.png,{force:!0}).catch(()=>{})}return await ie(),await Jt(r,"deleted"),{ok:!0,id:t,name:r.name}}async function Zf(t){let n=(await ve()).mocks.filter(i=>i.trackId===t.id);if(!n.length)return 0;let r=Mt.join(Ff,"mock-archive",t.id),o=Bs(t.cwd),s=Mt.join(r,".bostrat","mocks");await St.mkdir(s,{recursive:!0});let a=await St.readdir(o).catch(()=>[]);for(let i of a)await St.copyFile(Mt.join(o,i),Mt.join(s,i));for(let i of n){for(let c of i.versions||[])for(let l of[c.htmlRel,c.pngRel]){if(!l)continue;if(!(await St.stat(Mt.join(r,l)).catch(()=>null))?.isFile())throw new Error(`mock evacuation incomplete: ${l} missing from the archive`)}i.cwd=r,i.trackDeletedAt=Date.now(),i.trackName=t.name||t.id}return await ie(),n.length}async function Qf(t){let e=await ve(),n=e.mocks.filter(r=>r.trackId===t);return n.length&&(e.mocks=e.mocks.filter(r=>r.trackId!==t),await ie()),n.length}import ao from"node:fs/promises";import Ct from"node:path";import K0 from"node:os";import G0 from"node:net";import{randomUUID as z0}from"node:crypto";import{execFile as V0}from"node:child_process";import{promisify as J0}from"node:util";var le=J0(V0),X0=process.env.BOSTRAT_STATE_DIR||Ct.join(process.env.HOME||K0.tmpdir(),".bostrat"),op=Ct.join(X0,"views.json"),[tp,ep]=(process.env.BOSTRAT_VIEW_PORTS||"4300-4349").split("-").map(t=>Number(t)||0),We=Number(process.env.BOSTRAT_VIEW_MAX)||3,Y0="bostrat-view-",er=t=>`${Y0}${t}`,np=new Set(["bostrat","www","api","views","app"]),js=t=>Object.assign(new Error(t),{status:400}),Z0=new Set(["npm","pnpm","yarn","bun"]),Yi="package.json",Q0=/[|;&`]|\$\(/,tS=new Set(["sh","bash","zsh","dash","env","exec","nohup","time"]),eS=/(^|\s)(-C|--cwd|--manifest-path|--dir)(=|\s)/,sp=t=>String(t).trim().split(/\s+/).filter(Boolean),nS=/^[A-Za-z_][A-Za-z0-9_]*=/;function rS(t){for(let e of sp(t))if(!nS.test(e))return e;return null}function ap(t){let e=sp(t),n=e.indexOf("--");return n===-1?e:e.slice(0,n)}function oS(t,e){let n=ap(e);for(let r=0;r<n.length;r++){let o=/^--prefix=(.+)$/.exec(n[r]);if(o)return Ct.resolve(t,o[1]);if(n[r]==="--prefix"&&n[r+1])return Ct.resolve(t,n[r+1])}return t}var sS=t=>ao.stat(t).then(e=>e.isDirectory(),()=>!1),ip=t=>ao.stat(t).then(e=>e.isFile(),()=>!1),Ls=t=>ao.realpath(t).catch(()=>Ct.resolve(t));async function aS(t,e){if(Ct.resolve(t)===Ct.resolve(e))return!0;let[n,r]=await Promise.all([Ls(t),Ls(e)]);return n===r}async function iS(t){let n=(await ao.readdir(t,{withFileTypes:!0}).catch(()=>[])).filter(o=>o.isDirectory()&&!o.name.startsWith(".")&&o.name!=="node_modules");return(await Promise.all(n.map(async o=>await ip(Ct.join(t,o.name,Yi))?`./${o.name}`:null))).filter(Boolean)}async function cS(t,e){let[n,r]=await Promise.all([Ls(t),Ls(e)]),o=Ct.relative(n,r);if(o&&(o.startsWith("..")||Ct.isAbsolute(o)))throw js(`serve directory escapes the Track workspace: ${e}`)}async function lS(t,e){if(!await sS(t))throw js(`serve directory does not exist: ${t}`);let n=rS(e);if(!n)return;if(!n.includes("/")&&!await _i(n,{loginShell:!0,failOpen:!0}))throw js(`\`${n}\` is not on the box's PATH \u2014 install it in the box first`);if(Q0.test(e)||tS.has(n)||!Z0.has(n)||!ap(e).includes("run")||eS.test(e))return;let r=oS(t,e);if(await ip(Ct.join(r,Yi)))return;let o=await iS(r),s=o.length?` \u2014 found one in ${o.join(", ")}; run bostrat-view from there`:"";throw js(`no ${Yi} in ${r}${s}`)}var ce=null,Ji=null;async function _e(){return ce||(Ji||=(async()=>{try{ce=JSON.parse(await ao.readFile(op,"utf8"))}catch{ce={views:[],adoptWatermarks:{}}}finally{Ji=null}return ce.views||=[],ce.adoptWatermarks||={},ce})(),Ji)}async function ue(){await ae(op,ce)}function Ae(t,e,n={}){return O({id:t.trackId,cwd:t.cwd},{type:"view.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box||null,payload:{ts:Date.now(),change:e,viewId:t.id,name:t.name,slug:t.slug,port:t.port,...n}}).catch(()=>{})}var so=new Map,tc=Number(process.env.BOSTRAT_VIEW_JOB_TIMEOUT)||3e5,cp=12e3;function ec(t){return so.get(t)||null}function lp(t,{name:e,box:n}={}){let r=so.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 so.set(t,o),o}function Zi(t,e){let n=so.get(t);n&&!n.finishedAt&&!n.viewId&&(n.viewId=e,n.step="booting")}function Fs(t,{error:e=null}={}){let n=so.get(t);return!n||n.finishedAt?null:(n.finishedAt=Date.now(),e&&(n.error=e),n)}function up(t){let e=[];for(let n of t?.events||[])n.type!=="view-spinup"||!n.box||!n.startedAt||!n.endedAt||e.push({box:n.box,from:n.startedAt,to:n.endedAt});return e}function Qi(t){return String(t||"").trim().toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,40).replace(/-+$/g,"")||"view"}function uS(t,e){let n=Qi(e),r=new Set(t.views.map(o=>o.slug));if(!r.has(n)&&!np.has(n))return n;for(let o=2;;o++){let s=`${n}-${o}`;if(!r.has(s)&&!np.has(s))return s}}function io(t,{timeoutMs:e=1200}={}){return new Promise(n=>{let r=G0.connect({port:t,host:"127.0.0.1"}),o=s=>{r.destroy(),n(s)};r.setTimeout(e,()=>o(!1)),r.once("connect",()=>o(!0)),r.once("error",()=>o(!1))})}async function nc(t){if(process.platform!=="darwin"){await le("fuser",["-k",`${t}/tcp`]).catch(()=>{});return}let e="";try{({stdout:e}=await le("lsof",["-t","-sTCP:LISTEN","-i",`tcp:${t}`]))}catch{return}for(let n of e.split(/\s+/).filter(Boolean)){let r=Number(n);if(!(!r||r===process.pid))try{process.kill(r,"SIGKILL")}catch{}}}function dp(t=process.env){return new Set([Number(t.PORT)||3362,Number(t.BOSTRAT_TLS_PORT)||3443])}async function dS(t,e){let n=new Set(t.views.map(o=>o.port)),r=dp();if(e){let o=Number(e);if(!Number.isInteger(o)||o<1024||o>65535)throw new Error("invalid port");if(r.has(o))throw new Error(`port ${o} is the node's own API port`);if(n.has(o))throw new Error(`port ${o} is already owned by a view`);return o}for(let o=tp;o<=ep;o++)if(!(n.has(o)||r.has(o))&&!await io(o,{timeoutMs:250}))return o;throw new Error(`no free view port in ${tp}-${ep}`)}async function fS(t){try{let{stdout:e}=await le(N,B("list-sessions","-F","#{session_name}"));return e.split(`
|
|
73
|
+
`).some(n=>n.trim()===er(t))}catch{return!1}}async function co(t){try{await le(N,B("kill-session","-t",er(t)))}catch{}}async function rc(t){if(!await Xr())throw new Error("host tmux server not running and auto-start failed \u2014 cannot spawn a view server");let e=er(t.slug),n=t.serveCwd??t.cwd,r=Ct.dirname(process.execPath);await le(N,B("new-session","-d","-s",e,"-c",n)),await le(N,B("send-keys","-t",e,"-l",`export PORT=${t.port} PATH="${r}:$PATH"`)),await le(N,B("send-keys","-t",e,"Enter")),await le(N,B("send-keys","-t",e,"-l",t.cmd)),await le(N,B("send-keys","-t",e,"Enter"))}async function rp(t,e=12){try{let{stdout:n}=await le(N,B("capture-pane","-t",er(t),"-p"));return String(n||"").trimEnd().split(`
|
|
38
74
|
`).filter(Boolean).slice(-e).join(`
|
|
39
|
-
`)}catch{return""}}var me=new Map,lx=12e4;async function ux(t){if(t.state==="stopped"||t.state==="error")return!1;let e=t.origin!=="detected",n=e?await cx(t.slug):!0;if(e&&!n){let s=t.state==="starting";return t.state=s?"error":"stopped",s&&(t.error=await Jd(t.slug)||"server exited before listening"),me.delete(t.id),await he(t,t.state,t.error?{error:t.error}:{}),!0}if(await qr(t.port))return me.delete(t.id),t.lastHealthyAt=Date.now(),t.state!=="live"?(t.state="live",t.error=null,await he(t,"live"),!0):!1;if(t.state==="starting")return Date.now()-t.createdAt>lx?(t.state="error",t.error=await Jd(t.slug)||"never started listening",await he(t,"error",{error:t.error}),!0):!1;let o=(me.get(t.id)||0)+1;return me.set(t.id,o),o>=3&&t.state==="live"?(t.state="unhealthy",!0):!1}var ki=!1;async function bs(){if(!ki){ki=!0;try{let t=await we(),e=!1;for(let n of t.views)await ux(n)&&(e=!0);e&&await ne()}finally{ki=!1}}}async function ks({trackId:t}={}){await bs();let e=await we();return(t?e.views.filter(r=>r.trackId===t):e.views).map(r=>({...r}))}async function of(t){let n=(await we()).views.find(r=>r.id===t);return n?{...n}:null}function Ii(t){return te&&te.views.find(e=>e.slug===t)||null}async function Ri({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=$t.resolve(r,o??"."),f=i==="detected"?null:async()=>{await ox(r,u),await sx(u,n)},d=await we();if(i!=="detected"){let w=vi(e),y=String(n).trim(),b=d.views.find(A=>A.trackId===t&&(A.slug===w||vi(A.name)===w||A.cmd===y&&$t.resolve(A.serveCwd??A.cwd)===u));if(b)return dx(d,b,{cmd:y,box:a,branch:c,serveCwd:l?u:void 0,validate:f})}if(f&&await f(),d.views.filter(w=>w.state==="live"||w.state==="starting").length>=Oe)throw new Error(`view cap reached (${Oe} live) \u2014 stop one first`);let h={id:Wk().slice(0,8),trackId:t,box:a||null,name:e.trim(),slug:ax(d,e),cmd:n?String(n).trim():null,cwd:r,serveCwd:u,branch:c,port:await ix(d,s),origin:i,state:"starting",error:null,createdAt:Date.now(),lastHealthyAt:null};return i!=="detected"&&await Ei(h),d.views.push(h),await ne(),await he(h,"created",{origin:i}),Si(t,h.id),i==="detected"&&await qr(h.port)&&(h.state="live",h.lastHealthyAt=Date.now(),await ne(),await he(h,"live")),{...h}}async function dx(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 nx(e.serveCwd??e.cwd,s);if((e.state==="live"||e.state==="starting")&&!i&&!c&&await qr(e.port))return await ne(),Si(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>=Oe)throw new Error(`view cap reached (${Oe} live) \u2014 stop one first`);return e.origin==="detected"?(await _i(e.port),e.origin="agent"):await Gr(e.slug),e.cmd=n,s!==void 0&&(e.serveCwd=s),e.state="starting",e.error=null,e.createdAt=Date.now(),me.delete(e.id),await Ei(e),await ne(),await he(e,"created",{restarted:!0,reused:!0}),Si(e.trackId,e.id),{...e,reused:!0}}async function sf(t){let n=(await we()).views.find(r=>r.id===t);if(!n)throw new Error("view not found");return n.origin==="detected"?await _i(n.port):await Gr(n.slug),n.state!=="stopped"&&(n.state="stopped",me.delete(n.id),await ne(),await he(n,"stopped")),{...n}}async function af(t){let e=await we(),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>=Oe)throw new Error(`view cap reached (${Oe} live) \u2014 stop one first`);return await Gr(n.slug),n.state="starting",n.error=null,n.createdAt=Date.now(),me.delete(n.id),await Ei(n),await ne(),await he(n,"created",{restarted:!0}),{...n}}async function cf(t){let e=await we(),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 _i(r.port):await Gr(r.slug),e.views.splice(n,1),me.delete(r.id),await ne(),await he(r,"deleted"),{ok:!0,id:t,slug:r.slug}}async function lf(t){let e=await we(),n=e.views.filter(r=>r.trackId===t);for(let r of n)r.origin!=="detected"&&await Gr(r.slug),me.delete(r.id);return n.length&&(e.views=e.views.filter(r=>r.trackId!==t),await ne()),n.length}var fx=/https?:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0)(?::(\d{2,5}))?(?:\/|\b)/g;async function uf(t,e){if(!t?.id||!Array.isArray(e)||!e.length)return[];let n=await we(),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 ne()),!o.length)return[];let a=new Set;for(let c of o)for(let l of String(c.text||"").matchAll(fx)){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(!rf().has(c)&&!n.views.some(l=>l.port===c)&&await qr(c))try{i.push(await Ri({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 ln from"node:fs/promises";import Ce from"node:path";import px from"node:os";import{randomUUID as mx}from"node:crypto";var Kn=process.env.BOSTRAT_STATE_DIR||Ce.join(process.env.HOME||px.tmpdir(),".bostrat"),ff=Ce.join(Kn,"runbooks.json"),Oi=Ce.join(Kn,"runbooks"),df=256*1024,hx=[{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 Kr(t){return hx.filter(({re:e})=>e.test(t)).map(({label:e})=>e)}var cn=null,$i=null;async function Me(){return cn||($i||=(async()=>{try{cn=JSON.parse(await ln.readFile(ff,"utf8"))}catch{cn={runbooks:[]}}finally{$i=null}return cn.runbooks||=[],cn})(),$i)}async function Vn(){await Zt(ff,cn)}function zn(t,e,n={}){return O({id:t.trackId,cwd:t.cwd},{type:"runbook.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box||null,provider:t.agent||null,payload:{ts:Date.now(),change:e,runbookId:t.id,name:t.name,status:t.status,...n}}).catch(()=>{})}function Gn(t,e){return Ce.join(Oi,`${t.id}.v${e}.md`)}function pf(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 mf(t){if(typeof t!="string"||!t.trim())throw new Error("md (markdown string) required");let e=Buffer.byteLength(t,"utf8");if(e>df)throw new Error(`runbook is ${Math.round(e/1024)} KB \u2014 over the ${df/1024} KB cap; a doc that size belongs in the repo, not a runbook`);let n=Kr(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 hf(t,e){if(t.trackDeletedAt)throw new Error(`runbook is orphaned (its Track was deleted) \u2014 cannot ${e} it`)}async function xs({trackId:t,status:e}={}){let n=await Me(),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 Ci(t){let n=(await Me()).runbooks.find(r=>r.id===t);return n?{...n}:null}async function wf({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=mf(r),c=await Me(),l={id:mx().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 ln.mkdir(Oi,{recursive:!0}),await ln.writeFile(Gn(l,1),r),l.versions.push({v:1,mdRel:Ce.relative(Kn,Gn(l,1)),bytes:i,note:null,createdAt:Date.now()}),c.runbooks.push(l),await Vn(),await zn(l,"created",{version:1}),{...l}}async function gf(t,{md:e,note:n}={}){let o=(await Me()).runbooks.find(i=>i.id===t);if(!o)throw new Error("runbook not found");hf(o,"update");let s=mf(e),a=o.version+1;return o.version=a,o.updatedAt=Date.now(),await ln.mkdir(Oi,{recursive:!0}),await ln.writeFile(Gn(o,a),e),o.versions.push({v:a,mdRel:Ce.relative(Kn,Gn(o,a)),bytes:s,note:n?String(n).slice(0,200):null,createdAt:Date.now()}),await Vn(),await zn(o,"updated",{version:a,...n?{note:String(n).slice(0,200)}:{}}),{...o}}async function yf(t,e){if(e!=="open"&&e!=="done")throw new Error('status must be "open" or "done"');let r=(await Me()).runbooks.find(o=>o.id===t);if(!r)throw new Error("runbook not found");return hf(r,e==="done"?"complete":"reopen"),r.status===e?{...r}:(r.status=e,r.updatedAt=Date.now(),await Vn(),await zn(r,e==="done"?"done":"reopened"),{...r})}async function bf(t){let e=await Me(),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 ln.rm(Ce.join(Kn,o.mdRel),{force:!0}).catch(()=>{});return await Vn(),r.trackDeletedAt||await zn(r,"deleted"),{ok:!0,id:t,name:r.name}}async function kf(t){let n=(await Me()).runbooks.filter(r=>r.trackId===t&&!r.trackDeletedAt);for(let r of n)r.trackDeletedAt=Date.now(),await zn(r,"orphaned"),r.cwd=null;return n.length&&await Vn(),n.length}async function xf(t){let e=await Me(),n=e.runbooks.filter(r=>r.trackId===t);for(let r of n){await zn(r,"deleted");for(let o of r.versions||[])await ln.rm(Ce.join(Kn,o.mdRel),{force:!0}).catch(()=>{})}return n.length&&(e.runbooks=e.runbooks.filter(r=>r.trackId!==t),await Vn()),n.length}import Jr from"node:fs/promises";import Bf from"node:path";import Gx from"node:os";import{randomUUID as Df}from"node:crypto";import wx from"node:fs/promises";import Sf from"node:path";import gx from"node:os";var vf=()=>Sf.join(process.env.BOSTRAT_STATE_DIR||Sf.join(process.env.HOME||gx.tmpdir(),".bostrat"),"integrations.json");async function ge(){try{return JSON.parse(await wx.readFile(vf(),"utf8"))||{}}catch{return{}}}var Ss=["linked","prOpened","prMerged","create"],Mi=["prompt","auto","off"],yx={create:["prompt","off"]},Af=t=>yx[t]??Mi;function vs(t){let e=t.linear||{},n=e.transitions===!0?"auto":"prompt",r={};for(let o of Ss){let s=Af(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 un(){let t=await ge(),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:vs(t),defaultTeam:n}:null}async function As(){let t=await ge(),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:vs(t)}}async function Ni(){return vs(await ge())}async function Tf(t){let e=Object.entries(t||{});if(!e.length)throw new Error(`nothing to set \u2014 phases: ${Ss.join(", ")}`);for(let[r,o]of e){if(!Ss.includes(r))throw new Error(`unknown phase "${r}" \u2014 configurable phases: ${Ss.join(", ")}`);let s=Af(r);if(!s.includes(o))throw new Error(s.length<Mi.length?`mode "${o}" not allowed for "${r}" \u2014 ${r} is always confirmed (modes: ${s.join(", ")})`:`unknown mode "${o}" \u2014 modes: ${Mi.join(", ")}`)}let n=await ge();return n.ticketFlow={...vs(n),...Object.fromEntries(e)},n.linear&&(delete n.linear.ticketFlow,delete n.linear.transitions),await Zt(vf(),n,{mode:384}),n.ticketFlow}async function Vr(){let t=(await ge()).deepgram||{},e=process.env.DEEPGRAM_API_KEY||t.apiKey;return e?{apiKey:e}:null}async function bx(){let t=(await ge()).openai||{},e=process.env.BOSTRAT_OPENAI_API_KEY||t.apiKey;return e?{apiKey:e}:null}var kx={openai:{model:"gpt-realtime-2.1-mini",voice:"marin"}};async function Ts(){let e=(await ge()).voiceChat||{},n=e.provider||"openai",r=kx[n];if(!r)return null;let o=n==="openai"?await bx():null;return o?{provider:n,model:e.model||r.model,voice:e.voice||r.voice,apiKey:o.apiKey}:null}async function _f(){let t=(await ge()).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 dn(){let t=(await ge()).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 Bi={};bu(Bi,{createComment:()=>Rx,findIssueUrl:()=>Ax,issueStates:()=>Cx,mdToAdf:()=>Rf,moveIssue:()=>Mx,parseIssueRef:()=>vx,resolveIssue:()=>Tx,uploadImage:()=>$x,upsertCard:()=>_x,whoami:()=>Pi});var Jn=t=>String(t.baseUrl||"").replace(/\/$/,""),If=t=>`Basic ${Buffer.from(`${t.email}:${t.apiToken}`).toString("base64")}`;async function fn(t,e,n,r){let o=await fetch(`${Jn(t)}${n}`,{method:e,headers:{authorization:If(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 xx=/\/browse\/([A-Za-z][A-Za-z0-9]{0,9}-\d+)/,Sx=/^([A-Z][A-Z0-9]{0,9}-\d+)$/;function vx(t){let e=String(t||"").trim();if(/^https?:\/\//i.test(e)){let r=e.match(xx);return r?r[1].toUpperCase():null}let n=e.match(Sx);return n?n[1]:null}function Ax(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 Tx(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:`${Jn(t)}/browse/${n.key}`,state:n.fields?.status?.name??null}}async function _x(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 Ex=/(!\[[^\]]*\]\([^)\s]+\)|\*\*[^*\n]+\*\*|`[^`\n]+`|_[^_\n]+_|https?:\/\/[^\s)]+)/g;function Ix(t){let e=[],n=0;for(let r of t.matchAll(Ex)){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 Rf(t){let e=String(t??"").split(/\n{2,}/).map(n=>n.trim()).filter(Boolean).map(n=>{let r=[];return n.split(`
|
|
40
|
-
`).forEach((o,s)=>{s>0&&r.push({type:"hardBreak"}),r.push(...
|
|
75
|
+
`)}catch{return""}}var Te=new Map,pS=12e4;async function mS(t){if(t.state==="stopped"||t.state==="error")return!1;let e=t.origin!=="detected",n=e?await fS(t.slug):!0;if(e&&!n){let s=t.state==="starting";return t.state=s?"error":"stopped",s&&(t.error=await rp(t.slug)||"server exited before listening"),Te.delete(t.id),await Ae(t,t.state,t.error?{error:t.error}:{}),!0}if(await io(t.port))return Te.delete(t.id),t.lastHealthyAt=Date.now(),t.state!=="live"?(t.state="live",t.error=null,await Ae(t,"live"),!0):!1;if(t.state==="starting")return Date.now()-t.createdAt>pS?(t.state="error",t.error=await rp(t.slug)||"never started listening",await Ae(t,"error",{error:t.error}),!0):!1;let o=(Te.get(t.id)||0)+1;return Te.set(t.id,o),o>=3&&t.state==="live"?(t.state="unhealthy",!0):!1}var Xi=!1;async function Us(){if(!Xi){Xi=!0;try{let t=await _e(),e=!1;for(let n of t.views)await mS(n)&&(e=!0);e&&await ue()}finally{Xi=!1}}}async function Hs({trackId:t}={}){await Us();let e=await _e();return(t?e.views.filter(r=>r.trackId===t):e.views).map(r=>({...r}))}async function fp(t){let n=(await _e()).views.find(r=>r.id===t);return n?{...n}:null}function oc(t){return ce&&ce.views.find(e=>e.slug===t)||null}async function sc({trackId:t,name:e,cmd:n,cwd:r,serveCwd:o,port:s,box:a,origin:i="agent",branch:c=null}){if(!t)throw new Error("trackId required");if(typeof e!="string"||!e.trim())throw new Error("name (string) required");if(i!=="detected"&&(typeof n!="string"||!n.trim()))throw new Error("cmd (string) required");if(!r)throw new Error("cwd required");let l=o!=null,u=Ct.resolve(r,o??"."),f=i==="detected"?null:async()=>{await cS(r,u),await lS(u,n)},d=await _e();if(i!=="detected"){let w=Qi(e),g=String(n).trim(),b=d.views.find(v=>v.trackId===t&&(v.slug===w||Qi(v.name)===w||v.cmd===g&&Ct.resolve(v.serveCwd??v.cwd)===u));if(b)return hS(d,b,{cmd:g,box:a,branch:c,serveCwd:l?u:void 0,validate:f})}if(f&&await f(),d.views.filter(w=>w.state==="live"||w.state==="starting").length>=We)throw new Error(`view cap reached (${We} live) \u2014 stop one first`);let h={id:z0().slice(0,8),trackId:t,box:a||null,name:e.trim(),slug:uS(d,e),cmd:n?String(n).trim():null,cwd:r,serveCwd:u,branch:c,port:await dS(d,s),origin:i,state:"starting",error:null,createdAt:Date.now(),lastHealthyAt:null};return i!=="detected"&&await rc(h),d.views.push(h),await ue(),await Ae(h,"created",{origin:i}),Zi(t,h.id),i==="detected"&&await io(h.port)&&(h.state="live",h.lastHealthyAt=Date.now(),await ue(),await Ae(h,"live")),{...h}}async function hS(t,e,{cmd:n,box:r,branch:o,serveCwd:s,validate:a}){r&&(e.box=r),o&&(e.branch=o);let i=e.cmd!==n,c=s!==void 0&&!await aS(e.serveCwd??e.cwd,s);if((e.state==="live"||e.state==="starting")&&!i&&!c&&await io(e.port))return await ue(),Zi(e.trackId,e.id),{...e,reused:!0};if(a&&await a(),t.views.filter(f=>f.id!==e.id&&(f.state==="live"||f.state==="starting")).length>=We)throw new Error(`view cap reached (${We} live) \u2014 stop one first`);return e.origin==="detected"?(await nc(e.port),e.origin="agent"):await co(e.slug),e.cmd=n,s!==void 0&&(e.serveCwd=s),e.state="starting",e.error=null,e.createdAt=Date.now(),Te.delete(e.id),await rc(e),await ue(),await Ae(e,"created",{restarted:!0,reused:!0}),Zi(e.trackId,e.id),{...e,reused:!0}}async function pp(t){let n=(await _e()).views.find(r=>r.id===t);if(!n)throw new Error("view not found");return n.origin==="detected"?await nc(n.port):await co(n.slug),n.state!=="stopped"&&(n.state="stopped",Te.delete(n.id),await ue(),await Ae(n,"stopped")),{...n}}async function mp(t){let e=await _e(),n=e.views.find(o=>o.id===t);if(!n)throw new Error("view not found");if(!n.cmd)throw new Error("a detected view has no known command \u2014 recreate it as a managed view");if(e.views.filter(o=>o.id!==t&&(o.state==="live"||o.state==="starting")).length>=We)throw new Error(`view cap reached (${We} live) \u2014 stop one first`);return await co(n.slug),n.state="starting",n.error=null,n.createdAt=Date.now(),Te.delete(n.id),await rc(n),await ue(),await Ae(n,"created",{restarted:!0}),{...n}}async function hp(t){let e=await _e(),n=e.views.findIndex(o=>o.id===t);if(n===-1)throw new Error("view not found");let r=e.views[n];return r.origin==="detected"?await nc(r.port):await co(r.slug),e.views.splice(n,1),Te.delete(r.id),await ue(),await Ae(r,"deleted"),{ok:!0,id:t,slug:r.slug}}async function wp(t){let e=await _e(),n=e.views.filter(r=>r.trackId===t);for(let r of n)r.origin!=="detected"&&await co(r.slug),Te.delete(r.id);return n.length&&(e.views=e.views.filter(r=>r.trackId!==t),await ue()),n.length}var wS=/https?:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0)(?::(\d{2,5}))?(?:\/|\b)/g;async function gp(t,e){if(!t?.id||!Array.isArray(e)||!e.length)return[];let n=await _e(),r=n.adoptWatermarks[t.id]??null,o=e.filter(c=>c.role==="assistant"&&c.ts!=null&&(r==null?!1:c.ts>r)),s=e.reduce((c,l)=>l.ts>c?l.ts:c,r??0);if(n.adoptWatermarks[t.id]!==s&&(n.adoptWatermarks[t.id]=s,await ue()),!o.length)return[];let a=new Set;for(let c of o)for(let l of String(c.text||"").matchAll(wS)){let u=Number(l[1]||80);u>=1024&&u<=65535&&a.add(u)}if(!a.size)return[];let i=[];for(let c of a)if(!dp().has(c)&&!n.views.some(l=>l.port===c)&&await io(c))try{i.push(await sc({trackId:t.id,name:`${t.name} preview`,cmd:null,cwd:t.cwd,port:c,origin:"detected",branch:t.workspace?.branch??null}))}catch{}return i}import kn from"node:fs/promises";import qe from"node:path";import gS from"node:os";import{randomUUID as yS}from"node:crypto";var rr=process.env.BOSTRAT_STATE_DIR||qe.join(process.env.HOME||gS.tmpdir(),".bostrat"),bp=qe.join(rr,"runbooks.json"),ic=qe.join(rr,"runbooks"),yp=256*1024,bS=[{label:"Anthropic API key",re:/\bsk-ant-[A-Za-z0-9_-]{20,}/},{label:"OpenAI-style API key",re:/\bsk-(?:proj-|svcacct-)?[A-Za-z0-9_-]{32,}\b/},{label:"Stripe secret key",re:/\bsk_(?:live|test)_[A-Za-z0-9]{16,}/},{label:"GitHub token",re:/\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{36,}|\bgithub_pat_[A-Za-z0-9_]{22,}/},{label:"AWS access key id",re:/\bAKIA[0-9A-Z]{16}\b/},{label:"Slack token",re:/\bxox[baprs]-[A-Za-z0-9-]{10,}/},{label:"private key block",re:/-----BEGIN [A-Z ]*PRIVATE KEY-----/},{label:"JWT",re:/\beyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/}];function lo(t){return bS.filter(({re:e})=>e.test(t)).map(({label:e})=>e)}var bn=null,ac=null;async function Ke(){return bn||(ac||=(async()=>{try{bn=JSON.parse(await kn.readFile(bp,"utf8"))}catch{bn={runbooks:[]}}finally{ac=null}return bn.runbooks||=[],bn})(),ac)}async function or(){await ae(bp,bn)}function sr(t,e,n={}){return O({id:t.trackId,cwd:t.cwd},{type:"runbook.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box||null,provider:t.agent||null,payload:{ts:Date.now(),change:e,runbookId:t.id,name:t.name,status:t.status,...n}}).catch(()=>{})}function nr(t,e){return qe.join(ic,`${t.id}.v${e}.md`)}function kp(t,e=null){let n=t.versions||[];return e!=null?n.find(r=>r.v===Number(e))||null:n.length?n[n.length-1]:null}function xp(t){if(typeof t!="string"||!t.trim())throw new Error("md (markdown string) required");let e=Buffer.byteLength(t,"utf8");if(e>yp)throw new Error(`runbook is ${Math.round(e/1024)} KB \u2014 over the ${yp/1024} KB cap; a doc that size belongs in the repo, not a runbook`);let n=lo(t);if(n.length)throw new Error(`runbook appears to contain a secret VALUE (${n.join(", ")}) \u2014 runbooks ship off-box; name the secret and where it goes, never its value. Replace it with a placeholder like <your key> and retry.`);return e}function Sp(t,e){if(t.trackDeletedAt)throw new Error(`runbook is orphaned (its Track was deleted) \u2014 cannot ${e} it`)}async function Ws({trackId:t,status:e}={}){let n=await Ke(),r=t?n.runbooks.filter(o=>o.trackId===t):n.runbooks;return e&&(r=r.filter(o=>o.status===e)),r.map(o=>({...o}))}async function cc(t){let n=(await Ke()).runbooks.find(r=>r.id===t);return n?{...n}:null}async function vp({trackId:t,trackName:e,name:n,md:r,cwd:o,box:s,agent:a}){if(!t)throw new Error("trackId required");if(typeof n!="string"||!n.trim())throw new Error("name (string) required");let i=xp(r),c=await Ke(),l={id:yS().slice(0,8),trackId:t,trackName:e||t,box:s||null,agent:a||null,name:n.trim(),status:"open",version:1,versions:[],trackDeletedAt:null,cwd:o||null,createdAt:Date.now(),updatedAt:Date.now()};return await kn.mkdir(ic,{recursive:!0}),await kn.writeFile(nr(l,1),r),l.versions.push({v:1,mdRel:qe.relative(rr,nr(l,1)),bytes:i,note:null,createdAt:Date.now()}),c.runbooks.push(l),await or(),await sr(l,"created",{version:1}),{...l}}async function Tp(t,{md:e,note:n}={}){let o=(await Ke()).runbooks.find(i=>i.id===t);if(!o)throw new Error("runbook not found");Sp(o,"update");let s=xp(e),a=o.version+1;return o.version=a,o.updatedAt=Date.now(),await kn.mkdir(ic,{recursive:!0}),await kn.writeFile(nr(o,a),e),o.versions.push({v:a,mdRel:qe.relative(rr,nr(o,a)),bytes:s,note:n?String(n).slice(0,200):null,createdAt:Date.now()}),await or(),await sr(o,"updated",{version:a,...n?{note:String(n).slice(0,200)}:{}}),{...o}}async function Ap(t,e){if(e!=="open"&&e!=="done")throw new Error('status must be "open" or "done"');let r=(await Ke()).runbooks.find(o=>o.id===t);if(!r)throw new Error("runbook not found");return Sp(r,e==="done"?"complete":"reopen"),r.status===e?{...r}:(r.status=e,r.updatedAt=Date.now(),await or(),await sr(r,e==="done"?"done":"reopened"),{...r})}async function _p(t){let e=await Ke(),n=e.runbooks.findIndex(o=>o.id===t);if(n===-1)throw new Error("runbook not found");let r=e.runbooks[n];e.runbooks.splice(n,1);for(let o of r.versions||[])await kn.rm(qe.join(rr,o.mdRel),{force:!0}).catch(()=>{});return await or(),r.trackDeletedAt||await sr(r,"deleted"),{ok:!0,id:t,name:r.name}}async function Ep(t){let n=(await Ke()).runbooks.filter(r=>r.trackId===t&&!r.trackDeletedAt);for(let r of n)r.trackDeletedAt=Date.now(),await sr(r,"orphaned"),r.cwd=null;return n.length&&await or(),n.length}async function Ip(t){let e=await Ke(),n=e.runbooks.filter(r=>r.trackId===t);for(let r of n){await sr(r,"deleted");for(let o of r.versions||[])await kn.rm(qe.join(rr,o.mdRel),{force:!0}).catch(()=>{})}return n.length&&(e.runbooks=e.runbooks.filter(r=>r.trackId!==t),await or()),n.length}import po from"node:fs/promises";import Wp from"node:path";import JS from"node:os";import{randomUUID as qp}from"node:crypto";import kS from"node:fs/promises";import Rp from"node:path";import xS from"node:os";var $p=()=>Rp.join(process.env.BOSTRAT_STATE_DIR||Rp.join(process.env.HOME||xS.tmpdir(),".bostrat"),"integrations.json");async function Ee(){try{return JSON.parse(await kS.readFile($p(),"utf8"))||{}}catch{return{}}}var qs=["linked","prOpened","prMerged","create"],lc=["prompt","auto","off"],SS={create:["prompt","off"]},Op=t=>SS[t]??lc;function Ks(t){let e=t.linear||{},n=e.transitions===!0?"auto":"prompt",r={};for(let o of qs){let s=Op(o),a=s.includes(n)?n:"prompt",i=t.ticketFlow?.[o]??e.ticketFlow?.[o];r[o]=s.includes(i)?i:a}return r}async function xn(){let t=await Ee(),e=process.env.BOSTRAT_LINEAR_API_KEY||t.linear?.apiKey,n=process.env.BOSTRAT_LINEAR_DEFAULT_TEAM||t.linear?.defaultTeam||null;return e?{apiKey:e,ticketFlow:Ks(t),defaultTeam:n}:null}async function Gs(){let t=await Ee(),e=t.jira||{},n=process.env.BOSTRAT_JIRA_BASE_URL||e.baseUrl,r=process.env.BOSTRAT_JIRA_EMAIL||e.email,o=process.env.BOSTRAT_JIRA_API_TOKEN||e.apiToken;return!n||!r||!o?null:{baseUrl:String(n).replace(/\/$/,""),email:r,apiToken:o,tokenExpiresAt:e.tokenExpiresAt??null,ticketFlow:Ks(t)}}async function uc(){return Ks(await Ee())}async function Mp(t){let e=Object.entries(t||{});if(!e.length)throw new Error(`nothing to set \u2014 phases: ${qs.join(", ")}`);for(let[r,o]of e){if(!qs.includes(r))throw new Error(`unknown phase "${r}" \u2014 configurable phases: ${qs.join(", ")}`);let s=Op(r);if(!s.includes(o))throw new Error(s.length<lc.length?`mode "${o}" not allowed for "${r}" \u2014 ${r} is always confirmed (modes: ${s.join(", ")})`:`unknown mode "${o}" \u2014 modes: ${lc.join(", ")}`)}let n=await Ee();return n.ticketFlow={...Ks(n),...Object.fromEntries(e)},n.linear&&(delete n.linear.ticketFlow,delete n.linear.transitions),await ae($p(),n,{mode:384}),n.ticketFlow}async function uo(){let t=(await Ee()).deepgram||{},e=process.env.DEEPGRAM_API_KEY||t.apiKey;return e?{apiKey:e}:null}async function vS(){let t=(await Ee()).openai||{},e=process.env.BOSTRAT_OPENAI_API_KEY||t.apiKey;return e?{apiKey:e}:null}var TS={openai:{model:"gpt-realtime-2.1-mini",voice:"marin"}};async function zs(){let e=(await Ee()).voiceChat||{},n=e.provider||"openai",r=TS[n];if(!r)return null;let o=n==="openai"?await vS():null;return o?{provider:n,model:e.model||r.model,voice:e.voice||r.voice,apiKey:o.apiKey}:null}async function Cp(){let t=(await Ee()).cedar||{},e=process.env.BOSTRAT_CEDAR_INGEST_KEY||t.ingestKey,n=process.env.BOSTRAT_CEDAR_URL||t.url||"https://cedar-api.fly.dev";return e?{url:n,ingestKey:e}:null}async function Sn(){let t=(await Ee()).slack||{},e=process.env.BOSTRAT_SLACK_BOT_TOKEN||t.botToken,n=process.env.BOSTRAT_SLACK_APP_TOKEN||t.appToken;return!e||!n?null:{botToken:e,appToken:n,channel:process.env.BOSTRAT_SLACK_CHANNEL||t.channel||null,maxConcurrent:Number.isInteger(t.maxConcurrent)&&t.maxConcurrent>0?t.maxConcurrent:2}}var fc={};zu(fc,{createComment:()=>CS,findIssueUrl:()=>IS,issueStates:()=>BS,mdToAdf:()=>Bp,moveIssue:()=>DS,parseIssueRef:()=>ES,resolveIssue:()=>RS,uploadImage:()=>NS,upsertCard:()=>$S,whoami:()=>dc});var ar=t=>String(t.baseUrl||"").replace(/\/$/,""),Pp=t=>`Basic ${Buffer.from(`${t.email}:${t.apiToken}`).toString("base64")}`;async function vn(t,e,n,r){let o=await fetch(`${ar(t)}${n}`,{method:e,headers:{authorization:Pp(t),accept:"application/json",...r!==void 0?{"content-type":"application/json"}:{}},body:r!==void 0?JSON.stringify(r):void 0}),s=await o.text(),a=null;try{a=s?JSON.parse(s):null}catch{a=null}if(!o.ok){let i=a?.errorMessages?.[0]??Object.values(a?.errors??{})[0]??`HTTP ${o.status}`,c=new Error(`jira: ${i}`);throw c.status=o.status,c}return a}var AS=/\/browse\/([A-Za-z][A-Za-z0-9]{0,9}-\d+)/,_S=/^([A-Z][A-Z0-9]{0,9}-\d+)$/;function ES(t){let e=String(t||"").trim();if(/^https?:\/\//i.test(e)){let r=e.match(AS);return r?r[1].toUpperCase():null}let n=e.match(_S);return n?n[1]:null}function IS(t,e=null){let n=null;try{n=e?.baseUrl?new URL(e.baseUrl).host:null}catch{n=null}let r=/https?:\/\/[^\s)]+\/browse\/[A-Za-z][A-Za-z0-9]{0,9}-\d+[^\s)]*/g;for(let o of String(t||"").matchAll(r))try{let s=new URL(o[0]).host;if(s===n||/\.atlassian\.net$/i.test(s))return o[0]}catch{}return null}async function RS(t,e){let n;try{n=await vn(t,"GET",`/rest/api/3/issue/${encodeURIComponent(e)}?fields=summary,status`)}catch(r){throw r.status===404?new Error(`jira: issue "${e}" not found`):r}return{issueId:n.id,key:n.key,title:n.fields?.summary??null,url:`${ar(t)}/browse/${n.key}`,state:n.fields?.status?.name??null}}async function $S(t,e,{url:n,title:r,subtitle:o}){await vn(t,"POST",`/rest/api/3/issue/${encodeURIComponent(e.key)}/remotelink`,{globalId:n,application:{type:"com.bostrat.tracks",name:"Bostrat"},object:{url:n,title:r,summary:o,icon:{title:"Bostrat"}}})}var OS=/(!\[[^\]]*\]\([^)\s]+\)|\*\*[^*\n]+\*\*|`[^`\n]+`|_[^_\n]+_|https?:\/\/[^\s)]+)/g;function MS(t){let e=[],n=0;for(let r of t.matchAll(OS)){r.index>n&&e.push({type:"text",text:t.slice(n,r.index)});let o=r[0];if(o.startsWith("![")){let[,s,a]=o.match(/^!\[([^\]]*)\]\(([^)\s]+)\)$/);e.push({type:"text",text:s||a,marks:[{type:"link",attrs:{href:a}}]})}else o.startsWith("**")?e.push({type:"text",text:o.slice(2,-2),marks:[{type:"strong"}]}):o.startsWith("`")?e.push({type:"text",text:o.slice(1,-1),marks:[{type:"code"}]}):o.startsWith("_")?e.push({type:"text",text:o.slice(1,-1),marks:[{type:"em"}]}):e.push({type:"text",text:o,marks:[{type:"link",attrs:{href:o}}]});n=r.index+o.length}return n<t.length&&e.push({type:"text",text:t.slice(n)}),e}function Bp(t){let e=String(t??"").split(/\n{2,}/).map(n=>n.trim()).filter(Boolean).map(n=>{let r=[];return n.split(`
|
|
76
|
+
`).forEach((o,s)=>{s>0&&r.push({type:"hardBreak"}),r.push(...MS(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 CS(t,e,n){let r=await vn(t,"POST",`/rest/api/3/issue/${encodeURIComponent(e.key)}/comment`,{body:Bp(n)});return r?.id?`${ar(t)}/browse/${e.key}?focusedCommentId=${r.id}`:null}async function dc(t){let e=await vn(t,"GET","/rest/api/3/myself"),n=null;try{n=new URL(ar(t)).host}catch{n=t.baseUrl??null}return{user:e?.displayName??e?.emailAddress??null,org:n}}async function NS(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(`${ar(t)}/rest/api/3/issue/${encodeURIComponent(e)}/attachments`,{method:"POST",headers:{authorization:Pp(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??`${ar(t)}/rest/api/3/attachment/content/${c.id}`}var PS={new:"unstarted",indeterminate:"started",done:"completed"},Np=t=>PS[t?.key]??"started";async function BS(t,e){let n=encodeURIComponent(e.key),[r,o]=await Promise.all([vn(t,"GET",`/rest/api/3/issue/${n}?fields=status`),vn(t,"GET",`/rest/api/3/issue/${n}/transitions`)]),s=r?.fields?.status??null,a=s?{id:`status:${s.id}`,name:s.name,type:Np(s.statusCategory)}:null,i=(o?.transitions??[]).map((c,l)=>({id:c.id,name:c.to?.name??c.name,type:Np(c.to?.statusCategory),position:l}));return{current:a,states:i}}async function DS(t,e,n){await vn(t,"POST",`/rest/api/3/issue/${encodeURIComponent(e.key)}/transitions`,{transition:{id:String(n)}})}var wc={};zu(wc,{createComment:()=>KS,createIssue:()=>hc,createProject:()=>mc,findIssueUrl:()=>HS,issueStates:()=>zS,moveIssue:()=>VS,parseIssueRef:()=>US,resolveIssue:()=>WS,teams:()=>ir,uploadImage:()=>GS,upsertCard:()=>qS,whoami:()=>pc});var jS=()=>process.env.BOSTRAT_LINEAR_API_URL||"https://api.linear.app/graphql";async function de(t,e,n={}){let r=await fetch(jS(),{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 LS=/linear\.app\/[^/\s]+\/issue\/([A-Za-z][A-Za-z0-9]{0,9}-\d+)/,FS=/^([A-Z][A-Z0-9]{0,9}-\d+)$/;function US(t){let e=String(t||"").trim(),n=e.match(LS);if(n)return n[1].toUpperCase();let r=e.match(FS);return r?r[1]:null}function HS(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 WS(t,e){let r=(await de(t,"query($id: String!) { issue(id: $id) { id identifier title url state { name } team { key } } }",{id:e})).issue;if(!r)throw new Error(`linear: issue "${e}" not found`);return{issueId:r.id,key:r.identifier,title:r.title,url:r.url,state:r.state?.name??null,team:r.team?.key??null}}async function qS(t,e,{url:n,title:r,subtitle:o}){let s=await de(t,"mutation($input: AttachmentCreateInput!) { attachmentCreate(input: $input) { success attachment { id } } }",{input:{issueId:e.issueId,url:n,title:r,subtitle:o}});if(!s.attachmentCreate?.success)throw new Error("linear: attachmentCreate did not succeed");return s.attachmentCreate.attachment?.id??null}async function KS(t,e,n){let r=await de(t,"mutation($input: CommentCreateInput!) { commentCreate(input: $input) { success comment { url } } }",{input:{issueId:e.issueId,body:n}});if(!r.commentCreate?.success)throw new Error("linear: commentCreate did not succeed");return r.commentCreate.comment?.url??null}async function pc(t){let e=await de(t,"query { viewer { name email } organization { name urlKey } }");return{user:e.viewer?.name??e.viewer?.email??null,org:e.organization?.name??e.organization?.urlKey??null}}async function GS(t,{filename:e,contentType:n,buffer:r}){let o=await de(t,`mutation($contentType: String!, $filename: String!, $size: Int!) {
|
|
41
77
|
fileUpload(contentType: $contentType, filename: $filename, size: $size) {
|
|
42
78
|
success
|
|
43
79
|
uploadFile { uploadUrl assetUrl headers { key value } }
|
|
44
80
|
}
|
|
45
|
-
}`,{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 Wx(t,e){let n=await re(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 qx(t,e,n){if(!(await re(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 Xn(t){return((await re(t,"query { teams(first: 100) { nodes { id key name } } }")).teams?.nodes??[]).map(n=>({id:n.id,key:n.key,name:n.name}))}var $f=255;async function ji(t,{teamId:e,name:n,description:r}){let o=r||null,s=!!o&&o.length>$f,a=s?`${o.slice(0,$f-1).trimEnd()}\u2026`:o,i=await re(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 Li(t,{teamId:e,title:n,description:r,projectId:o,priority:s}){let a=await re(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 jf={linear:{api:Fi,config:un},jira:{api:Bi,config:As}};async function Ki(){let t=[];for(let[e,{api:n,config:r}]of Object.entries(jf)){let o=await r();o&&t.push({name:e,api:n,cfg:o})}return t}async function pn(t){let e=jf[t?.provider];if(!e)return null;let n=await e.config();return n?{name:t.provider,api:e.api,cfg:n}:null}function Vi(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 Lf=()=>process.env.BOSTRAT_STATE_DIR||Bf.join(process.env.HOME||Gx.tmpdir(),".bostrat"),qi=()=>Bf.join(Lf(),"tickets.json"),Ff=()=>(process.env.BOSTRAT_PUBLIC_URL||"http://bostrat.globopi").replace(/\/$/,""),Es=t=>`${Ff()}/track/${t}`,_s=null,Ui=null,Hi=Promise.resolve();async function Is(){return _s||(Ui??=Jr.readFile(qi(),"utf8").then(t=>JSON.parse(t)?.tickets??{}).catch(()=>({})).then(t=>_s=t).finally(()=>Ui=null),Ui)}async function Of(){let t=JSON.stringify({tickets:_s},null,2),e=`${qi()}.${process.pid}.tmp`;await Jr.mkdir(Lf(),{recursive:!0}),await Jr.writeFile(e,t),await Jr.rename(e,qi())}async function Ne(){Hi=Hi.then(Of,Of),await Hi.catch(()=>{})}async function oe(t){return(await Is())[t]??null}async function Rs(){return{...await Is()}}async function Uf(){return(await Ki()).length>0}async function Yn(t,e,{source:n="api"}={}){let r=await Ki();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 Is())[t.id]=c,await Ne(),await O({id:t.id,cwd:t.cwd},{type:"ticket.linked",actor:"user",source:"orchestrator",payload:{ts:Date.now(),provider:c.provider,key:c.key,url:c.url,title:c.title,linkSource:n}}).catch(()=>{}),await Xr(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 zr(c,`**Bostrat** \u2014 Track **${c.track.name}** linked${l.length?` (${l.join(", ")})`:""}.`),await qf(t.id,c,"linked"),c}async function zi(t){let e=await Is();return e[t]?(delete e[t],await Ne(),!0):!1}var Gi=t=>`$${t>=100?Math.round(t):t.toFixed(2)}`;async function Hf(t,e){if(!t.track?.cwd)return null;let n=Yt(await F({id:e,cwd:t.track.cwd}));return!n.totalUsd||n.totalUsd<.01?null:n}function Kx(t){let e=Object.entries(t.byProvider).sort((r,o)=>o[1].usd-r[1].usd).map(([r,o])=>`${r} ${Gi(o.usd)}`).join(" \xB7 "),n=Object.keys(t.byBox).length;return`${Gi(t.totalUsd)}${e?` (${e})`:""}${n>1?` across ${n} boxes`:""}`}function Vx(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(Gi(e.totalUsd)),n.join(" \xB7 ")||"linked"}async function Xr(t,e){let n=await pn(e);if(!n)return;let r=await Hf(e,t).catch(()=>null);await n.api.upsertCard(n.cfg,e,{url:Es(t),title:`Bostrat Track: ${e.track.name}`,subtitle:Vx(e,r)})}function $s(t){let e=new URL(Ff());return`${e.protocol}//${t}.${e.host}/`}var zx={linked:"linked",opened:"prOpened",merged:"prMerged"};function Jx(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 Wf(t,e,{from:n,to:r,mode:o,phase:s=null,actor:a="user"}){e.state=r,await Ne(),await O({id:t,cwd:e.track?.cwd},{type:"ticket.state.changed",actor:a,source:"orchestrator",payload:{ts:Date.now(),key:e.key,url:e.url,from:n??null,to:r,mode:o,...s?{phase:s}:{}}}).catch(()=>{})}async function qf(t,e,n){let r=await pn(e);if(!r)return;let o=r.cfg.ticketFlow?.[zx[n]]??"prompt";if(o==="off")return;let{current:s,states:a}=await r.api.issueStates(r.cfg,e),i=Vi(s,a),c=Jx(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 Wf(t,e,{from:l,to:c.name,mode:"auto",phase:n,actor:"system"});return}await O({id:t,cwd:e.track?.cwd},{type:"ticket.move.offered",actor:"system",source:"orchestrator",payload:{ts:Date.now(),offerId:Df(),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 Gf(t){let e=await oe(t);if(!e)throw new Error("no ticket linked to this Track");let n=await pn(e);if(!n)throw new Error("no ticketing integration configured");let{current:r,states:o}=await n.api.issueStates(n.cfg,e),s=Vi(r,o);return s?.name&&e.state!==s.name&&(e.state=s.name,await Ne()),{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 Ji(t,e,{mode:n="manual"}={}){if(!e)throw new Error("stateId required");let r=await oe(t);if(!r)throw new Error("no ticket linked to this Track");let o=await pn(r);if(!o)throw new Error("no ticketing integration configured");let{current:s,states:a}=await o.api.issueStates(o.cfg,r),i=Vi(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 Ne()),{key:r.key,from:l,to:c.name,moved:!1}):(await o.api.moveIssue(o.cfg,r,c.id),await Wf(t,r,{from:l,to:c.name,mode:n}),{key:r.key,from:l,to:c.name,moved:!0})}async function Kf(t,e){let n=await oe(t);if(!n)throw new Error("no ticket linked to this Track");let r=await pn(n);if(!r)throw new Error("no ticketing integration configured");let o=String(e||"").trim();if(!o)throw new Error("comment body required");let s=await r.api.createComment(r.cfg,n,o);return await O({id:t,cwd:n.track?.cwd},{type:"ticket.commented",actor:"user",source:"orchestrator",payload:{ts:Date.now(),key:n.key,url:s??n.url,excerpt:o.length>280?`${o.slice(0,279)}\u2026`:o}}).catch(()=>{}),{key:n.key,url:s??n.url}}async function Vf(t,e,n){if(!e)throw new Error("offerId required");let r=await F({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 Ji(t.id,o.payload.to?.id,{mode:"offer"})),await O({id:t.id,cwd:t.cwd},{type:"ticket.move.answered",actor:"user",source:"orchestrator",payload:{ts:Date.now(),offerId:e,action:n?"moved":"dismiss",...s?{from:s.from,to:s.to}:{}}}),{offerId:e,action:n?"moved":"dismiss",...s??{}}}var Cf=50;async function zf(){let t=await un();if(!t)throw new Error("no ticketing integration configured");return Xn(t)}function Xx(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>Cf)throw new Error(`too many issues (${n.length}); cap is ${Cf} per proposal`);return{epic:r,issues:n,team:e.team?String(e.team).trim():null,linkCurrentTrack:e.linkCurrentTrack===!0}}async function Yx(t,e,n,r){let o=r??await Xn(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 oe(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 Jf(t,e){let n=await un();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=Xx(e),o=await Xn(n),s=await Yx(n,r,t,o),a=s?{id:s.id,key:s.key,name:s.name}:null,i=Df();return await O({id:t.id,cwd:t.cwd},{type:"ticket.create.offered",actor:"system",source:"orchestrator",payload:{ts:Date.now(),proposalId:i,provider:"linear",epic:r.epic,issues:r.issues,team:a,needsTeam:!s,linkCurrentTrack:r.linkCurrentTrack}}),{proposalId:i,epic:r.epic,issues:r.issues,team:a,needsTeam:!s}}async function Xf(t,e,n,{teamId:r=null}={}){if(!e)throw new Error("proposalId required");let o=await F({id:t.id,cwd:t.cwd}),s=o.find(h=>h.type==="ticket.create.offered"&&h.payload?.proposalId===e);if(!s)throw new Error("unknown proposal");if(o.some(h=>h.type==="ticket.create.answered"&&h.payload?.proposalId===e))throw new Error("proposal already answered");if(!n)return await O({id:t.id,cwd:t.cwd},{type:"ticket.create.answered",actor:"user",source:"orchestrator",payload:{ts:Date.now(),proposalId:e,action:"dismiss"}}),{proposalId:e,action:"dismiss"};let a=await un();if(!a)throw new Error("no ticketing integration configured");let i=s.payload,c=i.team??null;if(!c&&r){let h=(await Xn(a)).find(w=>w.id===r);if(!h)throw new Error("unknown team");c={id:h.id,key:h.key,name:h.name}}if(!c)throw new Error("a team is required to create \u2014 pick one");let l=[],u=[],f=null;if(i.epic){let h=await ji(a,{teamId:c.id,name:i.epic.name,description:i.epic.description});f=h.id,l.push({kind:"epic",title:h.name,url:h.url})}for(let h of i.issues||[])try{let w=await Li(a,{teamId:c.id,title:h.title,description:h.description,projectId:f,priority:h.priority});l.push({kind:"issue",key:w.key,title:w.title,url:w.url})}catch(w){u.push({title:h.title,message:w?.message||String(w)})}await O({id:t.id,cwd:t.cwd},{type:"ticket.created",actor:"user",source:"orchestrator",payload:{ts:Date.now(),proposalId:e,team:{key:c.key,name:c.name},items:l,failed:u}}),await O({id:t.id,cwd:t.cwd},{type:"ticket.create.answered",actor:"user",source:"orchestrator",payload:{ts:Date.now(),proposalId:e,action:"created",createdCount:l.length,failedCount:u.length}});let d=l.filter(h=>h.kind==="issue"),m=null;return i.linkCurrentTrack&&d.length===1&&!await oe(t.id)&&(m=await Yn(t,d[0].key,{source:"created"}).catch(()=>null)),{proposalId:e,action:"created",created:l,failed:u,linked:m?m.key:null}}async function zr(t,e){let n=await pn(t);n&&await n.api.createComment(n.cfg,t,e)}async function Zx(t,e,n){let r=await pn(t);if(!r)return null;let o=await $e(e);if(!o)return null;let s=an(o,n);if(!s)return null;let a=await Jr.readFile(Ft(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 Mf=()=>Number(process.env.BOSTRAT_TICKET_COST_SYNC_MS??12e4),Wi=new Map;function Qx(t,e){if(Mf()<=0)return Xr(t,e);if(Wi.has(t))return;let n=setTimeout(()=>{Wi.delete(t),oe(t).then(r=>r?Xr(t,r):null).catch(()=>{})},Mf());n.unref?.(),Wi.set(t,n)}var Nf=new Map;async function Pf(t,e,n){let r=Nf.get(t)??0;Date.now()-r<600*1e3||(Nf.set(t,Date.now()),await O({id:t,cwd:e},{type:"ticket.sync.failed",actor:"system",source:"orchestrator",payload:{ts:Date.now(),message:n?.message||String(n)}}).catch(()=>{}))}async function t0(t,{resolveTrack:e=null}={}){let n=await oe(t.trackId),r=t.payload||{};if(!n){if(t.type!=="turn.user"||t.actor!=="user")return;let o=null;for(let a of await Ki())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 Yn(s,o,{source:"chat"})}catch(a){await Pf(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 Ne())}try{switch(t.type){case"handoff.done":n.track.leadAgent=r.toAgent??n.track.leadAgent,await Ne(),await Xr(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 Ne(),await Xr(t.trackId,n),r.change==="opened"||r.change==="merged"){let o=r.change==="merged"?await Hf(n,t.trackId).catch(()=>null):null;await zr(n,`**Bostrat** \u2014 PR ${r.change} for **${n.track.name}**${r.url?`: ${r.url}`:r.number?` (#${r.number})`:""}`+(o?`
|
|
46
|
-
|
|
47
|
-
Agent cost for this Track: ${
|
|
48
|
-
|
|
49
|
-
`)}else if(r.change==="picked"){let o=(r.siblings??[]).map(s=>`**${s.name}**`).join(", ");await
|
|
50
|
-
_(runs on the Track's branch; reachable on the operator's private network only)_`);break}case"cost.recorded":await Qx(t.trackId,n);break;default:break}}catch(o){let s=e?await e(t.trackId).catch(()=>null):null;await Pf(t.trackId,s?.cwd??null,o)}}function Yf({resolveTrack:t}){Lt.on("event",e=>{t0(e,{resolveTrack:t}).catch(()=>{})})}import Os from"node:fs/promises";import Zf from"node:path";import e0 from"node:os";var Qf=process.env.BOSTRAT_STATE_DIR||Zf.join(process.env.HOME||e0.tmpdir(),".bostrat"),Yi=Zf.join(Qf,"pr-links.json"),ut=(t,e)=>`${t}#${Number(e)}`,Cs=null,Xi=null,D=Promise.resolve();async function gt(){return Cs||(Xi??=Os.readFile(Yi,"utf8").then(t=>JSON.parse(t)?.links??{}).catch(()=>({})).then(t=>Cs=t).finally(()=>Xi=null),Xi)}async function Y(){let t=JSON.stringify({links:Cs},null,2),e=`${Yi}.${process.pid}.tmp`;await Os.mkdir(Qf,{recursive:!0}),await Os.writeFile(e,t),await Os.rename(e,Yi)}var n0=new Set(["api","observed","reconciled"]);async function Bt({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=ut(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:n0.has(a),firstSeen:Date.now()},D=D.then(Y,Y),await D.catch(()=>{}),{link:i[c],created:!0})}async function Yr(t,e){if(!t||e==null)return null;let n=(await gt())[ut(t,e)];return!n||n.announced?null:(n.announced=!0,D=D.then(Y,Y),await D.catch(()=>{}),n)}async function Tt(t,e){return!t||e==null?null:(await gt())[ut(t,e)]??null}async function Pe(t,e,n){if(!t||e==null)return null;let r=(await gt())[ut(t,e)];return r?(r.state=n,r.resolvedAt=Date.now(),delete r.armedAt,delete r.autoMerge,delete r.archiveOnMerge,delete r.notifyOnMerge,delete r.notifyPending,D=D.then(Y,Y),await D.catch(()=>{}),r):null}async function tp(t,e,{headSha:n=null,trackId:r=null}={}){if(!t||e==null)return null;let o=(await gt())[ut(t,e)];return o?(o.ciFix={delegatedAt:Date.now(),headSha:n,trackId:r},D=D.then(Y,Y),await D.catch(()=>{}),o):null}async function ep(t,e,{headSha:n=null,trackId:r=null}={}){if(!t||e==null)return null;let o=(await gt())[ut(t,e)];return o?(o.reconcile={delegatedAt:Date.now(),headSha:n,trackId:r},D=D.then(Y,Y),await D.catch(()=>{}),o):null}async function Ms(t,e,{headSha:n=null}={}){if(!t||e==null)return null;let r=(await gt())[ut(t,e)];return!r||!r.ciFix?null:(r.ciFixAwait={since:Date.now(),trackId:r.ciFix.trackId??null,headSha:n},delete r.ciFix,D=D.then(Y,Y),await D.catch(()=>{}),r)}async function Ns(t,e){if(!t||e==null)return null;let n=(await gt())[ut(t,e)];return!n||!n.ciFixAwait?null:(delete n.ciFixAwait,D=D.then(Y,Y),await D.catch(()=>{}),n)}async function Ps(t,e,{headSha:n=null}={}){if(!t||e==null)return null;let r=(await gt())[ut(t,e)];return!r||!r.reconcile?null:(r.reconcileAwait={since:Date.now(),trackId:r.reconcile.trackId??null,headSha:n},delete r.reconcile,D=D.then(Y,Y),await D.catch(()=>{}),r)}async function Bs(t,e){if(!t||e==null)return null;let n=(await gt())[ut(t,e)];return!n||!n.reconcileAwait?null:(delete n.reconcileAwait,D=D.then(Y,Y),await D.catch(()=>{}),n)}async function np(t,e,{at:n=Date.now()}={}){if(!t||e==null)return null;let r=(await gt())[ut(t,e)];return r?(r.baseFoldAt=n,D=D.then(Y,Y),await D.catch(()=>{}),r):null}async function rp(t,e){if(!t||e==null)return null;let n=(await gt())[ut(t,e)];return!n||!n.baseFoldAt?null:(delete n.baseFoldAt,D=D.then(Y,Y),await D.catch(()=>{}),n)}async function op(t,e,n){if(!t||e==null||!n)return null;let r=(await gt())[ut(t,e)];return r?(r.lastHeadSha=n,D=D.then(Y,Y),await D.catch(()=>{}),r):null}async function Zr(t,e,{merge:n=null}={}){if(!t||e==null)return null;let r=(await gt())[ut(t,e)];return r?(r.armedAt=Date.now(),n&&(r.autoMerge={method:n.method||"merge",armedAt:r.armedAt}),D=D.then(Y,Y),await D.catch(()=>{}),r):null}async function Zn(t,e,n=!0){if(!t||e==null)return null;let r=(await gt())[ut(t,e)];return r?(n?r.archiveOnMerge=!0:delete r.archiveOnMerge,D=D.then(Y,Y),await D.catch(()=>{}),r):null}async function Ds(t,e,n=!0){if(!t||e==null)return null;let r=(await gt())[ut(t,e)];return r?(n?r.notifyOnMerge=!0:delete r.notifyOnMerge,D=D.then(Y,Y),await D.catch(()=>{}),r):null}async function sp(t,e,n){if(!t||e==null)return null;let r=(await gt())[ut(t,e)];if(!r)return null;r.notifyOnMerge=!0;let o=new Map((r.notifyPending||[]).map(s=>[`${s.repo}#${s.number}`,s]));for(let s of n||[])s&&s.number!=null&&o.set(`${s.repo}#${s.number}`,s);return r.notifyPending=[...o.values()],D=D.then(Y,Y),await D.catch(()=>{}),r}async function Qr(t,e){if(!t||e==null)return null;let n=(await gt())[ut(t,e)];return!n||!n.armedAt&&!n.autoMerge?null:(delete n.armedAt,delete n.autoMerge,delete n.archiveOnMerge,delete n.notifyOnMerge,delete n.notifyPending,D=D.then(Y,Y),await D.catch(()=>{}),n)}var r0=new Set(["merged","closed","lost"]);async function ap(){return Object.values(await gt()).filter(t=>!r0.has(t.state))}async function se(){return Object.values(await gt()).sort((t,e)=>e.firstSeen-t.firstSeen)}var cp=t=>Math.round(t*100)/100;function o0(t){return!t||t.kind==="setup"?[]:Ot(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():ip.basename(e.base)}))}function lp(t){let e=new Map;for(let n of t||[])for(let r of o0(n)){let o=e.get(r.key);o||e.set(r.key,o={...r,tracks:[]}),o.tracks.push(n)}return e}function js(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 Zi=(t,e)=>`${t} ${e}${t===1?"":"s"}`;function s0(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 Qi(t,e=[]){let n=[],r=t?.events||[],o=new Map,s=0;for(let u of e){if(u.type!=="pr.state.changed")continue;let f=u.payload||{};!f.repo||f.number==null||(f.change==="merged"?o.set(`${f.repo}#${f.number}`,f):f.change==="opened"&&s++)}if(o.size){let u=[...o.keys()].map(f=>{let[d,m]=f.split("#");return`${d.split("/").pop()}#${m}`});n.push(`Merged ${u.slice(0,4).join(", ")}${u.length>4?` +${u.length-4}`:""}`)}else s&&n.push(Zi(s,"PR")+" opened");let a=Object.keys(t?.subtasks||{}).length||r.filter(u=>u.type==="subtask-delegated").length;a&&n.push(Zi(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(`${Zi(i.length,"handoff")} ${u}${i.length>3?"\u2026":""}`)}let c=Yt(e);c.totalUsd>=.01&&n.push(`$${c.totalUsd.toFixed(2)}`);let l=Number.isFinite(t?.archivedAt)&&Number.isFinite(t?.createdAt)?s0(t.archivedAt-t.createdAt):null;return n.length&&l&&n.push(l),n.length?n.join(" \xB7 "):null}function a0(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 up(t,e,n){let r=new Set(t.tracks.map(d=>d.id)),o=0,s=0,a=0,i=null,c=null,l=0;for(let d of t.tracks){d.archivedAt?s++:o++,d.beat?.text&&a++;let m=js(d);m&&(!i||m>i)&&(i=m),d.createdAt&&(!c||d.createdAt<c)&&(c=d.createdAt),l+=e.get(d.id)||0}let u=0,f=0;for(let d of n)r.has(d.trackId)&&(t.slug&&d.repo!==t.slug||t.slug&&(d.state==="merged"?f++:d.state!=="closed"&&u++));return{key:t.key,slug:t.slug,base:t.base,name:t.name,tracks:{total:t.tracks.length,live:o,archived:s,withBeat:a},prs:{open:u,merged:f},usd:cp(l),firstActivityAt:c,lastActivityAt:i}}async function dp(t){let e=lp(t),n=await se().catch(()=>[]),r=new Map,o=new Set;for(let a of e.values())for(let i of a.tracks){if(o.has(i.id))continue;o.add(i.id);let c=await F(i).catch(()=>[]);r.set(i.id,Yt(c).totalUsd)}let s=[...e.values()].map(a=>up(a,r,n)).sort((a,i)=>(i.lastActivityAt??0)-(a.lastActivityAt??0));return{generatedAt:Date.now(),arcs:s}}async function fp(t,e){let r=lp(t).get(e);if(!r)return null;let[o,s,a,i,c]=await Promise.all([se().catch(()=>[]),hs().catch(()=>[]),ks().catch(()=>[]),xs().catch(()=>[]),Rs().catch(()=>({}))]),l=new Map,u=[],f=(d,m)=>(js(m)||m.createdAt||0)-(js(d)||d.createdAt||0);for(let d of[...r.tracks].sort(f)){let m=await F(d).catch(()=>[]),h=Yt(m);l.set(d.id,h.totalUsd);let w=c[d.id]||null;u.push({trackId:d.id,name:d.name,createdAt:d.createdAt??null,archivedAt:d.archivedAt??null,live:!d.archivedAt,beat:d.beat??null,agents:a0(d),handoffs:(d.events||[]).filter(y=>y.type==="lead-handoff").length,subtasks:Object.keys(d.subtasks||{}).length,repos:Ot(d.workspace).map(y=>y.repo||ip.basename(y.base||"")),prs:o.filter(y=>y.trackId===d.id&&(!r.slug||y.repo===r.slug)).map(y=>({repo:y.repo,number:y.number,state:y.state??null,title:y.title??null,url:y.url??null})),mocks:s.filter(y=>y.trackId===d.id).length,views:a.filter(y=>y.trackId===d.id).length,runbooks:i.filter(y=>y.trackId===d.id).map(y=>({name:y.name,status:y.status})),ticket:w?{provider:w.provider,key:w.key,url:w.url??null}:null,usd:cp(h.totalUsd),lastActivityAt:js(d)})}return{...up(r,l,o),generatedAt:Date.now(),segments:u}}import{execFile as i0}from"node:child_process";import{promisify as c0}from"node:util";import{randomUUID as l0}from"node:crypto";var mn=c0(i0),Ls=t=>new Promise(e=>setTimeout(e,t)),u0=Number(process.env.BOSTRAT_SWAP_EXIT_TIMEOUT_MS)||15e3,pp=Number(process.env.BOSTRAT_SWAP_SHELL_TIMEOUT_MS)||8e3,d0=250,f0=400,mp={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 wp(t){let e=await Cu(t);if(!e)return!1;try{return await mn("pgrep",["-P",String(e)]),!1}catch(n){return n?.code===1}}async function tc(t,e){let n=Date.now()+e;for(;;){if(await wp(t))return!0;if(Date.now()>n)return!1;await Ls(d0)}}async function p0(t,e){let n=mp[e]||mp.default;n.escape&&(await kt(t,["Escape"],{kind:"swap-exit"}),await Ls(150)),n.wipe&&await Zo(t,400,{kind:"swap-exit"}),await Qo(t,n.text,{perCharDelayMs:90,kind:"swap-exit"});let r=fe(e).submitDelayMs||0;await Ls(Math.max(r,250)),await kt(t,["Enter"],{kind:"swap-exit"})}async function m0(t,e){try{let{stdout:n}=await mn(N,B("show-environment","-t",t,e));return!String(n||"").trimStart().startsWith("-")}catch{return!1}}async function hp(t){await mn(N,B("respawn-pane","-k","-t",t))}async function ec(t,e={}){if(!mt(t))throw new Error("invalid session name");let n=String(e.agentCmd||"").trim();if(!n)throw new Error("agentCmd is required");let r=await X(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||Bn(n),a=Number(e.exitTimeoutMs)||u0;await V({flow:"swap",phase:"start",sessionName:t,from:o,to:s,cwd:r.cwd});let i=!1;if(!await wp(t)&&(await p0(t,o),!await tc(t,a)&&(i=!0,await V({flow:"swap",phase:"respawn-forced",sessionName:t,from:o}),await hp(t),!await tc(t,pp))))throw new Error(`pane shell did not come back after respawn-pane in "${t}"`);let c=s==="claude"&&Pn(n),l=c?Ko():null,u=await m0(t,"ANTHROPIC_API_KEY"),f=!1;try{l?(await mn(N,B("set-environment","-t",t,"ANTHROPIC_API_KEY",l)),f=!0):u&&(await mn(N,B("set-environment","-u","-t",t,"ANTHROPIC_API_KEY")),f=!0)}catch(y){throw l?Jo(y,l):y}if(f&&!i&&(await hp(t),!await tc(t,pp)))throw new Error(`pane shell did not come back after env respawn in "${t}"`);let d=!!e.resumeSessionId,m=e.resumeSessionId||l0(),h=d?n:Ja(n,m),w=d?e.pinned===!0:/(^|\s)--session-id(\s|=)/.test(h);return c&&await Te(r.cwd),s==="codex"&&await Cn(r.cwd),s==="cursor"&&await Mn(r.cwd),await on(t),await mn(N,B("set-option","-t",t,"@bostrat-sid",m)),await mn(N,B("set-option","-t",t,"@bostrat-agent",s)),await Ls(f0),await xt(t,h,{kind:"swap-launch"}),await V({flow:"swap",phase:"done",sessionName:t,from:o,to:s,sid:m,forced:i,envRespawned:f}),{name:t,cwd:r.cwd,sessionId:m,agent:s,pinned:w,forced:i,envRespawned:f}}import T0 from"node:fs";import yp from"node:fs/promises";import eo from"node:path";import _0 from"node:os";import{fileURLToPath as E0}from"node:url";import{execFile as I0}from"node:child_process";import{promisify as R0}from"node:util";import rc from"node:fs/promises";import Be from"node:path";import{execFile as h0}from"node:child_process";import{promisify as w0}from"node:util";var g0=w0(h0),gp=process.env.HOME||"/home/globo",y0=Be.join(process.env.BOSTRAT_STATE_DIR||Be.join(gp,".bostrat"),"repos"),b0=[...(process.env.BOSTRAT_REPOS_ROOT||Be.join(gp,"Documents")).split(","),y0].map(t=>t.trim()).filter(Boolean),k0=Number(process.env.BOSTRAT_REPOS_DEPTH)||3,x0=new Set(["node_modules",".git",".cache","dist","build",".next","vendor","__pycache__","venv",".venv","target"]);async function S0(t){try{return(await rc.stat(Be.join(t,".git"))).isDirectory()?"repo":"worktree"}catch{return null}}async function v0(t){let e=i=>rc.readFile(Be.join(t,".git",i),"utf8").then(c=>c.trim()).catch(()=>null),r=(await e("refs/remotes/origin/HEAD"))?.match(/^ref:\s*refs\/remotes\/origin\/(.+)/);if(r)return r[1].trim();let o;for(let i of["main","master"])if(await e(`refs/heads/${i}`)||(o??=await e("packed-refs")||"",new RegExp(`^[0-9a-f]{40} refs/heads/${i}$`,"m").test(o)))return i;let a=(await e("HEAD"))?.match(/^ref:\s*refs\/heads\/(.+)/);return a?a[1].trim():null}async function ye(){let t=new Map;async function e(o,s){if(t.has(o))return;let a=await S0(o);if(a==="worktree")return;if(a==="repo"){t.set(o,{path:o,name:Be.basename(o),branch:await v0(o)});return}if(s>=k0)return;let i;try{i=await rc.readdir(o,{withFileTypes:!0})}catch{return}for(let c of i)!c.isDirectory()||c.name.startsWith(".")||x0.has(c.name)||await e(Be.join(o,c.name),s+1)}for(let o of b0)await e(Be.resolve(o),0);let n=new Set,r=[];for(let o of t.values()){let s=(await Z(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 A0(t){let e=String(t||"").trim().match(/github\.com[:/]+([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/i);return e?`${e[1]}/${e[2]}`:null}var nc=new Map;async function Z(t){if(!t)return null;if(nc.has(t))return nc.get(t);let e=null;try{let{stdout:n}=await g0("git",["-C",t,"config","--get","remote.origin.url"],{timeout:5e3});e=A0(n)}catch{e=null}return nc.set(t,e),e}async function to(t){let e=String(t||"").toLowerCase();if(!e)return null;for(let n of await ye())if((await Z(n.path))?.toLowerCase()===e)return n.path;return null}var bp=R0(I0),$0=process.env.BOSTRAT_STATE_DIR||eo.join(process.env.HOME||_0.tmpdir(),".bostrat"),O0=eo.join($0,"repos"),kp=/^[^/\s]+\/[^/\s]+$/,C0=5*6e4,M0=6e4,N0=10*6e4,xp=fetch,oc=new Map,hn=null;function tr(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 Fs(t=process.env){return!!(tr(t)&&t.BOSTRAT_PLANE_TOKEN)}var Sp=t=>({authorization:`Bearer ${t.BOSTRAT_PLANE_TOKEN||""}`}),Qn=(t,e)=>Object.assign(new Error(t),{status:e});async function Us(t,{env:e=process.env}={}){let n=String(t||"").toLowerCase();if(!kp.test(n))throw Qn("repo must be owner/name",400);let r=oc.get(n);if(r&&r.expiresAt-Date.now()>C0)return r;if(!Fs(e))throw Qn("node is not plane-linked \u2014 GitHub auth unavailable",503);let o;try{o=await xp(`${tr(e)}/v1/github/token?repo=${encodeURIComponent(t)}`,{headers:Sp(e)})}catch(i){if(r&&r.expiresAt>Date.now())return r;throw Qn(`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 Qn(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 oc.set(n,a),a}async function no({env:t=process.env,force:e=!1}={}){if(!Fs(t))return{configured:!1,repos:[],username:null};if(!e&&hn&&Date.now()-hn.at<M0)return hn.data;try{let n=await xp(`${tr(t)}/v1/github/repos`,{headers:Sp(t)});if(!n.ok)throw new Error(`plane replied ${n.status}`);let r=await n.json();return hn={at:Date.now(),data:r},r}catch(n){return hn?hn.data:{configured:!0,repos:[],username:null,error:n?.message||String(n)}}}function vp(){oc.clear(),hn=null}async function ae({cwd:t=null,slug:e=null,env:n=process.env}={}){try{let r=e||(t?await Z(t):null);if(!r)return{};let{token:o}=await Us(r,{env:n});return o?{GH_TOKEN:o}:{}}catch{return{}}}async function sc({env:t=process.env}={}){return(await no({env:t})).username??null}var P0=()=>E0(new URL("../bin/bostrat-git-credential",import.meta.url)),B0=(t=process.env)=>`http://127.0.0.1:${Number(t.PORT)||3362}`;function D0(t){let[e,n]=String(t).split("/");return eo.join(O0,e,n)}async function ac(t,{env:e=process.env}={}){let n=(...r)=>bp("git",["-C",t,...r],{timeout:1e4});await n("config","--replace-all","credential.helper",""),await n("config","--add","credential.helper",`!"${P0()}" ${B0(e)}`),await n("config","credential.useHttpPath","true")}async function ic(t,{env:e=process.env,baseUrl:n="https://github.com"}={}){if(!kp.test(String(t||"")))throw Qn(`bad repo slug "${t}"`,400);let r=D0(t);if(T0.existsSync(eo.join(r,".git")))return r;let{token:o}=await Us(t,{env:e});await yp.mkdir(eo.dirname(r),{recursive:!0});let s="!f() { echo username=x-access-token; echo password=$BOSTRAT_GIT_TOKEN; }; f";try{await bp("git",["-c","credential.helper=","-c",`credential.helper=${s}`,"clone","--no-tags","-q",`${n}/${t}.git`,r],{timeout:N0,env:{...e,BOSTRAT_GIT_TOKEN:o,GIT_TERMINAL_PROMPT:"0"}})}catch(a){throw await yp.rm(r,{recursive:!0,force:!0}).catch(()=>{}),Qn(`clone of ${t} failed: ${a?.stderr?.trim?.()||a?.message||a}`,502)}return await ac(r,{env:e}),r}import wS from"node:fs/promises";function j0(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===`
|
|
51
|
-
`){e.push(n),n="";continue}n+=s}return e.push(n),e.map(o=>o.trim()).filter(Boolean)}function
|
|
52
|
-
`,n);i!==-1&&(e=e.slice(0,i))}let r=
|
|
53
|
-
`).map(n=>n.trim()).find(Boolean)||t;return e.length>88?`${e.slice(0,88).trimEnd()}\u2026`:e}function
|
|
54
|
-
`)){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(
|
|
55
|
-
`)){let c=i.trim();if(!c||c[0]!=="{")continue;let l;try{l=JSON.parse(c)}catch{continue}if(l.isSidechain)continue;let u=l.message?.content;if(!Array.isArray(u))continue;let f=l.timestamp?Date.parse(l.timestamp):null;for(let d of u)if(!(!d||typeof d!="object"))if(d.type==="tool_use"&&d.id){if(d.name==="Agent"){let m=d.input||{};o.set(d.id,{id:d.id,kind:"agent",name:String(m.description||"").trim()||null,detail:String(m.subagent_type||"").trim()||null,ts:f,proven:!1})}else if(d.name==="Bash"&&d.input?.run_in_background===!0){let m=String(d.input.command||"").trim();o.set(d.id,{id:d.id,kind:"command",name:String(d.input.description||"").trim()||(m?
|
|
56
|
-
`).trim():"",
|
|
57
|
-
`);if(i!==-1)return o.slice(0,i);if(s+=a,o.length>=e)break}return o}finally{await n.close()}}async function
|
|
58
|
-
`).trim():"";async function
|
|
59
|
-
`)){let m=d.trim();if(!m||m[0]!=="{")continue;let h;try{h=JSON.parse(m)}catch{continue}if((h.type==="turn_context"&&h.payload?.model||h.type==="session_meta"&&h.payload?.model)&&(i=h.payload.model),h.type==="event_msg"){let
|
|
60
|
-
`;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
|
|
61
|
-
`)){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"&&
|
|
62
|
-
`),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(
|
|
63
|
-
`),r=n.length-1;for(;r>=0&&n[r].trim()==="";)r--;if(r<0||!
|
|
64
|
-
`),o=[];for(let c=0;c<r.length;c++){let l=r[c].match(
|
|
65
|
-
`),o=-1;for(let u=r.length-1;u>=0;u--)if(
|
|
66
|
-
`),o
|
|
67
|
-
`),o
|
|
68
|
-
`),o="";for(let s=0;s<r.length;s++)if(/^\s*https:\/\//.test(r[s])){o=
|
|
69
|
-
`)].reverse().find(a=>
|
|
70
|
-
`),o
|
|
71
|
-
`),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(
|
|
72
|
-
`),o=
|
|
73
|
-
`).pop()||"")?!0:
|
|
74
|
-
`).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
|
|
75
|
-
`).map(c=>c.replace(/\s+$/,"")),r=-1;for(let c=n.length-1;c>=0;c--)if(
|
|
76
|
-
|
|
77
|
-
`)
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
`),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 vm(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&&!km.has(o))throw new Error(`unknown codex effort "${o}" (low | medium | high | xhigh)`);return{model:r,effort:o||null}}if(t==="cursor"){let r=xm.find(o=>o.alias===n);return{model:n,filter:r?r.label:n}}if(!JS.has(n))throw new Error(`unknown model "${n}"`);return{model:n}}async function co(t,e,{timeoutMs:n=VS}={}){let r=Date.now()+n;for(;;){let o=await wt(t,{lines:80}).catch(()=>""),s=e(o);if(s)return s;if(Date.now()>r)return null;await Vs(zS)}}async function ZS(t,e){return await xt(t,`/model ${e.model}`,fe("claude")),{verified:!1,note:"confirmed when the next turn's transcript model flips"}}async function QS(t,e){await Yo(t),await xt(t,"/model",{enter:!1}),await Vs(250),await kt(t,["Enter"],{kind:"model-switch"});let n=await co(t,wm);if(!n)throw new Error("codex model picker didn't open");let r=n.find(i=>i.id===e.model);if(!r)throw await kt(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=YS(i);return!c||c.model!==e.model||e.effort&&c.effort&&c.effort.toLowerCase()!==e.effort.toLowerCase()?null:c};await xt(t,String(r.n),{enter:!1,kind:"model-switch"});let s=await co(t,i=>gm(i)||!wm(i)&&o(i)&&{done:o(i)});if(!s)throw await kt(t,["Escape"],{kind:"model-switch"}).catch(()=>{}),new Error("codex reasoning-level picker didn't open");if(!s.done){let i=e.effort?km.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 kt(t,["Escape","Escape"],{kind:"model-switch"}).catch(()=>{}),new Error(`codex doesn't offer effort "${e.effort}" for ${e.model}`);await xt(t,String(c.n),{enter:!1,kind:"model-switch"})}let a=s.done||await co(t,i=>!gm(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 tv(){let t=hm.join(process.env.CURSOR_CONFIG_DIR||hm.join(KS.homedir(),".cursor"),"cli-config.json");try{return JSON.parse(await GS.readFile(t,"utf8"))?.model||null}catch{return null}}async function ev(t,e){await Yo(t),await Qo(t,`/model ${e.filter}`,{kind:"model-switch"});let n=await co(t,ym),r=c=>String(c||"").toLowerCase(),o=c=>r(c.label).startsWith(r(e.filter)),s=n?.rows.find(c=>c.active)||null;if(n&&!(s&&o(s)))for(let c=0;c<12;c++){let l=await co(t,ym,{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 kt(t,["Down"],{kind:"model-switch"}),await Vs(250)}if(!n||!s||!o(s)){await kt(t,["Escape"],{kind:"model-switch"}).catch(()=>{});let c=Array(`/model ${e.filter}`.length).fill("BSpace");throw await kt(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 kt(t,["Enter"],{kind:"model-switch"}),await Vs(800);let a=await tv();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 Am(t,e,n){return t==="codex"?QS(e,n):t==="cursor"?ev(e,n):ZS(e,n)}var Dt=t=>t||null;function Js(t){let e=t||{},n=nv(e);return n&&(n.payload={ts:e.at,...n.payload}),n}function nv(t){switch(t.type){case"said":return{type:"turn.user",actor:"user",source:"orchestrator",boxId:t.box,provider:Dt(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:Dt(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:Dt(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:Dt(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:Dt(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:Dt(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:Dt(t.agent),payload:{...ur(t)}};case"subtask-failed":return{type:"error",actor:"system",source:"orchestrator",boxId:t.box||t.to,provider:Dt(t.agent||t.toAgent),payload:{kind:"subtask-failed",...ur(t)}};case"subtask-resumed":return{type:"box.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Dt(t.agent),payload:{change:"subtask-resumed",...ur(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",...ur(t)}};case"archived":case"unarchived":case"track-deleted":return{type:"box.state.changed",actor:"user",source:"orchestrator",payload:{change:t.type,...ur(t)}};case"rehydrated":return{type:"box.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Dt(t.agent),payload:{change:"rehydrated"}};case"retagged":return{type:"box.state.changed",actor:"user",source:"orchestrator",payload:{change:"retagged",...ur(t)}};case"rehydrate-failed":return{type:"error",actor:"system",source:"orchestrator",boxId:t.box,provider:Dt(t.agent),payload:{kind:"rehydrate-failed",reason:t.reason}};default:return null}}var zs=Promise.resolve(),qc=t=>(zs=zs.then(t).catch(()=>{}),zs),_m=()=>zs;function Em(t,e){let n=Js(e);if(!n)return;let r=e?.type==="track-deleted"?{id:t.id}:t;qc(()=>O(r,n))}var Tm=new Set;async function Im(t){if(!(!t?.id||Tm.has(t.id))){Tm.add(t.id);try{let e=await F(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=Js(r);!o||n.has(`${o.type}|${o.payload?.ts??"?"}`)||await O(t,o)}}catch{}}}function ur(t){let{type:e,at:n,...r}=t;return r}var Wc=new Map;function Rm(t,e,n){!n?.promptId||Wc.get(t.id)===n.promptId||(Wc.set(t.id,n.promptId),qc(()=>O(t,{type:"prompt.raised",actor:"agent",source:"pane",boxId:e?.name,provider:Dt(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 $m(t,e,n,r={}){Wc.delete(t.id),qc(()=>O(t,{type:"prompt.answered",actor:"user",source:"orchestrator",boxId:e?.name,provider:Dt(n?.provider||e?.agent),payload:{promptId:n?.promptId,...r}}))}import{randomUUID as Nv}from"node:crypto";var Cm=/(?<!\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,Mm=/(?<!\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,Nm=/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,Pm=/(?<!\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,rv=new RegExp(`\\b(?:${Cm}|${Mm}|${Nm}|${Pm})\\b`,"i"),ov=/\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,sv=/^(?:commit|push|open|create|raise|rebase|ready|ok(?:ay)?\s+to|good\s+to)\b/i,av=90;function iv(t){return String(t).split(/(?<=[.?!])\s+|\n+/).map(e=>e.trim()).filter(Boolean)}function Bm(t){let e=String(t||"").trim();if(!e)return null;let n=e.slice(-600);for(let r of iv(n).reverse()){if(!rv.test(r))continue;let o=r.endsWith("?")&&r.length<=av&&sv.test(r);if(ov.test(r)||o)return r.length>200?r.slice(0,197)+"\u2026":r}return null}var Dm=["commit","rebase","push","pr"],cv=new RegExp(`\\b(?:${Cm})\\b`,"i"),lv=new RegExp(`\\b(?:${Pm})\\b`,"i"),uv=new RegExp(`\\b(?:${Mm})\\b`,"i"),dv=new RegExp(`\\b(?:${Nm})\\b`,"i"),fv=/\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 jm(t){let e=String(t||""),n=[];cv.test(e)&&n.push("commit"),lv.test(e)&&n.push("rebase"),uv.test(e)&&n.push("push");let r=fv.test(e);!r&&dv.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 Xs(t,{branch:e=null,base:n=null,toPr:r=null,repos:o=null,offeredRebase:s=!1}={}){Array.isArray(t)||(t=t==="push"?["commit","push"]:["commit","push","pr"]);let a=new Set(t),i=Array.isArray(o)&&o.length>0,c=i?o.map(m=>`${m.name}/`).join(", "):null,l=e?` on this branch (${e})`:"",u=[];if(a.has("commit")&&u.push(i?`commit the work in each changed checkout (${c})${l} with a clear commit message per repo`:`commit the work${l} with a clear commit message`),a.has("rebase")){let m=a.has("commit")?"the branch":`this branch${e?` (${e})`:""}`;u.push(i?`rebase ${m} in each changed checkout (${c}) onto its own base branch (fetch origin first), resolving any conflicts so both sides' intent is preserved`:`rebase ${m} onto ${n?`the latest ${n}`:"its base branch"} (fetch origin first), resolving any conflicts so both sides' intent is preserved`)}if(a.has("push")||a.has("pr")){let m=r&&!a.has("pr")?` (the open PR${r.number?` #${r.number}`:""} picks it up)`:"",h=a.has("rebase")?" with `--force-with-lease` (the rebase rewrote history)":"";i?u.push(a.has("commit")||a.has("rebase")?`push each one to its remote${h}${m}`:`push this branch${e?` (${e})`:""} from each changed checkout (${c}) to its remote${m}`):u.push(a.has("commit")||a.has("rebase")?`push it to the remote${h}${m}`:`push this branch${l} to the remote${m}`)}a.has("pr")&&u.push(i?"open a pull request in each changed repo with a concise title and a description of what changed and why (run `gh pr create` inside each checkout)":`open a pull request${n?` against ${n}`:""} with a concise title and a description of what changed and why (use \`gh pr create\`)`);let f=[];a.has("commit")||f.push("don't create any new commits \u2014 ship what's already committed"),s&&!a.has("rebase")&&f.push("don't rebase \u2014 leave the branch's history as it is"),!a.has("push")&&!a.has("pr")&&f.push("don't push"),a.has("pr")||f.push("don't open a pull request");let d=`Yes \u2014 go ahead: ${u.join(", then ")}.`;return f.length&&(d+=` ${f.join(", and ").replace(/^./,m=>m.toUpperCase())}.`),a.has("pr")&&(d+=i?" If a repo's PR for this branch is already open, just push that repo's update instead.":" If a PR for this branch is already open, just push the update instead."),d}var pv=3e4;function Lm(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<=pv&&n.push({box:r.box,from:r.from,to:e+1e12}),n}function Fm(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 Om={commit:"commit",rebase:"rebase",push:"push",pr:"open a PR"};function Um(t){let e=(Array.isArray(t)?t:[]).filter(r=>Om[r]).map(r=>Om[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 yv}from"node:child_process";import{promisify as bv}from"node:util";import mv from"node:fs";import Gc from"node:fs/promises";import Gm from"node:path";import hv from"node:os";var Km=process.env.BOSTRAT_STATE_DIR||Gm.join(process.env.HOME||hv.tmpdir(),".bostrat"),Ys=Gm.join(Km,"github-metrics.json"),Vm=60*6e4,Hm=5*6e4,wv=6e4,dr=Date.now(),be=new Map,Le=null,vn=0,Wm=0,Zs=t=>[...t.values()].reduce((e,n)=>e+n,0);try{let t=JSON.parse(mv.readFileSync(Ys,"utf8"));t?.windowStart&&(Date.now()-t.windowStart<Vm?(dr=t.windowStart,be=new Map(Object.entries(t.byCaller||{})),Le=t.lastHour||null):Zs(new Map(Object.entries(t.byCaller||{})))&&(Le={start:t.windowStart,total:Zs(new Map(Object.entries(t.byCaller||{}))),byCaller:t.byCaller||{}}))}catch{}var qm=Promise.resolve();function zm(t,e=!1){if(!e&&t-Wm<wv)return;Wm=t;let n=JSON.stringify({windowStart:dr,byCaller:Object.fromEntries(be),lastHour:Le});qm=qm.then(()=>Gc.mkdir(Km,{recursive:!0})).then(()=>Gc.writeFile(`${Ys}.tmp`,n)).then(()=>Gc.rename(`${Ys}.tmp`,Ys)).catch(()=>{})}function Jm(t){t-dr<Vm||(Le={start:dr,total:Zs(be),byCaller:Object.fromEntries(be)},Le.total&&console.log(`[github] ${Le.total} calls last hour`,Le.byCaller),dr=t,be=new Map,zm(t,!0))}function ke(t="other",e=Date.now()){Jm(e),be.set(t,(be.get(t)||0)+1),zm(e)}function Xm(t=Date.now()){return Jm(t),{windowStart:dr,total:Zs(be),byCaller:Object.fromEntries(be),lastHour:Le,backoffUntil:vn>t?vn:null}}var gv=/rate limit|too quickly|abuse detection|HTTP 429/i;function Fe(t){return gv.test(String(t?.message||t||""))}function Ue(t,e=Date.now()){return vn<=e&&console.warn(`[github] rate limited \u2014 backing off ${Hm/6e4} min:`,String(t?.message||t||"").slice(0,200)),vn=Math.max(vn,e+Hm),vn}function Qs(t=Date.now()){return Math.max(0,vn-t)}async function Kc(t,e){let n={working:!1,prompted:!1};if(!t)return n;let r=await wt(t,{lines:At}).catch(()=>"");if(!Ln(e,r))return nd(e,r)?{working:!0,prompted:!1}:n;if(ir(e,r)){let o=await Ee(t).catch(()=>r);if(Sn({name:t,agent:e},o))return{working:!1,prompted:!0}}return{working:!0,prompted:!1}}async function ta(t,e){return(await Kc(t,e)).working}var Ym=bv(yv),Zm=[4e3,15e3,45e3],kv=2500,xv=5*6e4;async function Vc(t,e,{midTurn:n=ta,pollMs:r=kv,capMs:o=xv}={}){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 Sv=6e4,vv=9e4,Av=2e3,Tv=/(?:^|[;&|]\s*|\)\s*)gh\s+(?:[^\s;&|"'`$]+\s+)*?pr\s+create\b/,_v=/(?:^|[;&|]\s*|\)\s*)gh\s+(?:[^\s;&|"'`$]+\s+)*?pr\s+merge\b/,Ev=/\s--web\b/,Qm=/(?:--repo|-R)[\s=]+([\w.-]+\/[\w.-]+)/;function zc(t,e){return t.slice(e).replace(/^[\s;&|)]+/,"").replace(/"[^"]*"|'[^']*'/g,'""').split(/[;&|]/)[0]}function th(t){let e=String(t||""),n=e.match(Tv);if(!n)return null;let r=zc(e,n.index);return Ev.test(r)?null:{repo:r.match(Qm)?.[1]??null}}var Iv=new Set(["--subject","-t","--body","-b","--body-file","-F","--match-head-commit","--author-email","-A","--repo","-R"]);function eh(t){let e=String(t||""),n=e.match(_v);if(!n)return null;let r=zc(e,n.index),o=zc(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("-")){Iv.has(c)&&i++;continue}s=c.replace(/^["']+|["']+$/g,"")||null;break}return{repo:r.match(Qm)?.[1]??null,selector:s,auto:/\s--auto\b/.test(r)}}var Rv="number,url,title,headRefName,createdAt";async function $v(t,e){let n=async s=>{ke("observer");let{stdout:a}=await Ym("gh",["pr","list","--repo",t,"--author","@me","--state","all","--limit","10","--json",Rv],{timeout:3e4,maxBuffer:4*1024*1024,env:{...process.env,...s}}).catch(i=>{throw Fe(i)&&Ue(i),i});return JSON.parse(a||"[]")},r=await ae({slug:t}),o=await n(r);return!o.length&&r.GH_TOKEN&&(o=await n({}).catch(()=>[])),o.filter(s=>Date.parse(s.createdAt)>=e-Sv).sort((s,a)=>Date.parse(a.createdAt)-Date.parse(s.createdAt))}async function Ov(t,e){if(e)return[e];let n=[];for(let r of nh(t)){let o=await Z(r).catch(()=>null);o&&!n.includes(o)&&n.push(o)}for(let r of await ye().catch(()=>[])){let o=await Z(r.path).catch(()=>null);o&&!n.includes(o)&&n.push(o)}return n}function nh(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 Cv(t,e,n){let r=t.workspace?.branch,o=[];for(let s of await Ov(t,e)){let a;try{a=await $v(s,n)}catch{continue}for(let i of a){let c=await Tt(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 He=new Set;function rh(t,e,{delays:n=Zm,turnEnd:r}={}){let o=th(e.command);if(!o)return null;let s=!!eh(e.command),a=`${t.id}|${e.id??e.ts??e.command}`;if(He.has(a))return null;He.add(a);let i=e.ts??Date.now(),c=(async()=>{try{for(let l of n){await new Promise(w=>setTimeout(w,l));let u=await Cv(t,o.repo,i).catch(()=>null);if(!u)continue;let{slug:f,pr:d}=u,{link:m,created:h}=await Bt({repo:f,number:d.number,trackId:t.id,url:d.url,headRef:d.headRefName,title:d.title,source:"observed"});return!h&&!(m?.trackId===t.id&&await Yr(f,d.number))?null:(await Vc(e.box,e.agent,r),await O({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:Date.now(),change:"opened",number:d.number,url:d.url,title:d.title,branch:d.headRefName,repo:f,...s?{selfMerge:!0}:{}}}),d)}return null}finally{He.delete(a)}})();return c.catch(()=>He.delete(a)),c}var Mv="number,url,title,headRefName,state";function oh(t,e,{delays:n=Zm,turnEnd:r,openedWait:o}={}){let s=eh(e.command);if(!s)return null;let a=`merge|${t.id}|${e.id??e.ts??e.command}`;if(He.has(a))return null;He.add(a);let i=(async()=>{try{let c=s.repo,l=s.selector,u=l?.match(/github\.com\/([\w.-]+\/[\w.-]+)\/pull\/(\d+)/);u&&([,c,l]=u);let f=[];if(c)f=[c];else{let h=nh(t);if(h.length>1&&/^\d+$/.test(String(s.selector??"")))return null;for(let w of h){let y=await Z(w).catch(()=>null);y&&!f.includes(y)&&f.push(y)}}if(l||=t.workspace?.branch,!f.length||!l)return null;c=f[0];let d=async(h,w)=>{let y=await Tt(c,h.number);return y&&y.trackId!==t.id?null:(y||await Bt({repo:c,number:h.number,trackId:t.id,url:h.url,headRef:h.headRefName,title:h.title,source:"merge-observed"}),await Zr(c,h.number),w&&((await F(t).catch(()=>[])).some(_=>_.type==="pr.state.changed"&&(_.payload?.change==="auto-armed"||_.payload?.change==="merged")&&_.payload?.number===h.number)||(await Vc(e.box,e.agent,r),await O({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:Date.now(),change:"auto-armed",number:h.number,url:h.url,title:h.title,branch:h.headRefName,repo:c}}))),h)},m=null;for(let h of n){await new Promise(I=>setTimeout(I,h));let w=[];for(let I of f)try{ke("observer");let{stdout:L}=await Ym("gh",["pr","view",String(l),"--repo",I,"--json",Mv],{timeout:3e4,maxBuffer:4*1024*1024,env:{...process.env,...await ae({slug:I})}}),v=JSON.parse(L||"null");v?.number&&w.push({slug:I,pr:v})}catch(L){Fe(L)&&Ue(L)}let y=w.find(I=>I.pr.state==="MERGED")??(w.length===1?w[0]:null);if(!y)continue;c=y.slug;let b=y.pr;if(!b?.number)continue;if(b.state==="CLOSED")return null;if(b.state!=="MERGED"){if(m=b,s.auto)return d(b,!0);continue}let A=await Tt(c,b.number);if(A&&A.trackId!==t.id)return null;A||await Bt({repo:c,number:b.number,trackId:t.id,url:b.url,headRef:b.headRefName,title:b.title,source:"merge-observed"}),await Vc(e.box,e.agent,r);let _=await F(t).catch(()=>[]);if(_.some(I=>I.type==="pr.state.changed"&&I.payload?.change==="merged"&&I.payload?.number===b.number))return null;let C=I=>I.some(L=>L.type==="pr.state.changed"&&L.payload?.change==="opened"&&L.payload?.number===b.number),K=0;if(!C(_)){if(th(e.command)&&await Yr(c,b.number)){let I=Date.now();K=I+1,await O({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:I,change:"opened",number:b.number,url:b.url,title:b.title,branch:b.headRefName,repo:c,selfMerge:!0}})}else if((await Tt(c,b.number))?.announced){let I=o?.capMs??vv,L=o?.pollMs??Av,v=Date.now()+I;for(;Date.now()<v&&(await new Promise($=>setTimeout($,L)),!C(await F(t).catch(()=>[]))););}}return await O({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:Math.max(Date.now(),K),change:"merged",number:b.number,url:b.url,title:b.title,branch:b.headRefName,repo:c}}),await qe(c,b.number).catch(()=>{}),await We(c,b.number).catch(()=>{}),await Pe(c,b.number,"merged"),b}return m?d(m,!1):null}finally{He.delete(a)}})();return i.catch(()=>He.delete(a)),i}function Xc(t){return t.sourceId?`${t.role}|id:${t.sourceId}`:`${t.role}|${t.ts??"?"}|${String(t.text||"").slice(0,40)}`}function sh(t){return t.id?`cmd|${t.box}|id:${t.id}`:`cmd|${t.box}|${t.ts??"?"}|${String(t.command||"").slice(0,40)}`}function ah(t){return`job|${t.box}|id:${t.id}`}var Jc=new Map,ea=new Map;async function Yc(t){return Jc.has(t.id)?Jc.get(t.id):(ea.has(t.id)||ea.set(t.id,F(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(Xc({role:o,sourceId:r.payload.sourceId})),n.keys.add(Xc({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(sh({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(ah({id:r.payload.id,box:r.boxId}));return Jc.set(t.id,n),n}).finally(()=>ea.delete(t.id))),ea.get(t.id))}async function ih(t,e){try{let n=(await Yc(t)).keys;for(let r of e||[]){if(!r||r.ts==null||!String(r.text||"").trim()&&!r.images?.length)continue;let o=Xc(r);if(!n.has(o)&&(n.add(o),await O(t,{type:r.role==="user"?"turn.user":"turn.agent",actor:r.role==="user"?"user":"agent",source:"transcript",boxId:r.box,provider:r.agent||null,payload:{text:r.text,ts:r.ts,model:r.model||null,tools:r.tools||void 0,boxRole:r.boxRole,sourceId:r.sourceId||void 0,note:r.note||void 0,images:r.images?.length?r.images:void 0}}),r.role==="assistant"&&r.box===t.leadBox&&t.workspace?.branch)){let s=Bm(r.text);if(s){let{offered:a,toPr:i}=jm(s),c;if(t.workspace?.kind==="multi"){let l=await lo(t).catch(()=>[]);c=l.length?l.map(u=>({name:u.name,slug:u.repo??null})):void 0}await O(t,{type:"pr.offered",actor:"agent",source:"transcript",boxId:r.box,provider:r.agent||null,payload:{offerId:Nv(),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 ch(t,e){try{let n=await Yc(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=sh(o);n.keys.has(s)||(n.keys.add(s),n.cmdN+=1,await O(t,{type:"tool.called",actor:"agent",source:"transcript",boxId:o.box,provider:o.agent||null,payload:{id:o.id??null,tool:o.tool||null,command:o.command,description:o.description||"",summary:o.summary||o.command,ts:o.ts??null,boxRole:o.boxRole,n:n.cmdN}}),rh(t,o),oh(t,o))}}catch{}}async function lh(t,e){try{let n=await Yc(t),r=[...e||[]].filter(o=>o&&o.id).sort((o,s)=>(o.ts||0)-(s.ts||0));for(let o of r){let s=ah(o);n.keys.has(s)||(n.keys.add(s),await O(t,{type:"job.started",actor:"agent",source:"transcript",boxId:o.box,provider:o.agent||null,payload:{id:o.id,kind:o.kind||"agent",name:o.name||null,detail:o.detail||null,ts:o.ts??null,boxRole:o.boxRole}}))}}catch{}}var Pv=/^Base directory for this skill:\s*(\S+)/,Bv=/^Run the "([^"]+)" workflow\./,Dv=/<command-name>\s*\/?([a-z0-9:_-]+)\s*<\/command-name>/i,jv=/^<local-command-(?:stdout|caveat)>/i,Lv=/^\[Image: original \d+x\d+, displayed at \d+x\d+\.\s*Multiply coordinates by [\d.]+ to map to original image\.\]$/,Fv=/^Continue from where you left off\.$/,Uv=/^<task-notification>/i,Hv=/<summary>([\s\S]*?)<\/summary>/i,Wv=/<task-id>([\s\S]*?)<\/task-id>/i,qv=/<tool-use-id>([\s\S]*?)<\/tool-use-id>/i,Gv=/<status>([\s\S]*?)<\/status>/i,Kv=/<result>([\s\S]*?)(?:<\/result>|$)/i;var Zc=new Set(["model","clear","compact","login","exit","quit"]);function uh(t){return Zc.has(String(t||"").replace(/^\//,"").toLowerCase())}var Vv=/^\/([a-z0-9:_-]+)$/i;function na(t){let e=String(t||"").trim();if(!e)return null;let n=e.match(Pv);if(n)return{kind:"skill",name:n[1].split("/").filter(Boolean).pop()||"skill"};let r=e.match(Bv);if(r)return{kind:"skill",name:r[1]};let o=e.match(Dv);if(o){let a=o[1].toLowerCase();return Zc.has(a)?{kind:"suppress"}:{kind:"command",command:`/${a}`}}let s=e.match(Vv);if(s&&Zc.has(s[1].toLowerCase()))return{kind:"suppress"};if(jv.test(e))return{kind:"suppress"};if(Fv.test(e))return{kind:"suppress"};if(Lv.test(e))return{kind:"suppress"};if(Uv.test(e)){let a=c=>zv(e.match(c)?.[1].trim()||"")||null,i=a(Kv);return{kind:"task",summary:a(Hv)||"",taskId:a(Wv),toolUseId:a(qv),status:a(Gv)?.toLowerCase()||null,resultHead:i?i.length>600?`${i.slice(0,600).trimEnd()}\u2026`:i:null}}return null}function zv(t){return t.replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/'/g,"'").replace(/&/g,"&")}function dh(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 fh(t){return t?.kind==="skill"?"skill-run":t?.kind==="task"?"task-done":"command-run"}var ot=t=>t.payload?.ts??t.ts,Jv=new Set(["opened","ci-fixed","reconciled","pushed"]),ph=t=>`${t.repo??""}#${t.number}`;function Xv(t,e,n){let r=ph(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 ra(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 p=String(g.payload?.command||"");if(/\bbostrat-mock\s+(?:add|update)\b/.test(p))for(let S of p.matchAll(/[\w./~-]+\.(?:png|jpe?g|gif|webp|bmp|svg)\b/gi)){let T=S[0].split("/").pop().toLowerCase();f.has(T)||f.set(T,[]),f.get(T).push(g.payload?.ts??g.ts)}}let d=3*6e4,m=20*6e4,h=(g,p)=>!!(g?.name&&f.get(String(g.name).toLowerCase()))?.some(T=>T>=p-d&&T<=p+m);for(let g of t||[])if(g.type==="turn.retracted"){r.add(`${g.boxId}|${g.payload?.saidTs}`);let p=Kt(g.payload?.text);p&&(o.has(g.boxId)||o.set(g.boxId,[]),o.get(g.boxId).push(p))}else if(g.type==="turn.user"&&g.source==="orchestrator"){let p=Kt(g.payload?.text);p&&(s.has(g.boxId)||s.set(g.boxId,new Set),s.get(g.boxId).add(p))}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 p=g.payload.note,S={status:p.status||null,summary:p.summary||null,resultHead:p.resultHead??null,ts:ot(g)};p.toolUseId&&i.set(p.toolUseId,S);let T=String(p.summary||"").match(u);T&&l.set(T[1],S)}let w=new Map,y=new Map,b=null,A=new Map,_=new Map,C=new Map,K=new Map,I=new Map,L=new Map,v=new Map,$=6e4,W=new Map,nt=(g,p)=>{let S=o.get(g);if(!S?.length)return!1;let T=Kt(p),J=s.get(g);return S.some(R=>{if(!T.startsWith(R))return!1;let M=Kt(T.slice(R.length));return!!M&&!!J?.has(M)})};for(let g of t||[]){let p=g.payload||{};switch(g.type){case"turn.user":{if(g.source==="hook"||!String(p.text||"").trim()&&!p.images?.length)break;{let S=p.note||na(p.text);if(S){if(S.kind==="task"){if(S.toolUseId&&a.has(S.toolUseId))break;let T=String(S.summary||"").match(u);if(!S.toolUseId&&T&&c.has(T[1]))break}if(S.kind==="command"&&uh(S.command))break;S.kind!=="suppress"&&e.push({id:g.id,kind:"event",type:fh(S),name:S.name??null,command:S.command??null,summary:S.summary??null,ts:ot(g),box:g.boxId,agent:g.provider});break}}if(r.has(`${g.boxId}|${ot(g)}`)||g.source==="transcript"&&nt(g.boxId,p.text))break;if(p.kind==="merge-notify"||p.kind==="ship-notify"){e.push({id:g.id,kind:"event",type:p.kind==="ship-notify"?"ship-notified":"merge-notified",text:p.text,ts:ot(g),box:g.boxId,agent:g.provider});break}if(p.kind==="mock-picked"||p.kind==="ci-fix"||p.kind==="reconcile"||p.kind==="pr-go"||p.kind==="push-go"||p.kind==="spin-up-view")break;for(let S of A.values())S.status="stale";A.clear(),e.push({id:g.id,kind:"message",role:"user",text:p.text,ts:ot(g),box:g.boxId,agent:g.provider,boxRole:p.boxRole,...g.actor==="orchestrator"?{orchestration:p.kind||!0}:{},...p.sourceId?{sourceId:p.sourceId}:{},...p.attachments?.length?{attachments:p.attachments}:{},...p.images?.length?{images:p.images}:{}});break}case"turn.agent":{if(p.model==="<synthetic>"&&/^No response requested\.$/.test(String(p.text||"").trim()))break;v.clear();let S=(p.images||[]).filter(J=>!h(J,ot(g)));if(!String(p.text||"").trim()&&!S.length)break;if(g.provider==="cursor"){let J=`${g.boxId}|${p.text}`;if(n.has(J))break;n.add(J)}let T=g.provider==="codex"?(p.tools||[]).filter(J=>!yc.has(J)&&!ao.has(J)):p.tools||[];e.push({id:g.id,kind:"message",role:"assistant",text:p.text,ts:ot(g),box:g.boxId,agent:g.provider,boxRole:p.boxRole,...T.length?{tools:T}:{},...p.model?{model:p.model}:{},...p.sourceId?{sourceId:p.sourceId}:{},...S.length?{images:S}:{}});break}case"job.started":{if(!p.id)break;let S=ot(g),T=i.get(p.id);if(!T&&(p.kind||"agent")==="agent"&&p.name){let P=l.get(p.name);P&&P.ts>=S&&(T=P)}let J={jobId:p.id,jobKind:p.kind||"agent",name:p.name||null,detail:p.detail||null,status:T?T.status&&T.status!=="completed"?"failed":"done":"running",startedTs:S,finishedTs:T?.ts??null,summary:T?.summary??null,resultHead:T?.resultHead??null},R=W.get(g.boxId),M=R?.jobs[R.jobs.length-1]?.startedTs;if(R&&M!=null&&S-M<=$)R.jobs.push(J);else{let P={id:g.id,kind:"job-dock",dockId:p.id,ts:S,box:g.boxId,agent:g.provider,jobs:[J]};W.set(g.boxId,P),e.push(P)}break}case"handoff.done":e.push(Ge(g,"lead-handoff"));break;case"subtask.delegated":e.push({...Ge(g,"subtask-delegated"),to:g.boxId,toAgent:g.provider});break;case"subtask.merged":e.push(Ge(g,"subtask-merged"));break;case"pr.state.changed":{if(!p.change)break;if(p.change==="opened"){for(let T of A.values())T.status="stale";A.clear()}let S=Ge(g,`pr-${p.change}`);if(Jv.has(p.change)&&p.number!=null){let T=Xv(w,y,p);if(w.size>=2){for(let R of y.values())for(let M of R)M.grouped=!0;S.groupPrs=[...w.values()];let J=[S];for(let R of w.keys())y.set(R,J)}else{let J=y.get(T)??[];J.push(S),y.set(T,J)}}else if(p.change==="merged"||p.change==="closed"||p.change==="lost"){let T=ph(p),J=b&&!b.complete||w.size>=2;if(w.has(T))w.delete(T),y.delete(T);else if(p.number!=null)for(let R of[...w.keys()])R.endsWith(`#${p.number}`)&&(w.delete(R),y.delete(R));if(J&&p.number!=null){(!b||b.complete)&&(b={id:g.id,kind:"event",type:"pr-group-settled",prs:[],remaining:[],complete:!1,at:ot(g),ts:ot(g),box:g.boxId,agent:g.provider},e.push(b)),b.prs.push({number:p.number,url:p.url??null,title:p.title??null,repo:p.repo??null,branch:p.branch??null,change:p.change,ts:ot(g)}),b.remaining=[...w.values()],b.complete=w.size===0;break}}e.push(S);break}case"pr.offered":{if(!p.offerId)break;for(let T of A.values())T.status="stale";A.clear();let S={id:g.id,kind:"pr-offer",offerId:p.offerId,excerpt:p.excerpt||null,branch:p.branch||null,base:p.base||null,offered:Array.isArray(p.offered)&&p.offered.length?p.offered:null,toPr:p.toPr&&typeof p.toPr=="object"?{number:p.toPr.number??null}:null,repos:Array.isArray(p.repos)&&p.repos.length?p.repos:null,status:"open",ts:ot(g),box:g.boxId,agent:g.provider};A.set(p.offerId,S),_.set(p.offerId,S),e.push(S);break}case"pr.offer.answered":{let S=_.get(p.offerId);S&&(S.status=p.action||"dismiss",S.actions=Array.isArray(p.actions)?p.actions:null,S.notify=p.notify===!0,A.delete(p.offerId));break}case"box.state.changed":{let S=p.change==="model"?"model-change":p.change;if(!S)break;let T=Ge(g,S);S==="model-change"&&(T.to=p.model),e.push(T);break}case"view.state.changed":{if(p.change!=="created"||p.restarted)break;e.push({id:g.id,kind:"view-card",viewId:p.viewId,name:p.name,slug:p.slug,port:p.port,origin:p.origin,ts:ot(g),box:g.boxId,agent:g.provider});break}case"view.spinup.failed":{e.push({id:g.id,kind:"view-spinup-failed",name:p.name||null,reason:p.reason||"no-view",ts:ot(g),box:g.boxId,agent:g.provider});break}case"mock.state.changed":{if(p.change==="picked"){e.push({id:g.id,kind:"mock-picked",mockId:p.mockId,name:p.name,group:p.group??null,version:p.version??null,siblings:p.siblings??[],ts:ot(g),box:g.boxId,agent:g.provider});break}if(p.change!=="created")break;e.push({id:g.id,kind:"mock-card",mockId:p.mockId,name:p.name,group:p.group??null,ts:ot(g),box:g.boxId,agent:g.provider});break}case"runbook.state.changed":{if(p.change!=="created")break;e.push({id:g.id,kind:"runbook-card",runbookId:p.runbookId,name:p.name,ts:ot(g),box:g.boxId,agent:g.provider});break}case"ticket.linked":{e.push({id:g.id,kind:"ticket-card",provider:p.provider||"linear",key:p.key,url:p.url,title:p.title??null,linkSource:p.linkSource??null,ts:ot(g),box:g.boxId,agent:g.provider});break}case"ticket.move.offered":{if(!p.offerId)break;for(let T of C.values())T.key===p.key&&(T.status="stale",C.delete(T.offerId));let S={id:g.id,kind:"ticket-offer",offerId:p.offerId,provider:p.provider||"linear",key:p.key,url:p.url??null,phase:p.phase??null,from:p.from??null,to:p.to?.name??null,prNumber:p.prNumber??null,status:"open",ts:ot(g),box:g.boxId,agent:g.provider};C.set(p.offerId,S),K.set(p.offerId,S),e.push(S);break}case"ticket.move.answered":{let S=K.get(p.offerId);S&&(S.status=p.action==="moved"?"moved":"dismiss",C.delete(p.offerId));break}case"ticket.state.changed":{for(let S of C.values())S.key===p.key&&S.to===p.to&&(S.status="stale",C.delete(S.offerId));if(p.mode==="offer")break;e.push(Ge(g,"ticket-state"));break}case"ticket.commented":{e.push({id:g.id,kind:"ticket-comment",key:p.key,url:p.url??null,excerpt:p.excerpt??null,ts:ot(g),box:g.boxId,agent:g.provider});break}case"ticket.flow.changed":e.push(Ge(g,"ticket-flow"));break;case"ticket.create.offered":{if(!p.proposalId)break;for(let T of I.values())T.status="stale";I.clear();let S={id:g.id,kind:"ticket-create-offer",proposalId:p.proposalId,provider:p.provider||"linear",epic:p.epic??null,issues:Array.isArray(p.issues)?p.issues:[],team:p.team??null,needsTeam:!!p.needsTeam,linkCurrentTrack:!!p.linkCurrentTrack,status:"open",ts:ot(g),box:g.boxId,agent:g.provider};I.set(p.proposalId,S),L.set(p.proposalId,S),e.push(S);break}case"ticket.create.answered":{let S=L.get(p.proposalId);S&&(S.status=p.action==="created"?"created":"dismiss",S.createdCount=p.createdCount??null,S.failedCount=p.failedCount??null,I.delete(p.proposalId));break}case"ticket.created":{let S={items:Array.isArray(p.items)?p.items:[],failed:Array.isArray(p.failed)?p.failed:[],team:p.team??null},T=p.proposalId?L.get(p.proposalId):null;if(T){T.created=S;break}e.push({id:g.id,kind:"ticket-created",...S,ts:ot(g),box:g.boxId,agent:g.provider});break}case"error":{if(!p.kind)break;e.push(Ge(g,p.kind));break}case"provider.connected":{e.push({id:g.id,kind:"event",type:"provider-connected",provider:p.provider||g.provider,ts:ot(g),at:ot(g),box:g.boxId,agent:g.provider});break}case"prompt.answered":{if(p.control||!p.question)break;let S=p.groupId||p.promptId;if(!S)break;let T=v.get(S);T||(T={id:g.id,kind:"prompt",groupId:S,provider:g.provider,box:g.boxId,agent:g.provider,questionTotal:p.questionTotal??1,questions:[],ts:ot(g)},v.set(S,T));let J=p.questionIndex??0,R={index:J,header:p.header||"",question:p.question,multiSelect:!!p.multiSelect,chosen:Array.isArray(p.chosen)?p.chosen:[]},M=T.questions.findIndex(P=>P.index===J);M>=0?T.questions[M]=R:T.questions.push(R),T.questions.sort((P,et)=>P.index-et.index),T.questionTotal=Math.max(T.questionTotal,p.questionTotal??1),new Set(T.questions.map(P=>P.index)).size>=T.questionTotal&&(e.push(T),v.delete(S));break}default:break}}return Zv(e.sort((g,p)=>(g.ts??0)-(p.ts??0)))}var Yv=2*6e4;function Zv(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<=Yv){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 Qc(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:Qv(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??ra(t)).filter(o=>o.kind==="message");return Rp(n,r)}function Qv(t){let e=String(t.summary||"").trim();return e&&e!==t.command&&e!==rr(t.command)?e:De(t.command,t.description||"")}function Ge(t,e){let{ts:n,change:r,...o}=t.payload||{};return{id:t.id,kind:"event",type:e,...o,at:ot(t),ts:ot(t)}}function tA(t){let e=[],n=null;for(let r of String(t||"").split(`
|
|
83
|
-
|
|
84
|
-
`)}catch{}}function Sh(){let t={};for(let[e,n]of ol)t[e]={count:n.count,measured:n.measured,avgMs:n.measured?Math.round(n.totalMs/n.measured):null,maxMs:n.measured?n.maxMs:null};return{byChange:t,recent:aa.slice(-20)}}var ua=gA(wA),yA={timeout:3e4,maxBuffer:8*1024*1024};async function Ht(t,e,n={}){let{caller:r="prs",...o}=n,s=await ae({cwd:t});ke(r);try{let{stdout:a}=await ua("gh",e,{cwd:t,...yA,...o,env:{...process.env,...s,...o.env||{}}});return String(a||"")}catch(a){throw Fe(a)&&Ue(a),a}}var ca=null;async function il(){if(ca&&Date.now()-ca.at<6e4)return ca.data;let t;try{ke("auth");let e=await ua("gh",["auth","status"],{timeout:1e4});t={installed:!0,authenticated:!0,login:`${e.stdout||""}${e.stderr||""}`.match(/account\s+(\S+)/)?.[1]??null}}catch(e){t=e?.code==="ENOENT"?{installed:!1,authenticated:!1,login:null}:{installed:!0,authenticated:!1,login:null}}if(t.installed&&!t.authenticated)try{let e=await no();e.configured&&(t={installed:!0,authenticated:!0,login:e.username,via:"broker"})}catch{}return ca={at:Date.now(),data:t},t}async function bA(t){try{let{stdout:e}=await ua("git",["-C",t,"config","--get","remote.origin.url"],{timeout:5e3});return/github\.com/.test(e)}catch{return!1}}function Ah(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 sl(t){let e=(t||[]).map(Ah),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 cl="number,title,url,headRefName,headRefOid,baseRefName,isDraft,author,reviewDecision,mergeable,mergeStateStatus,statusCheckRollup,autoMergeRequest,additions,deletions,createdAt,updatedAt",Th=cl.replace("statusCheckRollup,","");function ll(t){return/Resource not accessible by personal access token/.test(String(t?.stderr||t?.message||""))}async function _h(t,e){if(!e)return null;let n=await Z(t);if(!n)return null;try{let r=await Ht(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 Eh(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 Ih(t,e){let n=await _h(t,e);return n===null?{...sl([]),unavailable:!0}:sl(Eh(n))}function la(t,e=sl(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 kA=9e4,fr=new Map;function xA(t,e,n){let r=new Set;for(let o of e){let s=`${t}#${o.number}`;if(o.mergeable!=="UNKNOWN"&&o.mergeStateStatus!=="UNKNOWN"){fr.delete(s);continue}r.add(s),fr.has(s)||fr.set(s,n),o.mergeabilityPending=n-fr.get(s)<kA}for(let o of fr.keys())o.startsWith(`${t}#`)&&!r.has(o)&&fr.delete(o);return e}async function pr(t,e){let n=await xe(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");try{let o=await Ht(n.path,["pr","view",String(r),"--json",cl]);return la(JSON.parse(o))}catch(o){if(!ll(o))throw o;let s=await Ht(n.path,["pr","view",String(r),"--json",Th]),a=JSON.parse(s);return la(a,await Ih(n.path,a.headRefOid))}}var vh=(t,e)=>t.number-e.number;async function fo(t,{caller:e="prs"}={}){try{let n=await Ht(t,["pr","list","--json",cl,"--limit","50"],{caller:e});return JSON.parse(n||"[]").map(r=>la(r)).sort(vh)}catch(n){if(!ll(n))throw n;let r=await Ht(t,["pr","list","--json",Th,"--limit","50"],{caller:e});return(await Promise.all(JSON.parse(r||"[]").map(async o=>la(o,await Ih(t,o.headRefOid))))).sort(vh)}}var SA=3e4,vA=1500,uo=new Map;function ce(){uo.clear()}async function AA(t,e){if(!t)return e;for(let n of e){let r=await Tt(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 Ms(t,n.number,{headSha:n.headSha}).catch(()=>{}):n.ciFix={delegatedAt:r.ciFix.delegatedAt,trackId:r.ciFix.trackId??null}),r.reconcile&&(o(r.reconcile)?await Ps(t,n.number,{headSha:n.headSha}).catch(()=>{}):n.reconcile={delegatedAt:r.reconcile.delegatedAt,trackId:r.reconcile.trackId??null})}return e}async function Rh(){let t=await il();if(!t.authenticated)return{auth:t,repos:[]};let e=[];for(let a of await ye())await bA(a.path)&&e.push(a);let n=Date.now(),r=Qs(n)>0,o=await Promise.all(e.map(async a=>{let i=uo.get(a.path),c=i?.pending?vA:SA;if(i&&(r||n-i.at<c))return i.entry;try{let l=await Z(a.path).catch(()=>null),u=xA(a.path,await fo(a.path,{caller:"tab"}),n);if(await AA(l,u),i?.entry?.prs)for(let m of u)i.entry.prs.find(w=>w.number===m.number)?.checks?.pending>0&&m.checks.total>0&&m.checks.pending===0&&ia({change:m.checks.failed>0?"checks-failed":"checks-green",repo:l||a.name,number:m.number,githubAt:m.checks.settledAt,source:"tab"});let f={name:a.name,path:a.path,prs:u},d=u.some(m=>m.mergeabilityPending||m.checks.pending>0||m.autoMergeArmed);return uo.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:ie(l)}}})),s=new Set(e.map(a=>a.path));for(let a of uo.keys())s.has(a)||uo.delete(a);return{auth:t,repos:o}}async function xe(t){let e=(await ye()).find(n=>n.path===t);if(!e)throw new Error(`unknown repo: ${t}`);return e}async function An(t){return to(t)}var po=new Set(["squash","merge","rebase"]);async function mr(t,e,{method:n="squash"}={}){let r=await xe(t);if(!po.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 sc().catch(()=>null);a&&s.push("--body",`Merged by bostrat on behalf of @${a}`)}try{await Ht(r.path,s)}catch(a){throw new Error(ie(a))}finally{ce()}return{ok:!0,number:o}}async function mo(t,e){let n=await xe(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");try{await Ht(n.path,["api","-X","PUT",`repos/{owner}/{repo}/pulls/${r}/update-branch`])}catch(s){throw new Error(ie(s))}finally{ce()}let o=await Z(n.path).catch(()=>null);return o&&await np(o,r).catch(()=>{}),{ok:!0,number:r}}async function ul(t,e){let n=await xe(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");try{await Ht(n.path,["pr","merge",String(r),"--disable-auto"])}catch(o){throw new Error(ie(o))}finally{ce()}return{ok:!0,number:r}}var TA=2500,_A=9e4,EA=t=>new Promise(e=>setTimeout(e,t));function dl(t){return t.green&&!t.isDraft}function IA(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 $h(t,{update:e=!0}={}){return t.isDraft?{action:"skip",reason:"draft"}:dl(t)?{action:"merge"}:e&&t.mergeStateStatus==="BEHIND"&&t.mergeable!=="CONFLICTING"&&t.checks.failed===0?{action:"update-arm"}:{action:"arm"}}function Oh(t){return[...t.filter(e=>e.plan.action!=="merge"),...t.filter(e=>e.plan.action==="merge")]}var al=new Map;function Ch(){let t={};for(let[e,n]of al)t[e]={...n};return t}async function Mh(t,{method:e="merge",onMerged:n=null}={}){let r=await xe(t);if(!po.has(e))throw new Error(`invalid merge method: ${e}`);if(al.get(r.path)?.running)throw new Error("a merge-all is already running for this repo");let o=(await fo(r.path)).filter(dl).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 al.set(r.path,s),$A(s,o,{listPrs:()=>fo(r.path),merge:a=>mr(r.path,a,{method:e}),sleep:EA,onMerged:n}).catch(()=>{}),{ok:!0,repo:r.path,total:s.total,queue:o.map(a=>a.number)}}async function RA(t,e){let n=Date.now()+(e.settleMaxMs??_A),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(TA)}}async function $A(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 RA(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: ${ie(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(!dl(a)){t.skipped.push({number:a.number,title:a.title,reason:IA(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:ie(i)})}}}finally{t.current=null,t.running=!1,t.finishedAt=Date.now(),ce()}}async function fl(t,{title:e,body:n="",base:r=null}={}){if(typeof e!="string"||!e.trim())throw new Error("title required");try{await ua("git",["-C",t,"push","-u","origin","HEAD"],{timeout:12e4,maxBuffer:4*1024*1024})}catch(l){throw new Error(`push failed: ${ie(l)}`)}let o=n||"";if((await ae({cwd:t})).GH_TOKEN){let l=await sc().catch(()=>null);l&&(o=`${o?`${o}
|
|
85
|
-
|
|
86
|
-
|
|
81
|
+
}`,{contentType:n,filename:e,size:r.length}),s=o.fileUpload?.uploadFile;if(!o.fileUpload?.success||!s?.uploadUrl)throw new Error("linear: fileUpload did not succeed");let a={"content-type":n};for(let c of s.headers||[])a[c.key]=c.value;let i=await fetch(s.uploadUrl,{method:"PUT",headers:a,body:r});if(!i.ok)throw new Error(`linear: asset PUT failed (HTTP ${i.status})`);return s.assetUrl}async function zS(t,e){let n=await de(t,"query($id: String!) { issue(id: $id) { state { id name type } team { states { nodes { id name type position } } } } }",{id:e.key});if(!n.issue)throw new Error(`linear: issue "${e.key}" not found`);return{current:n.issue.state??null,states:n.issue.team?.states?.nodes??[]}}async function VS(t,e,n){if(!(await de(t,"mutation($id: String!, $input: IssueUpdateInput!) { issueUpdate(id: $id, input: $input) { success } }",{id:e.issueId,input:{stateId:n}})).issueUpdate?.success)throw new Error("linear: issueUpdate did not succeed")}async function ir(t){return((await de(t,"query { teams(first: 100) { nodes { id key name } } }")).teams?.nodes??[]).map(n=>({id:n.id,key:n.key,name:n.name}))}var Dp=255;async function mc(t,{teamId:e,name:n,description:r}){let o=r||null,s=!!o&&o.length>Dp,a=s?`${o.slice(0,Dp-1).trimEnd()}\u2026`:o,i=await de(t,"mutation($input: ProjectCreateInput!) { projectCreate(input: $input) { success project { id url name } } }",{input:{teamIds:[e],name:n,...a?{description:a}:{},...s?{content:o}:{}}}),c=i.projectCreate?.project;if(!i.projectCreate?.success||!c)throw new Error("linear: projectCreate did not succeed");return{id:c.id,url:c.url,name:c.name}}async function hc(t,{teamId:e,title:n,description:r,projectId:o,priority:s}){let a=await de(t,"mutation($input: IssueCreateInput!) { issueCreate(input: $input) { success issue { id identifier url title } } }",{input:{teamId:e,title:n,...r?{description:r}:{},...o?{projectId:o}:{},...Number.isInteger(s)?{priority:s}:{}}}),i=a.issueCreate?.issue;if(!a.issueCreate?.success||!i)throw new Error("linear: issueCreate did not succeed");return{id:i.id,key:i.identifier,url:i.url,title:i.title}}var Kp={linear:{api:wc,config:xn},jira:{api:fc,config:Gs}};async function Sc(){let t=[];for(let[e,{api:n,config:r}]of Object.entries(Kp)){let o=await r();o&&t.push({name:e,api:n,cfg:o})}return t}async function Tn(t){let e=Kp[t?.provider];if(!e)return null;let n=await e.config();return n?{name:t.provider,api:e.api,cfg:n}:null}function vc(t,e){if(!t||t.name)return t;let n=e.find(r=>r.id===t.id);return n?{...t,name:n.name}:t}var Gp=()=>process.env.BOSTRAT_STATE_DIR||Wp.join(process.env.HOME||JS.tmpdir(),".bostrat"),kc=()=>Wp.join(Gp(),"tickets.json"),zp=()=>(process.env.BOSTRAT_PUBLIC_URL||"http://bostrat.globopi").replace(/\/$/,""),Js=t=>`${zp()}/track/${t}`,Vs=null,gc=null,yc=Promise.resolve();async function Xs(){return Vs||(gc??=po.readFile(kc(),"utf8").then(t=>JSON.parse(t)?.tickets??{}).catch(()=>({})).then(t=>Vs=t).finally(()=>gc=null),gc)}async function jp(){let t=JSON.stringify({tickets:Vs},null,2),e=`${kc()}.${process.pid}.tmp`;await po.mkdir(Gp(),{recursive:!0}),await po.writeFile(e,t),await po.rename(e,kc())}async function Ge(){yc=yc.then(jp,jp),await yc.catch(()=>{})}async function fe(t){return(await Xs())[t]??null}async function Ys(){return{...await Xs()}}async function Vp(){return(await Sc()).length>0}async function cr(t,e,{source:n="api"}={}){let r=await Sc();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 Xs())[t.id]=c,await Ge(),await O({id:t.id,cwd:t.cwd},{type:"ticket.linked",actor:"user",source:"orchestrator",payload:{ts:Date.now(),provider:c.provider,key:c.key,url:c.url,title:c.title,linkSource:n}}).catch(()=>{}),await mo(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 fo(c,`**Bostrat** \u2014 Track **${c.track.name}** linked${l.length?` (${l.join(", ")})`:""}.`),await Yp(t.id,c,"linked"),c}async function Tc(t){let e=await Xs();return e[t]?(delete e[t],await Ge(),!0):!1}var xc=t=>`$${t>=100?Math.round(t):t.toFixed(2)}`;async function Jp(t,e){if(!t.track?.cwd)return null;let n=se(await L({id:e,cwd:t.track.cwd}));return!n.totalUsd||n.totalUsd<.01?null:n}function XS(t){let e=Object.entries(t.byProvider).sort((r,o)=>o[1].usd-r[1].usd).map(([r,o])=>`${r} ${xc(o.usd)}`).join(" \xB7 "),n=Object.keys(t.byBox).length;return`${xc(t.totalUsd)}${e?` (${e})`:""}${n>1?` across ${n} boxes`:""}`}function YS(t,e){let n=[];return t.track.leadAgent&&n.push(`lead ${t.track.leadAgent}`),t.track.branch&&n.push(t.track.branch),t.track.repos?.length&&n.push(t.track.repos.join(" + ")),t.pr?.number&&n.push(`PR #${t.pr.number} ${t.pr.change==="merged"?"merged":"open"}`),e&&n.push(xc(e.totalUsd)),n.join(" \xB7 ")||"linked"}async function mo(t,e){let n=await Tn(e);if(!n)return;let r=await Jp(e,t).catch(()=>null);await n.api.upsertCard(n.cfg,e,{url:Js(t),title:`Bostrat Track: ${e.track.name}`,subtitle:YS(e,r)})}function Zs(t){let e=new URL(zp());return`${e.protocol}//${t}.${e.host}/`}var ZS={linked:"linked",opened:"prOpened",merged:"prMerged"};function QS(t,e,n){let r=o=>e.filter(s=>s.type===o).sort((s,a)=>s.position-a.position);if(n==="linked"){if(["triage","backlog","unstarted"].includes(t?.type))return r("started")[0]??null}else if(n==="opened"){if(t?.type!=="completed")return r("started").find(o=>/review/i.test(o.name))??null}else if(n==="merged"&&t?.type!=="completed")return r("completed")[0]??null;return null}async function Xp(t,e,{from:n,to:r,mode:o,phase:s=null,actor:a="user"}){e.state=r,await Ge(),await O({id:t,cwd:e.track?.cwd},{type:"ticket.state.changed",actor:a,source:"orchestrator",payload:{ts:Date.now(),key:e.key,url:e.url,from:n??null,to:r,mode:o,...s?{phase:s}:{}}}).catch(()=>{})}async function Yp(t,e,n){let r=await Tn(e);if(!r)return;let o=r.cfg.ticketFlow?.[ZS[n]]??"prompt";if(o==="off")return;let{current:s,states:a}=await r.api.issueStates(r.cfg,e),i=vc(s,a),c=QS(i,a,n);if(!c||c.id===i?.id)return;let l=i?.name??e.state??null;if(o==="auto"){await r.api.moveIssue(r.cfg,e,c.id),await Xp(t,e,{from:l,to:c.name,mode:"auto",phase:n,actor:"system"});return}await O({id:t,cwd:e.track?.cwd},{type:"ticket.move.offered",actor:"system",source:"orchestrator",payload:{ts:Date.now(),offerId:qp(),provider:e.provider,key:e.key,url:e.url,phase:n,from:l,to:{id:c.id,name:c.name,type:c.type},...e.pr?.number?{prNumber:e.pr.number}:{}}}).catch(()=>{})}async function Zp(t){let e=await fe(t);if(!e)throw new Error("no ticket linked to this Track");let n=await Tn(e);if(!n)throw new Error("no ticketing integration configured");let{current:r,states:o}=await n.api.issueStates(n.cfg,e),s=vc(r,o);return s?.name&&e.state!==s.name&&(e.state=s.name,await Ge()),{key:e.key,url:e.url,current:s?{id:s.id,name:s.name??null,type:s.type??null}:null,states:[...o].sort((a,i)=>a.position-i.position).map(({id:a,name:i,type:c,position:l})=>({id:a,name:i,type:c,position:l}))}}async function Ac(t,e,{mode:n="manual"}={}){if(!e)throw new Error("stateId required");let r=await fe(t);if(!r)throw new Error("no ticket linked to this Track");let o=await Tn(r);if(!o)throw new Error("no ticketing integration configured");let{current:s,states:a}=await o.api.issueStates(o.cfg,r),i=vc(s,a),c=a.find(u=>u.id===e);if(!c)throw new Error("unknown state for this ticket's team");let l=i?.name??r.state??null;return i?.id===c.id?(r.state!==c.name&&(r.state=c.name,await Ge()),{key:r.key,from:l,to:c.name,moved:!1}):(await o.api.moveIssue(o.cfg,r,c.id),await Xp(t,r,{from:l,to:c.name,mode:n}),{key:r.key,from:l,to:c.name,moved:!0})}async function Qp(t,e){let n=await fe(t);if(!n)throw new Error("no ticket linked to this Track");let r=await Tn(n);if(!r)throw new Error("no ticketing integration configured");let o=String(e||"").trim();if(!o)throw new Error("comment body required");let s=await r.api.createComment(r.cfg,n,o);return await O({id:t,cwd:n.track?.cwd},{type:"ticket.commented",actor:"user",source:"orchestrator",payload:{ts:Date.now(),key:n.key,url:s??n.url,excerpt:o.length>280?`${o.slice(0,279)}\u2026`:o}}).catch(()=>{}),{key:n.key,url:s??n.url}}async function tm(t,e,n){if(!e)throw new Error("offerId required");let r=await L({id:t.id,cwd:t.cwd}),o=r.find(a=>a.type==="ticket.move.offered"&&a.payload?.offerId===e);if(!o)throw new Error("unknown offer");if(r.some(a=>a.type==="ticket.move.answered"&&a.payload?.offerId===e))throw new Error("offer already answered");let s=null;return n&&(s=await Ac(t.id,o.payload.to?.id,{mode:"offer"})),await O({id:t.id,cwd:t.cwd},{type:"ticket.move.answered",actor:"user",source:"orchestrator",payload:{ts:Date.now(),offerId:e,action:n?"moved":"dismiss",...s?{from:s.from,to:s.to}:{}}}),{offerId:e,action:n?"moved":"dismiss",...s??{}}}var Lp=50;async function em(){let t=await xn();if(!t)throw new Error("no ticketing integration configured");return ir(t)}function tv(t){let e=t&&typeof t=="object"?t:{},n=(Array.isArray(e.issues)?e.issues:[]).map(o=>o&&typeof o=="object"?o:{title:String(o||"")}).map(o=>({title:String(o.title||"").trim(),description:o.description?String(o.description):null,priority:Number.isInteger(o.priority)&&o.priority>=0&&o.priority<=4?o.priority:null})).filter(o=>o.title),r=e.epic&&String(e.epic.name||"").trim()?{name:String(e.epic.name).trim(),description:e.epic.description?String(e.epic.description):null}:null;if(!n.length&&!r)throw new Error("a plan needs an epic or at least one issue");if(n.length>Lp)throw new Error(`too many issues (${n.length}); cap is ${Lp} per proposal`);return{epic:r,issues:n,team:e.team?String(e.team).trim():null,linkCurrentTrack:e.linkCurrentTrack===!0}}async function ev(t,e,n,r){let o=r??await ir(t),s=i=>o.find(c=>c.id===i||c.key.toLowerCase()===String(i).toLowerCase()||c.name.toLowerCase()===String(i).toLowerCase())??null;if(e.team){let i=s(e.team);if(i)return i}let a=await fe(n.id);if(a?.key){let i=a.key.split("-")[0],c=o.find(l=>l.key===i);if(c)return c}if(t.defaultTeam){let i=s(t.defaultTeam);if(i)return i}return o.length===1?o[0]:null}async function nm(t,e){let n=await xn();if(!n)throw new Error("no ticketing integration configured \u2014 add a linear apiKey to ~/.bostrat/integrations.json");if(n.ticketFlow?.create==="off")throw new Error("ticket creation offers are off (bostrat-ticket flow set create=prompt to re-enable)");let r=tv(e),o=await ir(n),s=await ev(n,r,t,o),a=s?{id:s.id,key:s.key,name:s.name}:null,i=qp();return await O({id:t.id,cwd:t.cwd},{type:"ticket.create.offered",actor:"system",source:"orchestrator",payload:{ts:Date.now(),proposalId:i,provider:"linear",epic:r.epic,issues:r.issues,team:a,needsTeam:!s,linkCurrentTrack:r.linkCurrentTrack}}),{proposalId:i,epic:r.epic,issues:r.issues,team:a,needsTeam:!s}}async function rm(t,e,n,{teamId:r=null}={}){if(!e)throw new Error("proposalId required");let o=await L({id:t.id,cwd:t.cwd}),s=o.find(h=>h.type==="ticket.create.offered"&&h.payload?.proposalId===e);if(!s)throw new Error("unknown proposal");if(o.some(h=>h.type==="ticket.create.answered"&&h.payload?.proposalId===e))throw new Error("proposal already answered");if(!n)return await O({id:t.id,cwd:t.cwd},{type:"ticket.create.answered",actor:"user",source:"orchestrator",payload:{ts:Date.now(),proposalId:e,action:"dismiss"}}),{proposalId:e,action:"dismiss"};let a=await xn();if(!a)throw new Error("no ticketing integration configured");let i=s.payload,c=i.team??null;if(!c&&r){let h=(await ir(a)).find(w=>w.id===r);if(!h)throw new Error("unknown team");c={id:h.id,key:h.key,name:h.name}}if(!c)throw new Error("a team is required to create \u2014 pick one");let l=[],u=[],f=null;if(i.epic){let h=await mc(a,{teamId:c.id,name:i.epic.name,description:i.epic.description});f=h.id,l.push({kind:"epic",title:h.name,url:h.url})}for(let h of i.issues||[])try{let w=await hc(a,{teamId:c.id,title:h.title,description:h.description,projectId:f,priority:h.priority});l.push({kind:"issue",key:w.key,title:w.title,url:w.url})}catch(w){u.push({title:h.title,message:w?.message||String(w)})}await O({id:t.id,cwd:t.cwd},{type:"ticket.created",actor:"user",source:"orchestrator",payload:{ts:Date.now(),proposalId:e,team:{key:c.key,name:c.name},items:l,failed:u}}),await O({id:t.id,cwd:t.cwd},{type:"ticket.create.answered",actor:"user",source:"orchestrator",payload:{ts:Date.now(),proposalId:e,action:"created",createdCount:l.length,failedCount:u.length}});let d=l.filter(h=>h.kind==="issue"),m=null;return i.linkCurrentTrack&&d.length===1&&!await fe(t.id)&&(m=await cr(t,d[0].key,{source:"created"}).catch(()=>null)),{proposalId:e,action:"created",created:l,failed:u,linked:m?m.key:null}}async function fo(t,e){let n=await Tn(t);n&&await n.api.createComment(n.cfg,t,e)}async function nv(t,e,n){let r=await Tn(t);if(!r)return null;let o=await He(e);if(!o)return null;let s=yn(o,n);if(!s)return null;let a=await po.readFile(qt(o,s.v).png).catch(()=>null);return a?r.api.uploadImage(r.cfg,{issue:t.key,filename:`${o.name.replace(/[^\w.-]+/g,"-")}.v${s.v}.png`,contentType:"image/png",buffer:a}):null}var Fp=()=>Number(process.env.BOSTRAT_TICKET_COST_SYNC_MS??12e4),bc=new Map;function rv(t,e){if(Fp()<=0)return mo(t,e);if(bc.has(t))return;let n=setTimeout(()=>{bc.delete(t),fe(t).then(r=>r?mo(t,r):null).catch(()=>{})},Fp());n.unref?.(),bc.set(t,n)}var Up=new Map;async function Hp(t,e,n){let r=Up.get(t)??0;Date.now()-r<600*1e3||(Up.set(t,Date.now()),await O({id:t,cwd:e},{type:"ticket.sync.failed",actor:"system",source:"orchestrator",payload:{ts:Date.now(),message:n?.message||String(n)}}).catch(()=>{}))}async function ov(t,{resolveTrack:e=null}={}){let n=await fe(t.trackId),r=t.payload||{};if(!n){if(t.type!=="turn.user"||t.actor!=="user")return;let o=null;for(let a of await Sc())if(o=a.api.findIssueUrl(r.text,a.cfg),o)break;if(!o)return;let s=e?await e(t.trackId):null;if(!s)return;try{await cr(s,o,{source:"chat"})}catch(a){await Hp(t.trackId,s.cwd,a)}return}if(!n.track.cwd&&e){let o=await e(t.trackId).catch(()=>null);o?.cwd&&(n.track.cwd=o.cwd,await Ge())}try{switch(t.type){case"handoff.done":n.track.leadAgent=r.toAgent??n.track.leadAgent,await Ge(),await mo(t.trackId,n);break;case"pr.state.changed":{if(!r.change)break;if(r.number&&(n.pr={number:r.number,url:r.url??n.pr?.url??null,change:r.change}),await Ge(),await mo(t.trackId,n),r.change==="opened"||r.change==="merged"){let o=r.change==="merged"?await Jp(n,t.trackId).catch(()=>null):null;await fo(n,`**Bostrat** \u2014 PR ${r.change} for **${n.track.name}**${r.url?`: ${r.url}`:r.number?` (#${r.number})`:""}`+(o?`
|
|
82
|
+
|
|
83
|
+
Agent cost for this Track: ${XS(o)}.`:"")),await Yp(t.trackId,n,r.change)}break}case"mock.state.changed":{if(r.change==="ready"&&r.mockId){let o=await nv(n,r.mockId,r.version??null);if(!o)break;let s=(r.version??1)>1?` revised (v${r.version})`:"";await fo(n,`**Bostrat** \u2014 mock **${r.name}**${s}${r.group?` (group \`${r.group}\`)`:""}:
|
|
84
|
+
|
|
85
|
+
`)}else if(r.change==="picked"){let o=(r.siblings??[]).map(s=>`**${s.name}**`).join(", ");await fo(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 fo(n,`**Bostrat** \u2014 live preview **${r.name}**: ${Zs(r.slug)}
|
|
86
|
+
_(runs on the Track's branch; reachable on the operator's private network only)_`);break}case"cost.recorded":await rv(t.trackId,n);break;default:break}}catch(o){let s=e?await e(t.trackId).catch(()=>null):null;await Hp(t.trackId,s?.cwd??null,o)}}function om({resolveTrack:t}){Wt.on("event",e=>{ov(e,{resolveTrack:t}).catch(()=>{})})}import Qs from"node:fs/promises";import sm from"node:path";import sv from"node:os";var am=process.env.BOSTRAT_STATE_DIR||sm.join(process.env.HOME||sv.tmpdir(),".bostrat"),Ec=sm.join(am,"pr-links.json"),dt=(t,e)=>`${t}#${Number(e)}`,ta=null,_c=null,D=Promise.resolve();async function kt(){return ta||(_c??=Qs.readFile(Ec,"utf8").then(t=>JSON.parse(t)?.links??{}).catch(()=>({})).then(t=>ta=t).finally(()=>_c=null),_c)}async function Z(){let t=JSON.stringify({links:ta},null,2),e=`${Ec}.${process.pid}.tmp`;await Qs.mkdir(am,{recursive:!0}),await Qs.writeFile(e,t),await Qs.rename(e,Ec)}var av=new Set(["api","observed","reconciled"]);async function Lt({repo:t,number:e,trackId:n,url:r=null,headRef:o=null,title:s=null,source:a="matched"}){if(!t||!Number.isInteger(Number(e))||!n)return{link:null,created:!1};let i=await kt(),c=dt(t,e);return i[c]?{link:i[c],created:!1}:(i[c]={repo:t,number:Number(e),trackId:n,url:r,headRef:o,title:s,source:a,announced:av.has(a),firstSeen:Date.now()},D=D.then(Z,Z),await D.catch(()=>{}),{link:i[c],created:!0})}async function ho(t,e){if(!t||e==null)return null;let n=(await kt())[dt(t,e)];return!n||n.announced?null:(n.announced=!0,D=D.then(Z,Z),await D.catch(()=>{}),n)}async function Et(t,e){return!t||e==null?null:(await kt())[dt(t,e)]??null}async function ze(t,e,n){if(!t||e==null)return null;let r=(await kt())[dt(t,e)];return r?(r.state=n,r.resolvedAt=Date.now(),delete r.armedAt,delete r.autoMerge,delete r.archiveOnMerge,delete r.notifyOnMerge,delete r.notifyPending,D=D.then(Z,Z),await D.catch(()=>{}),r):null}async function im(t,e,{headSha:n=null,trackId:r=null}={}){if(!t||e==null)return null;let o=(await kt())[dt(t,e)];return o?(o.ciFix={delegatedAt:Date.now(),headSha:n,trackId:r},D=D.then(Z,Z),await D.catch(()=>{}),o):null}async function cm(t,e,{headSha:n=null,trackId:r=null}={}){if(!t||e==null)return null;let o=(await kt())[dt(t,e)];return o?(o.reconcile={delegatedAt:Date.now(),headSha:n,trackId:r},D=D.then(Z,Z),await D.catch(()=>{}),o):null}async function ea(t,e,{headSha:n=null}={}){if(!t||e==null)return null;let r=(await kt())[dt(t,e)];return!r||!r.ciFix?null:(r.ciFixAwait={since:Date.now(),trackId:r.ciFix.trackId??null,headSha:n},delete r.ciFix,D=D.then(Z,Z),await D.catch(()=>{}),r)}async function na(t,e){if(!t||e==null)return null;let n=(await kt())[dt(t,e)];return!n||!n.ciFixAwait?null:(delete n.ciFixAwait,D=D.then(Z,Z),await D.catch(()=>{}),n)}async function ra(t,e,{headSha:n=null}={}){if(!t||e==null)return null;let r=(await kt())[dt(t,e)];return!r||!r.reconcile?null:(r.reconcileAwait={since:Date.now(),trackId:r.reconcile.trackId??null,headSha:n},delete r.reconcile,D=D.then(Z,Z),await D.catch(()=>{}),r)}async function oa(t,e){if(!t||e==null)return null;let n=(await kt())[dt(t,e)];return!n||!n.reconcileAwait?null:(delete n.reconcileAwait,D=D.then(Z,Z),await D.catch(()=>{}),n)}async function lm(t,e,{at:n=Date.now()}={}){if(!t||e==null)return null;let r=(await kt())[dt(t,e)];return r?(r.baseFoldAt=n,D=D.then(Z,Z),await D.catch(()=>{}),r):null}async function um(t,e){if(!t||e==null)return null;let n=(await kt())[dt(t,e)];return!n||!n.baseFoldAt?null:(delete n.baseFoldAt,D=D.then(Z,Z),await D.catch(()=>{}),n)}async function dm(t,e,n){if(!t||e==null||!n)return null;let r=(await kt())[dt(t,e)];return r?(r.lastHeadSha=n,D=D.then(Z,Z),await D.catch(()=>{}),r):null}async function wo(t,e,{merge:n=null}={}){if(!t||e==null)return null;let r=(await kt())[dt(t,e)];return r?(r.armedAt=Date.now(),n&&(r.autoMerge={method:n.method||"merge",armedAt:r.armedAt}),D=D.then(Z,Z),await D.catch(()=>{}),r):null}async function lr(t,e,n=!0){if(!t||e==null)return null;let r=(await kt())[dt(t,e)];return r?(n?r.archiveOnMerge=!0:delete r.archiveOnMerge,D=D.then(Z,Z),await D.catch(()=>{}),r):null}async function sa(t,e,n=!0){if(!t||e==null)return null;let r=(await kt())[dt(t,e)];return r?(n?r.notifyOnMerge=!0:delete r.notifyOnMerge,D=D.then(Z,Z),await D.catch(()=>{}),r):null}async function fm(t,e,n){if(!t||e==null)return null;let r=(await kt())[dt(t,e)];if(!r)return null;r.notifyOnMerge=!0;let o=new Map((r.notifyPending||[]).map(s=>[`${s.repo}#${s.number}`,s]));for(let s of n||[])s&&s.number!=null&&o.set(`${s.repo}#${s.number}`,s);return r.notifyPending=[...o.values()],D=D.then(Z,Z),await D.catch(()=>{}),r}async function go(t,e){if(!t||e==null)return null;let n=(await kt())[dt(t,e)];return!n||!n.armedAt&&!n.autoMerge?null:(delete n.armedAt,delete n.autoMerge,delete n.archiveOnMerge,delete n.notifyOnMerge,delete n.notifyPending,D=D.then(Z,Z),await D.catch(()=>{}),n)}var iv=new Set(["merged","closed","lost"]);async function pm(){return Object.values(await kt()).filter(t=>!iv.has(t.state))}async function pe(){return Object.values(await kt()).sort((t,e)=>e.firstSeen-t.firstSeen)}var hm=t=>Math.round(t*100)/100;function cv(t){return!t||t.kind==="setup"?[]:Nt(t.workspace).filter(e=>e&&(e.repo||e.base)).map(e=>({key:e.repo||e.base,slug:e.repo??null,base:e.base??null,name:e.repo?e.repo.split("/").pop():mm.basename(e.base)}))}function wm(t){let e=new Map;for(let n of t||[])for(let r of cv(n)){let o=e.get(r.key);o||e.set(r.key,o={...r,tracks:[]}),o.tracks.push(n)}return e}function aa(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 Ic=(t,e)=>`${t} ${e}${t===1?"":"s"}`;function lv(t){if(!Number.isFinite(t)||t<=0)return null;let e=t/864e5;if(e>=1)return`${Math.round(e)}d`;let n=t/36e5;return n>=1?`${Math.round(n)}h`:`${Math.max(1,Math.round(t/6e4))}m`}function Rc(t,e=[]){let n=[],r=t?.events||[],o=new Map,s=0;for(let u of e){if(u.type!=="pr.state.changed")continue;let f=u.payload||{};!f.repo||f.number==null||(f.change==="merged"?o.set(`${f.repo}#${f.number}`,f):f.change==="opened"&&s++)}if(o.size){let u=[...o.keys()].map(f=>{let[d,m]=f.split("#");return`${d.split("/").pop()}#${m}`});n.push(`Merged ${u.slice(0,4).join(", ")}${u.length>4?` +${u.length-4}`:""}`)}else s&&n.push(Ic(s,"PR")+" opened");let a=Object.keys(t?.subtasks||{}).length||r.filter(u=>u.type==="subtask-delegated").length;a&&n.push(Ic(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(`${Ic(i.length,"handoff")} ${u}${i.length>3?"\u2026":""}`)}let c=se(e);c.totalUsd>=.01&&n.push(`$${c.totalUsd.toFixed(2)}`);let l=Number.isFinite(t?.archivedAt)&&Number.isFinite(t?.createdAt)?lv(t.archivedAt-t.createdAt):null;return n.length&&l&&n.push(l),n.length?n.join(" \xB7 "):null}function uv(t){let e=[],n=[...t?.boxes||[]].sort((o,s)=>(o.createdAt||0)-(s.createdAt||0));for(let o of n){if(o.role!=="lead"&&o.role!=="handed-off")continue;let s=Array.isArray(o.epochs)&&o.epochs.length?o.epochs.map(a=>a.agent):[o.agent];for(let a of s)a&&e[e.length-1]!==a&&e.push(a)}let r=new Set(n.filter(o=>o.role==="subtask"&&o.agent&&!e.includes(o.agent)).map(o=>o.agent));return{lead:e,subtask:[...r]}}function gm(t,e,n){let r=new Set(t.tracks.map(d=>d.id)),o=0,s=0,a=0,i=null,c=null,l=0;for(let d of t.tracks){d.archivedAt?s++:o++,d.beat?.text&&a++;let m=aa(d);m&&(!i||m>i)&&(i=m),d.createdAt&&(!c||d.createdAt<c)&&(c=d.createdAt),l+=e.get(d.id)||0}let u=0,f=0;for(let d of n)r.has(d.trackId)&&(t.slug&&d.repo!==t.slug||t.slug&&(d.state==="merged"?f++:d.state!=="closed"&&u++));return{key:t.key,slug:t.slug,base:t.base,name:t.name,tracks:{total:t.tracks.length,live:o,archived:s,withBeat:a},prs:{open:u,merged:f},usd:hm(l),firstActivityAt:c,lastActivityAt:i}}async function ym(t){let e=wm(t),n=await pe().catch(()=>[]),r=new Map,o=new Set;for(let a of e.values())for(let i of a.tracks){if(o.has(i.id))continue;o.add(i.id);let c=await L(i).catch(()=>[]);r.set(i.id,se(c).totalUsd)}let s=[...e.values()].map(a=>gm(a,r,n)).sort((a,i)=>(i.lastActivityAt??0)-(a.lastActivityAt??0));return{generatedAt:Date.now(),arcs:s}}async function bm(t,e){let r=wm(t).get(e);if(!r)return null;let[o,s,a,i,c]=await Promise.all([pe().catch(()=>[]),Ds().catch(()=>[]),Hs().catch(()=>[]),Ws().catch(()=>[]),Ys().catch(()=>({}))]),l=new Map,u=[],f=(d,m)=>(aa(m)||m.createdAt||0)-(aa(d)||d.createdAt||0);for(let d of[...r.tracks].sort(f)){let m=await L(d).catch(()=>[]),h=se(m);l.set(d.id,h.totalUsd);let w=c[d.id]||null;u.push({trackId:d.id,name:d.name,createdAt:d.createdAt??null,archivedAt:d.archivedAt??null,live:!d.archivedAt,beat:d.beat??null,agents:uv(d),handoffs:(d.events||[]).filter(g=>g.type==="lead-handoff").length,subtasks:Object.keys(d.subtasks||{}).length,repos:Nt(d.workspace).map(g=>g.repo||mm.basename(g.base||"")),prs:o.filter(g=>g.trackId===d.id&&(!r.slug||g.repo===r.slug)).map(g=>({repo:g.repo,number:g.number,state:g.state??null,title:g.title??null,url:g.url??null})),mocks:s.filter(g=>g.trackId===d.id).length,views:a.filter(g=>g.trackId===d.id).length,runbooks:i.filter(g=>g.trackId===d.id).map(g=>({name:g.name,status:g.status})),ticket:w?{provider:w.provider,key:w.key,url:w.url??null}:null,usd:hm(h.totalUsd),lastActivityAt:aa(d)})}return{...gm(r,l,o),generatedAt:Date.now(),segments:u}}import{execFile as dv}from"node:child_process";import{promisify as fv}from"node:util";import{randomUUID as pv}from"node:crypto";var An=fv(dv),ia=t=>new Promise(e=>setTimeout(e,t)),mv=Number(process.env.BOSTRAT_SWAP_EXIT_TIMEOUT_MS)||15e3,km=Number(process.env.BOSTRAT_SWAP_SHELL_TIMEOUT_MS)||8e3,hv=250,wv=400,xm={claude:{escape:!0,wipe:!0,text:"/exit"},codex:{escape:!0,wipe:!0,text:"/quit"},cursor:{escape:!0,wipe:!0,text:"/quit"},default:{escape:!1,wipe:!1,text:"exit"}};async function vm(t){let e=await $d(t);if(!e)return!1;try{return await An("pgrep",["-P",String(e)]),!1}catch(n){return n?.code===1}}async function $c(t,e){let n=Date.now()+e;for(;;){if(await vm(t))return!0;if(Date.now()>n)return!1;await ia(hv)}}async function gv(t,e){let n=xm[e]||xm.default;n.escape&&(await xt(t,["Escape"],{kind:"swap-exit"}),await ia(150)),n.wipe&&await ys(t,400,{kind:"swap-exit"}),await bs(t,n.text,{perCharDelayMs:90,kind:"swap-exit"});let r=Se(e).submitDelayMs||0;await ia(Math.max(r,250)),await xt(t,["Enter"],{kind:"swap-exit"})}async function yv(t,e){try{let{stdout:n}=await An(N,B("show-environment","-t",t,e));return!String(n||"").trimStart().startsWith("-")}catch{return!1}}async function Sm(t){await An(N,B("respawn-pane","-k","-t",t))}async function Oc(t,e={}){if(!yt(t))throw new Error("invalid session name");let n=String(e.agentCmd||"").trim();if(!n)throw new Error("agentCmd is required");let r=await Y(t);if(!r)throw new Error(`session "${t}" not found`);if(!r.cwd)throw new Error(`can't resolve cwd for "${t}"`);let o=r.agent||"agent",s=e.agent||zn(n),a=Number(e.exitTimeoutMs)||mv;await G({flow:"swap",phase:"start",sessionName:t,from:o,to:s,cwd:r.cwd});let i=!1;if(!await vm(t)&&(await gv(t,o),!await $c(t,a)&&(i=!0,await G({flow:"swap",phase:"respawn-forced",sessionName:t,from:o}),await Sm(t),!await $c(t,km))))throw new Error(`pane shell did not come back after respawn-pane in "${t}"`);let c=s==="claude"&&Gn(n),l=c?is():null,u=await yv(t,"ANTHROPIC_API_KEY"),f=!1;try{l?(await An(N,B("set-environment","-t",t,"ANTHROPIC_API_KEY",l)),f=!0):u&&(await An(N,B("set-environment","-u","-t",t,"ANTHROPIC_API_KEY")),f=!0)}catch(g){throw l?hs(g,l):g}if(f&&!i&&(await Sm(t),!await $c(t,km)))throw new Error(`pane shell did not come back after env respawn in "${t}"`);let d=!!e.resumeSessionId,m=e.resumeSessionId||pv(),h=d?n:Ai(n,m),w=d?e.pinned===!0:/(^|\s)--session-id(\s|=)/.test(h);return c&&await Ne(r.cwd),s==="codex"&&await Un(r.cwd),s==="cursor"&&await Hn(r.cwd),await wn(t),await An(N,B("set-option","-t",t,"@bostrat-sid",m)),await An(N,B("set-option","-t",t,"@bostrat-agent",s)),await ia(wv),await vt(t,h,{kind:"swap-launch"}),await G({flow:"swap",phase:"done",sessionName:t,from:o,to:s,sid:m,forced:i,envRespawned:f}),{name:t,cwd:r.cwd,sessionId:m,agent:s,pinned:w,forced:i,envRespawned:f}}import Yv from"node:fs/promises";function bv(t){let e=[],n="",r=null;for(let o=0;o<t.length;o++){let s=t[o];if(r){n+=s,s===r&&t[o-1]!=="\\"&&(r=null);continue}if(s==="'"||s==='"'||s==="`"){r=s,n+=s;continue}if(s==="&"&&t[o+1]==="&"){e.push(n),n="",o++;continue}if(s==="|"&&t[o+1]==="|"){e.push(n),n="",o++;continue}if(s==="|"||s===";"||s===`
|
|
87
|
+
`){e.push(n),n="";continue}n+=s}return e.push(n),e.map(o=>o.trim()).filter(Boolean)}function yo(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 bo(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 dr=(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,ur=t=>{let e=t.replace(/\/+$/,"").split("/").filter(Boolean);return e.length>3?e.slice(-2).join("/"):t},_m=t=>t.startsWith("-"),Tm=new Set(["-g","--glob","-t","--type","-A","-B","-C","-m","--max-count","-e","--regexp","-f","--file"]);function st(t,e=new Set){let n=[];for(let r=1;r<t.length;r++){let o=t[r];if(o==="<<")break;if(_m(o)){e.has(o)&&r++;continue}n.push(o)}return n}var kv={status:()=>"Check git status",diff:()=>"Review changes",log:()=>"Review commit history",show:()=>"Inspect a commit",add:()=>"Stage files",commit:()=>"Commit changes",push:()=>"Push to remote",pull:()=>"Pull from remote",fetch:()=>"Fetch from remote",merge:()=>"Merge branches",rebase:()=>"Rebase branch",stash:()=>"Stash changes",clone:t=>t[1]?`Clone ${mt(t[1])}`:"Clone repository",checkout:()=>"Switch branches",switch:()=>"Switch branches",restore:()=>"Restore files",reset:()=>"Reset git state",branch:(t,e)=>e.includes("--show-current")?"Check current branch":"List branches",remote:()=>"Check git remotes","rev-parse":()=>"Resolve a git ref",worktree:()=>"Manage git worktrees","merge-base":()=>"Find the merge base","ls-files":()=>"List tracked files"},xv={pr:t=>({create:"Create a pull request",list:"List pull requests",view:"View a pull request",merge:"Merge a pull request",checks:"Check PR status"})[t[1]]||"Run a gh pr command",auth:()=>"Authenticate GitHub CLI",api:()=>"Call the GitHub API",repo:()=>"Inspect the GitHub repo",issue:()=>"Work with GitHub issues"};function Em(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 Sv(t){let e=Em(t),n=_t(st(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 Mc(t){if(t.includes("--files")){let s=_t(st(t,Tm)[0]);return s&&s!=="."?`List files in ${ur(s)}`:"List files"}let e=st(t,Tm),n=e[0];if(!n)return null;let r=_t(e.slice(1).find(s=>s!==".")),o=dr(n,40);return r?`Search for \u201C${o}\u201D in ${ur(r)}`:`Search for \u201C${o}\u201D`}function Cc(t){let e=st(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 Nc(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=st(t)[0];return n?`Run ${mt(n)}`:null}function vv(t){let e=st(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 Am(t){return t.find(e=>/^https?:\/\//.test(e))||null}var Tv={git:t=>{let e=st(t,new Set(["-C","-c"])),n=kv[e[0]];return n?n(e,t):null},gh:t=>{let e=st(t),n=xv[e[0]];return n?n(e):null},sed:Sv,rg:Mc,grep:Mc,egrep:Mc,nl:t=>{let e=_t(st(t)[0]);return e?`Read ${mt(e)}`:null},cat:t=>{let e=_t(st(t)[0]);return e?`Read ${mt(e)}`:null},head:t=>{let e=_t(st(t,new Set(["-n","-c"])).find(n=>!/^\d+$/.test(n)));return e?`Read the start of ${mt(e)}`:null},tail:t=>{let e=_t(st(t,new Set(["-n","-c"])).find(n=>!/^\d+$/.test(n)));return e?`Read the end of ${mt(e)}`:null},wc:t=>{let e=_t(st(t)[0]);return e?`Count lines in ${mt(e)}`:"Count input lines"},ls:t=>{let e=_t(st(t)[0]);return e&&e!=="."?`List files in ${ur(e)}`:"List files"},find:t=>_t(t[1])&&!_m(t[1])&&t[1]!=="."?`Find files in ${ur(t[1])}`:"Find files",tree:()=>"List the directory tree",mkdir:t=>{let e=_t(st(t)[0]);return e?`Create directory ${ur(e)}`:null},touch:t=>{let e=_t(st(t)[0]);return e?`Create ${mt(e)}`:null},rm:t=>{let e=_t(st(t)[0]);return e?`Delete ${ur(e)}`:null},mv:t=>{let e=_t(st(t)[0]);return e?`Move ${mt(e)}`:null},cp:t=>{let e=_t(st(t)[0]);return e?`Copy ${mt(e)}`:null},chmod:t=>{let e=_t(st(t)[1]);return e?`Change permissions on ${mt(e)}`:null},diff:t=>{let e=st(t);return e.length>=2?`Compare ${mt(e[0])} and ${mt(e[1])}`:null},curl:t=>{let e=Am(t);return e?`Fetch ${dr(e,56)}`:"Fetch a URL"},wget:t=>{let e=Am(t);return e?`Fetch ${dr(e,56)}`:"Fetch a URL"},npm:Cc,pnpm:Cc,yarn:Cc,npx:t=>{let e=st(t)[0];return e?`Run ${e}`:null},node:t=>Nc(t,"Node"),python:t=>Nc(t,"Python"),python3:t=>Nc(t,"Python"),make:t=>{let e=st(t)[0];return e?`Run make ${e}`:"Run make"},systemctl:vv,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=st(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=st(t)[0];return e?`Locate ${e}`:null},command:t=>t[1]==="-v"&&t[2]?`Locate ${t[2]}`:null,fly:t=>{let e=st(t)[0];return e==="deploy"?"Deploy to Fly":e?`Run fly ${e}`:null}},Av=new Set(["pwd","echo","cd","sleep","true","false","test","[","set","export","exit","read"]);function _v(t){let e=bo(yo(t));if(!e.length)return null;if(/^(ba|z)?sh$/.test(mt(e[0]))&&(e[1]==="-lc"||e[1]==="-c")&&e[2])return Pc(e[2]);let n=mt(e[0]);if(Av.has(n))return null;if(e.includes("--version"))return`Check ${n} version`;if(n.endsWith(".sh"))return`Run ${n}`;let r=Tv[n];return r?r(e.map((o,s)=>s===0?n:o)):null}function Pc(t){let e=String(t||"").trim();if(!e)return null;let n=e.indexOf("<<");if(n!==-1){let i=e.indexOf(`
|
|
88
|
+
`,n);i!==-1&&(e=e.slice(0,i))}let r=bv(e);for(let i=0;i<r.length-1;i++){let c=bo(yo(r[i])),l=bo(yo(r[i+1]));if(!(!c.length||!l.length)&&["nl","cat"].includes(mt(c[0]))&&mt(l[0])==="sed"){let u=Em(l),f=st(c)[0];if(u&&f)return dr(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=_v(i);c&&o.push({d:c,head:mt(bo(yo(i))[0]||""),sub:st(bo(yo(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":dr(s?s.d:o[0].d,88)}var Dc=new Set(["Bash"]);function fr(t){let e=t.split(`
|
|
89
|
+
`).map(n=>n.trim()).find(Boolean)||t;return e.length>88?`${e.slice(0,88).trimEnd()}\u2026`:e}function Ve(t,e=""){let n=e.trim();return n||Pc(t)||fr(t)}function Bc(t,e){if(t){if(Array.isArray(t)){for(let n of t)Bc(n,e);return}if(typeof t=="object"){if(t.type==="tool_use"&&Dc.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))Bc(n,e)}}}async function Im(t,{maxBytes:e=1048576,limit:n=400}={}){let r;try{r=await Ot(t,e)}catch{return[]}let o=[];for(let s of r.split(`
|
|
90
|
+
`)){let a=s.trim();if(!a||a[0]!=="{")continue;let i;try{i=JSON.parse(a)}catch{continue}let c=i.message;if(!c)continue;let l=[];if(Bc(c.content,l),!l.length)continue;let u=i.timestamp?Date.parse(i.timestamp):null;for(let f of l)o.push({...f,summary:Ve(f.command,f.description),ts:u})}return o.slice(-n)}function Rm(t,e){let n=e.filter(l=>l.role==="assistant"&&(l.text||"").trim()).sort((l,u)=>(l.ts||0)-(u.ts||0)),r=[...t].sort((l,u)=>(l.ts||0)-(u.ts||0)),o=[],s=0,a=0,i=l=>{let u=l.n??a+1;return u>a&&(a=u),u};for(let l of n){let u=r.findIndex((h,w)=>w>=s&&(h.ts||0)>(l.ts||0)),f=u===-1?r.length:u,d=r.slice(s,f);if(s=f,!d.length)continue;let m=d.map(h=>({...h,n:i(h)}));o.push({id:`batch-${l.ts}-${l.box}`,anchorTs:l.ts,anchorBox:l.box,agent:l.agent,boxRole:l.boxRole,commands:m,ts:Math.max(...m.map(h=>h.ts||0)),pending:!1})}let c=r.slice(s);if(c.length){let l=c.map(u=>({...u,n:i(u)}));o.push({id:`batch-pending-${l[0]?.box||"lead"}`,anchorTs:null,anchorBox:l[l.length-1]?.box??null,agent:l[l.length-1]?.agent??"claude",boxRole:l[l.length-1]?.boxRole??"lead",commands:l,ts:Math.max(...l.map(u=>u.ts||0)),pending:!0})}return o}var Ev=/^Async (?:agent|task) launched/i,Iv=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 $m(t,{maxBytes:e=2097152,limit:n=100}={}){let r;try{r=await Ot(t,e)}catch{return[]}let o=new Map,s=new Set;for(let i of r.split(`
|
|
91
|
+
`)){let c=i.trim();if(!c||c[0]!=="{")continue;let l;try{l=JSON.parse(c)}catch{continue}if(l.isSidechain)continue;let u=l.message?.content;if(!Array.isArray(u))continue;let f=l.timestamp?Date.parse(l.timestamp):null;for(let d of u)if(!(!d||typeof d!="object"))if(d.type==="tool_use"&&d.id){if(d.name==="Agent"){let m=d.input||{};o.set(d.id,{id:d.id,kind:"agent",name:String(m.description||"").trim()||null,detail:String(m.subagent_type||"").trim()||null,ts:f,proven:!1})}else if(d.name==="Bash"&&d.input?.run_in_background===!0){let m=String(d.input.command||"").trim();o.set(d.id,{id:d.id,kind:"command",name:String(d.input.description||"").trim()||(m?fr(m):null),detail:m?fr(m):null,ts:f,proven:!0})}}else d.type==="tool_result"&&d.tool_use_id&&Ev.test(Iv(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 ko from"node:fs";import $v from"node:fs/promises";import _n from"node:path";import Ov from"node:os";import Mv from"node:crypto";import Cv from"node:zlib";var ca=new Set(["Shell","Read","Grep","Glob","StrReplace","Write","ReadLints","Delete","ApplyPatch","Await"]),Xt=(t,e=88)=>t.length>e?`${t.slice(0,e).trimEnd()}\u2026`:t,pr=t=>String(t).replace(/\/+$/,"").split("/").pop()||String(t);function Je(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 jc=t=>`"${String(t).replace(/"/g,'\\"')}"`;function Rv(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 Om(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:Xt(`read ${Je(r,n)}${o?` (lines ${o}\u2013${s??"end"})`:""}`,140),description:o?`Read lines ${o}\u2013${s??"end"} of ${pr(r)}`:`Read ${pr(r)}`}}case"Grep":{let r=e?.pattern;if(!r)return null;let o=e?.path?Je(e.path,n):null,s=e?.glob?` --glob ${jc(e.glob)}`:"",a=Xt(String(r),40);return{command:Xt(`grep ${jc(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?Je(e.target_directory,n):null;return{command:Xt(`glob ${jc(r)}${o&&o!=="."?` ${o}`:""}`,140),description:`Find files matching \u201C${Xt(String(r),40)}\u201D`}}case"StrReplace":return e?.path?{command:Xt(`edit ${Je(e.path,n)}`,140),description:`Edit ${pr(e.path)}`}:null;case"Write":return e?.path?{command:Xt(`write ${Je(e.path,n)}`,140),description:`Write ${pr(e.path)}`}:null;case"Delete":return e?.path?{command:Xt(`delete ${Je(e.path,n)}`,140),description:`Delete ${pr(e.path)}`}:null;case"ReadLints":{let o=(Array.isArray(e?.paths)?e.paths:[]).map(s=>Je(s,n)).filter(Boolean);return{command:Xt(o.length?`lints ${o.join(" ")}`:"lints",140),description:"Check lints"}}case"ApplyPatch":{let r=Rv(e);if(!r.length)return null;let o=r.length>1?` (+${r.length-1} more)`:"";return{command:Xt(`apply_patch ${Je(r[0],n)}${o}`,140),description:r.length>1?`Patch ${r.length} files`:`Patch ${pr(r[0])}`}}case"Await":{let r=e?.shell_id??e?.task_id;return{command:Xt(`await ${r??"background output"}`,140),description:"Wait for a background command"}}default:return null}}var Nv=process.env.BOSTRAT_CURSOR_CHATS||_n.join(Ov.homedir(),".cursor","chats");function Nm(t){let e=Mv.createHash("md5").update(String(t)).digest("hex");return _n.join(Nv,e)}function Pm(t){try{let e=JSON.parse(ko.readFileSync(_n.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 Pv(t){let e=Nm(t),n;try{n=ko.readdirSync(e,{withFileTypes:!0}).filter(o=>o.isDirectory())}catch{return null}let r=null;for(let o of n){let s=_n.join(e,o.name),a=_n.join(s,"store.db");if(!ko.existsSync(a))continue;let i=Pm(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 la(t,e){if(!t)return null;if(e){let n=_n.join(Nm(t),e),r=_n.join(n,"store.db");if(ko.existsSync(r))return{id:e,dir:n,store:r,...Pm(n)||{createdAtMs:null,updatedAtMs:null}}}return Pv(t)}function Mm(t,e){let n=t.prepare("SELECT data FROM blobs WHERE id = ?").get(e);return n?Buffer.from(n.data):null}function Bv(t){if(!t)return null;let e=t;if(e.length>1&&e[0]===31&&e[1]===139)try{e=Cv.gunzipSync(e)}catch{}try{return JSON.parse(e.toString("utf8"))}catch{return null}}function Dv(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 jv(t,e){if(!Array.isArray(t))return[];let n=[];for(let r of t){if(!r||r.type!=="tool-call"||!ca.has(r.toolName))continue;let o=Om(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 Lv=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(`
|
|
92
|
+
`).trim():"",Fv=t=>Array.isArray(t)?t.filter(e=>e&&e.type==="tool-call"&&e.toolName).map(e=>e.toolName):[],Cm=process.getBuiltinModule?.("node:sqlite"),mr={turns:[],trailingCommands:[],model:null,requestIds:[]};async function Lc(t,e){if(!Cm)return mr;let{DatabaseSync:n}=Cm,r;try{r=new n(t,{readOnly:!0})}catch{return mr}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 mr;let s;try{s=JSON.parse(Buffer.from(String(o.value),"hex").toString("utf8"))}catch{return mr}let a=s.lastUsedModel||null,i=s.latestRootBlobId?Mm(r,s.latestRootBlobId):null;if(!i)return{...mr,model:a};let c=[],l=[],u=[],f=null,d=[];for(let m of Dv(i)){let h=Bv(Mm(r,m));if(!h)continue;let w=h.providerOptions?.cursor;if(w?.requestId&&d.push(w.requestId),w?.modelProviderMessageId&&d.push(w.modelProviderMessageId),h.role!=="assistant")continue;if(Array.isArray(h.content))for(let v of h.content){let A=v?.providerOptions?.cursor?.modelName;A&&(f=A)}l.push(...jv(h.content,e).map((v,A)=>({...v,id:v.id||`${m}:${A}`}))),u.push(...Fv(h.content));let g=Lv(h.content);if(!g)continue;let b=[...new Set(u)];u=[],c.push({id:m,text:g,tools:b.length?b:void 0,commands:l,model:f}),l=[]}return{turns:c,trailingCommands:l,model:a,requestIds:d}}catch{return mr}finally{try{r.close()}catch{}}}function Bm(t,e){let n=t.updatedAtMs;if(!n)try{n=Math.round(ko.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 Dm(t,e,{limit:n=80}={}){let r=la(t,e);if(!r)return[];let{turns:o,model:s}=await Lc(r.store,t);if(!o.length)return[];let a=o.slice(-n),{tsAt:i}=Bm(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 jm(t,e){let n=la(t,e);if(!n)return null;let{turns:r,requestIds:o,model:s}=await Lc(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 Lm(t,e,{limit:n=80}={}){let r=la(t,e);if(!r)return[];let{turns:o,trailingCommands:s}=await Lc(r.store,t);if(!o.length&&!s.length)return[];let a=o.slice(-n),{end:i,tsAt:c}=Bm(r,a.length||1),l=[];a.forEach((u,f)=>{for(let d of u.commands)l.push({...d,summary:Ve(d.command,d.description),ts:c(f)})});for(let u of s)l.push({...u,summary:Ve(u.command,u.description),ts:i+1});return l}async function Fm(t,e){let n=la(t,e);if(!n)return null;if(n.updatedAtMs)return n.updatedAtMs;try{return Math.round((await $v.stat(n.store)).mtimeMs)}catch{return null}}import Um from"node:fs";import Uc from"node:fs/promises";import Fc from"node:path";import Uv from"node:os";var Hv=process.env.BOSTRAT_CODEX_SESSIONS||Fc.join(process.env.CODEX_HOME||Fc.join(Uv.homedir(),".codex"),"sessions");async function Hm(t){try{return await Uc.realpath(t)}catch{return t}}function Wv(t=Hv){let e=[],n=r=>{let o;try{o=Um.readdirSync(r,{withFileTypes:!0})}catch{return}for(let s of o){let a=Fc.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:Um.statSync(a).mtimeMs})}catch{}}}};return n(t),e.sort((r,o)=>o.mtimeMs-r.mtimeMs)}async function qv(t,e=1048576){let n;try{n=await Uc.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(`
|
|
93
|
+
`);if(i!==-1)return o.slice(0,i);if(s+=a,o.length>=e)break}return o}finally{await n.close()}}async function Kv(t){let e=await qv(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 En(t,e){if(!t&&!e)return null;let n=Wv();if(e){let o=n.find(s=>s.id===e);return o||null}let r=await Hm(t);for(let o of n){let s=await Kv(o.file);if(s&&(s===t||await Hm(s)===r))return o}return null}var Gv=new Set(["output_text","text"]),zv=t=>typeof t=="string"?t.trim():Array.isArray(t)?t.filter(e=>e&&typeof e.text=="string"&&Gv.has(e.type)).map(e=>e.text).join(`
|
|
94
|
+
`).trim():"";async function Wm(t,e,{limit:n=80,maxBytes:r=524288}={}){let o=await En(t,e);if(!o)return[];let s;try{s=await Ot(o.file,r)}catch{return[]}let a=[],i=null,c=[],l=!1,u=!1,f=null;for(let d of s.split(`
|
|
95
|
+
`)){let m=d.trim();if(!m||m[0]!=="{")continue;let h;try{h=JSON.parse(m)}catch{continue}if((h.type==="turn_context"&&h.payload?.model||h.type==="session_meta"&&h.payload?.model)&&(i=h.payload.model),h.type==="event_msg"){let v=h.payload?.type;v==="task_started"?(l=!0,u=!1,f=h.payload?.turn_id||null):v==="task_complete"&&l&&(l=!1,!u&&h.payload?.last_agent_message==null&&a.push({role:"assistant",failed:!0,text:Wc,sourceId:h.payload?.turn_id||f||void 0,ts:h.timestamp?Date.parse(h.timestamp):null,model:i}));continue}if(h.type!=="response_item")continue;let w=h.payload;if(!w)continue;if((w.type==="function_call"||w.type==="custom_tool_call")&&w.name){u=!0,Hc.has(w.name)||c.push(w.name);continue}if(w.type!=="message"||w.role!=="assistant")continue;u=!0;let g=zv(w.content),b=c.length?c.slice():null;c=[],!(!g&&!b)&&a.push({role:"assistant",text:g,tools:b||void 0,ts:h.timestamp?Date.parse(h.timestamp):null,model:i})}if(i)for(let d of a)d.model||(d.model=i);return a.slice(-n)}var xo=new Set(["exec_command","shell","exec"]),Hc=new Set(["wait","write_stdin"]),Wc="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 Vv(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 Jv(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+=`
|
|
96
|
+
`;else if(s==="t")n+=" ";else if(s==="r")n+="\r";else if(s==="u")if(t[r+1]==="{"){let a=t.indexOf("}",r+2);if(a===-1)return null;n+=String.fromCodePoint(parseInt(t.slice(r+2,a),16)),r=a}else n+=String.fromCharCode(parseInt(t.slice(r+1,r+5),16)),r+=4;else n+=s}else{if(o===e)return n;if(e==="`"&&o==="$"&&t[r+1]==="{")return null;n+=o}}return null}function Xv(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=Jv(i.slice(c.index+c[0].length).trimStart());l&&l.trim()&&o.push(l.trim())}),o}async function qm(t,e,{limit:n=400,maxBytes:r=524288}={}){let o=await En(t,e);if(!o)return[];let s;try{s=await Ot(o.file,r)}catch{return[]}let a=[];for(let i of s.split(`
|
|
97
|
+
`)){let c=i.trim();if(!c||c[0]!=="{")continue;let l;try{l=JSON.parse(c)}catch{continue}if(l.type!=="response_item")continue;let u=l.payload;if(!u)continue;let f=[];if(u.type==="function_call"&&xo.has(u.name)){let m;try{m=JSON.parse(u.arguments||"{}")}catch{continue}let h=Vv(m);h&&(f=[h])}else u.type==="custom_tool_call"&&u.name==="exec"&&(f=Xv(u.input));if(!f.length)continue;let d=u.call_id||u.id||null;f.forEach((m,h)=>{a.push({id:d?h?`${d}:${h}`:d:null,tool:u.name,command:m,description:"",summary:Ve(m),ts:l.timestamp?Date.parse(l.timestamp):null})})}return a.slice(-n)}async function Km(t,e){let n=await En(t,e);if(!n)return null;try{return Math.round((await Uc.stat(n.file)).mtimeMs)}catch{return n.mtimeMs||null}}var hr=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}},qc=class extends hr{transcript(e){return!e?.cwd||!e?.sessionId?null:xe(e.cwd,e.sessionId)}async readMessages(e,n){let r=this.transcript(e);return r?Ni(r,n):[]}async readCommands(e,n){let r=this.transcript(e);return r?Im(r,n):[]}async readJobs(e,n){let r=this.transcript(e);return r?$m(r,n):[]}async updatedAt(e){let n=this.transcript(e);if(!n)return null;try{return Math.round((await Yv.stat(n)).mtimeMs)}catch{return null}}},Kc=class extends hr{async readMessages(e,n){return Dm(e.cwd,e.sessionId,n)}async readCommands(e,n){return Lm(e.cwd,e.sessionId,n)}async updatedAt(e){return Fm(e.cwd,e.sessionId)}},Gc=class extends hr{sessionId(e){return e?.pinned?e.sessionId:null}async readMessages(e,n){return Wm(e.cwd,this.sessionId(e),n)}async readCommands(e,n){return qm(e.cwd,this.sessionId(e),n)}async updatedAt(e){return Km(e.cwd,this.sessionId(e))}},Zv=new hr("unknown"),Gm=new Map;function zc(t){return Gm.set(t.agent,t),t}function So(t){return Gm.get(t)||Zv}zc(new qc("claude",Dc));zc(new Kc("cursor",ca));zc(new Gc("codex",xo));var Vc=t=>e=>({...e,box:t.name,agent:t.agent,boxRole:t.role});async function Jc(t,e={}){return(await So(t.agent).readMessages(t,e)).map(Vc(t))}async function zm(t,e={}){return(await So(t.agent).readCommands(t,e)).map(Vc(t))}async function Vm(t,e={}){return(await So(t.agent).readJobs(t,e)).map(Vc(t))}var Ym=/to navigate\b.*\bEsc to cancel|Enter to select\b/,Zc=/Chat about this/i,In=/^\s*(❯)?\s*(\d+)\.\s+(?:\[([ xX✔])\]\s+)?(.+?)\s*$/,Jm=/^(type something|chat about this|submit)\b/i,Rn=/^[─—-]{6,}\s*$/,Qc=/[\u2500-\u257F\u2550-\u256F┌┐└┘├┤│┬┴┼─✂]/;function $n(t){let e=t.search(Qc);return(e>=0?t.slice(0,e):t).trim()}function Qv(t){if(!t)return"";let e=t.replace(/[│┌└├─┘┤┬┴┼─✂].*$/,"").trim();return!e||/hidden|\d+\s+lines?/i.test(e)?"":e}function tT(t){let e=String(t).trim();return!!(!e||Rn.test(t)||Qc.test(e)||/hidden|\d+\s+lines?/i.test(e)||/notespress|press n to add/i.test(e)||/^\s*notes?\s*$/i.test(e))}function eT(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=nT(n,e);return n.forEach((a,i)=>{a.current=i===s}),{tabs:n,questionIndex:s,questionTotal:n.length,currentTabLabel:n[s].label}}function nT(t,e){let n=String(e).toLowerCase();if(n){let o=t.map((s,a)=>({i:a,label:s.label.toLowerCase()})).sort((s,a)=>a.label.length-s.label.length);for(let{i:s,label:a}of o){if(a.split(/\s+/).filter(l=>l.length>3).some(l=>n.includes(l)))return s;let c=a.slice(0,8).trim();if(c.length>=4&&n.includes(c))return s}}let r=t.findIndex(o=>!o.answered);return r>=0?r:t.length-1}function Xm(t){return t.replace(/[←→✔☐☒●▶]/g," ").replace(/\bSubmit\b/g," ").replace(/\s+/g," ").trim()}var Zm=/^\s*Switch model\?\s*$/m;function rT(t,e=""){let n=String(t);if(!Zm.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
98
|
+
`),o=-1;for(let u=r.length-1;u>=0;u--)if(/^\s*Switch model\?\s*$/.test(r[u])){o=u;break}if(o<0)return null;let s=[],a=[];for(let u=o+1;u<r.length;u++){let f=r[u].match(In);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&&!Rn.test(r[u])&&a.push(d)}}if(s.length<2)return null;let i=s.map(u=>({label:$n(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 Qm=/^\s*Enter to confirm\s*·\s*Esc to cancel\s*$/,oT=/^\s*[─—▔-]{6,}\s*$/;function sT(t,e=""){let n=String(t).replace(/\r/g,"").split(`
|
|
99
|
+
`),r=n.length-1;for(;r>=0&&n[r].trim()==="";)r--;if(r<0||!Qm.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(In);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&&!(oT.test(n[c])||Rn.test(n[c]));c--){let l=n[c].trim();l&&a.unshift(l)}let i=s.map(c=>({label:$n(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 aT(t,e=""){let n=String(t);if(!/ready to submit your answers/i.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
100
|
+
`),o=[];for(let c=0;c<r.length;c++){let l=r[c].match(In);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 Xc=/Resume from summary/i,iT=/^\s*This session is\b/;function cT(t,e=""){let n=String(t);if(!Xc.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
101
|
+
`),o=-1;for(let u=r.length-1;u>=0;u--)if(iT.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(In);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&&!Rn.test(r[u])&&a.push(d)}}if(s.length<2||!s.some(u=>Xc.test(u.label)))return null;let i=s.map(u=>({label:$n(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 On=/shift\+tab to cycle|⏵⏵ bypass permissions on|⏸ manual mode on/;function tl(t){let e=[];for(let r=0;r<t.length;r++){let o=t[r].match(In);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 th=/Choose the text style that looks best with your terminal/,eh=/Let's get started\./;function lT(t,e=""){let n=String(t);if(!th.test(n)||!eh.test(n)||On.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
102
|
+
`),o=tl(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:$n(s.label),description:""}))}]}:null}var nh=/Select login method:/;function uT(t,e=""){let n=String(t);if(!nh.test(n)||On.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
103
|
+
`),o=tl(r);if(!o)return null;let s=o.map(a=>{let[i,...c]=a.label.split("\xB7");return{label:$n(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 el=/Paste code here if prompted/,dT=/^[A-Za-z0-9%&=+._~#?/:\-]+$/;function fT(t,e){let n=t[e].trim();for(let r=e+1;r<t.length;r++){let o=t[r].trim();if(!o||!dT.test(o)||el.test(t[r]))break;n+=o}return n}function pT(t,e=""){let n=String(t);if(!el.test(n)||On.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
104
|
+
`),o="";for(let s=0;s<r.length;s++)if(/^\s*https:\/\//.test(r[s])){o=fT(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 Yc=/OAuth error:/,mT=/Press Enter to retry\./;function hT(t,e=""){let n=String(t);if(!Yc.test(n)||!mT.test(n)||On.test(n))return null;let s=([...n.replace(/\r/g,"").split(`
|
|
105
|
+
`)].reverse().find(a=>Yc.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 rh=/Quick safety check: Is this a project you created or one you trust/;function wT(t,e=""){let n=String(t);if(!rh.test(n)||On.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
106
|
+
`),o=tl(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:$n(s.label),description:""}))}]}:null}var oh=/^(.{0,60}?)Press\s+Enter\s+to\s+continue\s*[.…]{0,3}\s*$/;function gT(t,e=""){let n=String(t);if(On.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
107
|
+
`),o=-1;for(let l=r.length-1;l>=0;l--)if(r[l].trim()!==""){o=l;break}if(o<0)return null;let s=r[o].match(oh);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(Rn.test(r[l])||Qc.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 yT(t){let e=-1;for(let r=t.length-1;r>=0;r--)if(Zc.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 bT(t,e=""){let n=String(t);if(!Ym.test(n)||!Zc.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
108
|
+
`),o=yT(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(In);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)&&!(In.test(r[C])||Rn.test(r[C]));C++){let W=r[C].trim();if(!(!W||tT(r[C])||Jm.test(W))){T.desc=Qv(W);break}}let f=l.filter(T=>!Jm.test(T.label));if(!f.length)return null;let d=f.some(T=>T.hasCheckbox),m=Math.max(0,f.findIndex(T=>T.cursor)),h=s>=0?s+1:0,w=[];for(let T=h;T<u;T++){let C=r[T].trim();C&&!Rn.test(r[T])&&!/[☐☒]/.test(r[T])&&w.push(C)}let g=w.join(" ")||(s>=0?Xm(r[s]):"")||"The agent is asking a question",b=s>=0?eT(r[s],g):null,v=b?.currentTabLabel||(s>=0?Xm(r[s]):""),A=f.map(T=>({label:$n(T.label),description:T.desc})),M=A.map(T=>T.label).join("|"),$=`claude:${b?`q${b.questionIndex+1}/${b.questionTotal}`:"q1/1"}:${v}:${g}:${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:m,promptId:$,groupId:F,...b?{questionIndex:b.questionIndex,questionTotal:b.questionTotal,currentTabLabel:b.currentTabLabel,tabs:b.tabs}:{},questions:[{question:g,header:v,multiSelect:d,options:A}]}}function wr(t,e){let n=String(e||"");return n?t==="claude"?Ym.test(n)&&Zc.test(n)||Zm.test(n)||Xc.test(n)||Qm.test(n.trimEnd().split(`
|
|
109
|
+
`).pop()||"")?!0:On.test(n)?!1:th.test(n)&&eh.test(n)||nh.test(n)||el.test(n)||Yc.test(n)||rh.test(n)||oh.test(n.trimEnd().split(`
|
|
110
|
+
`).pop()||""):t==="codex"&&/Sign in with ChatGPT|one-time code|Paste or type your API key|link to authenticate/i.test(n)?!0:t==="cursor"||t==="codex"?/\b(allow|approve|apply (this )?patch|run (this )?command|do you want to run|grant)\b/i.test(n):!1:!1}function Mn(t,e){return!t||!e?null:t.agent==="claude"?aT(e,t.name)||bT(e,t.name)||rT(e,t.name)||cT(e,t.name)||hT(e,t.name)||pT(e,t.name)||uT(e,t.name)||lT(e,t.name)||wT(e,t.name)||gT(e,t.name)||sT(e,t.name):t.agent==="cursor"||t.agent==="codex"?Xd(t.agent,e,t.name):null}async function nl(t){if(!t)return null;let e=await je(t.name).catch(()=>"");return Mn(t,e)}function sh(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 ah(t,e){let n=Number.isInteger(t?.questionIndex)?t.questionIndex:0,o=(Number.isInteger(e)?e:n)-n;if(!o)return[];let s=o>0?"Tab":"BTab";return Array.from({length:Math.abs(o)},()=>s)}function gr(t){return typeof t=="string"?t.trim():""}var ih=/^\s*─{8,}\s*$/,kT=/^❯[ ]?(.*)$/;function vo(t,e){if(t!=="claude")return null;let n=String(e).replace(/\r/g,"").split(`
|
|
111
|
+
`).map(c=>c.replace(/\s+$/,"")),r=-1;for(let c=n.length-1;c>=0;c--)if(ih.test(n[c])){r=c;break}if(r<=0)return null;let o=-1;for(let c=r-1;c>=0;c--)if(ih.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(kT);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 Yt(t){return String(t||"").replace(/\s+/g," ").trim()}function ch(t,e,{slackMs:n=2e4}={}){let r=Yt(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&&Yt(o.text).includes(r))}function lh(t,e,n,{busy:r=!1}={}){let o=vo(t,n);return o&&rl(e,o)?"stuck":r||o?"submitted":"unknown"}function rl(t,e){let n=Yt(t),r=Yt(e).replace(/^…\s*/,"").replace(/\s*…$/,"");return!n||!r||r.length<Math.min(n.length,12)?!1:n===r||n.includes(r)}import xT from"node:crypto";var ol=new Set(["cursor","codex"]),dh=4,ST=4,vT=2,TT=24,uh=/^[ \t]{0,3}(`{3,})bostrat-question[ \t]*\r?\n([\s\S]*?)\r?\n[ \t]{0,3}\1`*[ \t]*$/gm,To=t=>typeof t=="string"?t.trim():"";function AT(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,dh)){let o=To(r?.question);if(!o)return null;let s=Array.isArray(r?.options)?r.options:[],a=[];for(let i of s.slice(0,ST)){let c=To(i?.label);if(!c)return null;let l=To(i?.description);a.push(l?{label:c,description:l}:{label:c})}if(a.length<vT)return null;n.push({question:o,header:To(r?.header).slice(0,TT),multiSelect:r?.multiSelect===!0,options:a})}return n.length?n:null}function sl(t){if(typeof t!="string"||!t.includes("```bostrat-question"))return null;uh.lastIndex=0;let e=null;for(let o;o=uh.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=AT(n);return r?{text:t.slice(0,e.index).trimEnd(),questions:r}:null}function _T(t,e,n){let r=xT.createHash("sha256").update(JSON.stringify(n)).digest("hex").slice(0,8);return`${t}:ask:${e||"turn"}:${r}`}function al(t,{leadBoxId:e,provider:n,isAnswered:r,maxQuestions:o=dh}={}){if(!Array.isArray(t)||!e||!ol.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||sl(s.text)?.questions;if(!a?.length)return null;let i=a.slice(0,Math.max(1,o)),c=_T(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 fh(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=To(n),s;return r.length?r.length===1?s=`${r[0].name}: ${r[0].picks}`:s=`Answers:
|
|
112
|
+
${r.map(a=>`- ${a.name}: ${a.picks}`).join(`
|
|
113
|
+
`)}`:s=o,r.length&&o&&(s+=`
|
|
114
|
+
${o}`),s||""}var ph=`
|
|
115
|
+
When you are BLOCKED on a decision only the operator can make \u2014 an ambiguous
|
|
116
|
+
requirement, an irreversible choice, several valid approaches \u2014 do not bury the
|
|
117
|
+
question in prose. End your message with exactly ONE fenced code block tagged
|
|
118
|
+
bostrat-question, holding JSON of this exact shape:
|
|
119
|
+
|
|
120
|
+
\`\`\`bostrat-question
|
|
121
|
+
{"questions":[{"header":"Auth method","question":"Which auth should the API use?","multiSelect":false,"options":[{"label":"JWT","description":"Stateless; works for the mobile client"},{"label":"Sessions","description":"Simplest with the current middleware"}]}]}
|
|
122
|
+
\`\`\`
|
|
123
|
+
|
|
124
|
+
Rules: valid JSON only; 1\u20134 questions; each question needs a short header (max
|
|
125
|
+
24 chars), the question text, and 2\u20134 options with short labels (descriptions
|
|
126
|
+
optional); set "multiSelect": true only when several options can apply at once.
|
|
127
|
+
The block must be the LAST thing in your message \u2014 emit it, then STOP and end
|
|
128
|
+
your turn. The operator sees the questions as an interactive card in their chat
|
|
129
|
+
(never your raw block), and their choice arrives as a normal chat message, e.g.
|
|
130
|
+
"Auth method: JWT". They may instead reply in free text \u2014 treat that as the
|
|
131
|
+
answer. Never use the block for rhetorical questions, progress updates, or
|
|
132
|
+
anything you can decide yourself; keep asking in prose for questions without
|
|
133
|
+
enumerable options.
|
|
134
|
+
`;import{execFile as ET}from"node:child_process";import{promisify as IT}from"node:util";var cl=IT(ET),mh=64*1024*1024;async function ht(t,e,{input:n}={}){let r=cl("git",["-C",t,...e],{maxBuffer:mh});return n!==void 0&&r.child.stdin&&(r.child.stdin.on("error",()=>{}),r.child.stdin.end(n)),r}async function ua(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 hh(t){let{stdout:e}=await ht(t,["rev-parse","--show-toplevel"]);return e.trim()}async function RT(t){try{return await ht(t,["rev-parse","--verify","HEAD"]),!0}catch{return!1}}async function ll(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)=>cl("git",["-C",t,...a],{maxBuffer:mh,env:o,...i});try{let a=await RT(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 cl("rm",["-f",r])}catch{}}}async function ul(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 dl(t,{ignorePlumbing:e=!1}={}){let{stdout:n}=await ht(t,["status","--porcelain"]),r=n.split(`
|
|
135
|
+
`).filter(Boolean);return e&&(r=r.filter(o=>{let s=o.slice(3);return!(s===".bostrat"||s===".bostrat/"||s.startsWith(".bostrat/")||o.startsWith("??")&&(s==="AGENTS.md"||s==="CLAUDE.md"))})),r.length>0}async function fl(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 yr(t,e,n,r){return await ht(t,["worktree","add","--no-track","-b",n,e,r]),e}async function wh(t,e){try{return await ht(t,["rev-parse","--verify","--quiet",`refs/heads/${e}`]),!0}catch{return!1}}async function gh(t,e,n){return await ht(t,["worktree","add",e,n]),e}async function da(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 yh(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 Kt(t,e){try{await ht(t,["worktree","remove","--force",e])}catch{try{await ht(t,["worktree","prune"])}catch{}}}function $T(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 pl(t,e){return!e||!e.length?t:[...t,"--",".",...e.map(n=>`:(exclude)${n}`)]}async function ml(t,e,n,r){let{stdout:o}=await ht(t,pl(["diff","--shortstat",e,n],r));return $T(o)}async function hl(t,e,n,r){let{stdout:o}=await ht(t,pl(["diff","--name-only",e,n],r));return o.split(`
|
|
136
|
+
`).map(s=>s.trim()).filter(Boolean)}async function wl(t,e,n,r){let{stdout:o}=await ht(t,pl(["diff","--binary",e,n],r));if(!o.trim())return{applied:!0,empty:!0,conflicts:[]};if((await il(t,o,["--whitespace=nowarn"])).ok)return{applied:!0,empty:!1,conflicts:[]};let s=await il(t,o,["--3way","--whitespace=nowarn"]),a=await OT(t);return a.length>0?{applied:!1,empty:!1,conflicts:a}:s.ok?{applied:!0,empty:!1,conflicts:[]}:(await il(t,o,["--reject","--whitespace=nowarn"]),{applied:!1,empty:!1,conflicts:[`patch did not apply cleanly: ${s.err}`]})}async function il(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 OT(t){try{let{stdout:e}=await ht(t,["diff","--name-only","--diff-filter=U"]);return e.split(`
|
|
137
|
+
`).map(n=>n.trim()).filter(Boolean)}catch{return[]}}import MT from"node:fs/promises";import CT from"node:os";import bh from"node:path";var fa=t=>new Promise(e=>setTimeout(e,t)),NT=Number(process.env.BOSTRAT_MODEL_PICKER_TIMEOUT_MS)||8e3,PT=300,vh=[{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"}],BT=new Set([...vh.map(t=>t.alias),"best","opus[1m]","sonnet[1m]","claude-opus-4-8","claude-sonnet-4-6","claude-haiku-4-5","claude-fable-5"]),DT=[{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"}],Th=new Map([["low","Low"],["medium","Medium"],["high","High"],["xhigh","Extra high"]]),Ah=[{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 _h(t){return t==="codex"?DT:t==="cursor"?Ah:vh}function kh(t){let e=String(t||"").split(`
|
|
138
|
+
`),n=e.findIndex(o=>/Select Model and Effort/i.test(o));if(n<0)return null;let r=[];for(let o=n+1;o<e.length;o++){let s=e[o].match(/^\s*(›\s*)?(\d+)\.\s+(\S+?)(?:\s+\((current|default)\))?(?:\s{2,}|\s*$)/);if(s)r.push({n:Number(s[2]),id:s[3],current:s[4]==="current",active:!!s[1]});else if(r.length&&/Press enter|esc to go back/i.test(e[o]))break}return r.length?r:null}function xh(t){let e=String(t||"").split(`
|
|
139
|
+
`),n=e.findIndex(o=>/Select Reasoning Level/i.test(o));if(n<0)return null;let r=[];for(let o=n+1;o<e.length;o++){let s=e[o].match(/^\s*(›\s*)?(\d+)\.\s+([A-Za-z ]+?)(?:\s+\((default|current)\))?(?:\s{2,}|\s*$)/);if(s)r.push({n:Number(s[2]),label:s[3].trim(),isDefault:!!s[4],active:!!s[1]});else if(r.length&&/Press enter|esc to go back/i.test(e[o]))break}return r.length?r:null}function jT(t){let e=String(t||"").split(`
|
|
140
|
+
`);for(let n=e.length-1;n>=0;n--){let r=e[n].match(/Model changed to\s+(\S+)(?:\s+(\S+))?\s*$/i);if(r)return{model:r[1],effort:r[2]||null}}return null}function Sh(t){let e=String(t||"").split(`
|
|
141
|
+
`),n=e.findIndex(s=>/Available models|Models matching/i.test(s));if(n<0)return null;let r=/Models matching/i.test(e[n]),o=[];for(let s=n+1;s<e.length;s++){let a=e[s];if(/^\s*Filter:/i.test(a)||!a.trim())continue;if(/to filter|Enter to select|^\s*\d+-\d+ of \d+/i.test(a))break;let i=a.match(/^\s*(→\s+)?(.+?)\s*$/);if(!i)continue;let c=i[2].replace(/\s*\(Tab to modify\)\s*$/i,""),l=null,u=c.split(/\s{2,}/);u.length>1&&(c=u[0],l=u.slice(1).join(" ").trim()||null),c&&o.push({label:c.trim(),params:l,active:!!i[1]})}return o.length?{filtered:r,rows:o}:null}function Eh(t,e){if(typeof e!="string"||!e.trim())throw new Error("model (string) required");let n=e.trim();if(t==="codex"){let[r,o]=n.split(/[\s:]+/);if(o&&!Th.has(o))throw new Error(`unknown codex effort "${o}" (low | medium | high | xhigh)`);return{model:r,effort:o||null}}if(t==="cursor"){let r=Ah.find(o=>o.alias===n);return{model:n,filter:r?r.label:n}}if(!BT.has(n))throw new Error(`unknown model "${n}"`);return{model:n}}async function Ao(t,e,{timeoutMs:n=NT}={}){let r=Date.now()+n;for(;;){let o=await gt(t,{lines:80}).catch(()=>""),s=e(o);if(s)return s;if(Date.now()>r)return null;await fa(PT)}}async function LT(t,e){return await vt(t,`/model ${e.model}`,Se("claude")),{verified:!1,note:"confirmed when the next turn's transcript model flips"}}async function FT(t,e){await gs(t),await vt(t,"/model",{enter:!1}),await fa(250),await xt(t,["Enter"],{kind:"model-switch"});let n=await Ao(t,kh);if(!n)throw new Error("codex model picker didn't open");let r=n.find(i=>i.id===e.model);if(!r)throw await xt(t,["Escape"],{kind:"model-switch"}).catch(()=>{}),new Error(`unknown model "${e.model}" \u2014 codex offers: ${n.map(i=>i.id).join(", ")}`);let o=i=>{let c=jT(i);return!c||c.model!==e.model||e.effort&&c.effort&&c.effort.toLowerCase()!==e.effort.toLowerCase()?null:c};await vt(t,String(r.n),{enter:!1,kind:"model-switch"});let s=await Ao(t,i=>xh(i)||!kh(i)&&o(i)&&{done:o(i)});if(!s)throw await xt(t,["Escape"],{kind:"model-switch"}).catch(()=>{}),new Error("codex reasoning-level picker didn't open");if(!s.done){let i=e.effort?Th.get(e.effort):null,c=i?s.find(l=>l.label.toLowerCase()===i.toLowerCase()):s.find(l=>l.isDefault)||s[0];if(!c)throw await xt(t,["Escape","Escape"],{kind:"model-switch"}).catch(()=>{}),new Error(`codex doesn't offer effort "${e.effort}" for ${e.model}`);await vt(t,String(c.n),{enter:!1,kind:"model-switch"})}let a=s.done||await Ao(t,i=>!xh(i)&&o(i));if(!a)throw new Error("codex never confirmed the model change");return{verified:!0,model:a.model,effort:a.effort}}async function UT(){let t=bh.join(process.env.CURSOR_CONFIG_DIR||bh.join(CT.homedir(),".cursor"),"cli-config.json");try{return JSON.parse(await MT.readFile(t,"utf8"))?.model||null}catch{return null}}async function HT(t,e){await gs(t),await bs(t,`/model ${e.filter}`,{kind:"model-switch"});let n=await Ao(t,Sh),r=c=>String(c||"").toLowerCase(),o=c=>r(c.label).startsWith(r(e.filter)),s=n?.rows.find(c=>c.active)||null;if(n&&!(s&&o(s)))for(let c=0;c<12;c++){let l=await Ao(t,Sh,{timeoutMs:1500});if(s=l?.rows.find(u=>u.active)||null,s&&o(s))break;if(!l||!l.rows.some(o)){s=null;break}await xt(t,["Down"],{kind:"model-switch"}),await fa(250)}if(!n||!s||!o(s)){await xt(t,["Escape"],{kind:"model-switch"}).catch(()=>{});let c=Array(`/model ${e.filter}`.length).fill("BSpace");throw await xt(t,c,{settleMs:15,kind:"model-switch"}).catch(()=>{}),new Error(n?`cursor picker has no model matching "${e.filter}"`:"cursor model picker didn't open")}await xt(t,["Enter"],{kind:"model-switch"}),await fa(800);let a=await UT();return{verified:!!a&&(a.modelId===e.model||a.displayModelId===e.model||r(a.displayName).startsWith(r(e.filter))),model:a?.modelId||null,note:"cursor applies this as the global default for new sessions too"}}async function Ih(t,e,n){return t==="codex"?FT(e,n):t==="cursor"?HT(e,n):LT(e,n)}var Ft=t=>t||null;function ma(t){let e=t||{},n=WT(e);return n&&(n.payload={ts:e.at,...n.payload}),n}function WT(t){switch(t.type){case"said":return{type:"turn.user",actor:"user",source:"orchestrator",boxId:t.box,provider:Ft(t.agent),payload:{text:t.text,boxRole:t.boxRole,...t.kind?{kind:t.kind}:{},...t.attachments?.length?{attachments:t.attachments}:{}}};case"unsaid":return{type:"turn.retracted",actor:"user",source:"orchestrator",boxId:t.box,provider:Ft(t.agent),payload:{saidTs:t.saidTs,text:t.text,boxRole:t.boxRole}};case"orchestration-prompt":return{type:"turn.user",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Ft(t.agent),payload:{text:t.prompt,kind:t.kind,boxRole:t.boxRole}};case"lead-handoff":return{type:"handoff.done",actor:"orchestrator",source:"orchestrator",boxId:t.to,provider:Ft(t.toAgent),payload:{from:t.from,fromAgent:t.fromAgent,to:t.to,toAgent:t.toAgent,reason:t.reason,handoffStartedAt:t.handoffStartedAt,fromSessionId:t.fromSessionId,toSessionId:t.toSessionId,epoch:t.epoch,singleBox:t.singleBox,forced:t.forced,recovered:t.recovered}};case"lead-handoff-failed":return{type:"handoff.failed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Ft(t.toAgent),payload:{box:t.box,fromAgent:t.fromAgent,toAgent:t.toAgent,reason:t.reason,error:t.error,rolledBack:t.rolledBack,handoffStartedAt:t.handoffStartedAt}};case"subtask-delegated":return{type:"subtask.delegated",actor:"orchestrator",source:"orchestrator",boxId:t.to,provider:Ft(t.toAgent),payload:{task:t.task,subtaskId:t.subtaskId,reason:t.reason}};case"subtask-merged":return{type:"subtask.merged",actor:"orchestrator",source:"orchestrator",boxId:t.box||t.from,provider:Ft(t.agent),payload:{...br(t)}};case"subtask-failed":return{type:"error",actor:"system",source:"orchestrator",boxId:t.box||t.to,provider:Ft(t.agent||t.toAgent),payload:{kind:"subtask-failed",...br(t)}};case"subtask-resumed":return{type:"box.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Ft(t.agent),payload:{change:"subtask-resumed",...br(t)}};case"model-change":return{type:"box.state.changed",actor:"user",source:"orchestrator",boxId:t.box,payload:{change:"model",model:t.to,from:t.from??null,verified:t.verified??null}};case"codex-update":return{type:"box.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:"codex",payload:{change:"cli-update",from:t.from??null,to:t.to??null,status:t.status,...t.error?{error:t.error}:{}}};case"opened":return{type:"box.state.changed",actor:"user",source:"orchestrator",boxId:t.box,payload:{change:"opened",...br(t)}};case"archived":case"unarchived":case"track-deleted":return{type:"box.state.changed",actor:"user",source:"orchestrator",payload:{change:t.type,...br(t)}};case"rehydrated":return{type:"box.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Ft(t.agent),payload:{change:"rehydrated"}};case"retagged":return{type:"box.state.changed",actor:"user",source:"orchestrator",payload:{change:"retagged",...br(t)}};case"rehydrate-failed":return{type:"error",actor:"system",source:"orchestrator",boxId:t.box,provider:Ft(t.agent),payload:{kind:"rehydrate-failed",reason:t.reason}};default:return null}}var pa=Promise.resolve(),yl=t=>(pa=pa.then(t).catch(()=>{}),pa),$h=()=>pa;function Oh(t,e){let n=ma(e);if(!n)return;let r=e?.type==="track-deleted"?{id:t.id}:t;yl(()=>O(r,n))}var Rh=new Set;async function Mh(t){if(!(!t?.id||Rh.has(t.id))){Rh.add(t.id);try{let e=await L(t),n=new Set(e.filter(r=>r.source==="orchestrator").map(r=>`${r.type}|${r.payload?.ts??"?"}`));for(let r of t.events||[]){let o=ma(r);!o||n.has(`${o.type}|${o.payload?.ts??"?"}`)||await O(t,o)}}catch{}}}function br(t){let{type:e,at:n,...r}=t;return r}var gl=new Map;function Ch(t,e,n){!n?.promptId||gl.get(t.id)===n.promptId||(gl.set(t.id,n.promptId),yl(()=>O(t,{type:"prompt.raised",actor:"agent",source:"pane",boxId:e?.name,provider:Ft(n.provider||e?.agent),payload:{promptId:n.promptId,question:n.question||n.title||null,options:(n.options||[]).map(r=>r?.label??r).slice(0,12)}})))}function bl(t,e,n,r={}){gl.delete(t.id),yl(()=>O(t,{type:"prompt.answered",actor:"user",source:"orchestrator",boxId:e?.name,provider:Ft(n?.provider||e?.agent),payload:{promptId:n?.promptId,...r}}))}import{randomUUID as xA}from"node:crypto";var Ph=/(?<!\b(?:a|an|the|this|that|another|one|each|any|no|separate|single|new|initial|final|first|second|third|last|later|next|subsequent|extra|additional|amend(?:ed)?|squash|merge|follow[\s-]?up|clean[\s-]?up)\s)commit(?!s\b)(?!\s+(?:history|log|message|hash|sha)s?\b)/.source,Bh=/(?<!\b(?:a|an|the|this|that|another|one|each|any|no|separate|single|new|initial|final|first|second|third|last|later|next|subsequent|extra|additional)\s)push(?!\s+notifications?\b)(?:\s+(?:it|this|that|them|everything|the\s+\w+))?\s*(?:up)?/.source,Dh=/open(?:\s+up)?\s+(?:a|the|that)\s+(?:draft\s+)?(?:pr|pull\s+request)|creat(?:e|ing)\s+(?:a|the)\s+(?:draft\s+)?(?:pr|pull\s+request)|rais(?:e|ing)\s+(?:a|the)\s+(?:pr|pull\s+request)|put\s+up\s+(?:a|the)\s+(?:pr|pull\s+request)|send\s+(?:up\s+)?(?:a|the)\s+(?:pr|pull\s+request)/.source,jh=/(?<!\b(?:a|an|the|this|that|another|one|each|any|no|clean|interactive)\s)rebase(?!s\b)(?:\s+(?:it|this|that|them|(?:this|that|the)\s+branch))?(?:\s+(?:on(?:to)?|against|atop|over)\s+\S+)?/.source,qT=new RegExp(`\\b(?:${Ph}|${Bh}|${Dh}|${jh})\\b`,"i"),KT=/\b(?:want\s+me\s+to|would\s+you\s+like(?:\s+me)?(?:\s+to)?|should\s+i|shall\s+i|do\s+you\s+want(?:\s+me)?(?:\s+to)?|happy\s+to|ready\s+for\s+me\s+to|let\s+me\s+know\s+(?:if|when|whether|and)|say\s+the\s+word|give\s+me\s+the\s+(?:word|go[\s-]?ahead)|if\s+you(?:'d|\s+would)?\s+(?:like|want|prefer)|just\s+say\s+(?:so|yes|go))\b/i,GT=/^(?:commit|push|open|create|raise|rebase|ready|ok(?:ay)?\s+to|good\s+to)\b/i,zT=90;function VT(t){return String(t).split(/(?<=[.?!])\s+|\n+/).map(e=>e.trim()).filter(Boolean)}function Lh(t){let e=String(t||"").trim();if(!e)return null;let n=e.slice(-600);for(let r of VT(n).reverse()){if(!qT.test(r))continue;let o=r.endsWith("?")&&r.length<=zT&>.test(r);if(KT.test(r)||o)return r.length>200?r.slice(0,197)+"\u2026":r}return null}var Fh=["commit","rebase","push","pr"],JT=new RegExp(`\\b(?:${Ph})\\b`,"i"),XT=new RegExp(`\\b(?:${jh})\\b`,"i"),YT=new RegExp(`\\b(?:${Bh})\\b`,"i"),ZT=new RegExp(`\\b(?:${Dh})\\b`,"i"),QT=/\b(?:to|into|onto|on|updat(?:e|es|ing))\s+(?:the|that|this)\s+(?:open\s+|existing\s+|current\s+|same\s+)?(?:draft\s+)?(?:pr|pull\s+request)\b/i;function Uh(t){let e=String(t||""),n=[];JT.test(e)&&n.push("commit"),XT.test(e)&&n.push("rebase"),YT.test(e)&&n.push("push");let r=QT.test(e);!r&&ZT.test(e)&&n.push("pr");let o=null;if(r){let s=e.match(/(?:pr|pull\s+request)\s*\(?\s*#(\d+)\)?/i)||e.match(/#(\d+)/);o={number:s?Number(s[1]):null}}return{offered:n.length?n:null,toPr:o}}function 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(m=>`${m.name}/`).join(", "):null,l=e?` on this branch (${e})`:"",u=[];if(a.has("commit")&&u.push(i?`commit the work in each changed checkout (${c})${l} with a clear commit message per repo`:`commit the work${l} with a clear commit message`),a.has("rebase")){let m=a.has("commit")?"the branch":`this branch${e?` (${e})`:""}`;u.push(i?`rebase ${m} in each changed checkout (${c}) onto its own base branch (fetch origin first), resolving any conflicts so both sides' intent is preserved`:`rebase ${m} onto ${n?`the latest ${n}`:"its base branch"} (fetch origin first), resolving any conflicts so both sides' intent is preserved`)}if(a.has("push")||a.has("pr")){let m=r&&!a.has("pr")?` (the open PR${r.number?` #${r.number}`:""} picks it up)`:"",h=a.has("rebase")?" with `--force-with-lease` (the rebase rewrote history)":"";i?u.push(a.has("commit")||a.has("rebase")?`push each one to its remote${h}${m}`:`push this branch${e?` (${e})`:""} from each changed checkout (${c}) to its remote${m}`):u.push(a.has("commit")||a.has("rebase")?`push it to the remote${h}${m}`:`push this branch${l} to the remote${m}`)}a.has("pr")&&u.push(i?"open a pull request in each changed repo with a concise title and a description of what changed and why (run `gh pr create` inside each checkout)":`open a pull request${n?` against ${n}`:""} with a concise title and a description of what changed and why (use \`gh pr create\`)`);let f=[];a.has("commit")||f.push("don't create any new commits \u2014 ship what's already committed"),s&&!a.has("rebase")&&f.push("don't rebase \u2014 leave the branch's history as it is"),!a.has("push")&&!a.has("pr")&&f.push("don't push"),a.has("pr")||f.push("don't open a pull request");let d=`Yes \u2014 go ahead: ${u.join(", then ")}.`;return f.length&&(d+=` ${f.join(", and ").replace(/^./,m=>m.toUpperCase())}.`),a.has("pr")&&(d+=i?" If a repo's PR for this branch is already open, just push that repo's update instead.":" If a PR for this branch is already open, just push the update instead."),d}var tA=3e4;function Hh(t,e=Date.now()){let n=[],r=null;for(let o of t||[]){let s=o.payload||{},a=s.ts??o.ts;if(o.type==="pr.offer.answered"&&s.action==="pr"){r={box:o.boxId,from:a,lastActivity:a};continue}!r||a==null||a<r.from||(o.type==="pr.offer.answered"?r=null:o.type==="pr.state.changed"&&s.change==="opened"?(n.push({box:r.box,from:r.from,to:a,settled:!0}),r=null):o.type==="turn.user"&&o.source!=="hook"&&!s.kind&&String(s.text||"").trim()?r=null:o.boxId===r.box&&/^(?:turn|tool)\./.test(o.type)&&(r.lastActivity=Math.max(r.lastActivity,a)))}return r&&e-r.lastActivity<=tA&&n.push({box:r.box,from:r.from,to:e+1e12}),n}function Wh(t,e){let n=new Set;for(let r of e||[]){if(!r.settled)continue;let o=null;for(let s of t||[])s.kind!=="message"||s.role!=="assistant"||s.box!==r.box||s.ts==null||s.ts<r.from||s.ts>r.to||(!o||s.ts>=o.ts)&&(o=s);o&&n.add(o)}return n}var Nh={commit:"commit",rebase:"rebase",push:"push",pr:"open a PR"};function qh(t){let e=(Array.isArray(t)?t:[]).filter(r=>Nh[r]).map(r=>Nh[r]);return`The ship you were given the go-ahead for${e.length?` (${e.join(", then ")})`:""} should now be complete. This is an automated ship notification the operator armed on the offer: carry straight on with this Track's follow-up work (dependent tasks, cleanup, next steps) \u2014 don't wait for review or merge. If a ship step failed or is unfinished, finish the ship first. If nothing needs doing, say so briefly.`}import{execFile as sA}from"node:child_process";import{promisify as aA}from"node:util";import eA from"node:fs";import kl from"node:fs/promises";import Vh from"node:path";import nA from"node:os";var Jh=process.env.BOSTRAT_STATE_DIR||Vh.join(process.env.HOME||nA.tmpdir(),".bostrat"),wa=Vh.join(Jh,"github-metrics.json"),Xh=60*6e4,Kh=5*6e4,rA=6e4,kr=Date.now(),Ie=new Map,Xe=null,Cn=0,Gh=0,ga=t=>[...t.values()].reduce((e,n)=>e+n,0);try{let t=JSON.parse(eA.readFileSync(wa,"utf8"));t?.windowStart&&(Date.now()-t.windowStart<Xh?(kr=t.windowStart,Ie=new Map(Object.entries(t.byCaller||{})),Xe=t.lastHour||null):ga(new Map(Object.entries(t.byCaller||{})))&&(Xe={start:t.windowStart,total:ga(new Map(Object.entries(t.byCaller||{}))),byCaller:t.byCaller||{}}))}catch{}var zh=Promise.resolve();function Yh(t,e=!1){if(!e&&t-Gh<rA)return;Gh=t;let n=JSON.stringify({windowStart:kr,byCaller:Object.fromEntries(Ie),lastHour:Xe});zh=zh.then(()=>kl.mkdir(Jh,{recursive:!0})).then(()=>kl.writeFile(`${wa}.tmp`,n)).then(()=>kl.rename(`${wa}.tmp`,wa)).catch(()=>{})}function Zh(t){t-kr<Xh||(Xe={start:kr,total:ga(Ie),byCaller:Object.fromEntries(Ie)},Xe.total&&console.log(`[github] ${Xe.total} calls last hour`,Xe.byCaller),kr=t,Ie=new Map,Yh(t,!0))}function Re(t="other",e=Date.now()){Zh(e),Ie.set(t,(Ie.get(t)||0)+1),Yh(e)}function Qh(t=Date.now()){return Zh(t),{windowStart:kr,total:ga(Ie),byCaller:Object.fromEntries(Ie),lastHour:Xe,backoffUntil:Cn>t?Cn:null}}var oA=/rate limit|too quickly|abuse detection|HTTP 429/i;function Ye(t){return oA.test(String(t?.message||t||""))}function Ze(t,e=Date.now()){return Cn<=e&&console.warn(`[github] rate limited \u2014 backing off ${Kh/6e4} min:`,String(t?.message||t||"").slice(0,200)),Cn=Math.max(Cn,e+Kh),Cn}function ya(t=Date.now()){return Math.max(0,Cn-t)}async function xl(t,e){let n={working:!1,prompted:!1};if(!t)return n;let r=await gt(t,{lines:Tt}).catch(()=>"");if(!Fe(e,r))return tf(e,r)?{working:!0,prompted:!1}:n;if(wr(e,r)){let o=await je(t).catch(()=>r);if(Mn({name:t,agent:e},o))return{working:!1,prompted:!0}}return{working:!0,prompted:!1}}async function ba(t,e){return(await xl(t,e)).working}var tw=aA(sA),ew=[4e3,15e3,45e3],iA=2500,cA=5*6e4;async function Sl(t,e,{midTurn:n=ba,pollMs:r=iA,capMs:o=cA}={}){let s=Date.now()+o;for(;await n(t,e).catch(()=>!1);){if(Date.now()>=s)return!1;await new Promise(a=>setTimeout(a,r))}return!0}var lA=6e4,uA=9e4,dA=2e3,fA=/(?:^|[;&|]\s*|\)\s*)gh\s+(?:[^\s;&|"'`$]+\s+)*?pr\s+create\b/,pA=/(?:^|[;&|]\s*|\)\s*)gh\s+(?:[^\s;&|"'`$]+\s+)*?pr\s+merge\b/,mA=/\s--web\b/,nw=/(?:--repo|-R)[\s=]+([\w.-]+\/[\w.-]+)/;function vl(t,e){return t.slice(e).replace(/^[\s;&|)]+/,"").replace(/"[^"]*"|'[^']*'/g,'""').split(/[;&|]/)[0]}function rw(t){let e=String(t||""),n=e.match(fA);if(!n)return null;let r=vl(e,n.index);return mA.test(r)?null:{repo:r.match(nw)?.[1]??null}}var hA=new Set(["--subject","-t","--body","-b","--body-file","-F","--match-head-commit","--author-email","-A","--repo","-R"]);function ow(t){let e=String(t||""),n=e.match(pA);if(!n)return null;let r=vl(e,n.index),o=vl(e,n.index+n[0].length),s=null,a=o.trim().split(/\s+/).filter(Boolean);for(let i=0;i<a.length;i++){let c=a[i];if(c.startsWith("-")){hA.has(c)&&i++;continue}s=c.replace(/^["']+|["']+$/g,"")||null;break}return{repo:r.match(nw)?.[1]??null,selector:s,auto:/\s--auto\b/.test(r)}}var wA="number,url,title,headRefName,createdAt";async function gA(t,e){let n=async s=>{Re("observer");let{stdout:a}=await tw("gh",["pr","list","--repo",t,"--author","@me","--state","all","--limit","10","--json",wA],{timeout:3e4,maxBuffer:4*1024*1024,env:{...process.env,...s}}).catch(i=>{throw Ye(i)&&Ze(i),i});return JSON.parse(a||"[]")},r=await ee({slug:t}),o=await n(r);return!o.length&&r.GH_TOKEN&&(o=await n({}).catch(()=>[])),o.filter(s=>Date.parse(s.createdAt)>=e-lA).sort((s,a)=>Date.parse(a.createdAt)-Date.parse(s.createdAt))}async function yA(t,e){if(e)return[e];let n=[];for(let r of sw(t)){let o=await tt(r).catch(()=>null);o&&!n.includes(o)&&n.push(o)}for(let r of await ye().catch(()=>[])){let o=await tt(r.path).catch(()=>null);o&&!n.includes(o)&&n.push(o)}return n}function sw(t){let e=t.workspace;return e?.kind==="multi"?(e.repos??[]).map(n=>n?.base).filter(Boolean):[e?.base||t.cwd].filter(Boolean)}async function bA(t,e,n){let r=t.workspace?.branch,o=[];for(let s of await yA(t,e)){let a;try{a=await gA(s,n)}catch{continue}for(let i of a){let c=await Et(s,i.number);if(!(c&&(c.trackId!==t.id||c.announced))){if(r&&i.headRefName===r)return{slug:s,pr:i};o.push({slug:s,pr:i})}}}return o.length?(o.sort((s,a)=>Date.parse(a.pr.createdAt)-Date.parse(s.pr.createdAt)),o[0]):null}var Qe=new Set;function aw(t,e,{delays:n=ew,turnEnd:r}={}){let o=rw(e.command);if(!o)return null;let s=!!ow(e.command),a=`${t.id}|${e.id??e.ts??e.command}`;if(Qe.has(a))return null;Qe.add(a);let i=e.ts??Date.now(),c=(async()=>{try{for(let l of n){await new Promise(w=>setTimeout(w,l));let u=await bA(t,o.repo,i).catch(()=>null);if(!u)continue;let{slug:f,pr:d}=u,{link:m,created:h}=await Lt({repo:f,number:d.number,trackId:t.id,url:d.url,headRef:d.headRefName,title:d.title,source:"observed"});return!h&&!(m?.trackId===t.id&&await ho(f,d.number))?null:(await Sl(e.box,e.agent,r),await O({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:Date.now(),change:"opened",number:d.number,url:d.url,title:d.title,branch:d.headRefName,repo:f,...s?{selfMerge:!0}:{}}}),d)}return null}finally{Qe.delete(a)}})();return c.catch(()=>Qe.delete(a)),c}var kA="number,url,title,headRefName,state";function iw(t,e,{delays:n=ew,turnEnd:r,openedWait:o}={}){let s=ow(e.command);if(!s)return null;let a=`merge|${t.id}|${e.id??e.ts??e.command}`;if(Qe.has(a))return null;Qe.add(a);let i=(async()=>{try{let c=s.repo,l=s.selector,u=l?.match(/github\.com\/([\w.-]+\/[\w.-]+)\/pull\/(\d+)/);u&&([,c,l]=u);let f=[];if(c)f=[c];else{let h=sw(t);if(h.length>1&&/^\d+$/.test(String(s.selector??"")))return null;for(let w of h){let g=await tt(w).catch(()=>null);g&&!f.includes(g)&&f.push(g)}}if(l||=t.workspace?.branch,!f.length||!l)return null;c=f[0];let d=async(h,w)=>{let g=await Et(c,h.number);return g&&g.trackId!==t.id?null:(g||await Lt({repo:c,number:h.number,trackId:t.id,url:h.url,headRef:h.headRefName,title:h.title,source:"merge-observed"}),await wo(c,h.number),w&&((await L(t).catch(()=>[])).some(A=>A.type==="pr.state.changed"&&(A.payload?.change==="auto-armed"||A.payload?.change==="merged")&&A.payload?.number===h.number)||(await Sl(e.box,e.agent,r),await O({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:Date.now(),change:"auto-armed",number:h.number,url:h.url,title:h.title,branch:h.headRefName,repo:c}}))),h)},m=null;for(let h of n){await new Promise($=>setTimeout($,h));let w=[];for(let $ of f)try{Re("observer");let{stdout:F}=await tw("gh",["pr","view",String(l),"--repo",$,"--json",kA],{timeout:3e4,maxBuffer:4*1024*1024,env:{...process.env,...await ee({slug:$})}}),T=JSON.parse(F||"null");T?.number&&w.push({slug:$,pr:T})}catch(F){Ye(F)&&Ze(F)}let g=w.find($=>$.pr.state==="MERGED")??(w.length===1?w[0]:null);if(!g)continue;c=g.slug;let b=g.pr;if(!b?.number)continue;if(b.state==="CLOSED")return null;if(b.state!=="MERGED"){if(m=b,s.auto)return d(b,!0);continue}let v=await Et(c,b.number);if(v&&v.trackId!==t.id)return null;v||await Lt({repo:c,number:b.number,trackId:t.id,url:b.url,headRef:b.headRefName,title:b.title,source:"merge-observed"}),await Sl(e.box,e.agent,r);let A=await L(t).catch(()=>[]);if(A.some($=>$.type==="pr.state.changed"&&$.payload?.change==="merged"&&$.payload?.number===b.number))return null;let M=$=>$.some(F=>F.type==="pr.state.changed"&&F.payload?.change==="opened"&&F.payload?.number===b.number),z=0;if(!M(A)){if(rw(e.command)&&await ho(c,b.number)){let $=Date.now();z=$+1,await O({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:$,change:"opened",number:b.number,url:b.url,title:b.title,branch:b.headRefName,repo:c,selfMerge:!0}})}else if((await Et(c,b.number))?.announced){let $=o?.capMs??uA,F=o?.pollMs??dA,T=Date.now()+$;for(;Date.now()<T&&(await new Promise(C=>setTimeout(C,F)),!M(await L(t).catch(()=>[]))););}}return await O({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:Math.max(Date.now(),z),change:"merged",number:b.number,url:b.url,title:b.title,branch:b.headRefName,repo:c}}),await en(c,b.number).catch(()=>{}),await tn(c,b.number).catch(()=>{}),await ze(c,b.number,"merged"),b}return m?d(m,!1):null}finally{Qe.delete(a)}})();return i.catch(()=>Qe.delete(a)),i}function Al(t){return t.sourceId?`${t.role}|id:${t.sourceId}`:`${t.role}|${t.ts??"?"}|${String(t.text||"").slice(0,40)}`}function cw(t){return t.id?`cmd|${t.box}|id:${t.id}`:`cmd|${t.box}|${t.ts??"?"}|${String(t.command||"").slice(0,40)}`}function lw(t){return`job|${t.box}|id:${t.id}`}var Tl=new Map,ka=new Map;async function _l(t){return Tl.has(t.id)?Tl.get(t.id):(ka.has(t.id)||ka.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(Al({role:o,sourceId:r.payload.sourceId})),n.keys.add(Al({role:o,ts:r.payload.ts,text:r.payload.text}))}else if(r.type==="tool.called"&&r.source==="transcript"&&r.payload?.command){let o=r.payload;n.keys.add(cw({id:o.id,box:r.boxId,ts:o.ts,command:o.command})),Number(o.n)>n.cmdN&&(n.cmdN=Number(o.n))}else r.type==="job.started"&&r.payload?.id&&n.keys.add(lw({id:r.payload.id,box:r.boxId}));return Tl.set(t.id,n),n}).finally(()=>ka.delete(t.id))),ka.get(t.id))}async function uw(t,e){try{let n=(await _l(t)).keys;for(let r of e||[]){if(!r||r.ts==null||!String(r.text||"").trim()&&!r.images?.length)continue;let o=Al(r);if(!n.has(o)&&(n.add(o),await O(t,{type:r.role==="user"?"turn.user":"turn.agent",actor:r.role==="user"?"user":"agent",source:"transcript",boxId:r.box,provider:r.agent||null,payload:{text:r.text,ts:r.ts,model:r.model||null,tools:r.tools||void 0,boxRole:r.boxRole,sourceId:r.sourceId||void 0,note:r.note||void 0,images:r.images?.length?r.images:void 0}}),r.role==="assistant"&&r.box===t.leadBox&&t.workspace?.branch)){let s=Lh(r.text);if(s){let{offered:a,toPr:i}=Uh(s),c;if(t.workspace?.kind==="multi"){let l=await _o(t).catch(()=>[]);c=l.length?l.map(u=>({name:u.name,slug:u.repo??null})):void 0}await O(t,{type:"pr.offered",actor:"agent",source:"transcript",boxId:r.box,provider:r.agent||null,payload:{offerId:xA(),ts:r.ts,excerpt:s,branch:t.workspace.branch,base:t.workspace.baseRef?.replace(/^origin\//,"")||null,boxRole:r.boxRole,offered:a||void 0,toPr:i||void 0,repos:c}})}}}}catch{}}async function dw(t,e){try{let n=await _l(t),r=[...e||[]].filter(o=>o&&String(o.command||"").trim()).sort((o,s)=>(o.ts||0)-(s.ts||0));for(let o of r){let s=cw(o);n.keys.has(s)||(n.keys.add(s),n.cmdN+=1,await O(t,{type:"tool.called",actor:"agent",source:"transcript",boxId:o.box,provider:o.agent||null,payload:{id:o.id??null,tool:o.tool||null,command:o.command,description:o.description||"",summary:o.summary||o.command,ts:o.ts??null,boxRole:o.boxRole,n:n.cmdN}}),aw(t,o),iw(t,o))}}catch{}}async function fw(t,e){try{let n=await _l(t),r=[...e||[]].filter(o=>o&&o.id).sort((o,s)=>(o.ts||0)-(s.ts||0));for(let o of r){let s=lw(o);n.keys.has(s)||(n.keys.add(s),await O(t,{type:"job.started",actor:"agent",source:"transcript",boxId:o.box,provider:o.agent||null,payload:{id:o.id,kind:o.kind||"agent",name:o.name||null,detail:o.detail||null,ts:o.ts??null,boxRole:o.boxRole}}))}}catch{}}var SA=/^Base directory for this skill:\s*(\S+)/,vA=/^Run the "([^"]+)" workflow\./,TA=/<command-name>\s*\/?([a-z0-9:_-]+)\s*<\/command-name>/i,AA=/^<local-command-(?:stdout|caveat)>/i,_A=/^\[Image: original \d+x\d+, displayed at \d+x\d+\.\s*Multiply coordinates by [\d.]+ to map to original image\.\]$/,EA=/^Continue from where you left off\.$/,IA=/^<task-notification>/i,RA=/<summary>([\s\S]*?)<\/summary>/i,$A=/<task-id>([\s\S]*?)<\/task-id>/i,OA=/<tool-use-id>([\s\S]*?)<\/tool-use-id>/i,MA=/<status>([\s\S]*?)<\/status>/i,CA=/<result>([\s\S]*?)(?:<\/result>|$)/i;var El=new Set(["model","clear","compact","login","exit","quit"]);function pw(t){return El.has(String(t||"").replace(/^\//,"").toLowerCase())}var NA=/^\/([a-z0-9:_-]+)$/i;function xa(t){let e=String(t||"").trim();if(!e)return null;let n=e.match(SA);if(n)return{kind:"skill",name:n[1].split("/").filter(Boolean).pop()||"skill"};let r=e.match(vA);if(r)return{kind:"skill",name:r[1]};let o=e.match(TA);if(o){let a=o[1].toLowerCase();return El.has(a)?{kind:"suppress"}:{kind:"command",command:`/${a}`}}let s=e.match(NA);if(s&&El.has(s[1].toLowerCase()))return{kind:"suppress"};if(AA.test(e))return{kind:"suppress"};if(EA.test(e))return{kind:"suppress"};if(_A.test(e))return{kind:"suppress"};if(IA.test(e)){let a=c=>PA(e.match(c)?.[1].trim()||"")||null,i=a(CA);return{kind:"task",summary:a(RA)||"",taskId:a($A),toolUseId:a(OA),status:a(MA)?.toLowerCase()||null,resultHead:i?i.length>600?`${i.slice(0,600).trimEnd()}\u2026`:i:null}}return null}function PA(t){return t.replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/'/g,"'").replace(/&/g,"&")}function mw(t){return t?.kind==="skill"?`Ran the ${t.name} skill`:t?.kind==="command"?`Ran ${t.command}`:t?.kind==="task"?t.summary||"A background task finished":""}function hw(t){return t?.kind==="skill"?"skill-run":t?.kind==="task"?"task-done":"command-run"}var it=t=>t.payload?.ts??t.ts,BA=new Set(["opened","ci-fixed","reconciled","pushed"]),ww=t=>`${t.repo??""}#${t.number}`;function DA(t,e,n){let r=ww(n),o=t.has(r)?r:null;if(!o&&n.number!=null)for(let[i,c]of t){if(Number(c.number)!==Number(n.number))continue;let l=!!(n.url&&c.url&&n.url===c.url),u=!c.repo&&!!n.repo||!!c.repo&&!n.repo;if(l||u&&(!c.url||!n.url||c.url===n.url)){o=i;break}}let s=n.repo||!o?r:o,a=o?t.get(o):t.get(s);if(o&&o!==s){t.delete(o);let i=e.get(o);if(e.delete(o),i?.length){let c=e.get(s)??[];e.set(s,c.concat(i))}}return t.set(s,{number:n.number,url:n.url??a?.url??null,title:n.title??a?.title??null,repo:n.repo??a?.repo??null,branch:n.branch??a?.branch??null}),s}function Eo(t){let e=[],n=new Set,r=new Set,o=new Map,s=new Map,a=new Set,i=new Map,c=new Set,l=new Map,u=/^Agent "([\s\S]+)" finished$/,f=new Map;for(let y of t||[]){if(y.type!=="tool.called"||y.source!=="transcript")continue;let p=String(y.payload?.command||"");if(/\bbostrat-mock\s+(?:add|update)\b/.test(p))for(let S of p.matchAll(/[\w./~-]+\.(?:png|jpe?g|gif|webp|bmp|svg)\b/gi)){let _=S[0].split("/").pop().toLowerCase();f.has(_)||f.set(_,[]),f.get(_).push(y.payload?.ts??y.ts)}}let d=3*6e4,m=20*6e4,h=(y,p)=>!!(y?.name&&f.get(String(y.name).toLowerCase()))?.some(_=>_>=p-d&&_<=p+m);for(let y of t||[])if(y.type==="turn.retracted"){r.add(`${y.boxId}|${y.payload?.saidTs}`);let p=Yt(y.payload?.text);p&&(o.has(y.boxId)||o.set(y.boxId,[]),o.get(y.boxId).push(p))}else if(y.type==="turn.user"&&y.source==="orchestrator"){let p=Yt(y.payload?.text);p&&(s.has(y.boxId)||s.set(y.boxId,new Set),s.get(y.boxId).add(p))}else if(y.type==="job.started"&&y.payload?.id)a.add(y.payload.id),(y.payload.kind||"agent")==="agent"&&y.payload.name&&c.add(y.payload.name);else if(y.type==="turn.user"&&y.payload?.note?.kind==="task"){let p=y.payload.note,S={status:p.status||null,summary:p.summary||null,resultHead:p.resultHead??null,ts:it(y)};p.toolUseId&&i.set(p.toolUseId,S);let _=String(p.summary||"").match(u);_&&l.set(_[1],S)}let w=new Map,g=new Map,b=null,v=new Map,A=new Map,M=new Map,z=new Map,$=new Map,F=new Map,T=new Map,C=6e4,W=new Map,ot=(y,p)=>{let S=o.get(y);if(!S?.length)return!1;let _=Yt(p),rt=s.get(y);return S.some(I=>{if(!_.startsWith(I))return!1;let R=Yt(_.slice(I.length));return!!R&&!!rt?.has(R)})};for(let y of t||[]){let p=y.payload||{};switch(y.type){case"turn.user":{if(y.source==="hook"||!String(p.text||"").trim()&&!p.images?.length)break;{let S=p.note||xa(p.text);if(S){if(S.kind==="task"){if(S.toolUseId&&a.has(S.toolUseId))break;let _=String(S.summary||"").match(u);if(!S.toolUseId&&_&&c.has(_[1]))break}if(S.kind==="command"&&pw(S.command))break;S.kind!=="suppress"&&e.push({id:y.id,kind:"event",type:hw(S),name:S.name??null,command:S.command??null,summary:S.summary??null,ts:it(y),box:y.boxId,agent:y.provider});break}}if(r.has(`${y.boxId}|${it(y)}`)||y.source==="transcript"&&ot(y.boxId,p.text))break;if(p.kind==="merge-notify"||p.kind==="ship-notify"){e.push({id:y.id,kind:"event",type:p.kind==="ship-notify"?"ship-notified":"merge-notified",text:p.text,ts:it(y),box:y.boxId,agent:y.provider});break}if(p.kind==="mock-picked"||p.kind==="ci-fix"||p.kind==="reconcile"||p.kind==="pr-go"||p.kind==="push-go"||p.kind==="spin-up-view"||p.kind==="prompt-answer")break;for(let S of v.values())S.status="stale";v.clear(),e.push({id:y.id,kind:"message",role:"user",text:p.text,ts:it(y),box:y.boxId,agent:y.provider,boxRole:p.boxRole,...y.actor==="orchestrator"?{orchestration:p.kind||!0}:{},...p.sourceId?{sourceId:p.sourceId}:{},...p.attachments?.length?{attachments:p.attachments}:{},...p.images?.length?{images:p.images}:{}});break}case"turn.agent":{if(p.model==="<synthetic>"&&/^No response requested\.$/.test(String(p.text||"").trim()))break;T.clear();let S=(p.images||[]).filter(R=>!h(R,it(y)));if(!String(p.text||"").trim()&&!S.length)break;if(y.provider==="cursor"){let R=`${y.boxId}|${p.text}`;if(n.has(R))break;n.add(R)}let _=y.provider==="codex"?(p.tools||[]).filter(R=>!Hc.has(R)&&!xo.has(R)):p.tools||[],rt=p.text,I=null;if(ol.has(y.provider)){let R=sl(String(p.text||""));R&&(rt=R.text,I={questions:R.questions})}e.push({id:y.id,kind:"message",role:"assistant",text:rt,ts:it(y),box:y.boxId,agent:y.provider,boxRole:p.boxRole,...I?{ask:I}:{},..._.length?{tools:_}:{},...p.model?{model:p.model}:{},...p.sourceId?{sourceId:p.sourceId}:{},...S.length?{images:S}:{}});break}case"job.started":{if(!p.id)break;let S=it(y),_=i.get(p.id);if(!_&&(p.kind||"agent")==="agent"&&p.name){let P=l.get(p.name);P&&P.ts>=S&&(_=P)}let rt={jobId:p.id,jobKind:p.kind||"agent",name:p.name||null,detail:p.detail||null,status:_?_.status&&_.status!=="completed"?"failed":"done":"running",startedTs:S,finishedTs:_?.ts??null,summary:_?.summary??null,resultHead:_?.resultHead??null},I=W.get(y.boxId),R=I?.jobs[I.jobs.length-1]?.startedTs;if(I&&R!=null&&S-R<=C)I.jobs.push(rt);else{let P={id:y.id,kind:"job-dock",dockId:p.id,ts:S,box:y.boxId,agent:y.provider,jobs:[rt]};W.set(y.boxId,P),e.push(P)}break}case"handoff.done":e.push(nn(y,"lead-handoff"));break;case"subtask.delegated":e.push({...nn(y,"subtask-delegated"),to:y.boxId,toAgent:y.provider});break;case"subtask.merged":e.push(nn(y,"subtask-merged"));break;case"pr.state.changed":{if(!p.change)break;if(p.change==="opened"){for(let _ of v.values())_.status="stale";v.clear()}let S=nn(y,`pr-${p.change}`);if(BA.has(p.change)&&p.number!=null){let _=DA(w,g,p);if(w.size>=2){for(let I of g.values())for(let R of I)R.grouped=!0;S.groupPrs=[...w.values()];let rt=[S];for(let I of w.keys())g.set(I,rt)}else{let rt=g.get(_)??[];rt.push(S),g.set(_,rt)}}else if(p.change==="merged"||p.change==="closed"||p.change==="lost"){let _=ww(p),rt=b&&!b.complete||w.size>=2;if(w.has(_))w.delete(_),g.delete(_);else if(p.number!=null)for(let I of[...w.keys()])I.endsWith(`#${p.number}`)&&(w.delete(I),g.delete(I));if(rt&&p.number!=null){(!b||b.complete)&&(b={id:y.id,kind:"event",type:"pr-group-settled",prs:[],remaining:[],complete:!1,at:it(y),ts:it(y),box:y.boxId,agent:y.provider},e.push(b)),b.prs.push({number:p.number,url:p.url??null,title:p.title??null,repo:p.repo??null,branch:p.branch??null,change:p.change,ts:it(y)}),b.remaining=[...w.values()],b.complete=w.size===0;break}}e.push(S);break}case"pr.offered":{if(!p.offerId)break;for(let _ of v.values())_.status="stale";v.clear();let S={id:y.id,kind:"pr-offer",offerId:p.offerId,excerpt:p.excerpt||null,branch:p.branch||null,base:p.base||null,offered:Array.isArray(p.offered)&&p.offered.length?p.offered:null,toPr:p.toPr&&typeof p.toPr=="object"?{number:p.toPr.number??null}:null,repos:Array.isArray(p.repos)&&p.repos.length?p.repos:null,status:"open",ts:it(y),box:y.boxId,agent:y.provider};v.set(p.offerId,S),A.set(p.offerId,S),e.push(S);break}case"pr.offer.answered":{let S=A.get(p.offerId);S&&(S.status=p.action||"dismiss",S.actions=Array.isArray(p.actions)?p.actions:null,S.notify=p.notify===!0,v.delete(p.offerId));break}case"box.state.changed":{let S=p.change==="model"?"model-change":p.change;if(!S)break;let _=nn(y,S);S==="model-change"&&(_.to=p.model),e.push(_);break}case"view.state.changed":{if(p.change!=="created"||p.restarted)break;e.push({id:y.id,kind:"view-card",viewId:p.viewId,name:p.name,slug:p.slug,port:p.port,origin:p.origin,ts:it(y),box:y.boxId,agent:y.provider});break}case"view.spinup.failed":{e.push({id:y.id,kind:"view-spinup-failed",name:p.name||null,reason:p.reason||"no-view",ts:it(y),box:y.boxId,agent:y.provider});break}case"mock.state.changed":{if(p.change==="picked"){e.push({id:y.id,kind:"mock-picked",mockId:p.mockId,name:p.name,group:p.group??null,version:p.version??null,siblings:p.siblings??[],ts:it(y),box:y.boxId,agent:y.provider});break}if(p.change!=="created")break;e.push({id:y.id,kind:"mock-card",mockId:p.mockId,name:p.name,group:p.group??null,ts:it(y),box:y.boxId,agent:y.provider});break}case"runbook.state.changed":{if(p.change!=="created")break;e.push({id:y.id,kind:"runbook-card",runbookId:p.runbookId,name:p.name,ts:it(y),box:y.boxId,agent:y.provider});break}case"ticket.linked":{e.push({id:y.id,kind:"ticket-card",provider:p.provider||"linear",key:p.key,url:p.url,title:p.title??null,linkSource:p.linkSource??null,ts:it(y),box:y.boxId,agent:y.provider});break}case"ticket.move.offered":{if(!p.offerId)break;for(let _ of M.values())_.key===p.key&&(_.status="stale",M.delete(_.offerId));let S={id:y.id,kind:"ticket-offer",offerId:p.offerId,provider:p.provider||"linear",key:p.key,url:p.url??null,phase:p.phase??null,from:p.from??null,to:p.to?.name??null,prNumber:p.prNumber??null,status:"open",ts:it(y),box:y.boxId,agent:y.provider};M.set(p.offerId,S),z.set(p.offerId,S),e.push(S);break}case"ticket.move.answered":{let S=z.get(p.offerId);S&&(S.status=p.action==="moved"?"moved":"dismiss",M.delete(p.offerId));break}case"ticket.state.changed":{for(let S of M.values())S.key===p.key&&S.to===p.to&&(S.status="stale",M.delete(S.offerId));if(p.mode==="offer")break;e.push(nn(y,"ticket-state"));break}case"ticket.commented":{e.push({id:y.id,kind:"ticket-comment",key:p.key,url:p.url??null,excerpt:p.excerpt??null,ts:it(y),box:y.boxId,agent:y.provider});break}case"ticket.flow.changed":e.push(nn(y,"ticket-flow"));break;case"ticket.create.offered":{if(!p.proposalId)break;for(let _ of $.values())_.status="stale";$.clear();let S={id:y.id,kind:"ticket-create-offer",proposalId:p.proposalId,provider:p.provider||"linear",epic:p.epic??null,issues:Array.isArray(p.issues)?p.issues:[],team:p.team??null,needsTeam:!!p.needsTeam,linkCurrentTrack:!!p.linkCurrentTrack,status:"open",ts:it(y),box:y.boxId,agent:y.provider};$.set(p.proposalId,S),F.set(p.proposalId,S),e.push(S);break}case"ticket.create.answered":{let S=F.get(p.proposalId);S&&(S.status=p.action==="created"?"created":"dismiss",S.createdCount=p.createdCount??null,S.failedCount=p.failedCount??null,$.delete(p.proposalId));break}case"ticket.created":{let S={items:Array.isArray(p.items)?p.items:[],failed:Array.isArray(p.failed)?p.failed:[],team:p.team??null},_=p.proposalId?F.get(p.proposalId):null;if(_){_.created=S;break}e.push({id:y.id,kind:"ticket-created",...S,ts:it(y),box:y.boxId,agent:y.provider});break}case"error":{if(!p.kind)break;e.push(nn(y,p.kind));break}case"provider.connected":{e.push({id:y.id,kind:"event",type:"provider-connected",provider:p.provider||y.provider,ts:it(y),at:it(y),box:y.boxId,agent:y.provider});break}case"prompt.answered":{if(p.control||!p.question)break;let S=p.groupId||p.promptId;if(!S)break;let _=T.get(S);_||(_={id:y.id,kind:"prompt",groupId:S,provider:y.provider,box:y.boxId,agent:y.provider,questionTotal:p.questionTotal??1,questions:[],ts:it(y)},T.set(S,_));let rt=p.questionIndex??0,I={index:rt,header:p.header||"",question:p.question,multiSelect:!!p.multiSelect,chosen:Array.isArray(p.chosen)?p.chosen:[]},R=_.questions.findIndex(P=>P.index===rt);R>=0?_.questions[R]=I:_.questions.push(I),_.questions.sort((P,et)=>P.index-et.index),_.questionTotal=Math.max(_.questionTotal,p.questionTotal??1),new Set(_.questions.map(P=>P.index)).size>=_.questionTotal&&(e.push(_),T.delete(S));break}default:break}}return LA(e.sort((y,p)=>(y.ts??0)-(p.ts??0)))}var jA=2*6e4;function LA(t){let e=new Map,n=[];for(let r of t){if(r.kind!=="message"||!r.box){n.push(r);continue}let o=r.role==="assistant"&&!String(r.text||"").trim()&&r.images?.length,s=e.get(r.box);if(o&&s&&(r.ts??0)-s.lastTs<=jA){s.item.images.push(...r.images),s.lastTs=r.ts??s.lastTs;continue}if(o){let a={...r,images:[...r.images]};e.set(r.box,{item:a,lastTs:r.ts??0}),n.push(a)}else e.delete(r.box),n.push(r)}return n}function Il(t,e=null){let n=[];for(let o of t||[]){if(o.type!=="tool.called"||o.source!=="transcript")continue;let s=o.payload||{};String(s.command||"").trim()&&n.push({id:s.id??null,tool:s.tool||null,command:s.command,description:s.description||"",summary:FA(s),ts:s.ts??o.ts,n:s.n,box:o.boxId,agent:o.provider,boxRole:s.boxRole})}if(!n.length)return[];let r=(e??Eo(t)).filter(o=>o.kind==="message");return Rm(n,r)}function FA(t){let e=String(t.summary||"").trim();return e&&e!==t.command&&e!==fr(t.command)?e:Ve(t.command,t.description||"")}function nn(t,e){let{ts:n,change:r,...o}=t.payload||{};return{id:t.id,kind:"event",type:e,...o,at:it(t),ts:it(t)}}function UA(t){let e=[],n=null;for(let r of String(t||"").split(`
|
|
142
|
+
`)){let o=r.trim();if(!o||o[0]!=="{")continue;let s;try{s=JSON.parse(o)}catch{continue}let a=s.payload||{};if(s.type==="turn_context"&&a.model&&(n=a.model),s.type==="event_msg"&&a.type==="token_count"&&a.info?.last_token_usage){let i=a.info.last_token_usage;e.push({ts:s.timestamp?Date.parse(s.timestamp):null,model:n,last:{input:i.input_tokens||0,cachedInput:i.cached_input_tokens||0,output:i.output_tokens||0,reasoningOutput:i.reasoning_output_tokens||0},occupancy:i.total_tokens??null,contextWindow:a.info.model_context_window??null})}}return e}var Rl=new Map;async function HA(t,e){let n=`${t.id}:${e.name}`;if(!Rl.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);Rl.set(n,o)}return Rl.get(n)}async function gw(t,e){try{if(e?.agent!=="codex"||!e.cwd)return;let n=await En(e.cwd,e.pinned?e.sessionId:null);if(!n)return;let r=await HA(t,e),o=UA(await Ot(n.file,262144)),s=o[o.length-1];for(let a of o){if(a.ts==null||r.has(a.ts))continue;r.add(a.ts);let i={input:Math.max(0,a.last.input-a.last.cachedInput),output:a.last.output,cacheRead:a.last.cachedInput,cacheWrite:0},c=a.model||e.model||"gpt-5.5";await O(t,{type:"cost.recorded",actor:"agent",source:"transcript",boxId:e.name,provider:"codex",payload:{usd:oe(c,i),model:c,tokens:i,reasoningOutput:a.last.reasoningOutput||void 0,codexTs:a.ts,ts:a.ts}}),a===s&&a.contextWindow&&await O(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 Sa from"node:fs";import Ol from"node:path";import WA from"node:os";var qA=process.env.BOSTRAT_STATE_DIR||Ol.join(process.env.HOME||WA.tmpdir(),".bostrat"),bw=(t=process.env)=>!!t.BOSTRAT_CURSOR_USAGE_COOKIE,KA="https://cursor.com/api/dashboard/get-filtered-usage-events",yw=100;async function GA({sinceMs:t,cookie:e,fetchImpl:n=fetch}){let r=[];for(let o=1;o<=20;o++){let s=await n(KA,{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:yw})});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<yw)break}return r}function zA(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 VA=5*6e4,rn={at:0,rows:null,error:null},va=()=>Ol.join(qA,"cursor-usage.json");function kw(){try{return JSON.parse(Sa.readFileSync(va(),"utf8"))}catch{return{sinceMs:0,seen:[]}}}function JA(t){try{Sa.mkdirSync(Ol.dirname(va()),{recursive:!0});let e=va()+".tmp";Sa.writeFileSync(e,JSON.stringify(t)),Sa.renameSync(e,va())}catch{}}var XA=5e3;async function YA({fetchImpl:t,now:e=Date.now()}={}){if(rn.rows&&e-rn.at<VA)return rn.rows;let n=process.env.BOSTRAT_CURSOR_USAGE_COOKIE;if(!n)return null;let r=kw(),o=Math.max(r.sinceMs-6e4,e-24*36e5);try{let s=await GA({sinceMs:o,cookie:n,fetchImpl:t});rn={at:e,rows:s.map(zA).filter(Boolean),error:null}}catch(s){rn={at:e,rows:null,error:String(s?.message||s)}}return rn.rows}var Ml=()=>({enabled:bw(),lastPollAt:rn.at||null,error:rn.error}),$l=new Map;async function ZA(t,e){let n=`${t.id}:${e.name}`;if(!$l.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);$l.set(n,o)}return $l.get(n)}async function xw(t,e,{fetchImpl:n}={}){try{if(e?.agent!=="cursor"||!e.cwd||!bw())return;let r=await YA({fetchImpl:n});if(!r||!r.length)return;let o=await jm(e.cwd,e.pinned?e.sessionId:null);if(!o?.chatId)return;let s=await ZA(t,e),a=kw(),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=oe(u,f)),!d&&l.chargedCents!=null&&(d=l.chargedCents/100),await O(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}})}JA({sinceMs:c,seen:[...i].slice(-XA)})}catch{}}import{execFile as r_}from"node:child_process";import{promisify as o_}from"node:util";import Sw from"node:fs/promises";import vw from"node:path";import QA from"node:os";var Tw=process.env.BOSTRAT_STATE_DIR||vw.join(process.env.HOME||QA.tmpdir(),".bostrat"),t_=vw.join(Tw,"github-latency.ndjson"),e_=100,Ta=[],Cl=new Map;function Aa({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};Ta.push(l),Ta.length>e_&&Ta.shift();let u=Cl.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)),Cl.set(t,u),n_(l),l}async function n_(t){try{await Sw.mkdir(Tw,{recursive:!0}),await Sw.appendFile(t_,JSON.stringify(t)+`
|
|
143
|
+
`)}catch{}}function Aw(){let t={};for(let[e,n]of Cl)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:Ta.slice(-20)}}var Ia=o_(r_),s_={timeout:3e4,maxBuffer:8*1024*1024};async function Gt(t,e,n={}){let{caller:r="prs",...o}=n,s=await ee({cwd:t});Re(r);try{let{stdout:a}=await Ia("gh",e,{cwd:t,...s_,...o,env:{...process.env,...s,...o.env||{}}});return String(a||"")}catch(a){throw Ye(a)&&Ze(a),a}}var _a=null;async function Bl(){if(_a&&Date.now()-_a.at<6e4)return _a.data;let t;try{Re("auth");let e=await Ia("gh",["auth","status"],{timeout:1e4});t={installed:!0,authenticated:!0,login:`${e.stdout||""}${e.stderr||""}`.match(/account\s+(\S+)/)?.[1]??null}}catch(e){t=e?.code==="ENOENT"?{installed:!1,authenticated:!1,login:null}:{installed:!0,authenticated:!1,login:null}}if(t.installed&&!t.authenticated)try{let e=await Gr();e.configured&&(t={installed:!0,authenticated:!0,login:e.username,via:"broker"})}catch{}return _a={at:Date.now(),data:t},t}async function a_(t){try{let{stdout:e}=await Ia("git",["-C",t,"config","--get","remote.origin.url"],{timeout:5e3});return/github\.com/.test(e)}catch{return!1}}function Ew(t){let e=t.name||t.context||"check",n=t.detailsUrl||t.targetUrl||null,r=t.completedAt||t.createdAt||null,o;return t.__typename==="StatusContext"?o=t.state==="SUCCESS"?"pass":t.state==="PENDING"||t.state==="EXPECTED"?"pending":"fail":t.status&&t.status!=="COMPLETED"?o="pending":["SUCCESS","NEUTRAL","SKIPPED"].includes(t.conclusion)?o="pass":o="fail",{name:e,state:o,url:n,completedAt:r}}function Nl(t){let e=(t||[]).map(Ew),n=e.filter(s=>s.state==="pending").length,r=e.map(s=>Date.parse(s.completedAt||"")).filter(s=>s>0),o=e.length&&!n&&r.length?Math.max(...r):null;return{total:e.length,passed:e.filter(s=>s.state==="pass").length,failed:e.filter(s=>s.state==="fail").length,pending:n,failing:e.filter(s=>s.state==="fail").map(({name:s,url:a})=>({name:s,url:a})),settledAt:o}}var Dl="number,title,url,headRefName,headRefOid,baseRefName,isDraft,author,reviewDecision,mergeable,mergeStateStatus,statusCheckRollup,autoMergeRequest,additions,deletions,createdAt,updatedAt",Iw=Dl.replace("statusCheckRollup,","");function jl(t){return/Resource not accessible by personal access token/.test(String(t?.stderr||t?.message||""))}async function Rw(t,e){if(!e)return null;let n=await tt(t);if(!n)return null;try{let r=await Gt(t,["api",`repos/${n}/actions/runs?head_sha=${encodeURIComponent(e)}&per_page=100`,"--jq",".workflow_runs"]),o=new Map;for(let s of JSON.parse(r||"[]"))o.has(s.name)||o.set(s.name,s);return[...o.values()]}catch{return null}}function $w(t){return(t||[]).map(e=>({name:e.name,detailsUrl:e.html_url||null,status:String(e.status||"").toUpperCase(),conclusion:e.conclusion?String(e.conclusion).toUpperCase():null,completedAt:e.status==="completed"&&e.updated_at||null}))}async function Ow(t,e){let n=await Rw(t,e);return n===null?{...Nl([]),unavailable:!0}:Nl($w(n))}function Ea(t,e=Nl(t.statusCheckRollup)){return{number:t.number,title:t.title,url:t.url,author:t.author?.login??null,headRefName:t.headRefName,headSha:t.headRefOid??null,baseRefName:t.baseRefName,isDraft:!!t.isDraft,reviewDecision:t.reviewDecision||null,mergeable:t.mergeable||"UNKNOWN",mergeStateStatus:t.mergeStateStatus||"UNKNOWN",additions:t.additions??0,deletions:t.deletions??0,createdAt:t.createdAt,updatedAt:t.updatedAt,checks:e,green:e.failed===0&&e.pending===0&&t.mergeStateStatus==="CLEAN",autoMergeArmed:!!t.autoMergeRequest,mergeabilityPending:!1}}var i_=9e4,xr=new Map;function c_(t,e,n){let r=new Set;for(let o of e){let s=`${t}#${o.number}`;if(o.mergeable!=="UNKNOWN"&&o.mergeStateStatus!=="UNKNOWN"){xr.delete(s);continue}r.add(s),xr.has(s)||xr.set(s,n),o.mergeabilityPending=n-xr.get(s)<i_}for(let o of xr.keys())o.startsWith(`${t}#`)&&!r.has(o)&&xr.delete(o);return e}async function Sr(t,e){let n=await $e(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");try{let o=await Gt(n.path,["pr","view",String(r),"--json",Dl]);return Ea(JSON.parse(o))}catch(o){if(!jl(o))throw o;let s=await Gt(n.path,["pr","view",String(r),"--json",Iw]),a=JSON.parse(s);return Ea(a,await Ow(n.path,a.headRefOid))}}var _w=(t,e)=>t.number-e.number;async function Ro(t,{caller:e="prs"}={}){try{let n=await Gt(t,["pr","list","--json",Dl,"--limit","50"],{caller:e});return JSON.parse(n||"[]").map(r=>Ea(r)).sort(_w)}catch(n){if(!jl(n))throw n;let r=await Gt(t,["pr","list","--json",Iw,"--limit","50"],{caller:e});return(await Promise.all(JSON.parse(r||"[]").map(async o=>Ea(o,await Ow(t,o.headRefOid))))).sort(_w)}}var l_=3e4,u_=1500,Io=new Map;function he(){Io.clear()}async function d_(t,e){if(!t)return e;for(let n of e){let r=await Et(t,n.number).catch(()=>null);if(!r)continue;r.trackId&&(n.trackId=r.trackId),r.autoMerge&&(n.autoMergeArmed=!0),r.archiveOnMerge&&(n.archiveOnMerge=!0),r.notifyOnMerge&&(n.notifyOnMerge=!0);let o=s=>n.headSha&&s.headSha&&n.headSha!==s.headSha;r.ciFix&&(o(r.ciFix)?await ea(t,n.number,{headSha:n.headSha}).catch(()=>{}):n.ciFix={delegatedAt:r.ciFix.delegatedAt,trackId:r.ciFix.trackId??null}),r.reconcile&&(o(r.reconcile)?await ra(t,n.number,{headSha:n.headSha}).catch(()=>{}):n.reconcile={delegatedAt:r.reconcile.delegatedAt,trackId:r.reconcile.trackId??null})}return e}async function Mw(){let t=await Bl();if(!t.authenticated)return{auth:t,repos:[]};let e=[];for(let a of await ye())await a_(a.path)&&e.push(a);let n=Date.now(),r=ya(n)>0,o=await Promise.all(e.map(async a=>{let i=Io.get(a.path),c=i?.pending?u_:l_;if(i&&(r||n-i.at<c))return i.entry;try{let l=await tt(a.path).catch(()=>null),u=c_(a.path,await Ro(a.path,{caller:"tab"}),n);if(await d_(l,u),i?.entry?.prs)for(let m of u)i.entry.prs.find(w=>w.number===m.number)?.checks?.pending>0&&m.checks.total>0&&m.checks.pending===0&&Aa({change:m.checks.failed>0?"checks-failed":"checks-green",repo:l||a.name,number:m.number,githubAt:m.checks.settledAt,source:"tab"});let f={name:a.name,path:a.path,prs:u},d=u.some(m=>m.mergeabilityPending||m.checks.pending>0||m.autoMergeArmed);return Io.set(a.path,{at:Date.now(),entry:f,pending:d}),f}catch(l){return i?i.entry:{name:a.name,path:a.path,prs:[],error:me(l)}}})),s=new Set(e.map(a=>a.path));for(let a of Io.keys())s.has(a)||Io.delete(a);return{auth:t,repos:o}}async function $e(t){let e=(await ye()).find(n=>n.path===t);if(!e)throw new Error(`unknown repo: ${t}`);return e}async function Nn(t){return qr(t)}var $o=new Set(["squash","merge","rebase"]);async function vr(t,e,{method:n="squash"}={}){let r=await $e(t);if(!$o.has(n))throw new Error(`invalid merge method: ${n}`);let o=Number(e);if(!Number.isInteger(o)||o<=0)throw new Error("invalid PR number");let s=["pr","merge",String(o),`--${n}`];if(n!=="rebase"&&(await ee({cwd:r.path})).GH_TOKEN){let a=await di().catch(()=>null);a&&s.push("--body",`Merged by bostrat on behalf of @${a}`)}try{await Gt(r.path,s)}catch(a){throw new Error(me(a))}finally{he()}return{ok:!0,number:o}}async function Oo(t,e){let n=await $e(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");try{await Gt(n.path,["api","-X","PUT",`repos/{owner}/{repo}/pulls/${r}/update-branch`])}catch(s){throw new Error(me(s))}finally{he()}let o=await tt(n.path).catch(()=>null);return o&&await lm(o,r).catch(()=>{}),{ok:!0,number:r}}async function Ll(t,e){let n=await $e(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");try{await Gt(n.path,["pr","merge",String(r),"--disable-auto"])}catch(o){throw new Error(me(o))}finally{he()}return{ok:!0,number:r}}var f_=2500,p_=9e4,m_=t=>new Promise(e=>setTimeout(e,t));function Fl(t){return t.green&&!t.isDraft}function h_(t){return t.isDraft?"draft":t.mergeable==="CONFLICTING"?`conflicts with ${t.baseRefName}`:t.checks.failed>0?"checks failing":t.checks.pending>0?"checks running":t.mergeable==="UNKNOWN"||t.mergeStateStatus==="UNKNOWN"?"mergeability unresolved":t.mergeStateStatus==="BEHIND"?`behind ${t.baseRefName}`:t.mergeStateStatus==="BLOCKED"?"blocked by branch protection":`not clean (${t.mergeStateStatus})`}function Cw(t,{update:e=!0}={}){return t.isDraft?{action:"skip",reason:"draft"}:Fl(t)?{action:"merge"}:e&&t.mergeStateStatus==="BEHIND"&&t.mergeable!=="CONFLICTING"&&t.checks.failed===0?{action:"update-arm"}:{action:"arm"}}function Nw(t){return[...t.filter(e=>e.plan.action!=="merge"),...t.filter(e=>e.plan.action==="merge")]}var Pl=new Map;function Pw(){let t={};for(let[e,n]of Pl)t[e]={...n};return t}async function Bw(t,{method:e="merge",onMerged:n=null}={}){let r=await $e(t);if(!$o.has(e))throw new Error(`invalid merge method: ${e}`);if(Pl.get(r.path)?.running)throw new Error("a merge-all is already running for this repo");let o=(await Ro(r.path)).filter(Fl).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 Pl.set(r.path,s),g_(s,o,{listPrs:()=>Ro(r.path),merge:a=>vr(r.path,a,{method:e}),sleep:m_,onMerged:n}).catch(()=>{}),{ok:!0,repo:r.path,total:s.total,queue:o.map(a=>a.number)}}async function w_(t,e){let n=Date.now()+(e.settleMaxMs??p_),r=null;for(;;){let o=null;try{if(o=await e.listPrs(),r=null,!o.some(a=>t.includes(a.number)&&(a.mergeable==="UNKNOWN"||a.mergeStateStatus==="UNKNOWN")))return o}catch(s){r=s}if(Date.now()>=n){if(o)return o;if(r)throw r;return[]}await e.sleep(f_)}}async function g_(t,e,n){try{let r=e;for(let o=0;o<e.length;o++){let s=e[o];if(t.current=s.number,o>0)try{r=await w_(e.slice(o).map(i=>i.number),n)}catch(i){for(let c of e.slice(o))t.failed.push({number:c.number,title:c.title,reason:`couldn't re-check PR state: ${me(i)}`});break}let a=r.find(i=>i.number===s.number);if(!a){t.skipped.push({number:s.number,title:s.title,reason:"no longer open"});continue}if(!Fl(a)){t.skipped.push({number:a.number,title:a.title,reason:h_(a)});continue}try{await n.merge(a.number),t.merged.push({number:a.number,title:a.title});try{await n.onMerged?.(a)}catch{}}catch(i){t.failed.push({number:a.number,title:a.title,reason:me(i)})}}}finally{t.current=null,t.running=!1,t.finishedAt=Date.now(),he()}}async function Ul(t,{title:e,body:n="",base:r=null}={}){if(typeof e!="string"||!e.trim())throw new Error("title required");try{await Ia("git",["-C",t,"push","-u","origin","HEAD"],{timeout:12e4,maxBuffer:4*1024*1024})}catch(l){throw new Error(`push failed: ${me(l)}`)}let o=n||"";if((await ee({cwd:t})).GH_TOKEN){let l=await di().catch(()=>null);l&&(o=`${o?`${o}
|
|
144
|
+
|
|
145
|
+
`:""}Opened by bostrat on behalf of @${l}`)}let s=["pr","create","--title",e.trim(),"--body",o];r&&s.push("--base",r);let a;try{a=await Gt(t,s)}catch(l){throw new Error(me(l))}finally{he()}let i=a.match(/https:\/\/\S+\/pull\/\d+/)?.[0]??null,c=i?Number(i.match(/\/pull\/(\d+)/)?.[1]):null;return{ok:!0,url:i,number:c}}async function Dw(t,e){let n=await $e(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");let o;try{let i;try{i=await Gt(n.path,["pr","checks",String(r),"--json","name,state,link,bucket"])}catch(c){if(i=String(c?.stdout||""),!i.trim())throw new Error(me(c))}o=JSON.parse(i||"[]").filter(c=>c.bucket==="fail")}catch(i){if(!jl(i))throw i;let c=await Gt(n.path,["pr","view",String(r),"--json","headRefOid"]),l=await Rw(n.path,JSON.parse(c).headRefOid);if(l===null)throw new Error(me(i));o=$w(l).map(Ew).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=y_(i.link),l=null;c&&!s.has(c)&&(s.add(c),l=await Gt(n.path,["run","view",c,"--log-failed"]).then(u=>b_(u)).catch(()=>null)),a.push({name:i.name,link:i.link||null,log:l})}return a}function y_(t){return String(t||"").match(/\/actions\/runs\/(\d+)/)?.[1]??null}function b_(t,{maxLines:e=400,maxBytes:n=64*1024}={}){let r=String(t||""),o=r.split(`
|
|
87
146
|
`);return o.length>e&&(r=`\u2026 (${o.length-e} earlier lines truncated)
|
|
88
147
|
`+o.slice(-e).join(`
|
|
89
148
|
`)),Buffer.byteLength(r)>n&&(r=`\u2026 (truncated)
|
|
90
|
-
`+r.slice(-n)),r}function
|
|
91
|
-
`).find(n=>n.trim())||"gh failed"}var
|
|
149
|
+
`+r.slice(-n)),r}function me(t){return String(t?.stderr||t?.message||t||"").trim().split(`
|
|
150
|
+
`).find(n=>n.trim())||"gh failed"}var k_=6e3,Ra=new Map;function Hl(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 jw(t){let e=t?.workspace?.branch;if(!e)return;let n;try{n=await L(t)}catch{return}if(Hl(n)==null)return;let r=(t.boxes||[]).find(c=>c.name===t.leadBox);if(await ba(t.leadBox,r?.agent).catch(()=>!1))return;let o=Date.now();if(o-(Ra.get(t.id)||0)<k_)return;Ra.set(t.id,o);let s;try{s=await Ro(t.cwd,{caller:"reconcile"})}catch{return}let a=(s||[]).find(c=>c.headRefName===e);if(!a)return;let i=await tt(t.cwd).catch(()=>null);if(i){let{link:c,created:l}=await Lt({repo:i,number:a.number,trackId:t.id,url:a.url,headRef:e,title:a.title,source:"reconciled"});if(!l&&!(c?.trackId===t.id&&await ho(i,a.number))){Ra.delete(t.id);return}}await O(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(()=>{}),Ra.delete(t.id)}import Wl from"node:fs/promises";import Mo from"node:path";import{randomUUID as x_}from"node:crypto";var Fw=".bostrat/uploads",ql=t=>Mo.join(t,Fw),Lw=Number(process.env.BOSTRAT_MAX_UPLOAD_BYTES)||24*1024*1024,S_=new Set(["png","jpg","jpeg","gif","webp","svg","bmp","tif","tiff","heic","heif","avif","ico"]),v_=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 Kl(t){let e=String(t||"").toLowerCase().replace(/^\./,"");return S_.has(e)?"image":v_.has(e)?"doc":null}function T_(t){let r=Mo.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 Uw(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>Lw)throw new Error(`file too large \u2014 max ${Math.floor(Lw/(1024*1024))} MB`);let r=T_(e),o=Mo.extname(r).slice(1),s=Kl(o);if(!s)throw new Error(`unsupported file type "${o||"unknown"}" \u2014 attach an image or a document`);let a=x_().slice(0,8),i=ql(t);await Wl.mkdir(i,{recursive:!0});let c=`${a}-${r}`;return await Wl.writeFile(Mo.join(i,c),n,{mode:384}),await Wl.chmod(Mo.join(i,c),384).catch(()=>{}),{id:a,name:r,rel:`${Fw}/${c}`,kind:s,size:n.length}}function Hw(t){let e=t.map(r=>`\`${r}\``).join(", ");return`[Attached \u2014 please read ${t.length===1?"this file":`these ${t.length} files`}: ${e}]`}var A_=/\[Attached — please read (?:this file|these \d+ files): [^\]]*\]$/;function Gl(t){return t.role==="user"&&A_.test((t.text||"").trim())}function Tr(t,e,n=Date.now()){return e?t.idleSince==null?(t.idleSince=n,!1):n-t.idleSince>=6500:(t.idleSince=null,!1)}var I_=E_(__),Oe=process.env.BOSTRAT_STATE_DIR||H.join(process.env.HOME||"/tmp",".bostrat"),Yw=H.join(Oe,"tracks.json"),R_=H.join(Oe,"events.jsonl"),Ql=H.join(Oe,"workspaces"),$_=process.env.BOSTRAT_DEFAULT_REPO||null,Ma={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"}},Ca=Object.keys(Ma);function O_(t,{skipPermissions:e=!0}={}){let n=Ma[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 Zw(t,e,{skipPermissions:n=!0}={}){let r=Ma[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 Fo(t){let e=Ma[t];if(e)return process.env[e.envVar]||e.base}var Pn={at:0,value:null};async function eu({maxAgeMs:t=6e4}={}){if(Pn.value&&Date.now()-Pn.at<t)return Pn.value;let e=await Promise.all(Ca.map(async n=>[n,await ws(Fo(n))]));return Pn.value=Object.fromEntries(e),Pn.at=Date.now(),Pn.value}async function Na(t,e=!0){let n=O_(t,{skipPermissions:e});if(!await ws(n)){Pn.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.":Ei[r]?`Install it on this node: ${Ei[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 Pa=".bostrat/handoff/latest.md";function M_(t,e=Pa){return`${t.trim()}
|
|
92
151
|
|
|
93
|
-
(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
|
|
152
|
+
(Read ${e} for handoff context from the prior lead \u2014 you are now the lead; none of the prior chat is in your context. Then respond to the user above.)`}var C_=/\(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 Ww(t){return t.role==="user"&&C_.test((t.text||"").trim())}var $a=".bostrat/handoff/subtask-result.md",No=".bostrat/handoff/lead-context.md",N_=(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 ${No} \u2014 read that first for context. Your scoped task:
|
|
94
153
|
|
|
95
154
|
${t}
|
|
96
155
|
|
|
97
|
-
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 ${
|
|
156
|
+
Work ONLY inside this directory. cd into the repo a change belongs to before running git commands, and commit per repo with a clear message (repos you don't touch need nothing). When you are finished: (1) make your code changes, (2) commit them in each changed repo, (3) LAST, write a short markdown digest to ${$a} (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 ${No} \u2014 read that first for context. Your scoped task:
|
|
98
157
|
|
|
99
158
|
${t}
|
|
100
159
|
|
|
101
|
-
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 ${
|
|
102
|
-
`;return bl=bl.then(()=>U.mkdir(Ve,{recursive:!0})).then(()=>U.appendFile(HA,n)).catch(()=>{}),bl}var st=(t,e,n={})=>{let r={at:Date.now(),type:e,...n};return t.events.push(r),JA(t.id,r),Em(t,r),r};Ku(async({sessionName:t,from:e,to:n,version:r,status:o,error:s})=>{let i=(await q()).tracks.find(c=>c.leadBox===t||(c.boxes||[]).some(l=>l.name===t));i&&(st(i,"codex-update",{box:t,from:e,to:r||n,status:o,...s?{error:s}:{}}),await G().catch(()=>{}))});var kl=(t,e,n)=>e!=null&&n.some(r=>t===r.box&&e>=r.from&&e<=r.to),XA=new Set(["stuck","wrong-approach","too-slow","want-different-model","context-refresh","cost","parallelize","other"]);function Jh(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=>XA.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 Xh(t,e,n,{kind:r}={}){let o=await X(e);if(!o)throw new Error(`box "${e}" is not running`);let s=vo(o.agent);await Re({cwd:o.cwd,sessionId:o.sessionId,sessionName:e,agent:o.agent,agentCmd:s}),await xt(e,n,{...fe(o.agent),kind:r||"orchestration"}),await V({flow:"orchestration",phase:"sent",trackId:t,sessionName:e,agent:o.agent,kind:r||"orchestration",promptHead:n.slice(0,160)});let a=await q(),i=tt(a,t);if(!i)return;let c=i.boxes.find(l=>l.name===e);st(i,"orchestration-prompt",{box:e,agent:c?.agent||o.agent,boxRole:c?.role,prompt:n,kind:r||"orchestration"}),await G()}var _n=new Set(["merged","conflict","canceled","failed","handed-off"]),Uh=new Map;async function YA(t){let e=H.join(us(t),`${t.id}.ndjson`),n;try{n=await U.stat(e)}catch{return null}let r=Uh.get(t.id);if(r&&r.mtimeMs===n.mtimeMs)return r.at;let o=null;for(let s of await F(t)){if(s.type!=="turn.agent")continue;let a=s.payload?.ts??s.ts;a&&(!o||a>o)&&(o=a)}return Uh.set(t.id,{mtimeMs:n.mtimeMs,at:o}),o}async function ZA(t,e){let n=e.find(r=>r.name===t.leadBox);return n?.state!=="live"?{working:!1,prompted:!1}:Kc(n.name,n.agent)}async function ze(t,e=null){let n=e??new Map((await nn()).map(i=>[i.name,i])),r=t.boxes.map(i=>{let c=n.get(i.name),l=_n.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([YA(t),ZA(t,r)]);return{...t,boxes:r,lastResponseAt:o,working:s,prompted:a}}var QA=5e3;function tT(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-QA,d=r[u+1]?.createdAt??1/0,m=o.find(h=>!s.has(h.id)&&h.birthMs>=f&&h.birthMs<d);m&&(s.add(m.id),c.push({box:l.name,sessionId:m.id}))}return c}async function Yh(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=Fu(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=tT(u,a,d=>i.has(d));for(let d of f){let m=u.find(h=>h.name===d.box);m&&(m.sessionId=d.sessionId),await $u(d.box,d.sessionId),n=!0}}return n}async function at(){let t=await q();await Yh(t)&&await G();let e=new Map((await nn()).map(n=>[n.name,n]));return Promise.all(t.tracks.map(n=>ze(n,e)))}async function Zh(t,e){let n=await q();if(t)for(let r of n.tracks||[]){let o=r.boxes.find(s=>s.sessionId&&s.sessionId===t);if(o)return{track:r,box:o}}if(e)for(let r of n.tracks||[]){let o=r.boxes.find(s=>s.cwd===e);if(o)return{track:r,box:o}}return null}async function Qh(t){let e=Or(t);if(!e)return null;let n=await q();for(let r of n.tracks||[])for(let o of r.boxes||[])if(o.hookTokenHash&&xu(o.hookTokenHash,e))return{track:r,box:o};return null}async function Q(t){let e=await q(),n=tt(e,t);return n?(await Yh(e)&&await G(),ze(n)):null}var eT={claude:[/^\[Request interrupted by user(?: for tool use)?\]$/i],codex:[/^\[?Request interrupted/i,/^Conversation interrupted/i],cursor:[/^\[?Request interrupted/i]};function Hh(t){if(t.role!=="user")return!1;let e=(t.text||"").trim();return(eT[t.agent]||[]).some(n=>n.test(e))}var nT=[/^\/model(?:\s|$)/i,/<command-(?:name|message|args)>[^<]*\bmodel\b/i,/^Model changed to\b/i];function Wh(t){let e=(t.text||"").trim();return e?nT.some(n=>n.test(e)):!1}var rT=10*6e4;async function _l(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=Ao(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=Ti(n.id),c=!!(i&&!i.finishedAt),l=c&&i.startedAt&&i.box?[{box:i.box,from:i.startedAt,to:Date.now()+1e12}]:[],u=[...nf(n),...l],d={windows:[...wd(n),...gd(n,cs),...a,...u]},h=(await Promise.all(n.boxes.map(k=>Tc(k,e)))).flat(),w=h.map(k=>{let j={kind:"message",...k};if(j.role==="assistant"&&(j.text||"").trim()&&j.tools?.length){let Rn=io(j.agent).cliTools,Ho=j.tools.filter($n=>!Rn.has($n));return{...j,tools:Ho.length?Ho:void 0}}return j}).map(k=>{if(k.role!=="user")return k;let j=na(k.text);return j?j.kind==="suppress"?null:{...k,note:j,text:dh(j)}:k}).filter(Boolean).filter(k=>!(k.role==="user"&&r.has((k.text||"").trim()))).filter(k=>!Hh(k)).filter(k=>!Wh(k)).filter(k=>!Fh(k)).filter(k=>!gl(k)).filter(k=>k.role!=="assistant"||!!(k.text||"").trim()||!!k.images?.length).filter(k=>!yd(k,d)),y=w.filter(k=>k.failed&&k.text===bc);if(y.length){let k=new Map;for(let j of y){if(!k.has(j.box)){let $n=[...(await wt(j.box,{lines:At}).catch(()=>"")).matchAll(/^\s*■\s+(.+?)\s*$/gm)].map(yu=>yu[1]);k.set(j.box,$n.length?$n[$n.length-1]:null)}let Rn=k.get(j.box);Rn&&(j.text=`\u25A0 ${Rn}`)}}let b=await Promise.all(n.boxes.map(k=>zp(k,e)));await lh(n,b.flat()),await ih(n,w),await Im(n),await uf(n,w).catch(()=>{});for(let k of n.boxes)k.agent==="codex"&&k.state==="live"&&await mh(n,k),k.agent==="cursor"&&k.state==="live"&&await yh(n,k);let A=await Promise.all(n.boxes.map(k=>Vp(k,e)));await ch(n,A.flat().filter(k=>!kl(k.box,k.ts,u)));let _=await F(n),C=c&&i.viewId?i.viewId:null,K=Lm(_),I=ra(_).filter(k=>!(k.kind==="message"&&(Hh(k)||Wh(k)||Fh(k)||gl(k)))&&!(k.kind==="view-card"&&C&&k.viewId===C)),L=Fm(I,K),v=I.filter(k=>!(k.kind==="message"&&k.role==="assistant"&&!L.has(k)&&kl(k.box,k.ts,K))),$=Qc(_,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=>!kl(k.anchorBox,k.anchorTs??k.ts,u)),W=[...v,...$].filter(k=>k.ts!=null).sort((k,j)=>k.ts-j.ts),nt=oT(n),g=nt.size?W.filter(k=>k.kind==="event"||!nt.has(k.box??k.anchorBox)):W,p=n.boxes.find(k=>k.name===n.leadBox)||null,S=p?await Dr(p.cwd,p.sessionId,{fallbackNewest:!1}):null;if(!S&&p){let k=[..._].reverse().find(j=>j.type==="context.usage"&&j.boxId===p.name);k?.payload?.window&&(S={pct:k.payload.pct??(k.payload.occupancy!=null?k.payload.occupancy/k.payload.window:null),occupancy:k.payload.occupancy??null,window:k.payload.window,windowKnown:!0,model:k.payload.model||null})}let T=[...h].reverse().find(k=>k.role==="assistant"&&k.model),J=T?.model||null,R=[...n.events||[]].reverse().find(k=>k.type==="model-change"),M=S?.model||J;R&&(!T?.ts||R.at>=T.ts)&&(M=R.to);let E=p?await wt(p.name,{lines:At}).catch(()=>""):"",P=p&&ir(p.agent,E)?await Ee(p.name).catch(()=>E):E,et=p?Sn(p,P):null;et&&Rm(n,p,et);let lt=!!p&&!et&&Ln(p.agent,E);p&&!p.readyAt&&(lt||et||E&&os(p.agent,E))&&(p.readyAt=Date.now(),await G());let bt=!!p&&!p.readyAt&&Date.now()-(n.createdAt||0)<rT;if(n.kind==="setup"&&n.setup?.provider&&!n.setup.connectedAt&&p&&os(p.agent,E)&&!et){let k=await q(),j=tt(k,n.id);j?.setup&&!j.setup.connectedAt&&(j.setup.connectedAt=Date.now(),n.setup.connectedAt=j.setup.connectedAt,await O(j,{type:"provider.connected",actor:"system",source:"pane",boxId:p.name,provider:p.agent,payload:{provider:p.agent,ts:j.setup.connectedAt}}).catch(()=>{}),await G())}let Et=s?{running:!0,step:o.step,toAgent:o.toAgent||null}:null,Nt=null;if(c){let k=Date.now()-i.startedAt;if(i.viewId&&!lt){let j=ys(n.id);j&&(st(n,"view-spinup",{box:i.box,startedAt:i.startedAt,endedAt:j.finishedAt}),await G())}else if(et||k>tf&&!lt&&!i.viewId||k>Ai){let j=et?"needs-input":k>Ai?"timeout":"no-view";ys(n.id,{error:j})&&await O(n,{type:"view.spinup.failed",actor:"orchestrator",source:"orchestrator",boxId:i.box,provider:p?.agent||null,payload:{ts:i.startedAt,name:i.name,reason:j}}).catch(()=>{})}else Nt={running:!0,step:i.step,name:i.name}}return{trackId:n.id,leadBox:n.leadBox,items:g,context:S?{pct:S.pct,occupancy:S.occupancy,window:S.window,windowKnown:S.windowKnown,model:S.model}:null,models:{current:M,options:Sm(p?.agent)},pendingPrompt:et,working:lt,leadStarting:bt,handoff:Et,view:Nt,queued:aT(n.id).filter(k=>k.kind!=="ship-notify")}}function oT(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 tw(t,e){let n=await Q(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 F(n),a=ra(s).filter(f=>f.box===o),i=Qc(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 wt(o,{lines:At}).catch(()=>"");u=Ln(l.agent||r.agent,f)}return{items:c,working:u,box:o,agent:r.agent,task:r.task}}async function El(t,{promptId:e,answers:n,text:r}={}){let o=tt(await q(),t);if(!o)throw new Error(`track "${t}" not found`);let s=o.boxes.find(d=>d.name===o.leadBox);if(!s)throw new Error(`track "${t}" has no lead box`);if(!await X(o.leadBox))throw new Error(`lead box "${o.leadBox}" is not running`);let i=await Cc(s);if(!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 xt(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 kt(o.leadBox,["Escape"],{kind:"answer-prompt"}),c={mode:"keys",value:"Escape",count:1};else if(i.provider==="claude"||i.answerMode==="keys"){let d=sm(i,n||[]);await kt(o.leadBox,d,{kind:"answer-prompt"}),c={mode:"keys",value:d.join(" "),count:d.length}}else{let d=Qu(i,n||[]);await xt(o.leadBox,d,{enter:!0,kind:"answer-prompt"}),c={mode:"text",value:d,count:1}}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 V({flow:"prompt",phase:"answered",sessionName:o.leadBox,agent:s.agent,provider:i.provider,promptId:i.promptId,...c}).catch(()=>{}),$m(o,s,i,c),{ok:!0,promptId:i.promptId,sent:c.count}}async function ew(t,{promptId:e,questionIndex:n}={}){let r=tt(await q(),t);if(!r)throw new Error(`track "${t}" not found`);let o=r.boxes.find(l=>l.name===r.leadBox);if(!o)throw new Error(`track "${t}" has no lead box`);if(!await X(r.leadBox))throw new Error(`lead box "${r.leadBox}" is not running`);let a=await Cc(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=am(a,n);return c.length?(await kt(r.leadBox,c,{kind:"navigate-prompt"}),await V({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 sT=1500,yo=new Map;function Il(t){let e=yo.get(t);return e||(e={items:[],timer:null,flushing:!1,idleSince:null},yo.set(t,e)),e}function aT(t){return(yo.get(t)?.items??[]).map(e=>({...e}))}function ya(t){let e=yo.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 nw(t){let e=Il(t);e.timer||(e.timer=setInterval(()=>iT(t).catch(()=>{}),sT))}async function iT(t){let e=yo.get(t);if(!(!e||e.flushing)){if(!e.items.length){e.timer&&(clearInterval(e.timer),e.timer=null);return}e.flushing=!0;try{let n=tt(await q(),t);if(!n||n.archivedAt){ya(t);return}let r=Ao(t);if(r&&!r.finishedAt&&r.step!=="done"&&!r.error){hr(e,!1);return}let o=await X(n.leadBox);if(!o){hr(e,!1);return}let s=n.boxes.find(c=>c.name===n.leadBox),a=await wt(n.leadBox,{lines:At}).catch(()=>"");if(rs(s?.agent,a)){hr(e,!1);return}if(ir(s?.agent,a)){let c=await Ee(n.leadBox).catch(()=>a);if(Sn(s,c)){hr(e,!1);return}}if(!hr(e,!0))return;let i=e.items.shift();e.idleSince=null,await rw(n,s,o,i.text,i.kind??null,i.attachments??null),await G()}finally{e.flushing=!1}}}async function rw(t,e,n,r,o=null,s=null){let a=r,i=!1;t.pendingHandoffHydrate&&(a=GA(r,t.pendingHandoffHydrate.artifact||ga),delete t.pendingHandoffHydrate,i=!0);let c=Array.isArray(s)?s.filter(u=>u&&u.rel):[];if(c.length){let u=Lh(c.map(f=>f.rel));a=a.trim()?`${a.trim()}
|
|
160
|
+
Work ONLY inside this directory. When you are finished: (1) make your code changes, (2) commit them with git and a clear message, (3) LAST, write a short markdown digest to ${$a} 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.`,P_=(t,e,n,r)=>`A subtask box you delegated has finished: "${t}". Its changes are now merged into your working tree (${e.files} file${e.files===1?"":"s"}, +${e.insertions} \u2212${e.deletions}). Read ${r} for exactly what it did. `+(n.length?`IMPORTANT: ${n.length} merge conflict${n.length===1?"":"s"} need resolving \u2014 fix the conflict markers in: ${n.join(", ")}, then continue.`:"Review the merged changes and continue the work."),we=null,zl=null;async function q(){return we||(zl||=(async()=>{try{we=JSON.parse(await U.readFile(Yw,"utf8"))}catch{we={tracks:[]}}finally{zl=null}return we})(),zl)}async function V(){await ae(Yw,we)}var nt=(t,e)=>t.tracks.find(n=>n.id===e)||null,Vl=Promise.resolve();function B_(t,e){let n=JSON.stringify({trackId:t,...e})+`
|
|
161
|
+
`;return Vl=Vl.then(()=>U.mkdir(Oe,{recursive:!0})).then(()=>U.appendFile(R_,n)).catch(()=>{}),Vl}var at=(t,e,n={})=>{let r={at:Date.now(),type:e,...n};return t.events.push(r),B_(t.id,r),Oh(t,r),r};qd(async({sessionName:t,from:e,to:n,version:r,status:o,error:s})=>{let i=(await q()).tracks.find(c=>c.leadBox===t||(c.boxes||[]).some(l=>l.name===t));i&&(at(i,"codex-update",{box:t,from:e,to:r||n,status:o,...s?{error:s}:{}}),await V().catch(()=>{}))});var Jl=(t,e,n)=>e!=null&&n.some(r=>t===r.box&&e>=r.from&&e<=r.to),D_=new Set(["stuck","wrong-approach","too-slow","want-different-model","context-refresh","cost","parallelize","other"]);function Qw(t){if(!t)return null;let e=typeof t=="string"?[t]:Array.isArray(t?.codes)&&t.codes.length?t.codes:t?.code?[t.code]:[],n=[...new Set(e.filter(s=>D_.has(s)))];if(!n.length)return null;let r=typeof t=="object"?t?.note:null,o=r&&String(r).slice(0,500).trim()||null;return{code:n[0],...n.length>1?{codes:n}:{},note:o}}async function tg(t,e,n,{kind:r}={}){let o=await Y(e);if(!o)throw new Error(`box "${e}" is not running`);let s=Fo(o.agent);await Ue({cwd:o.cwd,sessionId:o.sessionId,sessionName:e,agent:o.agent,agentCmd:s}),await vt(e,n,{...Se(o.agent),kind:r||"orchestration"}),await G({flow:"orchestration",phase:"sent",trackId:t,sessionName:e,agent:o.agent,kind:r||"orchestration",promptHead:n.slice(0,160)});let a=await q(),i=nt(a,t);if(!i)return;let c=i.boxes.find(l=>l.name===e);at(i,"orchestration-prompt",{box:e,agent:c?.agent||o.agent,boxRole:c?.role,prompt:n,kind:r||"orchestration"}),await V()}var Bn=new Set(["merged","conflict","canceled","failed","handed-off"]),qw=new Map;async function j_(t){let e=H.join(Rs(t),`${t.id}.ndjson`),n;try{n=await U.stat(e)}catch{return null}let r=qw.get(t.id);if(r&&r.mtimeMs===n.mtimeMs)return r.at;let o=null;for(let s of await L(t)){if(s.type!=="turn.agent")continue;let a=s.payload?.ts??s.ts;a&&(!o||a>o)&&(o=a)}return qw.set(t.id,{mtimeMs:n.mtimeMs,at:o}),o}async function L_(t,e){let n=e.find(r=>r.name===t.leadBox);return n?.state!=="live"?{working:!1,prompted:!1}:xl(n.name,n.agent)}async function on(t,e=null){let n=e??new Map((await mn()).map(i=>[i.name,i])),r=t.boxes.map(i=>{let c=n.get(i.name),l=Bn.has(i.state)?i.state:c?i.role==="lead"?"live":i.state:"stopped";return{...i,state:l,cwd:c?.cwd||i.cwd,agent:c?.agent||i.agent,sessionId:c?.sessionId||i.sessionId}}),[o,{working:s,prompted:a}]=await Promise.all([j_(t),L_(t,r)]);return{...t,boxes:r,lastResponseAt:o,working:s,prompted:a}}var F_=5e3;function U_(t,e,n){let r=[...t].sort((l,u)=>l.createdAt-u.createdAt),o=[...e].sort((l,u)=>l.birthMs-u.birthMs),s=new Set,a=[];for(let l of r)l.sessionId&&n(l.sessionId)&&!s.has(l.sessionId)?s.add(l.sessionId):a.push(l);let i=new Map(r.map((l,u)=>[l,u])),c=[];for(let l of a){if(l.sessionId&&n(l.sessionId)){c.push({box:l.name,sessionId:null});continue}if(!l.sessionId||l.pinned)continue;let u=i.get(l),f=l.createdAt-F_,d=r[u+1]?.createdAt??1/0,m=o.find(h=>!s.has(h.id)&&h.birthMs>=f&&h.birthMs<d);m&&(s.add(m.id),c.push({box:l.name,sessionId:m.id}))}return c}async function eg(t){let e=new Map;for(let r of t.tracks)for(let o of r.boxes)o.agent==="claude"&&o.cwd&&(e.has(o.cwd)||e.set(o.cwd,[]),e.get(o.cwd).push({track:r,box:o}));let n=!1;for(let[r,o]of e){let s=o.map(d=>d.box.sessionId),a=jd(r),i=new Set(a.map(d=>d.id)),c=s.every(d=>d&&i.has(d)),l=new Set(s).size===s.length;if(c&&l)continue;let u=o.map(d=>d.box),f=U_(u,a,d=>i.has(d));for(let d of f){let m=u.find(h=>h.name===d.box);m&&(m.sessionId=d.sessionId),await Id(d.box,d.sessionId),n=!0}}return n}async function ct(){let t=await q();await eg(t)&&await V();let e=new Map((await mn()).map(n=>[n.name,n]));return Promise.all(t.tracks.map(n=>on(n,e)))}async function ng(t,e){let n=await q();if(t)for(let r of n.tracks||[]){let o=r.boxes.find(s=>s.sessionId&&s.sessionId===t);if(o)return{track:r,box:o}}if(e)for(let r of n.tracks||[]){let o=r.boxes.find(s=>s.cwd===e);if(o)return{track:r,box:o}}return null}async function rg(t){let e=Hr(t);if(!e)return null;let n=await q();for(let r of n.tracks||[])for(let o of r.boxes||[])if(o.hookTokenHash&&Ju(o.hookTokenHash,e))return{track:r,box:o};return null}async function Q(t){let e=await q(),n=nt(e,t);return n?(await eg(e)&&await V(),on(n)):null}var H_={claude:[/^\[Request interrupted by user(?: for tool use)?\]$/i],codex:[/^\[?Request interrupted/i,/^Conversation interrupted/i],cursor:[/^\[?Request interrupted/i]};function Kw(t){if(t.role!=="user")return!1;let e=(t.text||"").trim();return(H_[t.agent]||[]).some(n=>n.test(e))}var W_=[/^\/model(?:\s|$)/i,/<command-(?:name|message|args)>[^<]*\bmodel\b/i,/^Model changed to\b/i];function Gw(t){let e=(t.text||"").trim();return e?W_.some(n=>n.test(e)):!1}var q_=10*6e4;async function nu(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=Uo(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=ec(n.id),c=!!(i&&!i.finishedAt),l=c&&i.startedAt&&i.box?[{box:i.box,from:i.startedAt,to:Date.now()+1e12}]:[],u=[...up(n),...l],d={windows:[..._f(n),...Ef(n,Ms),...a,...u]},h=(await Promise.all(n.boxes.map(k=>Jc(k,e)))).flat(),w=h.map(k=>{let K={kind:"message",...k};if(K.role==="assistant"&&(K.text||"").trim()&&K.tools?.length){let Ln=So(K.agent).cliTools,rs=K.tools.filter(Fn=>!Ln.has(Fn));return{...K,tools:rs.length?rs:void 0}}return K}).map(k=>{if(k.role!=="user")return k;let K=xa(k.text);return K?K.kind==="suppress"?null:{...k,note:K,text:mw(K)}:k}).filter(Boolean).filter(k=>!(k.role==="user"&&r.has((k.text||"").trim()))).filter(k=>!Kw(k)).filter(k=>!Gw(k)).filter(k=>!Ww(k)).filter(k=>!Gl(k)).filter(k=>k.role!=="assistant"||!!(k.text||"").trim()||!!k.images?.length).filter(k=>!If(k,d)),g=w.filter(k=>k.failed&&k.text===Wc);if(g.length){let k=new Map;for(let K of g){if(!k.has(K.box)){let Fn=[...(await gt(K.box,{lines:Tt}).catch(()=>"")).matchAll(/^\s*■\s+(.+?)\s*$/gm)].map(oi=>oi[1]);k.set(K.box,Fn.length?Fn[Fn.length-1]:null)}let Ln=k.get(K.box);Ln&&(K.text=`\u25A0 ${Ln}`)}}let b=await Promise.all(n.boxes.map(k=>Vm(k,e)));await fw(n,b.flat()),await uw(n,w),await Mh(n),await gp(n,w).catch(()=>{});for(let k of n.boxes)k.agent==="codex"&&k.state==="live"&&await gw(n,k),k.agent==="cursor"&&k.state==="live"&&await xw(n,k);let v=await Promise.all(n.boxes.map(k=>zm(k,e)));await dw(n,v.flat().filter(k=>!Jl(k.box,k.ts,u)));let A=await L(n),M=c&&i.viewId?i.viewId:null,z=Hh(A),$=Eo(A).filter(k=>!(k.kind==="message"&&(Kw(k)||Gw(k)||Ww(k)||Gl(k)))&&!(k.kind==="view-card"&&M&&k.viewId===M)),F=Wh($,z),T=$.filter(k=>!(k.kind==="message"&&k.role==="assistant"&&!F.has(k)&&Jl(k.box,k.ts,z))),C=Il(A,T).map(k=>({kind:"command-batch",id:k.id,ts:k.pending?k.ts:(k.anchorTs??k.ts)-1,anchorTs:k.anchorTs,anchorBox:k.anchorBox,agent:k.agent,boxRole:k.boxRole,commands:k.commands,pending:k.pending})).filter(k=>!Jl(k.anchorBox,k.anchorTs??k.ts,u)),W=[...T,...C].filter(k=>k.ts!=null).sort((k,K)=>k.ts-K.ts),ot=K_(n),y=ot.size?W.filter(k=>k.kind==="event"||!ot.has(k.box??k.anchorBox)):W,p=n.boxes.find(k=>k.name===n.leadBox)||null,S=p?await Zr(p.cwd,p.sessionId,{fallbackNewest:!1}):null;if(!S&&p){let k=[...A].reverse().find(K=>K.type==="context.usage"&&K.boxId===p.name);k?.payload?.window&&(S={pct:k.payload.pct??(k.payload.occupancy!=null?k.payload.occupancy/k.payload.window:null),occupancy:k.payload.occupancy??null,window:k.payload.window,windowKnown:!0,model:k.payload.model||null})}let _=[...h].reverse().find(k=>k.role==="assistant"&&k.model),rt=_?.model||null,I=[...n.events||[]].reverse().find(k=>k.type==="model-change"),R=S?.model||rt;I&&(!_?.ts||I.at>=_.ts)&&(R=I.to);let E=p?await gt(p.name,{lines:Tt}).catch(()=>""):"",P=p&&wr(p.agent,E)?await je(p.name).catch(()=>E):E,et=p?Mn(p,P):null;!et&&p&&!Fe(p.agent,E)&&!zw(n.id).length&&(et=al(y,{leadBoxId:p.name,provider:p.agent,isAnswered:k=>A.some(K=>K.type==="prompt.answered"&&K.payload?.promptId===k)})),et&&Ch(n,p,et);let wt=y.filter(k=>!(k.kind==="message"&&k.role==="assistant"&&k.ask&&!String(k.text||"").trim()&&!k.images?.length)),bt=!!p&&!et&&Fe(p.agent,E);p&&!p.readyAt&&(bt||et||E&&Ts(p.agent,E))&&(p.readyAt=Date.now(),await V());let Rt=!!p&&!p.readyAt&&Date.now()-(n.createdAt||0)<q_;if(n.kind==="setup"&&n.setup?.provider&&!n.setup.connectedAt&&p&&Ts(p.agent,E)&&!et){let k=await q(),K=nt(k,n.id);K?.setup&&!K.setup.connectedAt&&(K.setup.connectedAt=Date.now(),n.setup.connectedAt=K.setup.connectedAt,await O(K,{type:"provider.connected",actor:"system",source:"pane",boxId:p.name,provider:p.agent,payload:{provider:p.agent,ts:K.setup.connectedAt}}).catch(()=>{}),await V())}let Ht=s?{running:!0,step:o.step,toAgent:o.toAgent||null}:null,zt=null;if(c){let k=Date.now()-i.startedAt;if(i.viewId&&!bt){let K=Fs(n.id);K&&(at(n,"view-spinup",{box:i.box,startedAt:i.startedAt,endedAt:K.finishedAt}),await V())}else if(et||k>cp&&!bt&&!i.viewId||k>tc){let K=et?"needs-input":k>tc?"timeout":"no-view";Fs(n.id,{error:K})&&await O(n,{type:"view.spinup.failed",actor:"orchestrator",source:"orchestrator",boxId:i.box,provider:p?.agent||null,payload:{ts:i.startedAt,name:i.name,reason:K}}).catch(()=>{})}else zt={running:!0,step:i.step,name:i.name}}return{trackId:n.id,leadBox:n.leadBox,items:wt,context:S?{pct:S.pct,occupancy:S.occupancy,window:S.window,windowKnown:S.windowKnown,model:S.model}:null,models:{current:R,options:_h(p?.agent)},pendingPrompt:et,working:bt,leadStarting:Rt,handoff:Ht,view:zt,queued:zw(n.id).filter(k=>k.kind!=="ship-notify"&&k.kind!=="prompt-answer")}}function K_(t){let e=new Set;for(let n of t.boxes||[])n.role==="subtask"&&n.name&&e.add(n.name);for(let n of Object.values(t.subtasks||{}))n?.box&&e.add(n.box);return e}async function og(t,e){let n=await Q(t);if(!n)return null;let r=$r(t).find(f=>f.id===e)||null;if(!r)return null;let o=r.box||null;if(!o)return{items:[],working:!1,box:null,agent:r.agent,task:r.task};let s=await L(n),a=Eo(s).filter(f=>f.box===o&&!(f.kind==="message"&&f.role==="assistant"&&f.ask&&!String(f.text||"").trim()&&!f.images?.length)),i=Il(s.filter(f=>f.boxId===o),a.filter(f=>f.kind==="message")).map(f=>({kind:"command-batch",id:f.id,ts:f.pending?f.ts:(f.anchorTs??f.ts)-1,anchorTs:f.anchorTs,anchorBox:f.anchorBox,agent:f.agent,boxRole:f.boxRole,commands:f.commands,pending:f.pending})),c=[...a,...i].filter(f=>f.ts!=null).sort((f,d)=>f.ts-d.ts),l=n.boxes.find(f=>f.name===o)||null,u=!1;if(l&&l.state==="live"){let f=await gt(o,{lines:Tt}).catch(()=>"");u=Fe(l.agent||r.agent,f)}return{items:c,working:u,box:o,agent:r.agent,task:r.task}}async function ru(t,{promptId:e,answers:n,text:r}={}){let o=nt(await q(),t);if(!o)throw new Error(`track "${t}" not found`);let s=o.boxes.find(d=>d.name===o.leadBox);if(!s)throw new Error(`track "${t}" has no lead box`);if(!await Y(o.leadBox))throw new Error(`lead box "${o.leadBox}" is not running`);let i=await nl(s);if(i||(i=await G_(o,s)),!i)throw new Error("no pending prompt to answer");if(e&&e!==i.promptId)throw new Error("prompt changed since it was shown \u2014 reload and answer the current prompt");let c;if(i.answerMode==="text"){let d=typeof r=="string"?r.trim():"";if(!d)throw new Error("this prompt needs a code \u2014 paste it into the card");if(/\s/.test(d)||d.length>2048)throw new Error("that doesn't look like a sign-in code \u2014 paste the code exactly as shown");await vt(o.leadBox,d,{enter:!0,kind:"answer-prompt"}),c={mode:"text-input",value:"\u2039code\u203A",count:1,redacted:!0}}else if(i.answerMode==="esc")await xt(o.leadBox,["Escape"],{kind:"answer-prompt"}),c={mode:"keys",value:"Escape",count:1};else if(i.answerMode==="say"){let d=fh(i,n||[],typeof r=="string"?r:"");if(!d)throw new Error("no answer selected \u2014 pick an option or reply in chat");await Qt(o.id,d,{kind:"prompt-answer"}),c={mode:"say",value:d,count:1}}else if(i.provider==="claude"||i.answerMode==="keys"){let d=sh(i,n||[]);await xt(o.leadBox,d,{kind:"answer-prompt"}),c={mode:"keys",value:d.join(" "),count:d.length}}else{let d=Yd(i,n||[]);await vt(o.leadBox,d,{enter:!0,kind:"answer-prompt"}),c={mode:"text",value:d,count:1}}if(i.answerMode==="say"){let d=i.questions||[];for(let m=0;m<d.length;m++){let h=d[m],w=(Array.isArray(n?.[m])?n[m]:[]).map(g=>h.options?.[g]).filter(Boolean).map(g=>({label:g.label,description:g.description||""}));bl(o,s,i,{...c,groupId:i.groupId||i.promptId,questionIndex:m,questionTotal:i.questionTotal??d.length,header:h.header||"",question:h.question||"",multiSelect:!!h.multiSelect,chosen:w})}return await G({flow:"prompt",phase:"answered",sessionName:o.leadBox,agent:s.agent,provider:i.provider,promptId:i.promptId,...c}).catch(()=>{}),{ok:!0,promptId:i.promptId,sent:c.count}}let l=i.questions?.[0]||{},u=(Array.isArray(n?.[0])?n[0]:[]).map(d=>l.options?.[d]).filter(Boolean).map(d=>({label:d.label,description:d.description||""})),f=i.review?"review":/^claude:model-confirm:/.test(i.promptId||"")?"model-confirm":/^claude:resume-confirm:/.test(i.promptId||"")?"resume-confirm":void 0;return c={...c,groupId:i.groupId||i.promptId,questionIndex:i.questionIndex??0,questionTotal:i.questionTotal??1,header:l.header||"",question:l.question||"",multiSelect:!!l.multiSelect,chosen:u,...f?{control:f}:{}},await G({flow:"prompt",phase:"answered",sessionName:o.leadBox,agent:s.agent,provider:i.provider,promptId:i.promptId,...c}).catch(()=>{}),bl(o,s,i,c),{ok:!0,promptId:i.promptId,sent:c.count}}async function G_(t,e){if(!e)return null;let n=await L(t).catch(()=>[]);return al(Eo(n),{leadBoxId:e.name,provider:e.agent,isAnswered:r=>n.some(o=>o.type==="prompt.answered"&&o.payload?.promptId===r)})}async function sg(t,{promptId:e,questionIndex:n}={}){let r=nt(await q(),t);if(!r)throw new Error(`track "${t}" not found`);let o=r.boxes.find(l=>l.name===r.leadBox);if(!o)throw new Error(`track "${t}" has no lead box`);if(!await Y(r.leadBox))throw new Error(`lead box "${r.leadBox}" is not running`);let a=await nl(o);if(!a)throw new Error("no pending prompt to navigate");if(a.provider!=="claude")throw new Error("prompt navigation not supported for this provider");if(a.review)throw new Error("cannot navigate on the review screen \u2014 answer or cancel");let i=a.questionTotal??1;if(i<2)throw new Error("not a multi-question prompt");if(!Number.isInteger(n)||n<0||n>=i)throw new Error("questionIndex out of range");if(e&&e!==a.promptId)throw new Error("prompt changed since it was shown \u2014 reload and try again");let c=ah(a,n);return c.length?(await xt(r.leadBox,c,{kind:"navigate-prompt"}),await G({flow:"prompt",phase:"navigated",sessionName:r.leadBox,agent:o.agent,provider:a.provider,promptId:a.promptId,fromIndex:a.questionIndex??0,toIndex:n,mode:"keys",value:c.join(" "),count:c.length}).catch(()=>{}),{ok:!0,promptId:a.promptId,sent:c.length}):{ok:!0,promptId:a.promptId,sent:0}}var z_=1500,Po=new Map;function ou(t){let e=Po.get(t);return e||(e={items:[],timer:null,flushing:!1,idleSince:null},Po.set(t,e)),e}function zw(t){return(Po.get(t)?.items??[]).map(e=>({...e}))}function Ba(t){let e=Po.get(t);if(!e)return[];let n=e.items.splice(0);return e.idleSince=null,e.timer&&(clearInterval(e.timer),e.timer=null),n}function ag(t){let e=ou(t);e.timer||(e.timer=setInterval(()=>V_(t).catch(()=>{}),z_))}async function V_(t){let e=Po.get(t);if(!(!e||e.flushing)){if(!e.items.length){e.timer&&(clearInterval(e.timer),e.timer=null);return}e.flushing=!0;try{let n=nt(await q(),t);if(!n||n.archivedAt){Ba(t);return}let r=Uo(t);if(r&&!r.finishedAt&&r.step!=="done"&&!r.error){Tr(e,!1);return}let o=await Y(n.leadBox);if(!o){Tr(e,!1);return}let s=n.boxes.find(c=>c.name===n.leadBox),a=await gt(n.leadBox,{lines:Tt}).catch(()=>"");if(vs(s?.agent,a)){Tr(e,!1);return}if(wr(s?.agent,a)){let c=await je(n.leadBox).catch(()=>a);if(Mn(s,c)){Tr(e,!1);return}}if(!Tr(e,!0))return;let i=e.items.shift();e.idleSince=null,await ig(n,s,o,i.text,i.kind??null,i.attachments??null),await V()}finally{e.flushing=!1}}}async function ig(t,e,n,r,o=null,s=null){let a=r,i=!1;t.pendingHandoffHydrate&&(a=M_(r,t.pendingHandoffHydrate.artifact||Pa),delete t.pendingHandoffHydrate,i=!0);let c=Array.isArray(s)?s.filter(u=>u&&u.rel):[];if(c.length){let u=Hw(c.map(f=>f.rel));a=a.trim()?`${a.trim()}
|
|
162
|
+
|
|
163
|
+
${u}`:u}let l=at(t,"said",{box:t.leadBox,agent:e?.agent,boxRole:e?.role,text:r,...o?{kind:o}:{},...c.length?{attachments:c}:{}});delete t.draft;try{await Ue({cwd:n.cwd,sessionId:n.sessionId,sessionName:t.leadBox,agent:n.agent,agentCmd:Fo(n.agent)}),await vt(t.leadBox,a,{...Se(e?.agent),kind:i?"lead-handoff-first-prompt":"say"})}catch(u){throw at(t,"unsaid",{box:t.leadBox,agent:e?.agent,boxRole:e?.role,saidTs:l.at,text:r}),u}await sE(t,e,a)||(await ug(t.leadBox,e?.agent,a.length+8).catch(()=>{}),at(t,"unsaid",{box:t.leadBox,agent:e?.agent,boxRole:e?.role,saidTs:l.at,text:r,reason:"unsubmitted"}),o||(t.draft={text:r,updatedAt:Date.now()})),i&&await G({flow:"lead-handoff",phase:"first-prompt",trackId:t.id,sessionName:t.leadBox,agent:e?.agent,userTextLen:r.length})}function cg(t,{actions:e}={}){ou(t).items.push({id:Lo(),text:qh(e),ts:Date.now(),kind:"ship-notify"}),ag(t)}async function Qt(t,e,{kind:n=null,attachments:r=null}={}){let o=Array.isArray(r)?r.filter(d=>d&&typeof d.rel=="string"):[];if((typeof e!="string"||!gr(e))&&!o.length)throw new Error("text (string) or an attachment required");let s=nt(await q(),t);if(!s)throw new Error(`track "${t}" not found`);if(s.archivedAt)throw new Error(`track "${t}" is archived \u2014 restore it before driving it`);let a=await Y(s.leadBox);if(!a&&await cu(s)&&(await V(),a=await Y(s.leadBox)),!a)throw new Error(`lead box "${s.leadBox}" is not running`);let i=s.boxes.find(d=>d.name===s.leadBox),c=gr(e),l=ou(t),u=await gt(s.leadBox,{lines:Tt}).catch(()=>""),f=l.items.length>0||vs(i?.agent,u);if(!f&&wr(i?.agent,u)){let d=await je(s.leadBox).catch(()=>u);f=!!Mn(i,d)}if(f){let d={id:Lo(),text:c,ts:Date.now(),...n?{kind:n}:{},...o.length?{attachments:o}:{}};return l.items.push(d),ag(t),delete s.draft,await V(),{ok:!0,leadBox:s.leadBox,queued:!0,id:d.id,position:l.items.length}}return await ig(s,i,a,c,n,o),await V(),{ok:!0,leadBox:s.leadBox}}var Vw="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 lg(t,{hint:e}={}){let n=nt(await q(),t);if(!n)throw new Error(`track "${t}" not found`);if(n.archivedAt)throw new Error(`track "${t}" is archived \u2014 restore it first`);let r=ec(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";lp(n.id,{name:s,box:n.leadBox});let a=String(e||"").trim()?`${Vw}
|
|
103
164
|
|
|
104
|
-
${
|
|
165
|
+
What to preview: ${String(e).trim()}`:Vw;try{await Qt(t,a,{kind:"spin-up-view"})}catch(i){throw Fs(n.id,{error:"drive-failed"}),i}return{ok:!0,running:!0}}async function su(t){let e=nt(await q(),t);if(!e)throw new Error(`track "${t}" not found`);if(e.archivedAt)throw new Error(`track "${t}" is archived`);let n=e.boxes.find(f=>f.name===e.leadBox),r=Ba(t),o=await Y(e.leadBox),s=o?await Y_(e.leadBox,n?.agent):{stopped:!1,sends:0},a=o&&s.stopped?await tE(e,n):null,i=!a&&o&&s.stopped?await rE(e,n):null,c=o?await Z_(e).catch(()=>null):null,l=r.filter(f=>f.text&&!f.kind).map(f=>f.text),u=a||i;return l.length&&(u=[u,...l].filter(Boolean).join(`
|
|
105
166
|
|
|
106
|
-
|
|
167
|
+
`),e.draft={text:u,updatedAt:Date.now()},await V()),await G({flow:"interrupt",phase:"sent",trackId:t,sessionName:e.leadBox,agent:n?.agent,dropped:r.length,escSends:s.sends,stopped:s.stopped,unsent:!!a,lostRestored:!!i,queuedRestored:l.length,shipDismissed:!!c}).catch(()=>{}),{ok:!0,interrupted:!!o,stopped:s.stopped,dropped:r.length,restored:u||null}}var J_=[300,350,400],X_=3;async function Y_(t,e){let n=0;for(let r=0;r<X_;r++){await xt(t,["Escape"],{kind:"interrupt"}),n++;for(let o of J_){await Or(o);let s=await gt(t,{lines:Tt}).catch(()=>"");if(!Fe(e,s))return{stopped:!0,sends:n}}}return{stopped:!1,sends:n}}async function Z_(t){let e=Hl(await L(t));return e?.offerId?(await O(t,{type:"pr.offer.answered",actor:"user",source:"orchestrator",boxId:e.box,payload:{ts:Date.now(),offerId:e.offerId,action:"dismiss",actions:null,reason:"interrupt"}}),e.offerId):null}var Q_=[250,400,500,600];async function tE(t,e){if(e?.agent!=="claude")return null;let n=[...t.events||[]].reverse().find(r=>r.type==="said"&&r.text);if(!n)return null;for(let r of Q_){await Or(r);let o=await gt(t.leadBox,{lines:Tt}).catch(()=>""),s=vo(e.agent,o);if(s&&rl(n.text,s))return await ug(t.leadBox,e.agent,n.text.length+8),t.draft={text:n.text,updatedAt:Date.now()},at(t,"unsaid",{box:t.leadBox,agent:e.agent,boxRole:e.role,saidTs:n.at,text:n.text}),await V(),n.text}return null}var eE=3e4,nE=[350,650,900];async function rE(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>eE||n.some(s=>s.type==="unsaid"&&s.saidTs===r.at)||!Yt(r.text))return null;for(let s of nE){await Or(s);let a;try{a=await Jc(e,{})}catch{return null}if(ch(a,r))return null;let i=await gt(t.leadBox,{lines:Tt}).catch(()=>"");if(vo(e.agent,i))return null}return t.draft={text:r.text,updatedAt:Date.now()},at(t,"unsaid",{box:t.leadBox,agent:e.agent,boxRole:e.role,saidTs:r.at,text:r.text,reason:"lost"}),await V(),r.text}var oE=[250,450,700],Xl=2;async function sE(t,e,n){if(e?.agent!=="claude")return!0;for(let r=0;r<=Xl;r++){let o=!1;for(let s of oE){await Or(s);let a=await gt(t.leadBox,{lines:Tt}).catch(()=>""),i=lh(e.agent,n,a,{busy:Fe(e.agent,a)});if(i==="stuck"){o=!0;break}if(i==="submitted")return!0}if(!o)return!0;r<Xl&&(await xt(t.leadBox,["Enter"],{kind:"submit-retry"}),G({flow:"say",phase:"submit-retry",trackId:t.id,sessionName:t.leadBox,agent:e.agent,attempt:r+1,textHead:n.slice(0,120)}).catch(()=>{}))}return G({flow:"say",phase:"submit-failed",trackId:t.id,sessionName:t.leadBox,agent:e.agent,retries:Xl,textHead:n.slice(0,120)}).catch(()=>{}),!1}async function ug(t,e,n){for(let r=0;r<3;r++){await ys(t,n,{kind:"unsend-wipe"}).catch(()=>{}),await Or(200);let o=await gt(t,{lines:Tt}).catch(()=>""),s=vo(e,o);if(!s)return!0;n=s.length+8}return!1}async function dg(t,e){if(typeof e!="string")throw new Error("text (string) required");let n=nt(await q(),t);if(!n)throw new Error(`track "${t}" not found`);let r=gr(e);return r?n.draft={text:r,updatedAt:Date.now()}:delete n.draft,await V(),{ok:!0,draft:n.draft||null}}async function fg(t,e){if(typeof e!="string"||!e.trim())throw new Error("model (string) required");let n=e.trim(),r=nt(await q(),t);if(!r)throw new Error(`track "${t}" not found`);let o=await Y(r.leadBox);if(!o)throw new Error(`lead box "${r.leadBox}" is not running`);let a=r.boxes.find(f=>f.name===r.leadBox)?.agent||o.agent||"claude",i=Eh(a,n),l=[...r.events||[]].reverse().find(f=>f.type==="model-change")?.to??null;l||(l=(await Zr(o.cwd,o.sessionId,{fallbackNewest:!1}).catch(()=>null))?.model??null),await Ue({cwd:o.cwd,sessionId:o.sessionId,sessionName:r.leadBox,agent:o.agent,agentCmd:Fo(o.agent)});let u=await Ih(a,r.leadBox,i);return at(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 pg(t){for(let e of t){if(!e||!await ua(e))continue;let n=await hh(e).catch(()=>null);if(!n)continue;if((await U.stat(H.join(n,".git")).catch(()=>null))?.isDirectory())return n}return null}function Nt(t){let e=t;return e?e.kind==="multi"?Array.isArray(e.repos)?e.repos.filter(Boolean):[]:e.base?[{name:H.basename(e.base),dir:e.dir??null,base:e.base,baseRef:e.baseRef??null,repo:e.repo??null}]:[]:[]}async function _o(t){let e=[];for(let n of Nt(t?.workspace)){if(!n?.dir)continue;let r=await dl(n.dir,{ignorePlumbing:!0}).catch(()=>!1),o=await fl(n.dir,n.baseRef).catch(()=>0);(r||o>0)&&e.push({...n,dirty:r,ahead:o})}return e}async function mg(t){let e=t;typeof e=="string"&&!H.isAbsolute(e)&&/^[^/\s]+\/[^/\s]+$/.test(e)&&(e=await qr(e)??await pi(e));let n=await pg([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 aE(t,e,n,r){let o=`track/${du(e)}-${t}`,s=[];try{await U.mkdir(n,{recursive:!0});let a=new Set([".bostrat"]);for(let i of r){let c=await mg(i);if(s.some(m=>m.base===c))throw new Error(`repo tagged twice: ${i}`);let l=await tt(c).catch(()=>null),u=H.basename(c);a.has(u)&&l&&(u=l.replace("/","-"));for(let m=2;a.has(u);m++)u=`${H.basename(c)}-${m}`;a.add(u);let f=H.join(n,u),d=await da(c);await yr(c,f,o,d),s.push({name:u,dir:f,base:c,baseRef:d,repo:l})}return await Da(n,s,o),{dir:n,kind:"multi",branch:o,repos:s}}catch(a){for(let i of s)await Kt(i.base,i.dir).catch(()=>{});throw await U.rm(n,{recursive:!0,force:!0}).catch(()=>{}),a}}var hg=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 wg(t,e){let n=await q(),r=nt(n,t);if(!r)throw new Error(`track "${t}" not found`);if(r.archivedAt)throw new Error("track is archived \u2014 unarchive it first");let o=r.workspace;if(o?.kind!=="multi")throw new Error("live re-tagging needs a multi-repo Track \u2014 a single-repo Track's workspace IS its repo checkout; create the Track with multiple repos tagged instead");let s=await mg(typeof e=="string"?e.trim():e);if((o.repos??[]).some(d=>d?.base===s))throw new Error(`repo already tagged: ${e}`);let a=await tt(s).catch(()=>null),i=new Set([".bostrat",...(o.repos??[]).map(d=>d?.name).filter(Boolean)]),c=H.basename(s);i.has(c)&&a&&(c=a.replace("/","-"));for(let d=2;i.has(c);d++)c=`${H.basename(s)}-${d}`;let l=H.join(o.dir,c),u=await da(s);await wh(s,o.branch)?await gh(s,l,o.branch):await yr(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 Da(o.dir,o.repos,o.branch),at(r,"retagged",{action:"add",repo:a??s,name:c,workspace:hg(o)}),await V(),f}async function gg(t,e){let n=await q(),r=nt(n,t);if(!r)throw new Error(`track "${t}" not found`);let o=r.workspace;if(o?.kind!=="multi")throw new Error("live re-tagging needs a multi-repo Track");let s=(o.repos??[]).find(c=>c&&(c.name===e||c.repo===e||c.base===e));if(!s)throw new Error(`repo "${e}" is not tagged on this Track`);if((o.repos??[]).filter(Boolean).length<=1)throw new Error("can't untag the last repo \u2014 archive or delete the Track instead");for(let c of Zt.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 dl(s.dir,{ignorePlumbing:!0}).catch(()=>!1),i=await fl(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 Kt(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 Da(o.dir,o.repos,o.branch),at(r,"retagged",{action:"remove",repo:s.repo??s.base,name:s.name,workspace:hg(o)}),await V(),{removed:s.name}}async function iE(t,e,{repo:n,repos:r,cwd:o}={}){await U.mkdir(Ql,{recursive:!0});let s=H.join(Ql,t),a=Array.isArray(r)?r.map(c=>typeof c=="string"?c.trim():"").filter(Boolean):[];if(a.length>=2)return aE(t,e,s,a);a.length===1&&!n&&(n=a[0]),typeof n=="string"&&!H.isAbsolute(n)&&/^[^/\s]+\/[^/\s]+$/.test(n)&&(n=await qr(n)??await pi(n));let i=await pg([n,o,$_]);if(i){let c=`track/${du(e)}-${t}`;try{let l=await da(i);await yr(i,s,c,l),await tu(s);let u=await tt(i).catch(()=>null);return{dir:s,base:i,branch:c,kind:"worktree",baseRef:l,repo:u}}catch{await U.rm(s,{recursive:!0,force:!0}).catch(()=>{})}}return await U.mkdir(s,{recursive:!0}),await yh(s),await tu(s),{dir:s,base:null,branch:null,kind:"repo"}}var Bo="# bostrat workspace",yg=`${Bo}
|
|
107
168
|
|
|
108
|
-
|
|
169
|
+
TRACK MEMORY: if \`.bostrat/memory/MEMORY.md\` exists in your workspace, read it
|
|
170
|
+
before substantive work \u2014 it is the shared cross-provider memory for this repo
|
|
171
|
+
(one line per fact; the fact files sit beside it, grep them on demand). When you
|
|
172
|
+
learn a durable, non-obvious fact worth keeping across sessions (a decision, a
|
|
173
|
+
gotcha, a convention), add or update a fact file there \u2014 one fact per file,
|
|
174
|
+
frontmatter with \`name\`/\`description\` and \`metadata\` carrying \`modified\`
|
|
175
|
+
(ISO-8601, bump on every edit), \`scope\` (\`operator\` or \`repo:<slug>\`),
|
|
176
|
+
\`provenance\`, \`supersedes\` \u2014 and add a one-line hook to MEMORY.md. Facts sync
|
|
177
|
+
across this machine's Tracks automatically; never store secrets in them.
|
|
109
178
|
|
|
110
179
|
When asked to run, preview, or demo this app so the operator can review it: do NOT
|
|
111
180
|
start an ad-hoc dev server and do NOT share a localhost URL (the operator is on
|
|
@@ -244,9 +313,9 @@ which steps to run (and which to skip). An offer buried mid-message, phrased as
|
|
|
244
313
|
statement, or omitted entirely renders no controls \u2014 so close EVERY completed
|
|
245
314
|
implementation turn this way, every time. When the operator has already asked you
|
|
246
315
|
to commit / push / open the PR, just do it; do not re-offer.
|
|
247
|
-
|
|
248
|
-
`)),
|
|
249
|
-
`);return`${
|
|
316
|
+
`,cE=yg,Co=yg+ph;async function tu(t){try{await U.writeFile(H.join(t,"AGENTS.md"),Co,{flag:"wx"})}catch{return}await dE(t,["/AGENTS.md"])}var Yl=t=>typeof t=="string"&&(t===Bo||t.startsWith(`${Bo}
|
|
317
|
+
`)),lE="# bostrat multi-repo workspace";function uE(t,e){let n=t.map(r=>`- \`${r.name}/\`${r.repo?` \u2014 github.com/${r.repo}`:""}${r.baseRef?` (forked from ${r.baseRef})`:""}`).join(`
|
|
318
|
+
`);return`${lE}
|
|
250
319
|
|
|
251
320
|
This directory is NOT a git repo. It is a bostrat Track workspace holding one
|
|
252
321
|
git worktree per tagged repo, ALL on the same branch \`${e}\`:
|
|
@@ -258,25 +327,25 @@ push / open PRs) in each repo separately, always on that shared branch. Work
|
|
|
258
327
|
only needs to land in the repos it actually touches \u2014 an untouched repo's
|
|
259
328
|
branch simply stays at its fork point and never gets pushed. When asked to run
|
|
260
329
|
or preview an app, run it from that app's own repo directory.
|
|
261
|
-
`}async function
|
|
262
|
-
`,`${
|
|
330
|
+
`}async function Da(t,e,n){let r=uE(e,n);for(let o of["CLAUDE.md","AGENTS.md"])await U.writeFile(H.join(t,o),r).catch(()=>{})}var Zl=Co.replace(`${Bo}
|
|
331
|
+
`,`${Bo}
|
|
263
332
|
|
|
264
333
|
(Global copy managed by bostrat \u2014 rewritten on service start; edits will be lost.)
|
|
265
334
|
SCOPE: these instructions apply ONLY when your working directory is under
|
|
266
335
|
~/.bostrat/workspaces/ or ~/.bostrat/worktrees/ \u2014 a bostrat-orchestrated box.
|
|
267
336
|
When working anywhere else, ignore everything below.
|
|
268
|
-
`);async function
|
|
337
|
+
`);async function bg(t){let{stdout:e}=await I_("git",["-C",t,"rev-parse","--git-path","info/exclude"]);return H.resolve(t,e.trim())}async function dE(t,e){try{let n=await bg(t);await U.mkdir(H.dirname(n),{recursive:!0});let r=await U.readFile(n,"utf8").catch(()=>""),o=e.filter(s=>!r.split(`
|
|
269
338
|
`).includes(s));o.length&&await U.appendFile(n,(r.endsWith(`
|
|
270
339
|
`)||!r?"":`
|
|
271
340
|
`)+o.join(`
|
|
272
341
|
`)+`
|
|
273
|
-
`)}catch{}}async function
|
|
342
|
+
`)}catch{}}async function fE(t,e){try{let n=await bg(t),r=await U.readFile(n,"utf8"),o=r.split(`
|
|
274
343
|
`).filter(s=>s!==e);o.length!==r.split(`
|
|
275
344
|
`).length&&await U.writeFile(n,o.join(`
|
|
276
|
-
`))}catch{}}async function
|
|
345
|
+
`))}catch{}}async function kg(){let t={refreshed:0,removed:0,codexGlobal:null};try{await U.mkdir(Oe,{recursive:!0}),await U.writeFile(H.join(Oe,"CLAUDE.md"),cE).catch(()=>{}),await U.writeFile(H.join(Oe,"AGENTS.md"),Co).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,Zl,{flag:"wx"}).catch(()=>{}),t.codexGlobal="created"):Yl(o)&&o!==Zl&&(await U.writeFile(r,Zl).catch(()=>{}),t.codexGlobal="refreshed")}for(let r of[Ql,Er]){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&&Yl(c)&&c!==Co&&(await U.writeFile(i,Co).catch(()=>{}),t.refreshed++);let l=H.join(a,"CLAUDE.md"),u=await U.readFile(l,"utf8").catch(()=>null);u!==null&&Yl(u)&&(await U.rm(l,{force:!0}).catch(()=>{}),await fE(a,"/CLAUDE.md"),t.removed++)}}}catch{}return t}async function pE(t){let e=t.workspace;if(!(!e||!e.dir))try{if(e.kind==="multi"){for(let n of Nt(e))n.base&&n.dir&&await Kt(n.base,n.dir).catch(()=>{});await U.rm(e.dir,{recursive:!0,force:!0})}else e.kind==="worktree"&&e.base?await Kt(e.base,e.dir):await U.rm(e.dir,{recursive:!0,force:!0})}catch{}}async function au({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 Na(o,s),i=await q(),c=Lo().slice(0,8),l=await iE(c,t,{repo:n,repos:r,cwd:e}),u=new Set(i.tracks.flatMap(h=>(h.boxes||[]).map(w=>w.name))),f=`${t.trim()} lead`;u.has(Ii(f))&&(f=`${t.trim().slice(0,50).trim()} ${c} lead`);let d=await hn(f,l.dir,{agentCmd:a,agent:o});As({cwd:d.cwd,sessionName:d.name,agent:o,agentCmd:a});let m={id:c,name:t.trim(),cwd:d.cwd,workspace:l,leadBox:d.name,boxes:[$g(d,"lead","live")],events:[],createdAt:Date.now()};return at(m,"opened",{agent:o,box:d.name,skipPermissions:s,name:m.name,cwd:m.cwd,workspace:{base:l.base??null,branch:l.branch,kind:l.kind,...l.baseRef?{baseRef:l.baseRef}:{},...l.repo?{repo:l.repo}:{},...l.repos?{repos:l.repos.map(({name:h,base:w,baseRef:g,repo:b})=>({name:h,base:w,baseRef:g,repo:b}))}:{}}}),i.tracks.push(m),await V(),on(m)}var mE=new Set(["claude","codex","cursor"]);async function xg({provider:t="claude",fresh:e=!1}={}){if(!mE.has(t))throw new Error(`unknown setup provider "${t}" (claude | codex | cursor)`);let r=(await q()).tracks.find(u=>u.kind==="setup"&&u.setup?.provider===t&&!u.archivedAt);if(r){if(!e){let u=await on(r);if(u.boxes.find(d=>d.name===u.leadBox)?.state==="live")return u}await Rr(r.id).catch(()=>{})}let o=(await Na(t,!1)).replace(/\s+--(?:dangerously-skip-permissions|dangerously-bypass-approvals-and-sandbox)\b/g,""),s=Lo().slice(0,8),a=H.join(Oe,"setup",s);await U.mkdir(a,{recursive:!0});let i=await hn(`Connect ${t} ${s}`,a,{agentCmd:o,agent:t,firstRun:!0});t==="codex"&&As({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:[$g(i,"lead","live")],events:[],createdAt:Date.now()};return at(c,"opened",{agent:t,box:i.name,skipPermissions:!1,trackKind:"setup",name:c.name,cwd:c.cwd,workspace:{base:null,branch:null,kind:"scratch"}}),(await q()).tracks.push(c),await V(),on(c)}async function Sg(t,e,n){await wp(t).catch(()=>{});let r=Do.get(t);r&&!r.finishedAt&&(r.error=n,r.finishedAt=Date.now()),Do.delete(t),e.handoff&&!e.handoff.finishedAt&&(e.handoff.step="error",e.handoff.error=n,e.handoff.finishedAt=Date.now());for(let a of $r(t))a.finishedAt||(a.canceled=!0,a.finishedAt=Date.now()),await pu(a).catch(()=>{}),Zt.delete(a.id);for(let a of Object.values(e.subtasks||{}))a.finishedAt||(a.step="error",a.error=n,a.canceled=!0,a.finishedAt=Date.now());let o=await q(),s=new Set((o.tracks||[]).filter(a=>a.id!==t&&!a.archivedAt).flatMap(a=>(a.boxes||[]).map(i=>i.name)));for(let a of e.boxes)s.has(a.name)||await re(a.name).catch(()=>{})}async function vg(t,{cascadeMocks:e=!0,cascadeRunbooks:n=!1}={}){let r=await q(),o=r.tracks.findIndex(a=>a.id===t);if(o===-1)throw new Error(`track "${t}" not found`);let s=r.tracks[o];return e||await Zf(s),await Sg(t,s,"track deleted"),n?await Ip(t).catch(()=>{}):await Ep(t).catch(()=>{}),await $h(),await pE(s),e&&await Qf(t).catch(()=>{}),at(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 Rr(t){let e=await q(),n=nt(e,t);if(!n)throw new Error(`track "${t}" not found`);if(n.archivedAt)throw new Error(`track "${t}" is already archived`);return n.archivedAt=Date.now(),at(n,"archived",{boxes:n.boxes.length}),await V(),n.kind==="setup"?await Tg(t):wE(t).catch(()=>{}),on(n)}var hE=9e4;async function wE(t){try{await gE(t)}catch{}await Tg(t).catch(()=>{})}async function Tg(t){let e=await q(),n=nt(e,t);if(!(!n||!n.archivedAt)){await Sg(t,n,"track archived");for(let r of n.boxes)Bn.has(r.state)||(r.state="stopped");await V()}}async function gE(t){let e=await q(),n=nt(e,t);if(!n||!n.archivedAt)return;let r=null,o=[],s="derived",a=(n.events||[]).some(l=>l.type==="said"),i=n.leadBox?await Y(n.leadBox).catch(()=>null):null;if(i?.cwd&&a)try{({text:r,items:o}=await Tf(n.leadBox,i.cwd,{timeoutMs:hE})),s="lead-cli"}catch{}if(r||(r=Rc(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 O(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 Ag(){let t=await q(),e=0,n=0,r=0;for(let o of t.tracks){if(!o.archivedAt||o.kind==="setup"||o.beat?.text){n++;continue}let s=Rc(o,await L(o).catch(()=>[]));if(!s){r++;continue}o.beat={text:s,source:"derived",at:Date.now()},await O(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 tn(t,e){let n=await Et(t,e).catch(()=>null);if(!n?.archiveOnMerge||!n.trackId)return!1;let r=await _g(n);if(r.length){for(let o of r)await lr(o.repo,o.number,!0).catch(()=>{});return!1}try{return await Rr(n.trackId),!0}catch(o){if(/already archived/i.test(String(o?.message||o)))return!1;throw o}}async function _g(t){return(await pe().catch(()=>[])).filter(n=>n.trackId===t.trackId&&n.state!=="merged"&&n.state!=="closed"&&n.state!=="lost"&&!(n.repo===t.repo&&n.number===t.number))}async function en(t,e){return iu(t,e,"merged")}async function iu(t,e,n="merged"){let r=await Et(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 _g(r);if(s.length){let i=[...r.notifyPending||[],o];for(let c of s)await fm(c.repo,c.number,i).catch(()=>{});return!1}let a=[...r.notifyPending||[],o];try{return await Qt(r.trackId,yE(a),{kind:"merge-notify"}),!0}catch{return!1}}var Jw={merged:"merged",closed:"closed on GitHub WITHOUT merging",lost:"no longer found on GitHub (deleted, or its repo left the grant)"};function yE(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 ${Jw[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 ${Jw[i.outcome]??i.outcome}`).join(`
|
|
277
346
|
`),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}
|
|
278
347
|
${s}
|
|
279
|
-
${a}`}async function Cl(t){if(t.archivedAt)return!1;let e=t.boxes.find(r=>r.name===t.leadBox);if(!e||_n.has(e.state)||await X(e.name))return!1;let n=zh(e.agent,e.sessionId);if(!await Xo(n))return st(t,"rehydrate-failed",{box:e.name,agent:e.agent,reason:`agent "${e.agent}" not available on this box`}),!1;try{let r=await rn(e.name,e.cwd,{agentCmd:n,agent:e.agent,resumeSessionId:e.sessionId,pinned:e.pinned===!0});return e.hookTokenHash=Or(r.hookToken),ss({cwd:e.cwd,sessionName:e.name,agent:e.agent,agentCmd:n}),e.state="live",st(t,"rehydrated",{box:e.name,agent:e.agent}),!0}catch(r){return st(t,"rehydrate-failed",{box:e.name,agent:e.agent,reason:String(r?.message||r)}),!1}}async function xw(t){let e=await q(),n=tt(e,t);if(!n)throw new Error(`track "${t}" not found`);if(!n.archivedAt)throw new Error(`track "${t}" is not archived`);return delete n.archivedAt,st(n,"unarchived",{}),await Cl(n),await G(),ze(n)}async function Sw(){let t=await q(),e=new Set((await nn()).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 Jt(a.name).catch(()=>{}),st(s,"orphan-reaped",{box:a.name,agent:a.agent}),r++,o=!0),!_n.has(a.state)&&a.state!=="stopped"&&(a.state="stopped",o=!0);return o&&await G(),r}async function vw(t){let e=await q(),n=tt(e,t);if(!n)throw new Error(`track "${t}" not found`);if(n.archivedAt)throw new Error(`track "${t}" is archived \u2014 restore it before restarting the lead`);let r=n.boxes.find(a=>a.name===n.leadBox);if(!r)throw new Error(`track "${t}" has no lead box`);if(_n.has(r.state))throw new Error("lead box is terminal \u2014 nothing to restart");if(await X(n.leadBox)){let a=await wt(n.leadBox,{lines:At}).catch(()=>"");if(rs(r.agent,a))throw new Error("lead is mid-turn \u2014 retry when it goes idle");await Jt(n.leadBox),r.state="stopped"}let s=await Cl(n);if(await G(),!s)throw new Error("rehydrate failed \u2014 see the track's rehydrate-failed event");return ze(n)}var Aw=(t,e,n)=>({name:t.name,agent:t.agent,role:e,state:n,cwd:t.cwd,sessionId:t.sessionId,pinned:t.pinned===!0,hookTokenHash:Or(t.hookToken),createdAt:Date.now()});function Tw(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 ko=new Map,Ao=t=>ko.get(t)||(le?tt(le,t)?.handoff:null)||null;async function pa(t,e){let n=await q(),r=tt(n,t);r&&(r.handoff={...r.handoff||{},...e},await G())}function $T(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 _w(){let t=await q(),e=!1;for(let n of t.tracks||[]){let r=n.handoff,o=$T(n);if(!o)continue;e=!0;let s=!!r.toBox&&r.toBox===r.fromBox;if(s&&o.step==="error"){let c=await X(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=Tw(l),d=f[f.length-1],m=d.agent,h=d.sessionId??null;d.endedAt=u,r.reason&&(d.endReason=r.reason);let w=c.agent==="claude"&&!!c.sessionId;f.push({agent:r.toAgent,sessionId:c.sessionId||null,pinned:w,startedAt:u,endedAt:null}),l.agent=r.toAgent,l.sessionId=c.sessionId||null,l.pinned=w,l.state="live",n.pendingHandoffHydrate={artifact:ga,at:u},st(n,"lead-handoff",{from:r.fromBox,to:r.fromBox,fromAgent:m,toAgent:r.toAgent,fromSessionId:h,toSessionId:c.sessionId||null,epoch:f.length-1,singleBox:!0,skipPermissions:r.skipPermissions,reason:r.reason??null,handoffStartedAt:r.startedAt,recovered:!0}),n.handoff={...r,step:"done",finishedAt:u,recovered:"swap adopted from live tmux tags after restart"},await V({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 V({flow:"lead-handoff",phase:"reconciled",trackId:n.id,outcome:o.step,recovered:o.recovered,fromBox:o.fromBox,toBox:o.toBox}).catch(()=>{}),i&&await Jt(i).catch(()=>{}),a&&await Jt(a).catch(()=>{})}return e&&await G(),e}async function Ml(t,e,n=!0,r=null){let o=ko.get(t);if(o&&!o.finishedAt)throw new Error("a lead handoff is already running for this Track");let s=tt(await q(),t);if(!s)throw new Error(`track "${t}" not found`);let a=await wa(e,n),i=await X(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:Jh(r),toBox:s.leadBox,startedAt:Date.now(),finishedAt:null,error:null};return ko.set(t,c),await pa(t,{step:c.step,fromBox:c.fromBox,toAgent:e,toBox:c.toBox,skipPermissions:n,startedAt:c.startedAt,finishedAt:null,error:null}),OT(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 pa(t,{step:c.step,error:c.error,finishedAt:c.finishedAt}).catch(()=>{}),V({flow:"lead-handoff",phase:"failed",trackId:t,step:c.step,error:c.error,fromBox:c.fromBox,toAgent:c.toAgent,toBox:c.toBox}).catch(()=>{})}),V({flow:"lead-handoff",phase:"started",trackId:t,fromBox:c.fromBox,toAgent:e,cwd:i.cwd}).catch(()=>{}),c}async function OT(t,e,n,r,o){let s=async(_,C={})=>{t.step=_,console.log(`[lead-handoff ${t.trackId}] step \u2192 ${_}`),await pa(t.trackId,{step:_,toBox:t.toBox}),await V({flow:"lead-handoff",phase:_,trackId:t.trackId,fromBox:t.fromBox,toBox:t.toBox,toAgent:o,sessionName:C.sessionName,...C})};console.log(`[lead-handoff ${t.trackId}] ${t.fromBox}: ${n.agent} \u2192 ${o} IN PLACE`);let a=await q(),i=tt(a,t.trackId)?.boxes.find(_=>_.name===t.fromBox)||null,c=i?.agent||n.agent,l=i?.sessionId??n.sessionId??null,u=i?.pinned===!0;await s("checkpoint",{sessionName:t.fromBox}),await xd(t.fromBox),await s("swap",{cwd:n.cwd,agentCmd:r});let f=await ec(t.fromBox,{agentCmd:r,agent:o});await s("hydrate",{sessionName:t.fromBox}),await Re({cwd:n.cwd,sessionId:f.sessionId,sessionName:t.fromBox,agent:o,agentCmd:vo(o)});let d=await wt(t.fromBox,{lines:At}).catch(()=>"");if(!os(o,d)){await s("rollback",{sessionName:t.fromBox,fromAgent:c});let _=zh(c,l,{skipPermissions:t.skipPermissions});await ec(t.fromBox,{agentCmd:_,agent:c,resumeSessionId:l,pinned:u});let C=await q(),K=tt(C,t.trackId);if(K){let I=K.boxes.find(L=>L.name===t.fromBox);I&&(I.state="live"),st(K,"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 G()}throw new Error(`incoming ${o} CLI never drew its composer \u2014 rolled back to ${c} (--resume, context intact)`)}await s("commit");let m=await q(),h=tt(m,t.trackId);if(!h)throw new Error(`track "${t.trackId}" not found during handoff commit \u2014 was it deleted or archived mid-flight?`);let w=h.boxes.find(_=>_.name===t.fromBox);if(!w)throw new Error(`lead box row "${t.fromBox}" missing during handoff commit`);let y=Date.now(),b=Tw(w),A=b[b.length-1];A.endedAt=y,t.reason&&(A.endReason=t.reason),b.push({agent:o,sessionId:f.sessionId,pinned:f.pinned,startedAt:y,endedAt:null}),w.agent=o,w.sessionId=f.sessionId,w.pinned=f.pinned,w.state="live",w.role="lead",h.pendingHandoffHydrate={artifact:ga,at:y},st(h,"lead-handoff",{from:t.fromBox,to:t.fromBox,fromAgent:c,toAgent:o,fromSessionId:l,toSessionId:f.sessionId,epoch:b.length-1,singleBox:!0,forced:f.forced||void 0,skipPermissions:t.skipPermissions,reason:t.reason,handoffStartedAt:t.startedAt}),await G(),await s("done",{sessionName:t.fromBox}),await pa(t.trackId,{finishedAt:Date.now(),error:null}),t.finishedAt=Date.now()}var CT=Number(process.env.BOSTRAT_SUBTASK_TIMEOUT_MS)||18e5,MT=Number(process.env.BOSTRAT_SUBTASK_POLL_MS)||3e3,yr=H.join(Ve,"worktrees"),wr=[go,fa],Vt=new Map;var xr=t=>{let e=new Map([...Vt.values()].filter(r=>r.trackId===t).map(r=>[r.id,r])),n=le?tt(le,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)},Nl=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 NT(t,e,n){let r=await q(),o=tt(r,t);o&&(o.subtasks=o.subtasks||{},o.subtasks[e]={...o.subtasks[e]||{},...n},await G())}var br=t=>NT(t.trackId,t.id,Nl(t));async function gr(t,e){t.step=e,await br(t)}function PT(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 BT(t){return!!t&&!t.finishedAt&&!t.canceled&&t.step==="working"&&!!t.box&&!!t.dir&&!!t.base&&!!t.branch}async function DT(t,e,n){if(!BT(n)||Vt.has(e)||!await X(n.box).catch(()=>null))return!1;try{await U.access(n.dir)}catch{return!1}let o=await X(t.leadBox).catch(()=>null);if(!o?.cwd)return!1;let s={...n,id:e,leadBox:t.leadBox,step:"working",canceled:!1,forceMerge:!1};return Vt.set(e,s),Ow(s,o).catch(a=>Rw(s,a)),!0}async function Ew(){let t=await q(),e=!1;for(let n of t.tracks||[]){for(let[r,o]of Object.entries(n.subtasks||{})){if(Vt.has(r))continue;if(await DT(n,r,o)){e=!0,st(n,"subtask-resumed",{box:o.box,agent:o.agent,task:o.task,subtaskId:r,resumedStep:o.step}),await V({flow:"subtask",phase:"resumed",trackId:n.id,subtaskId:r,box:o.box,branch:o.branch}).catch(()=>{});continue}let s=o.step,a=PT(o);if(!a)continue;e=!0,n.subtasks[r]=a;let i=n.boxes.find(c=>c.subtaskId===r);i&&!_n.has(i.state)&&(i.state="canceled"),st(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 V({flow:"subtask",phase:"reconciled",trackId:n.id,subtaskId:r,outcome:a.step,recovered:a.recovered,box:o.box,branch:o.branch}).catch(()=>{}),await Kh(n,o.box,o.dir)}for(let r of n.boxes){if(r.role!=="subtask"||_n.has(r.state)||Vt.has(r.subtaskId)||n.subtasks?.[r.subtaskId])continue;e=!0,r.state="canceled";let o=r.subtaskId?H.join(yr,`${n.id}-${r.subtaskId}`):null;await V({flow:"subtask",phase:"reaped-legacy-orphan",trackId:n.id,subtaskId:r.subtaskId,box:r.name}).catch(()=>{}),await Kh(n,r.name,o)}}return e=await jT(t)||e,e&&await G(),e}async function Kh(t,e,n){if(e&&await Jt(e).catch(()=>{}),!n)return;if(t.workspace?.kind==="multi"){for(let o of Ot(t.workspace)){let s=H.join(n,o.name);await Ut(o.base,s).then(()=>!0,()=>!1)||await Ut(o.dir,s).catch(()=>{})}await U.rm(n,{recursive:!0,force:!0}).catch(()=>{});return}let r=await X(t.leadBox).catch(()=>null);r?.cwd&&await Ut(r.cwd,n).catch(()=>{})}async function jT(t){let e;try{e=await U.readdir(yr)}catch{return!1}let n=new Set;for(let o of t.tracks||[]){for(let s of Object.keys(o.subtasks||{}))n.add(`${o.id}-${s}`);for(let s of o.boxes||[])s.subtaskId&&n.add(`${o.id}-${s.subtaskId}`)}for(let o of Vt.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(yr,o),a=(t.tracks||[]).find(i=>o.startsWith(`${i.id}-`));if(a?.workspace?.kind==="multi"){for(let i of Ot(a.workspace))await Ut(i.base,H.join(s,i.name)).catch(()=>{});await U.rm(s,{recursive:!0,force:!0}).catch(()=>{})}else{let i=a?await X(a.leadBox).catch(()=>null):null;i?.cwd?await Ut(i.cwd,s).catch(()=>{}):await U.rm(s,{recursive:!0,force:!0}).catch(()=>{})}await V({flow:"subtask",phase:"swept-orphan-worktree",dir:s}).catch(()=>{}),r=!0}return r}var Pl=t=>t.trim().replace(/\s+/g,"-").replace(/[^A-Za-z0-9._-]/g,"").toLowerCase()||"track",xo=class extends Error{};async function Iw(t,{agent:e,task:n,skipPermissions:r=!0,reason:o=null}={}){if(typeof n!="string"||!cr(n))throw new Error("task (string) required");let s=await wa(e,r),a=tt(await q(),t);if(!a)throw new Error(`track "${t}" not found`);let i=await X(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=Ot(a.workspace);if(!d.length)throw new Error("this multi-repo Track has no tagged repos to delegate into");for(let m of d)if(!m?.dir||!await Gs(m.dir))throw new Error(`tagged checkout "${m?.name??"?"}" is missing from the lead workspace (${m?.dir??"no dir"})`);l=d.map(m=>({name:m.name,leadDir:m.dir,base:null}))}else if(!await Gs(i.cwd))throw new Error(`subtask delegation needs the lead's workspace to be a git repo (got ${i.cwd})`);let u=So().slice(0,8),f={id:u,trackId:t,trackName:a.name,agent:e,task:cr(n),skipPermissions:r,reason:Jh(o),step:"snapshot",leadBox:a.leadBox,box:null,branch:`bostrat/${Pl(a.name)}-${u}`,base:null,repos:l,dir:H.join(yr,`${t}-${u}`),stat:null,conflicts:[],applied:null,startedAt:Date.now(),finishedAt:null,error:null,canceled:!1,forceMerge:!1};return Vt.set(u,f),await br(f),LT(f,i,s).catch(d=>Rw(f,d)),f}async function Rw(t,e){t.failedStep=t.step,t.error=e instanceof xo?"canceled":e?.message||String(e),t.step=e instanceof xo?"canceled":"error",t.finishedAt=Date.now(),await Dl(t).catch(()=>{}),await br(t).catch(()=>{}),await WT(t).catch(()=>{})}function Bl(t){let e=Vt.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 $w(t){let e=Vt.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 LT(t,e,n){if(await gr(t,"snapshot"),t.repos){for(let s of t.repos)s.base=await Pc(s.leadDir,`bostrat: snapshot for subtask ${t.id}`);await br(t)}else t.base=await Pc(e.cwd,`bostrat: snapshot for subtask ${t.id}`);if(await gr(t,"spawn"),await U.mkdir(yr,{recursive:!0}),t.repos){await U.mkdir(t.dir,{recursive:!0});for(let a of t.repos)await lr(a.leadDir,H.join(t.dir,a.name),t.branch,a.base);let s=Ot((tt(await q(),t.trackId)||{}).workspace);await ba(t.dir,t.repos.map(a=>{let i=s.find(c=>c?.name===a.name);return{name:a.name,repo:i?.repo??null,baseRef:null}}),t.branch)}else await lr(e.cwd,t.dir,t.branch,t.base),await Al(t.dir);await FT(t,e);let r=t.trackName.slice(0,38).trim(),o=await rn(`${r} ${t.agent} sub ${t.id}`,t.dir,{agentCmd:n,agent:t.agent});t.box=o.name,await UT(t,o),await Xh(t.trackId,o.name,VA(t.task,!!t.repos),{kind:"subtask"}),await Ow(t,e)}async function Ow(t,e){await gr(t,"working");let n=H.join(t.dir,fa);if(await GT(n,t),t.canceled)throw new xo;if(await gr(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 Bc(l,`bostrat: subtask ${t.id} result (auto-committed)`),c.stat=await Fc(c.leadDir,c.base,t.branch,wr),c.files=await Uc(c.leadDir,c.base,t.branch,wr);let u=await Hc(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=>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 Bc(t.dir,`bostrat: subtask ${t.id} result (auto-committed)`),t.stat=await Fc(e.cwd,t.base,t.branch,wr),t.files=await Uc(e.cwd,t.base,t.branch,wr);let o=await Hc(e.cwd,t.base,t.branch,wr);t.applied=o.applied,t.conflicts=o.conflicts}await br(t);let r=await qT(t,e,n);await gr(t,"resume"),await Xh(t.trackId,t.leadBox,zA(t.task,t.stat,t.conflicts,r),{kind:"subtask-resume"}),await gr(t,"reap"),await Dl(t),t.step="done",t.finishedAt=Date.now(),await HT(t),await br(t)}async function FT(t,e){let n=H.join(t.dir,H.dirname(go));await U.mkdir(n,{recursive:!0});let r="";try{r=await Hu(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}
|
|
348
|
+
${a}`}async function cu(t){if(t.archivedAt)return!1;let e=t.boxes.find(r=>r.name===t.leadBox);if(!e||Bn.has(e.state)||await Y(e.name))return!1;let n=Zw(e.agent,e.sessionId);if(!await ws(n))return at(t,"rehydrate-failed",{box:e.name,agent:e.agent,reason:`agent "${e.agent}" not available on this box`}),!1;try{let r=await hn(e.name,e.cwd,{agentCmd:n,agent:e.agent,resumeSessionId:e.sessionId,pinned:e.pinned===!0});return e.hookTokenHash=Hr(r.hookToken),As({cwd:e.cwd,sessionName:e.name,agent:e.agent,agentCmd:n}),e.state="live",at(t,"rehydrated",{box:e.name,agent:e.agent}),!0}catch(r){return at(t,"rehydrate-failed",{box:e.name,agent:e.agent,reason:String(r?.message||r)}),!1}}async function Eg(t){let e=await q(),n=nt(e,t);if(!n)throw new Error(`track "${t}" not found`);if(!n.archivedAt)throw new Error(`track "${t}" is not archived`);return delete n.archivedAt,at(n,"unarchived",{}),await cu(n),await V(),on(n)}async function Ig(){let t=await q(),e=new Set((await mn()).map(s=>s.name)),n=new Set((t.tracks||[]).filter(s=>!s.archivedAt).flatMap(s=>(s.boxes||[]).map(a=>a.name))),r=0,o=!1;for(let s of t.tracks||[])if(s.archivedAt)for(let a of s.boxes)e.has(a.name)&&!n.has(a.name)&&(await re(a.name).catch(()=>{}),at(s,"orphan-reaped",{box:a.name,agent:a.agent}),r++,o=!0),!Bn.has(a.state)&&a.state!=="stopped"&&(a.state="stopped",o=!0);return o&&await V(),r}async function Rg(t){let e=await q(),n=nt(e,t);if(!n)throw new Error(`track "${t}" not found`);if(n.archivedAt)throw new Error(`track "${t}" is archived \u2014 restore it before restarting the lead`);let r=n.boxes.find(a=>a.name===n.leadBox);if(!r)throw new Error(`track "${t}" has no lead box`);if(Bn.has(r.state))throw new Error("lead box is terminal \u2014 nothing to restart");if(await Y(n.leadBox)){let a=await gt(n.leadBox,{lines:Tt}).catch(()=>"");if(vs(r.agent,a))throw new Error("lead is mid-turn \u2014 retry when it goes idle");await re(n.leadBox),r.state="stopped"}let s=await cu(n);if(await V(),!s)throw new Error("rehydrate failed \u2014 see the track's rehydrate-failed event");return on(n)}var $g=(t,e,n)=>({name:t.name,agent:t.agent,role:e,state:n,cwd:t.cwd,sessionId:t.sessionId,pinned:t.pinned===!0,hookTokenHash:Hr(t.hookToken),createdAt:Date.now()});function Og(t){return(!Array.isArray(t.epochs)||t.epochs.length===0)&&(t.epochs=[{agent:t.agent??null,sessionId:t.sessionId??null,pinned:t.pinned===!0,startedAt:t.createdAt??null,endedAt:null}]),t.epochs}var Do=new Map,Uo=t=>Do.get(t)||(we?nt(we,t)?.handoff:null)||null;async function Oa(t,e){let n=await q(),r=nt(n,t);r&&(r.handoff={...r.handoff||{},...e},await V())}function bE(t,e=Date.now()){let n=t?.handoff;if(!n||n.finishedAt)return null;let r=n.step,o=!!n.toBox&&n.toBox===n.fromBox,s=t.boxes?.find?.(i=>i.name===t.leadBox)||null;return(o?t.leadBox===n.fromBox&&!!n.startedAt&&Array.isArray(s?.epochs)&&s.epochs.some(i=>i.startedAt!=null&&i.startedAt>=n.startedAt):!!n.toBox&&t.leadBox===n.toBox)?{...n,step:"done",finishedAt:e,recovered:`committed before restart (was "${r}")`}:{...n,step:"error",finishedAt:e,error:`interrupted by an agent restart during "${r}"; lead "${t.leadBox}" retained \u2014 retry the handoff`,recovered:"rolled-back"}}async function Mg(){let t=await q(),e=!1;for(let n of t.tracks||[]){let r=n.handoff,o=bE(n);if(!o)continue;e=!0;let s=!!r.toBox&&r.toBox===r.fromBox;if(s&&o.step==="error"){let c=await Y(r.fromBox).catch(()=>null),l=n.boxes.find(u=>u.name===r.fromBox);if(c&&l&&c.agent===r.toAgent&&r.toAgent!==l.agent){let u=Date.now(),f=Og(l),d=f[f.length-1],m=d.agent,h=d.sessionId??null;d.endedAt=u,r.reason&&(d.endReason=r.reason);let w=c.agent==="claude"&&!!c.sessionId;f.push({agent:r.toAgent,sessionId:c.sessionId||null,pinned:w,startedAt:u,endedAt:null}),l.agent=r.toAgent,l.sessionId=c.sessionId||null,l.pinned=w,l.state="live",n.pendingHandoffHydrate={artifact:Pa,at:u},at(n,"lead-handoff",{from:r.fromBox,to:r.fromBox,fromAgent:m,toAgent:r.toAgent,fromSessionId:h,toSessionId:c.sessionId||null,epoch:f.length-1,singleBox:!0,skipPermissions:r.skipPermissions,reason:r.reason??null,handoffStartedAt:r.startedAt,recovered:!0}),n.handoff={...r,step:"done",finishedAt:u,recovered:"swap adopted from live tmux tags after restart"},await G({flow:"lead-handoff",phase:"reconciled",trackId:n.id,outcome:"done",recovered:n.handoff.recovered,fromBox:r.fromBox,toBox:r.toBox}).catch(()=>{});continue}}let a=!s&&o.recovered==="rolled-back"&&r.toBox&&r.toBox!==n.leadBox?r.toBox:null,i=!s&&o.step==="done"?r.fromBox:null;n.handoff=o,await G({flow:"lead-handoff",phase:"reconciled",trackId:n.id,outcome:o.step,recovered:o.recovered,fromBox:o.fromBox,toBox:o.toBox}).catch(()=>{}),i&&await re(i).catch(()=>{}),a&&await re(a).catch(()=>{})}return e&&await V(),e}async function lu(t,e,n=!0,r=null){let o=Do.get(t);if(o&&!o.finishedAt)throw new Error("a lead handoff is already running for this Track");let s=nt(await q(),t);if(!s)throw new Error(`track "${t}" not found`);let a=await Na(e,n),i=await Y(s.leadBox);if(!i)throw new Error(`lead box "${s.leadBox}" is not running`);let c={trackId:t,step:"checkpoint",fromBox:s.leadBox,toAgent:e,skipPermissions:n,reason:Qw(r),toBox:s.leadBox,startedAt:Date.now(),finishedAt:null,error:null};return Do.set(t,c),await Oa(t,{step:c.step,fromBox:c.fromBox,toAgent:e,toBox:c.toBox,skipPermissions:n,startedAt:c.startedAt,finishedAt:null,error:null}),kE(c,s,i,a,e).catch(async l=>{c.error=l?.message||String(l),c.finishedAt=Date.now(),console.error(`[lead-handoff ${t}] FAILED at step "${c.step}": ${c.error}`),await Oa(t,{step:c.step,error:c.error,finishedAt:c.finishedAt}).catch(()=>{}),G({flow:"lead-handoff",phase:"failed",trackId:t,step:c.step,error:c.error,fromBox:c.fromBox,toAgent:c.toAgent,toBox:c.toBox}).catch(()=>{})}),G({flow:"lead-handoff",phase:"started",trackId:t,fromBox:c.fromBox,toAgent:e,cwd:i.cwd}).catch(()=>{}),c}async function kE(t,e,n,r,o){let s=async(A,M={})=>{t.step=A,console.log(`[lead-handoff ${t.trackId}] step \u2192 ${A}`),await Oa(t.trackId,{step:A,toBox:t.toBox}),await G({flow:"lead-handoff",phase:A,trackId:t.trackId,fromBox:t.fromBox,toBox:t.toBox,toAgent:o,sessionName:M.sessionName,...M})};console.log(`[lead-handoff ${t.trackId}] ${t.fromBox}: ${n.agent} \u2192 ${o} IN PLACE`);let a=await q(),i=nt(a,t.trackId)?.boxes.find(A=>A.name===t.fromBox)||null,c=i?.agent||n.agent,l=i?.sessionId??n.sessionId??null,u=i?.pinned===!0;await s("checkpoint",{sessionName:t.fromBox}),await Of(t.fromBox),await s("swap",{cwd:n.cwd,agentCmd:r});let f=await Oc(t.fromBox,{agentCmd:r,agent:o});await s("hydrate",{sessionName:t.fromBox}),await Ue({cwd:n.cwd,sessionId:f.sessionId,sessionName:t.fromBox,agent:o,agentCmd:Fo(o)});let d=await gt(t.fromBox,{lines:Tt}).catch(()=>"");if(!Ts(o,d)){await s("rollback",{sessionName:t.fromBox,fromAgent:c});let A=Zw(c,l,{skipPermissions:t.skipPermissions});await Oc(t.fromBox,{agentCmd:A,agent:c,resumeSessionId:l,pinned:u});let M=await q(),z=nt(M,t.trackId);if(z){let $=z.boxes.find(F=>F.name===t.fromBox);$&&($.state="live"),at(z,"lead-handoff-failed",{box:t.fromBox,fromAgent:c,toAgent:o,reason:t.reason,error:`incoming ${o} CLI never drew its composer`,rolledBack:!0,handoffStartedAt:t.startedAt}),await V()}throw new Error(`incoming ${o} CLI never drew its composer \u2014 rolled back to ${c} (--resume, context intact)`)}await s("commit");let m=await q(),h=nt(m,t.trackId);if(!h)throw new Error(`track "${t.trackId}" not found during handoff commit \u2014 was it deleted or archived mid-flight?`);let w=h.boxes.find(A=>A.name===t.fromBox);if(!w)throw new Error(`lead box row "${t.fromBox}" missing during handoff commit`);let g=Date.now(),b=Og(w),v=b[b.length-1];v.endedAt=g,t.reason&&(v.endReason=t.reason),b.push({agent:o,sessionId:f.sessionId,pinned:f.pinned,startedAt:g,endedAt:null}),w.agent=o,w.sessionId=f.sessionId,w.pinned=f.pinned,w.state="live",w.role="lead",h.pendingHandoffHydrate={artifact:Pa,at:g},at(h,"lead-handoff",{from:t.fromBox,to:t.fromBox,fromAgent:c,toAgent:o,fromSessionId:l,toSessionId:f.sessionId,epoch:b.length-1,singleBox:!0,forced:f.forced||void 0,skipPermissions:t.skipPermissions,reason:t.reason,handoffStartedAt:t.startedAt}),await V(),await s("done",{sessionName:t.fromBox}),await Oa(t.trackId,{finishedAt:Date.now(),error:null}),t.finishedAt=Date.now()}var xE=Number(process.env.BOSTRAT_SUBTASK_TIMEOUT_MS)||18e5,SE=Number(process.env.BOSTRAT_SUBTASK_POLL_MS)||3e3,Er=H.join(Oe,"worktrees"),Ar=[No,$a],Zt=new Map;var $r=t=>{let e=new Map([...Zt.values()].filter(r=>r.trackId===t).map(r=>[r.id,r])),n=we?nt(we,t):null;if(n?.subtasks)for(let[r,o]of Object.entries(n.subtasks))e.has(r)||e.set(r,o);return[...e.values()].sort((r,o)=>r.startedAt-o.startedAt)},uu=t=>({id:t.id,trackId:t.trackId,trackName:t.trackName,agent:t.agent,task:t.task,skipPermissions:t.skipPermissions,reason:t.reason||null,step:t.step,failedStep:t.failedStep||null,box:t.box,branch:t.branch,base:t.base,repos:t.repos||null,dir:t.dir,stat:t.stat,files:t.files||null,conflicts:t.conflicts||[],applied:t.applied,error:t.error,canceled:t.canceled,startedAt:t.startedAt,finishedAt:t.finishedAt});async function vE(t,e,n){let r=await q(),o=nt(r,t);o&&(o.subtasks=o.subtasks||{},o.subtasks[e]={...o.subtasks[e]||{},...n},await V())}var Ir=t=>vE(t.trackId,t.id,uu(t));async function _r(t,e){t.step=e,await Ir(t)}function TE(t,e=Date.now()){if(!t||t.finishedAt)return null;let n=t.step;return["snapshot","spawn","working"].includes(n)?{...t,step:"error",finishedAt:e,error:`interrupted by an agent restart during "${n}"; no changes were applied to the lead \u2014 retry the delegation`,recovered:"rolled-back"}:{...t,step:"error",finishedAt:e,error:`interrupted by an agent restart during "${n}"; the lead's working tree may hold a PARTIAL merge \u2014 review git status. The subtask's work is preserved on branch ${t.branch}`,recovered:"partial-merge"}}function AE(t){return!!t&&!t.finishedAt&&!t.canceled&&t.step==="working"&&!!t.box&&!!t.dir&&!!t.base&&!!t.branch}async function _E(t,e,n){if(!AE(n)||Zt.has(e)||!await Y(n.box).catch(()=>null))return!1;try{await U.access(n.dir)}catch{return!1}let o=await Y(t.leadBox).catch(()=>null);if(!o?.cwd)return!1;let s={...n,id:e,leadBox:t.leadBox,step:"working",canceled:!1,forceMerge:!1};return Zt.set(e,s),Dg(s,o).catch(a=>Pg(s,a)),!0}async function Cg(){let t=await q(),e=!1;for(let n of t.tracks||[]){for(let[r,o]of Object.entries(n.subtasks||{})){if(Zt.has(r))continue;if(await _E(n,r,o)){e=!0,at(n,"subtask-resumed",{box:o.box,agent:o.agent,task:o.task,subtaskId:r,resumedStep:o.step}),await G({flow:"subtask",phase:"resumed",trackId:n.id,subtaskId:r,box:o.box,branch:o.branch}).catch(()=>{});continue}let s=o.step,a=TE(o);if(!a)continue;e=!0,n.subtasks[r]=a;let i=n.boxes.find(c=>c.subtaskId===r);i&&!Bn.has(i.state)&&(i.state="canceled"),at(n,"subtask-failed",{from:o.box,fromAgent:o.agent,task:o.task,subtaskId:r,failedStep:s,error:a.error,recovered:a.recovered,durationMs:o.startedAt?a.finishedAt-o.startedAt:null}),await G({flow:"subtask",phase:"reconciled",trackId:n.id,subtaskId:r,outcome:a.step,recovered:a.recovered,box:o.box,branch:o.branch}).catch(()=>{}),await Xw(n,o.box,o.dir)}for(let r of n.boxes){if(r.role!=="subtask"||Bn.has(r.state)||Zt.has(r.subtaskId)||n.subtasks?.[r.subtaskId])continue;e=!0,r.state="canceled";let o=r.subtaskId?H.join(Er,`${n.id}-${r.subtaskId}`):null;await G({flow:"subtask",phase:"reaped-legacy-orphan",trackId:n.id,subtaskId:r.subtaskId,box:r.name}).catch(()=>{}),await Xw(n,r.name,o)}}return e=await EE(t)||e,e&&await V(),e}async function Xw(t,e,n){if(e&&await re(e).catch(()=>{}),!n)return;if(t.workspace?.kind==="multi"){for(let o of Nt(t.workspace)){let s=H.join(n,o.name);await Kt(o.base,s).then(()=>!0,()=>!1)||await Kt(o.dir,s).catch(()=>{})}await U.rm(n,{recursive:!0,force:!0}).catch(()=>{});return}let r=await Y(t.leadBox).catch(()=>null);r?.cwd&&await Kt(r.cwd,n).catch(()=>{})}async function EE(t){let e;try{e=await U.readdir(Er)}catch{return!1}let n=new Set;for(let o of t.tracks||[]){for(let s of Object.keys(o.subtasks||{}))n.add(`${o.id}-${s}`);for(let s of o.boxes||[])s.subtaskId&&n.add(`${o.id}-${s.subtaskId}`)}for(let o of Zt.values())o.dir&&n.add(H.basename(o.dir));let r=!1;for(let o of e){if(n.has(o))continue;let s=H.join(Er,o),a=(t.tracks||[]).find(i=>o.startsWith(`${i.id}-`));if(a?.workspace?.kind==="multi"){for(let i of Nt(a.workspace))await Kt(i.base,H.join(s,i.name)).catch(()=>{});await U.rm(s,{recursive:!0,force:!0}).catch(()=>{})}else{let i=a?await Y(a.leadBox).catch(()=>null):null;i?.cwd?await Kt(i.cwd,s).catch(()=>{}):await U.rm(s,{recursive:!0,force:!0}).catch(()=>{})}await G({flow:"subtask",phase:"swept-orphan-worktree",dir:s}).catch(()=>{}),r=!0}return r}var du=t=>t.trim().replace(/\s+/g,"-").replace(/[^A-Za-z0-9._-]/g,"").toLowerCase()||"track",jo=class extends Error{};async function Ng(t,{agent:e,task:n,skipPermissions:r=!0,reason:o=null}={}){if(typeof n!="string"||!gr(n))throw new Error("task (string) required");let s=await Na(e,r),a=nt(await q(),t);if(!a)throw new Error(`track "${t}" not found`);let i=await Y(a.leadBox);if(!i)throw new Error(`lead box "${a.leadBox}" is not running`);if(!i.cwd)throw new Error("can't resolve the lead's working directory");let c=a.workspace?.kind==="multi",l=null;if(c){let d=Nt(a.workspace);if(!d.length)throw new Error("this multi-repo Track has no tagged repos to delegate into");for(let m of d)if(!m?.dir||!await ua(m.dir))throw new Error(`tagged checkout "${m?.name??"?"}" is missing from the lead workspace (${m?.dir??"no dir"})`);l=d.map(m=>({name:m.name,leadDir:m.dir,base:null}))}else if(!await ua(i.cwd))throw new Error(`subtask delegation needs the lead's workspace to be a git repo (got ${i.cwd})`);let u=Lo().slice(0,8),f={id:u,trackId:t,trackName:a.name,agent:e,task:gr(n),skipPermissions:r,reason:Qw(o),step:"snapshot",leadBox:a.leadBox,box:null,branch:`bostrat/${du(a.name)}-${u}`,base:null,repos:l,dir:H.join(Er,`${t}-${u}`),stat:null,conflicts:[],applied:null,startedAt:Date.now(),finishedAt:null,error:null,canceled:!1,forceMerge:!1};return Zt.set(u,f),await Ir(f),IE(f,i,s).catch(d=>Pg(f,d)),f}async function Pg(t,e){t.failedStep=t.step,t.error=e instanceof jo?"canceled":e?.message||String(e),t.step=e instanceof jo?"canceled":"error",t.finishedAt=Date.now(),await pu(t).catch(()=>{}),await Ir(t).catch(()=>{}),await ME(t).catch(()=>{})}function fu(t){let e=Zt.get(t);if(!e)throw new Error(`subtask "${t}" not found`);if(e.finishedAt)throw new Error("subtask already finished");return e.canceled=!0,e}function Bg(t){let e=Zt.get(t);if(!e)throw new Error(`subtask "${t}" not found`);if(e.finishedAt)throw new Error("subtask already finished");if(e.step!=="working")throw new Error(`subtask is not waiting to merge (step: ${e.step})`);return e.forceMerge=!0,e}async function IE(t,e,n){if(await _r(t,"snapshot"),t.repos){for(let s of t.repos)s.base=await ll(s.leadDir,`bostrat: snapshot for subtask ${t.id}`);await Ir(t)}else t.base=await ll(e.cwd,`bostrat: snapshot for subtask ${t.id}`);if(await _r(t,"spawn"),await U.mkdir(Er,{recursive:!0}),t.repos){await U.mkdir(t.dir,{recursive:!0});for(let a of t.repos)await yr(a.leadDir,H.join(t.dir,a.name),t.branch,a.base);let s=Nt((nt(await q(),t.trackId)||{}).workspace);await Da(t.dir,t.repos.map(a=>{let i=s.find(c=>c?.name===a.name);return{name:a.name,repo:i?.repo??null,baseRef:null}}),t.branch)}else await yr(e.cwd,t.dir,t.branch,t.base),await tu(t.dir);await RE(t,e);let r=t.trackName.slice(0,38).trim(),o=await hn(`${r} ${t.agent} sub ${t.id}`,t.dir,{agentCmd:n,agent:t.agent});t.box=o.name,await $E(t,o),await tg(t.trackId,o.name,N_(t.task,!!t.repos),{kind:"subtask"}),await Dg(t,e)}async function Dg(t,e){await _r(t,"working");let n=H.join(t.dir,$a);if(await NE(n,t),t.canceled)throw new jo;if(await _r(t,"merge"),t.repos){let o={files:0,insertions:0,deletions:0},s=[],a=[],i=!0;for(let c of t.repos){let l=H.join(t.dir,c.name);await ul(l,`bostrat: subtask ${t.id} result (auto-committed)`),c.stat=await ml(c.leadDir,c.base,t.branch,Ar),c.files=await hl(c.leadDir,c.base,t.branch,Ar);let u=await wl(c.leadDir,c.base,t.branch,Ar);c.applied=u.applied,c.conflicts=u.conflicts??[],o.files+=c.stat.files,o.insertions+=c.stat.insertions,o.deletions+=c.stat.deletions,s.push(...c.files.map(f=>H.join(c.name,f))),a.push(...c.conflicts.map(f=>f.startsWith("patch did not apply cleanly")?`${c.name}: ${f}`:H.join(c.name,f))),u.applied||(i=!1)}t.stat=o,t.files=s,t.conflicts=a,t.applied=i}else{await ul(t.dir,`bostrat: subtask ${t.id} result (auto-committed)`),t.stat=await ml(e.cwd,t.base,t.branch,Ar),t.files=await hl(e.cwd,t.base,t.branch,Ar);let o=await wl(e.cwd,t.base,t.branch,Ar);t.applied=o.applied,t.conflicts=o.conflicts}await Ir(t);let r=await CE(t,e,n);await _r(t,"resume"),await tg(t.trackId,t.leadBox,P_(t.task,t.stat,t.conflicts,r),{kind:"subtask-resume"}),await _r(t,"reap"),await pu(t),t.step="done",t.finishedAt=Date.now(),await OE(t),await Ir(t)}async function RE(t,e){let n=H.join(t.dir,H.dirname(No));await U.mkdir(n,{recursive:!0});let r="";try{r=await Fd(e.cwd,e.sessionId)}catch{}if(!r)try{r=await U.readFile(H.join(e.cwd,".bostrat/handoff/latest.md"),"utf8")}catch{r="_(no prior conversation digest available \u2014 rely on the working tree.)_"}let o=`# Lead context for subtask ${t.id}
|
|
280
349
|
|
|
281
350
|
**Delegated task:** ${t.task}
|
|
282
351
|
|
|
@@ -287,47 +356,48 @@ ${a}`}async function Cl(t){if(t.archivedAt)return!1;let e=t.boxes.find(r=>r.name
|
|
|
287
356
|
## Conversation so far (lead \u2192 you)
|
|
288
357
|
|
|
289
358
|
${r}
|
|
290
|
-
`;await U.writeFile(H.join(t.dir,
|
|
291
|
-
${n}`}},{type:"section",text:{type:"mrkdwn",text:r}},{type:"context",elements:[{type:"mrkdwn",text:`<${
|
|
359
|
+
`;await U.writeFile(H.join(t.dir,No),o)}async function $E(t,e){let n=await q(),r=nt(n,t.trackId);r&&(r.boxes.push({name:e.name,agent:e.agent,role:"subtask",state:"live",cwd:e.cwd,sessionId:e.sessionId,hookTokenHash:Hr(e.hookToken),createdAt:Date.now(),subtaskId:t.id,branch:t.branch,task:t.task}),at(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]||{},...uu(t)},await V())}async function OE(t){let e=await q(),n=nt(e,t.trackId);if(!n)return;let r=n.boxes.find(o=>o.subtaskId===t.id);r&&(r.state=t.conflicts.length?"conflict":"merged",r.stat=t.stat),at(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 ME(t){let e=await q(),n=nt(e,t.trackId);if(!n)return;let r=t.canceled||t.error==="canceled",o=n.boxes.find(s=>s.subtaskId===t.id);o&&(o.state=r?"canceled":"failed"),at(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]||{},...uu(t)},await V()}async function CE(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 pu(t){if(t.box&&await re(t.box).catch(()=>{}),t.repos&&t.dir){for(let e of t.repos)await Kt(e.leadDir,H.join(t.dir,e.name)).catch(()=>{});await U.rm(t.dir,{recursive:!0,force:!0}).catch(()=>{})}else if(t.dir){let n=(await Y(t.leadBox).catch(()=>null))?.cwd;n&&await Kt(n,t.dir).catch(()=>{})}}async function NE(t,e){let n=(e.startedAt||Date.now())+xE;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 Or(SE)}}function Or(t){return new Promise(e=>setTimeout(e,t))}import Ho from"node:fs/promises";import Fa from"node:path";import LE from"node:os";import PE from"ws";var jg=()=>process.env.BOSTRAT_SLACK_API_BASE||"https://slack.com/api";async function ja(t,e,n={}){let r=await fetch(`${jg()}/${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 mu=(t,e)=>ja("chat.postMessage",t,e),Mr=(t,e)=>ja("chat.update",t,e);async function Pt(t,e){await fetch(t,{method:"POST",headers:{"content-type":"application/json; charset=utf-8"},body:JSON.stringify(e)}).catch(()=>{})}async function BE(t,e,n={}){let r=await fetch(`${jg()}/${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 Lg(t,{filename:e,buffer:n,title:r,channelId:o,threadTs:s,initialComment:a}){let{upload_url:i,file_id:c}=await BE("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 ja("files.completeUploadExternal",t,{files:[{id:c,title:r}],channel_id:o,thread_ts:s,...a?{initial_comment:a}:{}})}var DE=2200;function Fg({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 ja("apps.connections.open",u))}catch(u){return n(`connections.open failed: ${u?.message||u}`),c()}o=new PE(l),o.on("message",u=>{let f;try{f=JSON.parse(u)}catch{return}if(f.type==="hello"){a=1e3,n("socket connected");return}if(f.type==="disconnect"){try{o.close()}catch{}return}if(!f.envelope_id)return;let d=w=>{try{o.send(JSON.stringify(w?{envelope_id:f.envelope_id,payload:w}:{envelope_id:f.envelope_id}))}catch{}},m=!1,h=setTimeout(()=>{m=!0,d()},DE);Promise.resolve().then(()=>e(f)).then(w=>{clearTimeout(h),!m&&(m=!0,d(f.accepts_response_payload?w:void 0))},w=>{clearTimeout(h),m||(m=!0,d()),n(`envelope handler failed: ${w?.message||w}`)})}),o.on("close",c),o.on("error",u=>{n(`socket error: ${u?.message||u}`);try{o.close()}catch{}})}function c(){if(r||s)return;let l=a;a=Math.min(a*2,6e4),s=setTimeout(()=>{s=null,i()},l)}return i(),()=>{r=!0,s&&clearTimeout(s);try{o?.close()}catch{}}}var Wg=()=>process.env.BOSTRAT_STATE_DIR||Fa.join(process.env.HOME||LE.tmpdir(),".bostrat"),bu=()=>Fa.join(Wg(),"slack.json"),La=null,hu=null,wu=Promise.resolve();async function xu(){return La||(hu??=Ho.readFile(bu(),"utf8").then(t=>JSON.parse(t)?.threads??{}).catch(()=>({})).then(t=>La=t).finally(()=>hu=null),hu)}async function Ug(){let t=JSON.stringify({threads:La},null,2),e=`${bu()}.${process.pid}.tmp`;await Ho.mkdir(Wg(),{recursive:!0}),await Ho.writeFile(e,t),await Ho.rename(e,bu())}async function Me(){wu=wu.then(Ug,Ug),await wu.catch(()=>{})}async function Su(t){return(await xu())[t]??null}async function FE(t,e){let n=await xu();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 Wo(t,e){let n=String(t??"").trim();return n.length<=e?n:`${n.slice(0,e-1).trimEnd()}\u2026`}function UE(t,e=500){let n=String(t??"").trim().split(/\n\s*\n/)[0]??"";return Wo(n.replace(/\n+/g," "),e)}function qg(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(Wo(t.kickoff,300))}`:"_no kickoff yet \u2014 reply in this thread to steer the lead_";return[{type:"section",text:{type:"mrkdwn",text:`${e}
|
|
360
|
+
${n}`}},{type:"section",text:{type:"mrkdwn",text:r}},{type:"context",elements:[{type:"mrkdwn",text:`<${Js(t.trackId)}|open the Track> \xB7 reachable on the operator's private network only`}]}]}async function ku(t,e){await Mr(t.botToken,{channel:e.channel,ts:e.ts,text:`Track: ${e.name}`,blocks:qg(e)})}async function jn(t,e,n,r=void 0){return mu(t.botToken,{channel:e.channel,thread_ts:e.ts,text:n,...r?{blocks:r}:{},unfurl_links:!1})}var Hg=new Set;function Dn(t){Hg.add(t),t.catch(()=>{}).finally(()=>Hg.delete(t))}var ft=t=>({response_type:"ephemeral",text:t}),HE="*/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._",WE=["claude","cursor","codex"];function gu(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 yu=(t,e)=>ft(`No live Track matches \u201C${X(e)}\u201D. Live: ${t.map(n=>`\`${X(n.name)}\``).join(" \xB7 ")||"none"}`);async function Kg(t,e,{name:n,repo:r,responseUrl:o}){let s=t.deps;try{let a=(await s.listTracks()).filter(f=>!f.archivedAt);if(a.length>=e.maxConcurrent){await Pt(o,{...ft(`At capacity \u2014 ${a.length} live Tracks (limit ${e.maxConcurrent}). Archive or stop something first.`),replace_original:!0});return}if(!e.channel){await Pt(o,{...ft("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?Fa.basename(c.base):"fresh workspace"),lead:"claude"},u=await mu(e.botToken,{channel:e.channel,text:`Track started: ${l.name}`,blocks:qg(l),unfurl_links:!1});l.ts=u.ts,(await xu())[i.id]=l,await Me(),await Pt(o,{...ft(`\u2713 *${X(l.name)}* started \u2014 root message posted.`),replace_original:!0})}catch(a){await Pt(o,{...ft(`Start failed: ${a?.message||a}`),replace_original:!0})}}async function qE(t,e,{track:n,responseUrl:r}){let o=t.deps;try{await o.archiveTrack(n.id);let s=await Su(n.id);s&&(s.archivedAt=Date.now(),await Me(),await ku(e,s).catch(()=>{})),await Pt(r,{...ft(`\u2713 *${X(n.name)}* archived.`),replace_original:!0})}catch(s){await Pt(r,{...ft(`Archive failed: ${s?.message||s}`),replace_original:!0})}}async function KE(t,e,{track:n,agent:r,responseUrl:o}){try{await t.deps.startLeadHandoff(n.id,r),await Pt(o,{...ft(`\u2713 Handing *${X(n.name)}* to ${X(r)} \u2014 the root message updates when the baton lands.`),replace_original:!0})}catch(s){await Pt(o,{...ft(`Handoff failed: ${s?.message||s}`),replace_original:!0})}}async function GE(t,e,{track:n,responseUrl:r}){let o=t.deps;try{await o.interruptLead(n.id);let s=(o.listSubtaskJobs?.(n.id)??[]).filter(a=>!a.finishedAt);for(let a of s)try{o.cancelSubtask(a.id)}catch{}await Pt(r,{...ft(`\u2713 Stopped *${X(n.name)}*${s.length?` (+ ${s.length} subtask${s.length>1?"s":""} canceled)`:""} \u2014 Track parked, resume from the Track page.`),replace_original:!0})}catch(s){await Pt(r,{...ft(`Stop failed: ${s?.message||s}`),replace_original:!0})}}function zE(t,e){let n=[...e.slice(0,99).map(r=>({text:{type:"plain_text",text:Wo(`${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 VE(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?Fa.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(`
|
|
292
361
|
`)+(o?`
|
|
293
|
-
_${o} archived_`:"")}async function
|
|
294
|
-
\u2713 *${
|
|
295
|
-
\u2713 _answered_`}}]})}async function
|
|
296
|
-
_runs on the Track's branch; reachable on the operator's private network only_`}}]);return}case"mock.state.changed":return
|
|
297
|
-
`)){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 v_(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 A_(t,e){return t?.transcript_path?t.transcript_path:e?.cwd&&e?.sessionId?de(e.cwd,e.sessionId):e?.cwd?ni(e.cwd):null}async function Uw(t,e){let n=await e(t?.session_id,t?.cwd);if(!n?.track)return[];let{track:r,box:o}=n,s={boxId:o?.name||null,provider:"claude",source:"hook"},a=[],i=async c=>{a.push(await O(r,{...s,...c}))};switch(t?.hook_event_name){case"SessionStart":await i({type:"session.ready",actor:"system",payload:{sessionId:t.session_id}});break;case"UserPromptSubmit":await i({type:"turn.user",actor:"user",payload:{promptHead:String(t.prompt??t.user_prompt??"").slice(0,160)||null}});break;case"PostToolUse":await i({type:"tool.called",actor:"agent",payload:{tool:t.tool_name??null}});break;case"Stop":await T_(r,o,t,i);break;default:break}return a}async function T_(t,e,n,r){let o=[],s=A_(n,e);if(s)try{o=S_(await x_.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=v_(await F(t)),c=o.filter(u=>!i.has(u.messageId));if(c.length){let u=new Map;for(let f of c){let d=f.model||"unknown",m=u.get(d)||{tokens:{input:0,output:0,cacheWrite:0,cacheRead:0},messageIds:[]};for(let h of Object.keys(m.tokens))m.tokens[h]+=f.tokens[h];m.messageIds.push(f.messageId),u.set(d,m)}for(let[f,d]of u)await r({type:"cost.recorded",actor:"system",source:"transcript",payload:{model:f,usd:Xt(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 I_ from"node:fs";import Vl from"node:fs/promises";import R_ from"node:http";import vr from"node:path";import Aa from"node:os";import{createRequire as $_}from"node:module";import zl from"ws";import __ from"node:path";import E_ from"node:os";var Hw=process.env.BOSTRAT_STATE_DIR||__.join(process.env.HOME||E_.tmpdir(),".bostrat");function Ww(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 qw(t){let e=String(t||"").trim();if(!e)return"";let n=/^[a-z][a-z0-9+.-]*:\/\//i.test(e)?e:`wss://${e}`,r;try{r=new URL(n)}catch{return e}let o={"https:":"wss:","http:":"ws:","wss:":"wss:","ws:":"ws:"}[r.protocol];return o?(r.protocol=o,(r.pathname===""||r.pathname==="/")&&(r.pathname="/v1/node"),r.toString()):e}var va=(()=>{try{return $_(import.meta.url)("../package.json").version||null}catch{return null}})(),O_=process.env.BOSTRAT_STATE_DIR||vr.join(process.env.HOME||Aa.tmpdir(),".bostrat");function C_(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 M_({home:t=process.env.HOME||Aa.homedir(),env:e=process.env}={}){let n=r=>{try{return I_.statSync(r),!0}catch{return!1}};return{claude:n(vr.join(t,".claude",".credentials.json")),codex:n(vr.join(e.CODEX_HOME||vr.join(t,".codex"),"auth.json"))}}var _t=1,Gw=1e3,N_=3e4,P_=6e4,B_=1500,D_=500,Kw=100,j_=2e3,L_=25e3;function Vw({url:t,token:e,nodeId:n=Aa.hostname(),name:r=Aa.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=qw(t);let c=a.replace(/^http/,"ws"),l=new Map,u=new Map,f=vr.join(O_,"plane-link","cursor.json"),d={},m=new Map,h=null,w=!1,y=!1,b=Gw,A=null,_=null,C=null,K=null,I=!1;async function L(){try{d=JSON.parse(await Vl.readFile(f,"utf8"))}catch{d={}}}function v(){I=!0,!C&&(C=setTimeout(async()=>{if(C=null,!!I){I=!1;try{await Vl.mkdir(vr.dirname(f),{recursive:!0}),await Vl.writeFile(f,JSON.stringify(d))}catch(R){i.error("[plane-link] cursor flush failed:",R?.message||R)}}},j_),C.unref?.())}function $(R){return!h||h.readyState!==zl.OPEN?!1:(h.send(JSON.stringify(R)),!0)}function W(R){for(let{trackId:M,event:E}of R)m.set(E.id,{trackId:M,seq:E.seq});return $({v:_t,ch:"events",type:"append",events:R})}async function nt(){let R;try{R=await o()}catch(M){i.error("[plane-link] backfill listTracks failed:",M?.message||M);return}$({v:_t,ch:"ctl",type:"tracks",ids:R.map(M=>M.id).filter(M=>typeof M=="string")});for(let M of R){if(!w)return;let E;try{E=await F(M)}catch{continue}let P=d[M.id]??0,et=E.filter(lt=>lt.seq>P&&typeof lt.id=="string");for(let lt=0;lt<et.length;lt+=Kw){let bt=et.slice(lt,lt+Kw).map(Et=>({trackId:M.id,event:Et}));if(!W(bt))return}}}function g(R){!w||!R?.trackId||typeof R.id!="string"||W([{trackId:R.trackId,event:R}])}let p=new Map;async function S(R){let{reqId:M,method:E,path:P,headers:et={},body:lt}=R,bt=Object.fromEntries(Object.entries(et).filter(([,Nt])=>Nt!=null)),Et=new AbortController;p.set(M,Et);try{if(R.host){await T(R,bt,Et);return}let Nt=await fetch(a+P,{method:E,headers:bt,body:lt?Buffer.from(lt,"base64"):void 0,signal:Et.signal}),k=Object.fromEntries(Nt.headers);for(let j of["connection","keep-alive","transfer-encoding","upgrade","content-encoding","content-length"])delete k[j];if(k["content-type"]||(k["content-type"]="application/octet-stream"),$({v:_t,ch:"control",type:"http_head",reqId:M,status:Nt.status,headers:k}),Nt.body){for await(let j of Nt.body)if(!$({v:_t,ch:"control",type:"http_data",reqId:M,data:Buffer.from(j).toString("base64")}))break}$({v:_t,ch:"control",type:"http_end",reqId:M,error:null})}finally{p.delete(M)}}function T(R,M,E){let{reqId:P,method:et,path:lt,body:bt}=R,Et=new URL(a);return new Promise(Nt=>{let k=R_.request({host:Et.hostname,port:Et.port,method:et,path:lt,headers:{...M,host:R.host},signal:E.signal},j=>{let{connection:Rn,"keep-alive":Ho,"transfer-encoding":$n,upgrade:yu,...Iy}=j.headers;$({v:_t,ch:"control",type:"http_head",reqId:P,status:j.statusCode||502,headers:Iy}),j.on("data",Ry=>{$({v:_t,ch:"control",type:"http_data",reqId:P,data:Buffer.from(Ry).toString("base64")})||j.destroy()}),j.on("end",()=>{$({v:_t,ch:"control",type:"http_end",reqId:P,error:null}),Nt()}),j.on("error",()=>{$({v:_t,ch:"control",type:"http_end",reqId:P,error:"view upstream error"}),Nt()})});k.on("error",j=>{$({v:_t,ch:"control",type:"http_end",reqId:P,error:j?.message||"view request failed"}),Nt()}),bt&&k.write(Buffer.from(bt,"base64")),k.end()})}function J(){if(y)return;h=new zl(t,{headers:{authorization:`Bearer ${e}`}}),h.on("open",async()=>{let M=null;try{M=await s?.()||null}catch{M=null}$({v:_t,ch:"ctl",type:"hello",nodeId:n,name:r,platform:process.platform,version:va,providers:M_(),...M?{agents:M}:{}})}),h.on("message",M=>{let E;try{E=JSON.parse(M)}catch{return}if(E.ch==="ctl"&&E.type==="hello_ok"){w=!0,b=Gw,A=null,i.log(`[plane-link] connected to plane as ${n}`),E.minNodeVersion&&va&&C_(va,E.minNodeVersion)&&i.error(`[plane-link] this node is bostrat-node ${va}; the plane asks for \u2265${E.minNodeVersion} \u2014 update with: npm install -g bostrat-node`),K&&clearInterval(K),K=setInterval(()=>$({v:_t,ch:"ctl",type:"ping"}),L_),K.unref?.(),nt().catch(P=>i.error("[plane-link] backfill failed:",P?.message||P));return}if(E.ch==="events"&&E.type==="ack"&&Array.isArray(E.ids)){for(let P of E.ids){let et=m.get(P);et&&(m.delete(P),(d[et.trackId]??0)<et.seq&&(d[et.trackId]=et.seq))}v();return}if(E.ch==="control"&&E.type==="http_open"){S(E).catch(P=>$({v:_t,ch:"control",type:"http_end",reqId:E.reqId,error:P?.message||"tunnel error"}));return}if(E.ch==="control"&&E.type==="http_cancel"){p.get(E.reqId)?.abort();return}if(E.ch==="attach"&&E.type==="open"){let P=new zl(c+E.path,Array.isArray(E.protocols)?E.protocols:[],E.host?{headers:{host:E.host}}:void 0);l.set(E.streamId,P),u.set(E.streamId,[]),P.on("open",()=>{let lt=u.get(E.streamId)||[];u.delete(E.streamId);for(let{buf:bt,bin:Et}of lt)P.send(bt,{binary:Et})}),P.on("message",(lt,bt)=>{let Et=bt?lt:Buffer.from(String(lt));$({v:_t,ch:"attach",type:"data",streamId:E.streamId,data:Et.toString("base64"),...bt?{bin:!0}:{}})});let et=()=>{u.delete(E.streamId),l.delete(E.streamId)&&$({v:_t,ch:"attach",type:"close",streamId:E.streamId})};P.on("close",et),P.on("error",et);return}if(E.ch==="attach"&&E.type==="data"){let P=l.get(E.streamId);if(!P)return;let et=Buffer.from(E.data,"base64"),lt=E.bin===!0,bt=u.get(E.streamId);bt?bt.push({buf:et,bin:lt}):P.send(et,{binary:lt});return}if(E.ch==="attach"&&E.type==="close"){let P=l.get(E.streamId);l.delete(E.streamId),u.delete(E.streamId),P?.close();return}});let R=()=>{w&&i.log("[plane-link] disconnected from plane"),w=!1,K&&(clearInterval(K),K=null),m.clear();for(let P of l.values())P.close();l.clear(),u.clear();for(let P of p.values())P.abort();if(p.clear(),y||_)return;A==null&&(A=Date.now());let M=Date.now()-A<P_,E=M?B_+Math.random()*D_:b;_=setTimeout(()=>{_=null,J()},E),_.unref?.(),M||(b=Math.min(b*2,N_))};h.on("close",R),h.on("error",R)}return Lt.on("event",g),L().then(J),{stop:()=>{y=!0,Lt.off("event",g),_&&clearTimeout(_),C&&clearTimeout(C),K&&clearInterval(K),h?.close()},status:()=>({connected:w,nodeId:n,cursor:{...d},inflight:m.size})}}function Xl(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 zw=()=>({input:0,output:0,cacheRead:0,cacheCreation:0}),Jw=(t,e)=>{for(let n of Object.keys(t))t[n]+=e?.[n]||0},Jl=t=>Math.round(t*1e4)/1e4;async function Xw(t){let e=[],n={},r={},o=0,s=0,a=0,i=0,c=new Set,l=zw(),u=0,f=[];for(let d of t||[]){let m=await F(d),h=Yt(m,{boxLabels:Xl(d)});if(h.turns===0){d.archivedAt?u++:f.push(d.name||d.id);continue}o+=h.totalUsd,s+=h.turns,a+=h.repricedTurns||0,i+=h.unpricedTurns||0;for(let w of h.unpricedModels||[])c.add(w);Jw(l,h.totalTokens);for(let[w,y]of Object.entries(h.byModel))n[w]||={usd:0,events:0,tokens:zw()},n[w].usd=Jl(n[w].usd+y.usd),n[w].events+=y.events,Jw(n[w].tokens,y.tokens);for(let[w,y]of Object.entries(h.byProvider))r[w]||={usd:0,events:0},r[w].usd=Jl(r[w].usd+y.usd),r[w].events+=y.events;e.push({id:d.id,name:d.name,usd:h.totalUsd,rollup:h})}return e.sort((d,m)=>m.usd-d.usd),{generatedAt:new Date().toISOString(),pricingBasis:"api-equivalent (subscription usage priced at public API rates)",fleet:{totalUsd:Jl(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 Io=t=>`$${t.toFixed(4)}`,Ar=(t,e)=>String(t).padEnd(e);function Yw(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 ${Io(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(` ${Ar(a,26)} ${Ar(Io(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(` ${Ar(a.name||a.id,30)} ${Io(a.usd)}`);let i=Object.values(a.rollup.byBox).sort((c,l)=>l.usd-c.usd);for(let c of i)if(r.push(` ${Ar(c.label,40)} ${Ar(Io(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))}${Ar(Io(u.usd),10)} ${u.models.join(",")||"?"}`)}return r.join(`
|
|
298
|
-
`)}import z_ from"node:fs/promises";import J_ from"node:path";var F_=["bug-fix","orchestration","design","ops-infra","docs-writing","research","feature-build"],U_={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 Ta(t){let e=String(t||"").toLowerCase();if(!e.trim())return null;let n=null;for(let r of F_){let o=0;for(let s of U_[r])s.test(e)&&o++;o>0&&(!n||o>n.score)&&(n={category:r,score:o})}return n}var H_=new Set(["Read","Grep","Glob","LS","WebSearch","WebFetch","NotebookRead"]),W_=new Set(["Edit","Write","MultiEdit","NotebookEdit"]);function Zw(t){let e=0,n=0;for(let[r,o]of Object.entries(t||{}))H_.has(r)&&(e+=o||0),W_.has(r)&&(n+=o||0);return e>=5&&n===0?{category:"research",score:1}:n>=3?{category:"feature-build",score:1}:null}function Yl(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 _a=t=>t.payload?.ts??t.ts,q_=12e4,Je=t=>Math.round(t*1e4)/1e4,G_=()=>({usd:0,activeMs:0,turns:0,toolCalls:0,models:{},providers:{},byTrack:new Map});function K_(t,{since:e}){let{track:n,events:r,brief:o}=t,s=A=>_a(A)>=e,a=Ta([Yl(n?.name),Yl(n?.workspace?.branch),o].filter(Boolean).join(" ")),i=new Map;for(let A of r)if(A.type==="subtask.delegated"&&A.boxId&&A.payload?.task){let _=Ta(A.payload.task);_&&i.set(A.boxId,_)}let c=new Map,l=A=>{let _=A||"unattributed";return c.has(_)||c.set(_,{segments:[u(null)],lastTs:null,provider:null}),c.get(_)},u=A=>({textCat:A,tools:{},usd:0,tokens:{input:0,output:0,cacheRead:0,cacheWrite:0},models:{},provider:null,activeMs:0,turns:0,toolCalls:0}),f=[...r].sort((A,_)=>_a(A)-_a(_)),d=0,m=0,h=0,w={turns:0};for(let A of f){let _=l(A.boxId),C=_.segments[_.segments.length-1],K=_a(A);switch(_.lastTs!=null&&K>_.lastTs&&s(A)&&(C.activeMs+=Math.min(K-_.lastTs,q_)),_.lastTs=K,A.provider&&(_.provider=A.provider),A.type){case"turn.user":{let I=A.payload?.text;if(A.source==="hook"||!String(I||"").trim())break;let L=u(Ta(I));_.segments.push(L),s(A)&&(L.turns+=1);break}case"tool.called":{if(A.source==="transcript")break;let I=A.payload?.tool;I&&(C.tools[I]=(C.tools[I]||0)+1),s(A)&&(C.toolCalls+=1);break}case"cost.recorded":{if(!s(A))break;let I=A.payload||{},L={input:I.tokens?.input||0,output:I.tokens?.output||0,cacheRead:I.tokens?.cacheRead||0,cacheWrite:I.tokens?.cacheWrite??I.tokens?.cacheCreation??0},v=Number(I.usd)||0;!v&&I.tokens&&(v=Xt(I.model,L)),!v&&I.tokens&&!ds(I.model)&&(w.turns+=1),C.usd+=v,C.tokens.input+=L.input,C.tokens.output+=L.output,C.tokens.cacheRead+=L.cacheRead,C.tokens.cacheWrite+=L.cacheWrite;let $=I.model||"unknown";C.models[$]=(C.models[$]||0)+v,A.provider&&(C.provider=A.provider);break}case"handoff.done":s(A)&&(d+=1);break;case"subtask.merged":s(A)&&(m+=1);break;case"error":s(A)&&A.payload?.kind==="subtask-failed"&&(h+=1);break;default:break}}let y=new Map,b=!1;for(let[A,_]of c)for(let C of _.segments){if(!(C.usd>0||C.activeMs>0||C.turns>0||C.toolCalls>0))continue;let I=C.textCat?.category??i.get(A)?.category??Zw(C.tools)?.category??a?.category??"uncategorized";y.has(I)||y.set(I,{usd:0,activeMs:0,turns:0,toolCalls:0,models:{},providers:{}});let L=y.get(I);L.usd+=C.usd,L.activeMs+=C.activeMs,L.turns+=C.turns,L.toolCalls+=C.toolCalls;for(let[$,W]of Object.entries(C.models))L.models[$]=(L.models[$]||0)+W;let v=C.provider||_.provider;v&&(L.providers[v]=(L.providers[v]||0)+C.usd),v==="cursor"&&C.usd===0&&(b=!0)}return{perCategory:y,handoffs:d,merged:m,failed:h,sawCursorActivity:b,unpricedTurns:w.turns}}function tg(t,{windowMs:e=null,now:n=Date.now()}={}){let r=e==null?0:n-e,o=new Map,s=[],a=0,i=!1,c=0;for(let w of t||[]){let{track:y,events:b}=w;if(!b?.length){a+=1;continue}let{perCategory:A,handoffs:_,merged:C,failed:K,sawCursorActivity:I,unpricedTurns:L}=K_(w,{since:r});I&&(i=!0),c+=L;let v={trackId:y?.id,name:y?.name||y?.id,archived:!!y?.archivedAt,usd:0,activeMs:0,turns:0,categories:{},models:{},providers:{},handoffs:_,subtasksMerged:C,subtasksFailed:K};for(let[$,W]of A){v.usd+=W.usd,v.activeMs+=W.activeMs,v.turns+=W.turns,v.categories[$]={usd:Je(W.usd),activeMs:W.activeMs};for(let[p,S]of Object.entries(W.models))v.models[p]=Je((v.models[p]||0)+S);for(let[p,S]of Object.entries(W.providers))v.providers[p]=Je((v.providers[p]||0)+S);o.has($)||o.set($,G_());let nt=o.get($);nt.usd+=W.usd,nt.activeMs+=W.activeMs,nt.turns+=W.turns,nt.toolCalls+=W.toolCalls;for(let[p,S]of Object.entries(W.models))nt.models[p]=(nt.models[p]||0)+S;for(let[p,S]of Object.entries(W.providers))nt.providers[p]=(nt.providers[p]||0)+S;let g=nt.byTrack.get(y?.id)||{usd:0,activeMs:0};g.usd+=W.usd,g.activeMs+=W.activeMs,nt.byTrack.set(y?.id,g)}(v.usd>0||v.activeMs>0||v.turns>0)&&(v.usd=Je(v.usd),v.dominantCategory=V_(v.categories),s.push(v))}let l=[...o.values()].reduce((w,y)=>w+y.usd,0),u=[...o.values()].reduce((w,y)=>w+y.activeMs,0),f=l>0?"usd":"time",d=f==="usd"?l:u,m=new Map((t||[]).map(w=>[w.track?.id,w.track?.name||w.track?.id])),h=[...o.entries()].map(([w,y])=>({category:w,sharePct:d>0?Math.round((f==="usd"?y.usd:y.activeMs)/d*100):0,shareBasis:f,usd:Je(y.usd),activeMs:y.activeMs,turns:y.turns,toolCalls:y.toolCalls,models:Qw(y.models),providers:Qw(y.providers),topWorkstreams:[...y.byTrack.entries()].sort((b,A)=>A[1].usd-b[1].usd||A[1].activeMs-b[1].activeMs).slice(0,2).map(([b,A])=>({trackId:b,name:m.get(b),usd:Je(A.usd),activeMs:A.activeMs}))})).sort((w,y)=>y.usd-w.usd||y.activeMs-w.activeMs);return s.sort((w,y)=>y.usd-w.usd||y.activeMs-w.activeMs),{since:r,capturedAt:n,totalUsd:Je(l),activeMs:u,turns:[...o.values()].reduce((w,y)=>w+y.turns,0),bullets:h,workstreams:s,gaps:{tracksWithoutEvents:a,cursorCostUnknown:i,unpricedTurns:c}}}var V_=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},Qw=t=>Object.fromEntries(Object.entries(t).map(([e,n])=>[e,Je(n)]));var Tr={"24h":24*36e5,"7d":168*36e5,"30d":720*36e5,all:null};function X_(t){let e=String(t||"7d");return e in Tr?{key:e,ms:Tr[e]}:{key:"7d",ms:Tr["7d"]}}function Y_(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=Js(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 Z_(t){if(!t?.cwd)return null;try{let n=(await z_.readFile(J_.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 eg(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}:X_(e),f=[];for(let d of t||[]){let m=await F(d),h=Y_(d,m);i&&c<o&&(h=h.filter(w=>(w.ts??0)<=c)),f.push({track:d,events:h,brief:await Z_(d)})}return{window:l,...tg(f,{windowMs:u,now:c})}}var Xe=36e5,sg=24*Xe,Q_=400,tE=12,_r=t=>Math.round(t*1e4)/1e4;function eE({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 Tr?String(t||"7d"):"7d",i=Tr[a];return{key:a,since:i==null?null:r-i,until:r}}var nE=(t,e)=>t!=null&&e-t<=48*Xe?Xe:sg;function ng(t,e){if(e===Xe)return Math.floor(t/Xe)*Xe;let n=new Date(t);return n.setHours(0,0,0,0),n.getTime()}function rE(t,e){if(e===Xe)return t+Xe;let n=new Date(t);return n.setDate(n.getDate()+1),n.setHours(0,0,0,0),n.getTime()}var rg=()=>({input:0,output:0,cacheRead:0,cacheCreation:0}),og=(t,e)=>{for(let n of Object.keys(t))t[n]+=e?.[n]||0},oE=t=>Object.fromEntries(Object.entries(t).map(([e,n])=>[e,_r(n)]));async function ag(t,e={}){let n=e.now??Date.now(),r=eE({...e,now:n}),o=r.since==null?sg:nE(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=rg(),f=0,d=0,m=0,h=0,w=0,y=new Set,b=new Map,A=[],_=[],C=[],K=null;for(let v of t||[]){let W=(await F(v)).filter(p=>p.type==="cost.recorded");if(!W.length){v.archivedAt||C.push(v.name||v.id);continue}let nt=[];for(let p of W){let S=p.ts??0;s(S)?nt.push(p):i(S)&&(w+=ps(p).usd)}if(!nt.length)continue;let g=Yt(nt,{boxLabels:Xl(v)});f+=g.totalUsd,d+=g.turns,m+=g.repricedTurns||0,h+=g.unpricedTurns||0;for(let p of g.unpricedModels||[])y.add(p);og(u,g.totalTokens);for(let[p,S]of Object.entries(g.byModel))c[p]||={usd:0,events:0,tokens:rg()},c[p].usd=_r(c[p].usd+S.usd),c[p].events+=S.events,og(c[p].tokens,S.tokens);for(let[p,S]of Object.entries(g.byProvider))l[p]||={usd:0,events:0},l[p].usd=_r(l[p].usd+S.usd),l[p].events+=S.events;for(let p of nt){let{usd:S}=ps(p);if(!S)continue;let T=ng(p.ts??0,o),J=b.get(T)??{t:T,total:0,byProvider:{}};J.total+=S;let R=p.provider||"unknown";J.byProvider[R]=(J.byProvider[R]||0)+S,b.set(T,J),(K==null||T<K)&&(K=T)}A.push({id:v.id,name:v.name||v.id,archived:!!v.archivedAt,usd:g.totalUsd,turns:g.turns,providers:Object.keys(g.byProvider)});for(let[p,S]of Object.entries(g.byBox))_.push({trackId:v.id,trackName:v.name||v.id,boxId:p,...S})}let I=r.since!=null?ng(r.since,o):K,L=[];for(let v=I,$=0;v!=null&&v<r.until&&$<Q_;v=rE(v,o),$++){let W=b.get(v);L.push(W?{t:v,total:_r(W.total),byProvider:oE(W.byProvider)}:{t:v,total:0,byProvider:{}})}return A.sort((v,$)=>$.usd-v.usd),_.sort((v,$)=>$.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:L,fleet:{totalUsd:_r(f),turns:d,repricedTurns:m,unpricedTurns:h,unpricedModels:[...y],totalTokens:u,byModel:c,byProvider:l,tracksWithCost:A.length,tracksSilentLive:C},prior:a!=null?{totalUsd:_r(w),since:r.since-a,until:r.since}:null,tracks:A.slice(0,50),boxes:_.slice(0,tE)}}import Er from"node:fs/promises";import sE from"node:fs";import ht from"node:path";import aE from"node:os";import{execFile as iE}from"node:child_process";import{promisify as cE}from"node:util";var lE=cE(iE),ig=process.env.BOSTRAT_STATE_DIR||ht.join(process.env.HOME||aE.tmpdir(),".bostrat"),Zl=!1;async function Ro(t,e){let n=await Er.stat(t).catch(()=>null);if(!n?.isFile())return!1;let r=await Er.stat(e).catch(()=>null);return r&&r.size===n.size&&r.mtimeMs>=n.mtimeMs?!1:(await Er.mkdir(ht.dirname(e),{recursive:!0}),await Er.copyFile(t,e),!0)}var uE=new Set([".claude",".codex",".credentials.json","auth.json",".claude.json","integrations.json"]);async function $o(t,e){let n=0,r=await Er.readdir(t,{withFileTypes:!0}).catch(()=>[]);for(let o of r){if(uE.has(o.name))continue;let s=ht.join(t,o.name);o.isDirectory()?n+=await $o(s,ht.join(e,o.name)):o.isFile()&&(n+=await Ro(s,ht.join(e,o.name))?1:0)}return n}async function cg({stateDir:t=ig,dest:e=process.env.BOSTRAT_SYNC_DIR||`${ig}-sync`,remote:n=process.env.BOSTRAT_STATE_REMOTE||null,tracks:r=null}={}){try{await Er.mkdir(e,{recursive:!0});let o=0;o+=await Ro(ht.join(t,"tracks.json"),ht.join(e,"tracks.json"))?1:0,o+=await Ro(ht.join(t,"mocks.json"),ht.join(e,"mocks.json"))?1:0,o+=await Ro(ht.join(t,"runbooks.json"),ht.join(e,"runbooks.json"))?1:0,o+=await $o(ht.join(t,"runbooks"),ht.join(e,"runbooks")),o+=await $o(ht.join(t,"mock-archive"),ht.join(e,"mock-archive")),o+=await Ro(ht.join(t,"events.jsonl"),ht.join(e,"events.jsonl"))?1:0,o+=await $o(ht.join(t,"events"),ht.join(e,"events"));for(let l of r||[])!l?.cwd||!l?.id||(o+=await $o(ht.join(l.cwd,".bostrat"),ht.join(e,"workspaces",l.id,".bostrat")));let s=(...l)=>lE("git",["-C",e,...l]);sE.existsSync(ht.join(e,".git"))||(await s("init","-q"),await s("config","user.email","agent@bostrat.local"),await s("config","user.name","bostrat state sync")),n&&/^https:\/\/github\.com\//i.test(n)&&(await s("config","--get","credential.useHttpPath").then(()=>!0).catch(()=>!1)||await ac(e).catch(()=>{}));let a=!1,{stdout:i}=await s("status","--porcelain");i.trim()&&(await s("add","-A"),await s("commit","-q","-m",`state sync ${new Date().toISOString()}`),a=!0);let c=!1;if(n&&(a||Zl))try{await s("push","-q",n,"HEAD:refs/heads/main"),c=!0,Zl=!1}catch{Zl=!0}return{staged:o,committed:a,pushed:c}}catch(o){return{staged:0,committed:!1,pushed:!1,error:o?.message||String(o)}}}import{execFile as dE}from"node:child_process";import{promisify as fE}from"node:util";import{randomUUID as pE}from"node:crypto";import mE from"node:fs";import hE from"node:os";import wE from"node:path";var Ze=fE(dE),Ea=t=>new Promise(e=>setTimeout(e,t)),Qe=za,Ql=process.env.BOSTRAT_USAGE_PROBE_CWD||wE.join(hE.homedir(),".bostrat","usage-probe"),gE=Number(process.env.BOSTRAT_USAGE_IDLE_REAP_MS)||5*6e4,yE=Number(process.env.BOSTRAT_USAGE_PANEL_TIMEOUT_MS)||15e3,bE=Number(process.env.BOSTRAT_USAGE_READY_TIMEOUT_MS)||45e3,lg=Number(process.env.BOSTRAT_USAGE_CACHE_MS)||2e4,Ye=null,Oo=null,Co=null;async function kE(){try{return await Ze(N,B("has-session","-t",Qe)),!0}catch{return!1}}function xE(){Ye&&clearTimeout(Ye),Ye=setTimeout(()=>SE().catch(()=>{}),gE),Ye.unref&&Ye.unref()}async function SE(){Ye&&(clearTimeout(Ye),Ye=null);try{await Ze(N,B("kill-session","-t",Qe))}catch{}}async function vE(){let t=Date.now()+bE;for(;;){let e=await tu().catch(()=>"");if(/for agents|for shortcuts|bypass permissions on/i.test(e)){await Ea(500);return}if(Date.now()>t)throw new Error("usage probe didn't reach the prompt (trust/onboarding may be blocking)");await Ea(500)}}async function AE(){if(await kE())return;if(!await _e())throw new Error("host tmux server not running \u2014 start globo-tmux.service");mE.mkdirSync(Ql,{recursive:!0}),await Te(Ql);let t=pE();await Ze(N,B("new-session","-d","-s",Qe,"-c",Ql,"-x","200","-y","60"));let e=`${Va} --session-id ${t}`;await Ze(N,B("send-keys","-t",Qe,e,"Enter")),await vE()}async function tu(){let{stdout:t}=await Ze(N,B("capture-pane","-t",Qe,"-p"));return t}async function TE(){await AE(),await Ze(N,B("send-keys","-t",Qe,"-l","/usage")),await Ze(N,B("send-keys","-t",Qe,"Enter"));let t=Date.now()+yE,e="";for(;await Ea(400),e=await tu(),!(/Current\s+(session|week)/i.test(e)||/\d+%\s*used/i.test(e)||Date.now()>t););return await Ea(900),e=await tu(),await Ze(N,B("send-keys","-t",Qe,"Escape")).catch(()=>{}),xE(),e}function _E(t){let e=String(t||"").split(`
|
|
299
|
-
`).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
|
|
300
|
-
`).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
|
|
301
|
-
`),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:
|
|
302
|
-
`,{mode:384})
|
|
362
|
+
_${o} archived_`:"")}async function JE(t,e){let n=await Sn();if(!n)return ft("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 ft("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?(Dn(Kg(t,n,{name:u,repo:l.path,responseUrl:e.response_url})),ft(`Starting *${X(u)}* on ${X(l.name)}\u2026`)):{response_type:"ephemeral",text:`Pick a repo for \u201C${u}\u201D`,blocks:zE(u,i)}}case"archive":{if(!a.length)return ft("Usage: `/bostrat archive <track>`");let{match:i,live:c}=gu(await r.listTracks(),a.join(" "));return i?(Dn(qE(t,n,{track:i,responseUrl:e.response_url})),ft(`Archiving *${X(i.name)}*\u2026`)):yu(c,a.join(" "))}case"handoff":{let i=r.knownAgents??WE,c=a.at(-1)?.toLowerCase();if(a.length<2||!i.includes(c))return ft(`Usage: \`/bostrat handoff <track> <${i.join("|")}>\``);let l=a.slice(0,-1).join(" "),{match:u,live:f}=gu(await r.listTracks(),l);return u?(Dn(KE(t,n,{track:u,agent:c,responseUrl:e.response_url})),ft(`Handing *${X(u.name)}* to ${X(c)}\u2026`)):yu(f,l)}case"stop":{if(!a.length)return ft("Usage: `/bostrat stop <track>`");let{match:i,live:c}=gu(await r.listTracks(),a.join(" "));return i?(Dn(GE(t,n,{track:i,responseUrl:e.response_url})),ft(`Stopping *${X(i.name)}*\u2026`)):yu(c,a.join(" "))}case"list":return ft(await VE(r));default:return ft(HE)}}async function XE(t,e,n,r){let{trackId:o,promptId:s,label:a}=r,i=await Su(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 Me(),await Mr(e.botToken,{channel:i.channel,ts:c,text:`\u2713 ${a}`,blocks:[{type:"section",text:{type:"mrkdwn",text:`~${X(l||"question")}~
|
|
363
|
+
\u2713 *${X(a)}*`}}]}).catch(()=>{})}}catch(c){await Pt(n.response_url,ft(`Couldn't answer: ${c?.message||c}`))}}async function YE(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 Pt(n.response_url,ft(`Couldn't act on the offer: ${c?.message||c}`))}}async function ZE(t,e){let n=e.actions?.[0];if(!n?.action_id)return;let r=await Sn();if(!r)return;if(n.action_id==="bostrat-start-repo"){let s;try{({name:s}=JSON.parse(n.block_id))}catch{return}let a=n.selected_option?.value;await Pt(e.response_url,{...ft(`Starting *${X(s)}*\u2026`),replace_original:!0}),Dn(Kg(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"?Dn(XE(t,r,e,o)):n.action_id==="bostrat-ship"&&Dn(YE(t,r,e,o))}async function QE(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 FE(e.channel,e.thread_ts);if(!n)return;let r=await Sn();if(r)try{await t.deps.sayToLead(n.trackId,e.text.trim())}catch(o){await jn(r,n.rec,`Couldn't deliver that to the lead: ${o?.message||o}`).catch(()=>{})}}function tI(t){return async e=>{if(e.type==="slash_commands")return JE(t,e.payload);if(e.type==="interactive"){await ZE(t,e.payload);return}if(e.type==="events_api"){await QE(t,e.payload?.event);return}}}var eI=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 nI(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:Wo(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 rI(t,e,n){let r=n.payload||{};if(!r.promptId||e.prompt?.promptId===r.promptId)return;e.prompt&&await Mr(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 jn(t,e,r.question||"The lead needs an answer",nI(n.trackId,r));e.prompt={promptId:r.promptId,ts:o.ts,question:r.question||null},await Me()}async function oI(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 Me(),await Mr(t.botToken,{channel:e.channel,ts:o,text:"answered",blocks:[{type:"section",text:{type:"mrkdwn",text:`~${X(s||"question")}~
|
|
364
|
+
\u2713 _answered_`}}]})}async function sI(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 jn(t,e,r.excerpt||"Ship it?",[{type:"section",text:{type:"mrkdwn",text:`\u{1F6A2} ${X(Wo(r.excerpt||"The lead offered to ship.",300))}`}},{type:"actions",elements:s}]);e.offers=e.offers||{},e.offers[r.offerId]=a.ts,await Me()}async function aI(t,e,n){let r=n.payload||{},o=e.offers?.[r.offerId];if(!o)return;delete e.offers[r.offerId],await Me();let s=r.action==="dismiss"?"dismissed":r.action==="pr"?"\u2713 opening a PR":"\u2713 pushing";await Mr(t.botToken,{channel:e.channel,ts:o,text:s,blocks:[{type:"section",text:{type:"mrkdwn",text:`\u{1F6A2} ${s}`}}]})}async function iI(t,e,n,r){let o=n.payload||{};if(o.change==="ready"&&o.mockId){let s=await(r.readMockPng??cI)(o.mockId,o.version??null);if(!s)return;let a=(o.version??1)>1?` (v${o.version})`:"";await Lg(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 jn(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 cI(t,e){let n=await He(t);if(!n)return null;let r=yn(n,e);if(!r)return null;let o=await Ho.readFile(qt(n,r.v).png).catch(()=>null);return o?{filename:`${n.name.replace(/[^\w.-]+/g,"-")}.v${r.v}.png`,buffer:o}:null}async function lI(t,e){if(!eI.has(e.type))return;let n=await Su(e.trackId);if(!n)return;let r=await Sn();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 Me(),await ku(r,n);return;case"turn.agent":{if(!o.text)return;let s=UE(o.text);if(!s)return;await jn(r,n,s,[{type:"section",text:{type:"mrkdwn",text:X(s)}},{type:"context",elements:[{type:"mrkdwn",text:`<${Js(e.trackId)}|full turn \u2192>`}]}]);return}case"prompt.raised":return rI(r,n,e);case"prompt.answered":return oI(r,n,e);case"pr.offered":return sI(r,n,e);case"pr.offer.answered":return aI(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 jn(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 Me(),await ku(r,n);return;case"view.state.changed":{if(o.change!=="created"||o.restarted||!o.slug)return;await jn(r,n,`Live preview ${o.name}: ${Zs(o.slug)}`,[{type:"section",text:{type:"mrkdwn",text:`\u{1F50D} live preview *${X(o.name)}*: ${Zs(o.slug)}
|
|
365
|
+
_runs on the Track's branch; reachable on the operator's private network only_`}}]);return}case"mock.state.changed":return iI(r,n,e,t.deps)}}function Gg(t){let e={deps:t};Wt.on("event",s=>{lI(e,s).catch(()=>{})});let n=null,r=null,o=async()=>{n||!await Sn()||(r&&clearInterval(r),r=null,n=Fg({getToken:async()=>(await Sn())?.appToken,onEnvelope:tI(e),log:s=>console.log(`[slack] ${s}`)}))};return r=setInterval(()=>o().catch(()=>{}),3e4),r.unref?.(),o().catch(()=>{}),()=>{r&&clearInterval(r),n?.()}}import uI from"node:fs/promises";function dI(t){let e=new Map;for(let n of String(t||"").split(`
|
|
366
|
+
`)){let r=n.trim();if(!r||r[0]!=="{")continue;let o;try{o=JSON.parse(r)}catch{continue}let s=o?.message?.usage;if(!s)continue;let a={input:s.input_tokens||0,output:s.output_tokens||0,cacheWrite:s.cache_creation_input_tokens||0,cacheRead:s.cache_read_input_tokens||0};if(a.input+a.output+a.cacheWrite+a.cacheRead===0)continue;let i=o.message?.id||o.uuid||null;i&&e.set(i,{messageId:i,tokens:a,model:o.message?.model||o.model||null})}return[...e.values()]}function fI(t){let e=new Set;for(let n of t)if(n.type==="cost.recorded"){n.payload?.messageId&&e.add(n.payload.messageId);for(let r of n.payload?.messageIds||[])e.add(r)}return e}function pI(t,e){return t?.transcript_path?t.transcript_path:e?.cwd&&e?.sessionId?xe(e.cwd,e.sessionId):e?.cwd?Mi(e.cwd):null}async function zg(t,e){let n=await e(t?.session_id,t?.cwd);if(!n?.track)return[];let{track:r,box:o}=n,s={boxId:o?.name||null,provider:"claude",source:"hook"},a=[],i=async c=>{a.push(await O(r,{...s,...c}))};switch(t?.hook_event_name){case"SessionStart":await i({type:"session.ready",actor:"system",payload:{sessionId:t.session_id}});break;case"UserPromptSubmit":await i({type:"turn.user",actor:"user",payload:{promptHead:String(t.prompt??t.user_prompt??"").slice(0,160)||null}});break;case"PostToolUse":await i({type:"tool.called",actor:"agent",payload:{tool:t.tool_name??null}});break;case"Stop":await mI(r,o,t,i);break;default:break}return a}async function mI(t,e,n,r){let o=[],s=pI(n,e);if(s)try{o=dI(await uI.readFile(s,"utf8"))}catch{}let a=o.length?o[o.length-1]:null;if(await r({type:"turn.agent",actor:"agent",payload:{model:a?.model??e?.model??null,messageId:a?.messageId??null}}),!o.length)return;let i=fI(await L(t)),c=o.filter(u=>!i.has(u.messageId));if(c.length){let u=new Map;for(let f of c){let d=f.model||"unknown",m=u.get(d)||{tokens:{input:0,output:0,cacheWrite:0,cacheRead:0},messageIds:[]};for(let h of Object.keys(m.tokens))m.tokens[h]+=f.tokens[h];m.messageIds.push(f.messageId),u.set(d,m)}for(let[f,d]of u)await r({type:"cost.recorded",actor:"system",source:"transcript",payload:{model:f,usd:oe(f,d.tokens),tokens:d.tokens,messageId:d.messageIds[d.messageIds.length-1],messageIds:d.messageIds,apiCalls:d.messageIds.length}})}let l=a.tokens.input+a.tokens.cacheWrite+a.tokens.cacheRead;await r({type:"context.usage",actor:"system",source:"transcript",payload:{model:a.model,occupancy:l,tokens:a.tokens}})}import gI from"node:fs";import vu from"node:fs/promises";import yI from"node:http";import Cr from"node:path";import Ha from"node:os";import{createRequire as bI}from"node:module";import Tu from"ws";import hI from"node:path";import wI from"node:os";var Vg=process.env.BOSTRAT_STATE_DIR||hI.join(process.env.HOME||wI.tmpdir(),".bostrat");function Jg(t=process.env){let e=(t.BOSTRAT_HOST||"").trim();if(e!=="all")return e||(t.BOSTRAT_PLANE_URL?"127.0.0.1":void 0)}function Xg(t){let e=String(t||"").trim();if(!e)return"";let n=/^[a-z][a-z0-9+.-]*:\/\//i.test(e)?e:`wss://${e}`,r;try{r=new URL(n)}catch{return e}let o={"https:":"wss:","http:":"ws:","wss:":"wss:","ws:":"ws:"}[r.protocol];return o?(r.protocol=o,(r.pathname===""||r.pathname==="/")&&(r.pathname="/v1/node"),r.toString()):e}var Ua=(()=>{try{return bI(import.meta.url)("../package.json").version||null}catch{return null}})(),kI=process.env.BOSTRAT_STATE_DIR||Cr.join(process.env.HOME||Ha.tmpdir(),".bostrat");function xI(t,e){let n=String(t).split(".").map(Number),r=String(e).split(".").map(Number);if(n.some(Number.isNaN)||r.some(Number.isNaN))return!1;for(let o=0;o<Math.max(n.length,r.length);o++){let s=(n[o]||0)-(r[o]||0);if(s)return s<0}return!1}function SI({home:t=process.env.HOME||Ha.homedir(),env:e=process.env}={}){let n=r=>{try{return gI.statSync(r),!0}catch{return!1}};return{claude:n(Cr.join(t,".claude",".credentials.json")),codex:n(Cr.join(e.CODEX_HOME||Cr.join(t,".codex"),"auth.json"))}}var It=1,Yg=1e3,vI=3e4,TI=6e4,AI=1500,_I=500,Zg=100,EI=2e3,II=25e3;function Qg({url:t,token:e,nodeId:n=Ha.hostname(),name:r=Ha.hostname(),listTracks:o,detectAgents:s=null,selfUrl:a=`http://127.0.0.1:${process.env.PORT||3362}`,log:i=console}){if(!t||!e)throw new Error("plane-link: url and token are required");if(typeof o!="function")throw new Error("plane-link: listTracks is required");t=Xg(t);let c=a.replace(/^http/,"ws"),l=new Map,u=new Map,f=Cr.join(kI,"plane-link","cursor.json"),d={},m=new Map,h=null,w=!1,g=!1,b=Yg,v=null,A=null,M=null,z=null,$=!1;async function F(){try{d=JSON.parse(await vu.readFile(f,"utf8"))}catch{d={}}}function T(){$=!0,!M&&(M=setTimeout(async()=>{if(M=null,!!$){$=!1;try{await vu.mkdir(Cr.dirname(f),{recursive:!0}),await vu.writeFile(f,JSON.stringify(d))}catch(I){i.error("[plane-link] cursor flush failed:",I?.message||I)}}},EI),M.unref?.())}function C(I){return!h||h.readyState!==Tu.OPEN?!1:(h.send(JSON.stringify(I)),!0)}function W(I){for(let{trackId:R,event:E}of I)m.set(E.id,{trackId:R,seq:E.seq});return C({v:It,ch:"events",type:"append",events:I})}async function ot(){let I;try{I=await o()}catch(R){i.error("[plane-link] backfill listTracks failed:",R?.message||R);return}C({v:It,ch:"ctl",type:"tracks",ids:I.map(R=>R.id).filter(R=>typeof R=="string")});for(let R of I){if(!w)return;let E;try{E=await L(R)}catch{continue}let P=d[R.id]??0,et=E.filter(wt=>wt.seq>P&&typeof wt.id=="string");for(let wt=0;wt<et.length;wt+=Zg){let bt=et.slice(wt,wt+Zg).map(Rt=>({trackId:R.id,event:Rt}));if(!W(bt))return}}}function y(I){!w||!I?.trackId||typeof I.id!="string"||W([{trackId:I.trackId,event:I}])}let p=new Map;async function S(I){let{reqId:R,method:E,path:P,headers:et={},body:wt}=I,bt=Object.fromEntries(Object.entries(et).filter(([,Ht])=>Ht!=null)),Rt=new AbortController;p.set(R,Rt);try{if(I.host){await _(I,bt,Rt);return}let Ht=await fetch(a+P,{method:E,headers:bt,body:wt?Buffer.from(wt,"base64"):void 0,signal:Rt.signal}),zt=Object.fromEntries(Ht.headers);for(let k of["connection","keep-alive","transfer-encoding","upgrade","content-encoding","content-length"])delete zt[k];if(zt["content-type"]||(zt["content-type"]="application/octet-stream"),C({v:It,ch:"control",type:"http_head",reqId:R,status:Ht.status,headers:zt}),Ht.body){for await(let k of Ht.body)if(!C({v:It,ch:"control",type:"http_data",reqId:R,data:Buffer.from(k).toString("base64")}))break}C({v:It,ch:"control",type:"http_end",reqId:R,error:null})}finally{p.delete(R)}}function _(I,R,E){let{reqId:P,method:et,path:wt,body:bt}=I,Rt=new URL(a);return new Promise(Ht=>{let zt=yI.request({host:Rt.hostname,port:Rt.port,method:et,path:wt,headers:{...R,host:I.host},signal:E.signal},k=>{let{connection:K,"keep-alive":Ln,"transfer-encoding":rs,upgrade:Fn,...oi}=k.headers;C({v:It,ch:"control",type:"http_head",reqId:P,status:k.statusCode||502,headers:oi}),k.on("data",Cb=>{C({v:It,ch:"control",type:"http_data",reqId:P,data:Buffer.from(Cb).toString("base64")})||k.destroy()}),k.on("end",()=>{C({v:It,ch:"control",type:"http_end",reqId:P,error:null}),Ht()}),k.on("error",()=>{C({v:It,ch:"control",type:"http_end",reqId:P,error:"view upstream error"}),Ht()})});zt.on("error",k=>{C({v:It,ch:"control",type:"http_end",reqId:P,error:k?.message||"view request failed"}),Ht()}),bt&&zt.write(Buffer.from(bt,"base64")),zt.end()})}function rt(){if(g)return;h=new Tu(t,{headers:{authorization:`Bearer ${e}`}}),h.on("open",async()=>{let R=null;try{R=await s?.()||null}catch{R=null}C({v:It,ch:"ctl",type:"hello",nodeId:n,name:r,platform:process.platform,version:Ua,providers:SI(),...R?{agents:R}:{}})}),h.on("message",R=>{let E;try{E=JSON.parse(R)}catch{return}if(E.ch==="ctl"&&E.type==="hello_ok"){w=!0,b=Yg,v=null,i.log(`[plane-link] connected to plane as ${n}`),E.minNodeVersion&&Ua&&xI(Ua,E.minNodeVersion)&&i.error(`[plane-link] this node is bostrat-node ${Ua}; the plane asks for \u2265${E.minNodeVersion} \u2014 update with: npm install -g bostrat-node`),z&&clearInterval(z),z=setInterval(()=>C({v:It,ch:"ctl",type:"ping"}),II),z.unref?.(),ot().catch(P=>i.error("[plane-link] backfill failed:",P?.message||P));return}if(E.ch==="events"&&E.type==="ack"&&Array.isArray(E.ids)){for(let P of E.ids){let et=m.get(P);et&&(m.delete(P),(d[et.trackId]??0)<et.seq&&(d[et.trackId]=et.seq))}T();return}if(E.ch==="control"&&E.type==="http_open"){S(E).catch(P=>C({v:It,ch:"control",type:"http_end",reqId:E.reqId,error:P?.message||"tunnel error"}));return}if(E.ch==="control"&&E.type==="http_cancel"){p.get(E.reqId)?.abort();return}if(E.ch==="attach"&&E.type==="open"){let P=new Tu(c+E.path,Array.isArray(E.protocols)?E.protocols:[],E.host?{headers:{host:E.host}}:void 0);l.set(E.streamId,P),u.set(E.streamId,[]),P.on("open",()=>{let wt=u.get(E.streamId)||[];u.delete(E.streamId);for(let{buf:bt,bin:Rt}of wt)P.send(bt,{binary:Rt})}),P.on("message",(wt,bt)=>{let Rt=bt?wt:Buffer.from(String(wt));C({v:It,ch:"attach",type:"data",streamId:E.streamId,data:Rt.toString("base64"),...bt?{bin:!0}:{}})});let et=()=>{u.delete(E.streamId),l.delete(E.streamId)&&C({v:It,ch:"attach",type:"close",streamId:E.streamId})};P.on("close",et),P.on("error",et);return}if(E.ch==="attach"&&E.type==="data"){let P=l.get(E.streamId);if(!P)return;let et=Buffer.from(E.data,"base64"),wt=E.bin===!0,bt=u.get(E.streamId);bt?bt.push({buf:et,bin:wt}):P.send(et,{binary:wt});return}if(E.ch==="attach"&&E.type==="close"){let P=l.get(E.streamId);l.delete(E.streamId),u.delete(E.streamId),P?.close();return}});let I=()=>{w&&i.log("[plane-link] disconnected from plane"),w=!1,z&&(clearInterval(z),z=null),m.clear();for(let P of l.values())P.close();l.clear(),u.clear();for(let P of p.values())P.abort();if(p.clear(),g||A)return;v==null&&(v=Date.now());let R=Date.now()-v<TI,E=R?AI+Math.random()*_I:b;A=setTimeout(()=>{A=null,rt()},E),A.unref?.(),R||(b=Math.min(b*2,vI))};h.on("close",I),h.on("error",I)}return Wt.on("event",y),F().then(rt),{stop:()=>{g=!0,Wt.off("event",y),A&&clearTimeout(A),M&&clearTimeout(M),z&&clearInterval(z),h?.close()},status:()=>({connected:w,nodeId:n,cursor:{...d},inflight:m.size})}}function _u(t){let e={};for(let n of t.boxes||[]){let r=n.role&&n.role!=="lead"?` \xB7 ${n.role}`:"";e[n.name]=`${n.name} (${n.agent||"?"}${r})`}return e}var ty=()=>({input:0,output:0,cacheRead:0,cacheCreation:0}),ey=(t,e)=>{for(let n of Object.keys(t))t[n]+=e?.[n]||0},Au=t=>Math.round(t*1e4)/1e4;async function ny(t){let e=[],n={},r={},o=0,s=0,a=0,i=0,c=new Set,l=ty(),u=0,f=[];for(let d of t||[]){let m=await L(d),h=se(m,{boxLabels:_u(d)});if(h.turns===0){d.archivedAt?u++:f.push(d.name||d.id);continue}o+=h.totalUsd,s+=h.turns,a+=h.repricedTurns||0,i+=h.unpricedTurns||0;for(let w of h.unpricedModels||[])c.add(w);ey(l,h.totalTokens);for(let[w,g]of Object.entries(h.byModel))n[w]||={usd:0,events:0,tokens:ty()},n[w].usd=Au(n[w].usd+g.usd),n[w].events+=g.events,ey(n[w].tokens,g.tokens);for(let[w,g]of Object.entries(h.byProvider))r[w]||={usd:0,events:0},r[w].usd=Au(r[w].usd+g.usd),r[w].events+=g.events;e.push({id:d.id,name:d.name,usd:h.totalUsd,rollup:h})}return e.sort((d,m)=>m.usd-d.usd),{generatedAt:new Date().toISOString(),pricingBasis:"api-equivalent (subscription usage priced at public API rates)",fleet:{totalUsd:Au(o),turns:s,repricedTurns:a,unpricedTurns:i,unpricedModels:[...c],totalTokens:l,byModel:n,byProvider:r,tracksWithCost:e.length,tracksSilent:u+f.length,tracksSilentArchived:u,tracksSilentLive:f},tracks:e}}var qo=t=>`$${t.toFixed(4)}`,Nr=(t,e)=>String(t).padEnd(e);function ry(t){let{fleet:e,tracks:n}=t,r=[];r.push(`BOSTRAT COST READ-OUT \xB7 ${t.generatedAt}`),t.pricingBasis&&r.push(`basis: ${t.pricingBasis}`);let o=e.tracksSilentLive||[];r.push(`fleet total ${qo(e.totalUsd)} over ${e.turns} turn(s) \xB7 ${e.tracksWithCost} track(s) with cost, ${e.tracksSilentArchived??e.tracksSilent} silent (archived)`+(o.length?`, ${o.length} LIVE with no cost data: ${o.join(", ")}`:"")),e.repricedTurns&&r.push(`${e.repricedTurns} turn(s) imputed at current rates (recorded before their model was priced)`),e.unpricedTurns&&r.push(`\u26A0 ${e.unpricedTurns} turn(s) UNPRICED \u2014 total under-reports. Add PER_MTOK rate(s) for: ${(e.unpricedModels||[]).join(", ")}`),r.push(""),r.push("BY MODEL");let s=Object.entries(e.byModel).sort((a,i)=>i[1].usd-a[1].usd);s.length||r.push(" (no cost events yet \u2014 hooks emit on each turn of a newly started box)");for(let[a,i]of s){let c=i.tokens;r.push(` ${Nr(a,26)} ${Nr(qo(i.usd),10)} ${i.events} turn(s) \xB7 in ${c.input} out ${c.output} cacheR ${c.cacheRead} cacheW ${c.cacheCreation}`)}r.push(""),r.push("BY TRACK / TASK");for(let a of n){r.push(` ${Nr(a.name||a.id,30)} ${qo(a.usd)}`);let i=Object.values(a.rollup.byBox).sort((c,l)=>l.usd-c.usd);for(let c of i)if(r.push(` ${Nr(c.label,40)} ${Nr(qo(c.usd),10)} ${c.models.join(",")||"?"}`),c.epochs)for(let[l,u]of Object.entries(c.epochs).sort((f,d)=>Number(f[0])-Number(d[0])))r.push(` \u21B3 tenure ${l} (${u.provider||"?"})${" ".repeat(Math.max(1,24-String(u.provider||"?").length))}${Nr(qo(u.usd),10)} ${u.models.join(",")||"?"}`)}return r.join(`
|
|
367
|
+
`)}import CI from"node:fs/promises";import NI from"node:path";var Wa=t=>t.payload?.ts??t.ts,RI=12e4,sn=t=>Math.round(t*1e4)/1e4,$I=()=>({usd:0,activeMs:0,turns:0,toolCalls:0,models:{},providers:{},byTrack:new Map});function OI(t,{since:e}){let{track:n,events:r,brief:o}=t,s=v=>Wa(v)>=e,a=Xn([to(n?.name),to(n?.workspace?.branch),o].filter(Boolean).join(" ")),i=new Map;for(let v of r)if(v.type==="subtask.delegated"&&v.boxId&&v.payload?.task){let A=Xn(v.payload.task);A&&i.set(v.boxId,A)}let c=new Map,l=v=>{let A=v||"unattributed";return c.has(A)||c.set(A,{segments:[u(null)],lastTs:null,provider:null}),c.get(A)},u=v=>({textCat:v,tools:{},usd:0,tokens:{input:0,output:0,cacheRead:0,cacheWrite:0},models:{},provider:null,activeMs:0,turns:0,toolCalls:0}),f=[...r].sort((v,A)=>Wa(v)-Wa(A)),d=0,m=0,h=0,w={turns:0};for(let v of f){let A=l(v.boxId),M=A.segments[A.segments.length-1],z=Wa(v);switch(A.lastTs!=null&&z>A.lastTs&&s(v)&&(M.activeMs+=Math.min(z-A.lastTs,RI)),A.lastTs=z,v.provider&&(A.provider=v.provider),v.type){case"turn.user":{let $=v.payload?.text;if(v.source==="hook"||!String($||"").trim())break;let F=u(Xn($));A.segments.push(F),s(v)&&(F.turns+=1);break}case"tool.called":{if(v.source==="transcript")break;let $=v.payload?.tool;$&&(M.tools[$]=(M.tools[$]||0)+1),s(v)&&(M.toolCalls+=1);break}case"cost.recorded":{if(!s(v))break;let $=v.payload||{},F={input:$.tokens?.input||0,output:$.tokens?.output||0,cacheRead:$.tokens?.cacheRead||0,cacheWrite:$.tokens?.cacheWrite??$.tokens?.cacheCreation??0},T=Number($.usd)||0;!T&&$.tokens&&(T=oe($.model,F)),!T&&$.tokens&&!Cs($.model)&&(w.turns+=1),M.usd+=T,M.tokens.input+=F.input,M.tokens.output+=F.output,M.tokens.cacheRead+=F.cacheRead,M.tokens.cacheWrite+=F.cacheWrite;let C=$.model||"unknown";M.models[C]=(M.models[C]||0)+T,v.provider&&(M.provider=v.provider);break}case"handoff.done":s(v)&&(d+=1);break;case"subtask.merged":s(v)&&(m+=1);break;case"error":s(v)&&v.payload?.kind==="subtask-failed"&&(h+=1);break;default:break}}let g=new Map,b=!1;for(let[v,A]of c)for(let M of A.segments){if(!(M.usd>0||M.activeMs>0||M.turns>0||M.toolCalls>0))continue;let $=M.textCat?.category??i.get(v)?.category??nf(M.tools)?.category??a?.category??"uncategorized";g.has($)||g.set($,{usd:0,activeMs:0,turns:0,toolCalls:0,models:{},providers:{}});let F=g.get($);F.usd+=M.usd,F.activeMs+=M.activeMs,F.turns+=M.turns,F.toolCalls+=M.toolCalls;for(let[C,W]of Object.entries(M.models))F.models[C]=(F.models[C]||0)+W;let T=M.provider||A.provider;T&&(F.providers[T]=(F.providers[T]||0)+M.usd),T==="cursor"&&M.usd===0&&(b=!0)}return{perCategory:g,handoffs:d,merged:m,failed:h,sawCursorActivity:b,unpricedTurns:w.turns}}function sy(t,{windowMs:e=null,now:n=Date.now()}={}){let r=e==null?0:n-e,o=new Map,s=[],a=0,i=!1,c=0;for(let w of t||[]){let{track:g,events:b}=w;if(!b?.length){a+=1;continue}let{perCategory:v,handoffs:A,merged:M,failed:z,sawCursorActivity:$,unpricedTurns:F}=OI(w,{since:r});$&&(i=!0),c+=F;let T={trackId:g?.id,name:g?.name||g?.id,archived:!!g?.archivedAt,usd:0,activeMs:0,turns:0,categories:{},models:{},providers:{},handoffs:A,subtasksMerged:M,subtasksFailed:z};for(let[C,W]of v){T.usd+=W.usd,T.activeMs+=W.activeMs,T.turns+=W.turns,T.categories[C]={usd:sn(W.usd),activeMs:W.activeMs};for(let[p,S]of Object.entries(W.models))T.models[p]=sn((T.models[p]||0)+S);for(let[p,S]of Object.entries(W.providers))T.providers[p]=sn((T.providers[p]||0)+S);o.has(C)||o.set(C,$I());let ot=o.get(C);ot.usd+=W.usd,ot.activeMs+=W.activeMs,ot.turns+=W.turns,ot.toolCalls+=W.toolCalls;for(let[p,S]of Object.entries(W.models))ot.models[p]=(ot.models[p]||0)+S;for(let[p,S]of Object.entries(W.providers))ot.providers[p]=(ot.providers[p]||0)+S;let y=ot.byTrack.get(g?.id)||{usd:0,activeMs:0};y.usd+=W.usd,y.activeMs+=W.activeMs,ot.byTrack.set(g?.id,y)}(T.usd>0||T.activeMs>0||T.turns>0)&&(T.usd=sn(T.usd),T.dominantCategory=MI(T.categories),s.push(T))}let l=[...o.values()].reduce((w,g)=>w+g.usd,0),u=[...o.values()].reduce((w,g)=>w+g.activeMs,0),f=l>0?"usd":"time",d=f==="usd"?l:u,m=new Map((t||[]).map(w=>[w.track?.id,w.track?.name||w.track?.id])),h=[...o.entries()].map(([w,g])=>({category:w,sharePct:d>0?Math.round((f==="usd"?g.usd:g.activeMs)/d*100):0,shareBasis:f,usd:sn(g.usd),activeMs:g.activeMs,turns:g.turns,toolCalls:g.toolCalls,models:oy(g.models),providers:oy(g.providers),topWorkstreams:[...g.byTrack.entries()].sort((b,v)=>v[1].usd-b[1].usd||v[1].activeMs-b[1].activeMs).slice(0,2).map(([b,v])=>({trackId:b,name:m.get(b),usd:sn(v.usd),activeMs:v.activeMs}))})).sort((w,g)=>g.usd-w.usd||g.activeMs-w.activeMs);return s.sort((w,g)=>g.usd-w.usd||g.activeMs-w.activeMs),{since:r,capturedAt:n,totalUsd:sn(l),activeMs:u,turns:[...o.values()].reduce((w,g)=>w+g.turns,0),bullets:h,workstreams:s,gaps:{tracksWithoutEvents:a,cursorCostUnknown:i,unpricedTurns:c}}}var MI=t=>{let e=null;for(let[n,r]of Object.entries(t))(!e||r.usd>e.v.usd||r.usd===e.v.usd&&r.activeMs>e.v.activeMs)&&(e={cat:n,v:r});return e?.cat??null},oy=t=>Object.fromEntries(Object.entries(t).map(([e,n])=>[e,sn(n)]));var Pr={"24h":24*36e5,"7d":168*36e5,"30d":720*36e5,all:null};function PI(t){let e=String(t||"7d");return e in Pr?{key:e,ms:Pr[e]}:{key:"7d",ms:Pr["7d"]}}function BI(t,e){let n=new Set((e||[]).filter(o=>o.source==="orchestrator").map(o=>`${o.type}|${o.payload?.ts??"?"}`)),r=[...e||[]];for(let o of t?.events||[]){let s=ma(o);!s||n.has(`${s.type}|${s.payload?.ts??"?"}`)||r.push({trackId:t.id,boxId:s.boxId??null,ts:o.at,type:s.type,actor:s.actor,provider:s.provider??null,source:s.source,payload:s.payload})}return r}async function DI(t){if(!t?.cwd)return null;try{let n=(await CI.readFile(NI.join(t.cwd,".bostrat","handoff","latest.md"),"utf8")).match(/^##\s*Objective\s*\n([\s\S]*?)(?:\n##\s|\n*$)/m);return n?n[1].trim().slice(0,1e3):null}catch{return null}}async function ay(t,{window:e="7d",from:n,to:r,now:o=Date.now()}={}){let s=Number(n),a=Number(r),i=Number.isFinite(s)&&s>0,c=i&&Number.isFinite(a)&&a>s?Math.min(a,o):o,{key:l,ms:u}=i?{key:"custom",ms:c-s}:PI(e),f=[];for(let d of t||[]){let m=await L(d),h=BI(d,m);i&&c<o&&(h=h.filter(w=>(w.ts??0)<=c)),f.push({track:d,events:h,brief:await DI(d)})}return{window:l,...sy(f,{windowMs:u,now:c})}}var an=36e5,uy=24*an,jI=400,LI=12,Br=t=>Math.round(t*1e4)/1e4;function FI({window:t,from:e,to:n,now:r=Date.now()}={}){let o=Number(e),s=Number(n);if(Number.isFinite(o)&&o>0){let c=Number.isFinite(s)&&s>o?Math.min(s,r):r;return{key:"custom",since:o,until:c}}let a=String(t||"7d")in Pr?String(t||"7d"):"7d",i=Pr[a];return{key:a,since:i==null?null:r-i,until:r}}var UI=(t,e)=>t!=null&&e-t<=48*an?an:uy;function iy(t,e){if(e===an)return Math.floor(t/an)*an;let n=new Date(t);return n.setHours(0,0,0,0),n.getTime()}function HI(t,e){if(e===an)return t+an;let n=new Date(t);return n.setDate(n.getDate()+1),n.setHours(0,0,0,0),n.getTime()}var cy=()=>({input:0,output:0,cacheRead:0,cacheCreation:0}),ly=(t,e)=>{for(let n of Object.keys(t))t[n]+=e?.[n]||0},WI=t=>Object.fromEntries(Object.entries(t).map(([e,n])=>[e,Br(n)]));async function dy(t,e={}){let n=e.now??Date.now(),r=FI({...e,now:n}),o=r.since==null?uy:UI(r.since,r.until),s=T=>(r.since==null||T>=r.since)&&T<r.until,a=r.since!=null?r.until-r.since:null,i=T=>a!=null&&T>=r.since-a&&T<r.since,c={},l={},u=cy(),f=0,d=0,m=0,h=0,w=0,g=new Set,b=new Map,v=[],A=[],M=[],z=null;for(let T of t||[]){let W=(await L(T)).filter(p=>p.type==="cost.recorded");if(!W.length){T.archivedAt||M.push(T.name||T.id);continue}let ot=[];for(let p of W){let S=p.ts??0;s(S)?ot.push(p):i(S)&&(w+=Ps(p).usd)}if(!ot.length)continue;let y=se(ot,{boxLabels:_u(T)});f+=y.totalUsd,d+=y.turns,m+=y.repricedTurns||0,h+=y.unpricedTurns||0;for(let p of y.unpricedModels||[])g.add(p);ly(u,y.totalTokens);for(let[p,S]of Object.entries(y.byModel))c[p]||={usd:0,events:0,tokens:cy()},c[p].usd=Br(c[p].usd+S.usd),c[p].events+=S.events,ly(c[p].tokens,S.tokens);for(let[p,S]of Object.entries(y.byProvider))l[p]||={usd:0,events:0},l[p].usd=Br(l[p].usd+S.usd),l[p].events+=S.events;for(let p of ot){let{usd:S}=Ps(p);if(!S)continue;let _=iy(p.ts??0,o),rt=b.get(_)??{t:_,total:0,byProvider:{}};rt.total+=S;let I=p.provider||"unknown";rt.byProvider[I]=(rt.byProvider[I]||0)+S,b.set(_,rt),(z==null||_<z)&&(z=_)}v.push({id:T.id,name:T.name||T.id,archived:!!T.archivedAt,usd:y.totalUsd,turns:y.turns,providers:Object.keys(y.byProvider)});for(let[p,S]of Object.entries(y.byBox))A.push({trackId:T.id,trackName:T.name||T.id,boxId:p,...S})}let $=r.since!=null?iy(r.since,o):z,F=[];for(let T=$,C=0;T!=null&&T<r.until&&C<jI;T=HI(T,o),C++){let W=b.get(T);F.push(W?{t:T,total:Br(W.total),byProvider:WI(W.byProvider)}:{t:T,total:0,byProvider:{}})}return v.sort((T,C)=>C.usd-T.usd),A.sort((T,C)=>C.usd-T.usd),{generatedAt:new Date().toISOString(),pricingBasis:"api-equivalent (subscription usage priced at public API rates)",window:r.key,since:r.since,until:r.until,bucketMs:o,series:F,fleet:{totalUsd:Br(f),turns:d,repricedTurns:m,unpricedTurns:h,unpricedModels:[...g],totalTokens:u,byModel:c,byProvider:l,tracksWithCost:v.length,tracksSilentLive:M},prior:a!=null?{totalUsd:Br(w),since:r.since-a,until:r.since}:null,tracks:v.slice(0,50),boxes:A.slice(0,LI)}}import jr from"node:fs/promises";import qI from"node:fs";import pt from"node:path";import KI from"node:os";import{execFile as GI}from"node:child_process";import{promisify as zI}from"node:util";var VI=zI(GI),fy=process.env.BOSTRAT_STATE_DIR||pt.join(process.env.HOME||KI.tmpdir(),".bostrat"),Eu=!1;async function Ko(t,e){let n=await jr.stat(t).catch(()=>null);if(!n?.isFile())return!1;let r=await jr.stat(e).catch(()=>null);return r&&r.size===n.size&&r.mtimeMs>=n.mtimeMs?!1:(await jr.mkdir(pt.dirname(e),{recursive:!0}),await jr.copyFile(t,e),!0)}var JI=new Set([".claude",".codex",".credentials.json","auth.json",".claude.json","integrations.json"]);async function Dr(t,e){let n=0,r=await jr.readdir(t,{withFileTypes:!0}).catch(()=>[]);for(let o of r){if(JI.has(o.name))continue;let s=pt.join(t,o.name);o.isDirectory()?n+=await Dr(s,pt.join(e,o.name)):o.isFile()&&(n+=await Ko(s,pt.join(e,o.name))?1:0)}return n}async function py({stateDir:t=fy,dest:e=process.env.BOSTRAT_SYNC_DIR||`${fy}-sync`,remote:n=process.env.BOSTRAT_STATE_REMOTE||null,tracks:r=null}={}){try{await jr.mkdir(e,{recursive:!0});let o=0;o+=await Ko(pt.join(t,"tracks.json"),pt.join(e,"tracks.json"))?1:0,o+=await Ko(pt.join(t,"mocks.json"),pt.join(e,"mocks.json"))?1:0,o+=await Ko(pt.join(t,"runbooks.json"),pt.join(e,"runbooks.json"))?1:0,o+=await Dr(pt.join(t,"runbooks"),pt.join(e,"runbooks")),o+=await Dr(pt.join(t,"mock-archive"),pt.join(e,"mock-archive")),o+=await Ko(pt.join(t,"events.jsonl"),pt.join(e,"events.jsonl"))?1:0,o+=await Dr(pt.join(t,"events"),pt.join(e,"events")),o+=await Dr(pt.join(t,"memory"),pt.join(e,"memory"));for(let l of r||[])!l?.cwd||!l?.id||(o+=await Dr(pt.join(l.cwd,".bostrat"),pt.join(e,"workspaces",l.id,".bostrat")));let s=(...l)=>VI("git",["-C",e,...l]);qI.existsSync(pt.join(e,".git"))||(await s("init","-q"),await s("config","user.email","agent@bostrat.local"),await s("config","user.name","bostrat state sync")),n&&/^https:\/\/github\.com\//i.test(n)&&(await s("config","--get","credential.useHttpPath").then(()=>!0).catch(()=>!1)||await fi(e).catch(()=>{}));let a=!1,{stdout:i}=await s("status","--porcelain");i.trim()&&(await s("add","-A"),await s("commit","-q","-m",`state sync ${new Date().toISOString()}`),a=!0);let c=!1;if(n&&(a||Eu))try{await s("push","-q",n,"HEAD:refs/heads/main"),c=!0,Eu=!1}catch{Eu=!0}return{staged:o,committed:a,pushed:c}}catch(o){return{staged:0,committed:!1,pushed:!1,error:o?.message||String(o)}}}import{execFile as XI}from"node:child_process";import{promisify as YI}from"node:util";import{randomUUID as ZI}from"node:crypto";import QI from"node:fs";import tR from"node:os";import eR from"node:path";var ln=YI(XI),qa=t=>new Promise(e=>setTimeout(e,t)),un=Ti,Iu=process.env.BOSTRAT_USAGE_PROBE_CWD||eR.join(tR.homedir(),".bostrat","usage-probe"),nR=Number(process.env.BOSTRAT_USAGE_IDLE_REAP_MS)||5*6e4,rR=Number(process.env.BOSTRAT_USAGE_PANEL_TIMEOUT_MS)||15e3,oR=Number(process.env.BOSTRAT_USAGE_READY_TIMEOUT_MS)||45e3,my=Number(process.env.BOSTRAT_USAGE_CACHE_MS)||2e4,cn=null,Go=null,zo=null;async function sR(){try{return await ln(N,B("has-session","-t",un)),!0}catch{return!1}}function aR(){cn&&clearTimeout(cn),cn=setTimeout(()=>iR().catch(()=>{}),nR),cn.unref&&cn.unref()}async function iR(){cn&&(clearTimeout(cn),cn=null);try{await ln(N,B("kill-session","-t",un))}catch{}}async function cR(){let t=Date.now()+oR;for(;;){let e=await Ru().catch(()=>"");if(/for agents|for shortcuts|bypass permissions on/i.test(e)){await qa(500);return}if(Date.now()>t)throw new Error("usage probe didn't reach the prompt (trust/onboarding may be blocking)");await qa(500)}}async function lR(){if(await sR())return;if(!await De())throw new Error("host tmux server not running \u2014 start globo-tmux.service");QI.mkdirSync(Iu,{recursive:!0}),await Ne(Iu);let t=ZI();await ln(N,B("new-session","-d","-s",un,"-c",Iu,"-x","200","-y","60"));let e=`${vi} --session-id ${t}`;await ln(N,B("send-keys","-t",un,e,"Enter")),await cR()}async function Ru(){let{stdout:t}=await ln(N,B("capture-pane","-t",un,"-p"));return t}async function uR(){await lR(),await ln(N,B("send-keys","-t",un,"-l","/usage")),await ln(N,B("send-keys","-t",un,"Enter"));let t=Date.now()+rR,e="";for(;await qa(400),e=await Ru(),!(/Current\s+(session|week)/i.test(e)||/\d+%\s*used/i.test(e)||Date.now()>t););return await qa(900),e=await Ru(),await ln(N,B("send-keys","-t",un,"Escape")).catch(()=>{}),aR(),e}function dR(t){let e=String(t||"").split(`
|
|
368
|
+
`).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 fR(t){let e=String(t||"").split(`
|
|
369
|
+
`).map(s=>s.replace(/\s+$/,"")),n=e.findIndex(s=>/What's contributing/i.test(s));if(n<0)return null;let r=/(Usage credits|Esc to cancel|d to day · w to week|^\s*d to day)/i,o=[];for(let s=n+1;s<e.length&&!r.test(e[s]);s++)e[s].trim()&&o.push(e[s].trim());return o.length?o:null}async function hy({fresh:t=!1}={}){return!t&&Go&&Date.now()-Go.at<my?Go.data:zo||(zo=(async()=>{try{let e=await uR(),n=dR(e),r={...n,contributors:fR(e),subscription:!!(n.session||n.week||n.weekModels),capturedAt:Date.now()};return Go={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 Go={at:Date.now()-(my-3e3),data:n},n}finally{zo=null}})(),zo)}var pR=262144;function mR(t){let e=Number(t);if(!Number.isFinite(e)||e<0)return null;let n=Math.floor(e/3600),r=Math.round(e%3600/60);return n>=48?`in ${Math.round(n/24)}d`:n>0?`in ${n}h ${r}m`:`in ${Math.max(1,r)}m`}function wy(t){if(!t||typeof t!="object")return null;let e=Number(t.used_percent);return Number.isFinite(e)?{pct:Math.round(e),resets:mR(t.resets_in_seconds)}:null}function hR(t){let e=String(t||"").split(`
|
|
370
|
+
`),n=!1;for(let r=e.length-1;r>=0;r--){let o=e[r].trim();if(!o||o[0]!=="{"||!o.includes("token_count"))continue;let s;try{s=JSON.parse(o)}catch{continue}let a=s?.payload;if(a?.type!=="token_count")continue;n=!0;let i=a.rate_limits;if(!i)return{session:null,week:null,sawTokenCount:n};if(i.primary||i.secondary)return{session:wy(i.primary),week:wy(i.secondary),sawTokenCount:n};if(i.primary_used_percent!=null||i.secondary_used_percent!=null){let c=l=>Number.isFinite(Number(l))?{pct:Math.round(Number(l)),resets:null}:null;return{session:c(i.primary_used_percent),week:c(i.secondary_used_percent),sawTokenCount:n}}return{session:null,week:null,sawTokenCount:n}}return{session:null,week:null,sawTokenCount:n}}async function $u(t){let e={provider:"codex",supported:!0,contributors:null};try{let n=await En(t?.cwd,t?.sessionId);if(!n)return{...e,session:null,week:null,subscription:!1,error:"no codex rollout found for this box yet",capturedAt:Date.now()};let r=await Ot(n.file,pR),o=hR(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 gy(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 Ka({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 $u(i);if(c.subscription){a=c;break}(!a||a.error&&!c.error)&&(a=c)}r.push(a||await $u(null))}return n.has("cursor")&&r.push(await Ka({agent:"cursor"})),{providers:r,capturedAt:Date.now()}}async function Ka(t,{fresh:e=!1}={}){let n=t?.agent||"claude";return n==="codex"?$u(t):n==="cursor"?{provider:"cursor",supported:!1,subscription:!1,session:null,week:null,contributors:null,capturedAt:Date.now()}:{provider:"claude",supported:!0,...await hy({fresh:e})}}function wR(t=process.env){return!!(be(t)&&t.BOSTRAT_PLANE_TOKEN)}var gR=t=>({authorization:`Bearer ${t.BOSTRAT_PLANE_TOKEN||""}`}),Ga=(t,e)=>Object.assign(new Error(t),{status:e});async function yy(t,e){if(!wR(e))throw Ga("node is not plane-linked \u2014 Gmail unavailable",503);let n;try{n=await fetch(`${be(e)}${t}`,{headers:gR(e)})}catch(o){throw Ga(`plane unreachable \u2014 Gmail unavailable (${o?.message||o})`,503)}let r=await n.json().catch(()=>({}));if(!n.ok)throw Ga(r?.error||`plane replied ${n.status}`,n.status);return r}async function by(t,{max:e=10,env:n=process.env}={}){let r=new URLSearchParams({q:t||""});return e&&r.set("max",String(e)),(await yy(`/v1/gmail/messages?${r.toString()}`,n)).messages||[]}async function ky(t,{env:e=process.env}={}){if(!t)throw Ga("message id required",400);return(await yy(`/v1/gmail/messages/${encodeURIComponent(t)}`,e)).message}function xy(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 yR(t){let[e,n]=t.split("/"),r=xy(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 bR(t){return(t||"").split(",").map(e=>e.trim()).filter(Boolean).map(yR).filter(Boolean)}function kR(t){let e=t.headers["fly-client-ip"];return typeof e=="string"&&e.trim()?e.trim():(t.socket&&t.socket.remoteAddress||"").replace(/^::ffff:/,"")}function Sy(t){let e=bR(t),n=String(t||"").trim().toLowerCase()==="open";return{open:n,clientIp:kR,allowed(r){if(n)return!0;let o=xy((r||"").replace(/^::ffff:/,""));return o==null?!1:e.some(s=>s(o))}}}import vy from"node:crypto";import za from"node:fs";import xR from"node:path";function Ty(t=process.env,{stateDir:e=Vg,log:n=console}={}){let r=String(t.BOSTRAT_NODE_API_TOKEN||"").trim();if(r)return r;let o=xR.join(e,"api-token");try{let a=za.readFileSync(o,"utf8").trim();if(a)return a}catch{}let s=vy.randomBytes(32).toString("base64url");try{za.mkdirSync(e,{recursive:!0}),za.writeFileSync(o,s+`
|
|
371
|
+
`,{mode:384}),za.chmodSync(o,384)}catch(a){n.error?.(`[bostrat-agent] could not persist the node API token: ${a?.message||a}`)}return s}function Vo(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 Ay(t,e){if(!t||!e)return!1;let n=Buffer.from(String(t)),r=Buffer.from(String(e));return n.length!==r.length?!1:vy.timingSafeEqual(n,r)}function _y(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 SR=[/^\/api\/github\/token\b/],vR=t=>SR.some(e=>e.test(t));function Ey({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=_y(o);return Ay(c,t)?a():vR(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||Vo(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 Iy(t,e,{enforce:n=!0}={}){return!n||Ay(_y(t),e)?!0:Vo(t)}import TR from"node:http";import AR from"node:net";function Ry(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 $y(t){let e=Ry(t.headers.host);return e?{slug:e,view:oc(e)}:null}var _R=t=>`<!doctype html><meta charset="utf-8">
|
|
303
372
|
<meta http-equiv="refresh" content="3">
|
|
304
373
|
<title>${t.name} \u2014 starting</title>
|
|
305
374
|
<body style="font-family:system-ui;background:#0b0e14;color:#dce3f0;display:grid;place-items:center;height:100vh;margin:0">
|
|
306
375
|
<div style="text-align:center"><p style="font-size:2rem;margin:0">\u23F3</p>
|
|
307
|
-
<p><b>${t.name}</b> is waking up\u2026</p><p style="opacity:.6">auto-refreshing</p></div>`;function
|
|
376
|
+
<p><b>${t.name}</b> is waking up\u2026</p><p style="opacity:.6">auto-refreshing</p></div>`;function Oy(t,e,n){let r=$y(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(_R(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=TR.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 My(t,e,n){let r=$y(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
|
|
308
377
|
\r
|
|
309
378
|
bostrat: no live view "${s}"\r
|
|
310
|
-
`),e.destroy(),!0;let a=
|
|
379
|
+
`),e.destroy(),!0;let a=AR.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
|
|
311
380
|
`)+`\r
|
|
312
381
|
\r
|
|
313
|
-
`),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
|
|
314
|
-
`)){let s=o.trim();if(!s)continue;if(s.startsWith("#")){n==null&&(n=s.replace(/^#+\s*/,"").trim().slice(0,WE)||null);continue}let a=/^[-*]\s+(.*)$/.exec(s);if(!a)continue;let i=a[1].trim(),c=null,l=new RegExp(`^\\[([^\\]]{1,${qE}})\\]\\s+(.*)$`).exec(i);if(l&&(c=l[1].trim(),i=l[2].trim()),!!i){if(i.length>$g)throw new Error(`bullet ${r.length+1} is ${i.length} chars \u2014 over the ${$g}-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>Rg)throw new Error(`${r.length} bullets \u2014 over the ${Rg}-bullet cap; group related changes into one line`);return{title:n||null,bullets:r}}async function Cg(t,{repos:e=nu()}={}){let n=new Set(e);return(await se()).filter(r=>r.state==="merged"&&(r.resolvedAt??0)>=t&&n.has(r.repo)).sort((r,o)=>(r.resolvedAt??0)-(o.resolvedAt??0)).map(r=>({repo:r.repo,number:r.number,title:r.title||null,url:r.url||null,mergedAt:r.resolvedAt??null}))}async function Mg(t,{md:e,day:n=null}){let r=VE(e),o=n==null||n===""?KE():String(n);if(!GE.test(o)||Number.isNaN(Date.parse(o)))throw new Error(`invalid day "${n}" \u2014 expected YYYY-MM-DD`);let s=await O({id:t.id,cwd:t.cwd},{type:"changelog.published",actor:"agent",source:"orchestrator",boxId:t.leadBox||null,payload:{ts:Date.now(),day:o,title:r.title,bullets:r.bullets}});return{day:o,title:r.title,bullets:r.bullets,eventId:s.id}}import{execFile as zE}from"node:child_process";import{promisify as JE}from"node:util";var ou=JE(zE),Ng=3e4,XE=30*6e4,YE=Ng,ZE=10*6e4,QE=10080*6e4,tI=60*6e4,eI=8,nI=1440*6e4,rI=3,Pg="number,url,title,headRefName,headRefOid,state,mergedAt,closedAt",ru=60*6e4,oI=15*6e4,Oa=new Map,Ca=new Map,sI=/could not resolve|not found|no pull requests?/i,aI=t=>sI.test(String(t?.message||t||""));function iI(t,e){return[t.ciFix?.delegatedAt,t.reconcile?.delegatedAt,t.ciFixAwait?.since,t.reconcileAwait?.since].some(n=>n&&e-n<ru)}var Bg={fast:YE,slow:ZE,decay:tI};function Dg(t,e){return t.armedAt&&e-t.armedAt<XE||iI(t,e)?"fast":e-(t.firstSeen||0)>QE?"decay":"slow"}function cI(t,e){return Bg[Dg(t,e)]}function Do(t,e,n,r){let o=t.lanes?.get(ut(e.repo,e.number))||{};return ia({change:n,repo:e.repo,number:Number(e.number),githubAt:r||null,lane:o.lane||null,intervalMs:o.intervalMs||null,source:"watch"})}async function lI(t,e){ke("watch");try{let{stdout:n}=await ou("gh",["pr","view",String(e),"--repo",t,"--json",Pg],{timeout:3e4,maxBuffer:4194304,env:{...process.env,...await ae({slug:t})}});return JSON.parse(n||"null")}catch(n){throw Fe(n)&&Ue(n),n}}async function uI(t){ke("watch");try{let{stdout:e}=await ou("gh",["pr","list","--repo",t,"--state","all","--limit","100","--json",Pg],{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 Fe(e)&&Ue(e),e}}async function dI(){let t=new Map;for(let e of await at().catch(()=>[]))t.set(e.id,e);return t}async function fI(t,e){await O({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"user",source:"orchestrator",boxId:null,payload:{ts:Date.now(),change:"merged",...e}})}async function pI(t,e){let n=await An(t);return n?pr(n,e):null}async function mI(t,e,n){let r=await An(t);if(!r)throw new Error(`no local checkout for ${t}`);return mr(r,e,{method:n})}async function hI(t,e){let n=await An(t);if(!n)throw new Error(`no local checkout for ${t}`);return mo(n,e)}async function wI(t,e){await O({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"user",source:"orchestrator",boxId:null,payload:{ts:Date.now(),change:"auto-canceled",...e}})}async function Ir(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 gI=10*6e4;async function yI(t,e,n){let r=t.autoMerge?.armedAt||0;if(r&&(n.now??Date.now())-r>nI){await Ir(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 Ir(t,o,e,n,`check${o.checks.failed===1?"":"s"} failed: ${a}`);return}if(o.mergeable==="CONFLICTING"){await Ir(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<gI)return;await Ir(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 Ir(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 Ir(t,o,e,n,String(a?.message||a).trim()||"merge failed");return}await jg(t,{...o,state:"MERGED"},e,n,{change:"auto-merged",githubAt:o.checks.settledAt||null})}}async function No(t,e,n){await O({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"user",source:"orchestrator",boxId:null,payload:{ts:Date.now(),change:e,...n}})}async function bI(t,e){try{return await ou("git",["-C",t.cwd,"cat-file","-e",`${e}^{commit}`],{timeout:1e4}),!0}catch{return!1}}async function Po(t,e,n,r=()=>!0){return(await F(t).catch(()=>[])).some(s=>s.type==="pr.state.changed"&&s.payload?.change===e&&s.payload?.number===Number(n)&&r(s))}var Bo=(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 kI(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 Ms(t.repo,t.number,{headSha:s}).catch(()=>{}),t.reconcile?.headSha&&s!==t.reconcile.headSha&&await Ps(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<oI;await op(t.repo,t.number,s).catch(()=>{}),t.baseFoldAt&&await rp(t.repo,t.number).catch(()=>{}),!i&&!c&&!l&&a&&!await r.localHasCommit(a,s).catch(()=>!1)&&!await Po(a,"pushed",t.number,f=>f.payload?.sha===s)&&await No(a,"pushed",{...Bo(t,e),sha:s}).catch(()=>{})}if(t.ciFixAwait)if(o-(t.ciFixAwait.since||0)>ru)await Ns(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 Ns(t.repo,t.number).catch(()=>{});else if(i.checks.total>0&&i.checks.pending===0){let c=Do(r,t,"ci-fixed",i.checks.settledAt||null),l=t.ciFixAwait.since||0;a&&!await Po(a,"ci-fixed",t.number,u=>(u.payload?.ts||0)>=l)&&await No(a,"ci-fixed",{...Bo(t,e),passed:i.checks.passed,githubAt:c.githubAt,latencyMs:c.latencyMs,lane:c.lane}).catch(()=>{}),await Ns(t.repo,t.number).catch(()=>{})}}}if(t.reconcileAwait)if(o-(t.reconcileAwait.since||0)>ru)await Bs(t.repo,t.number).catch(()=>{});else{let i=await r.detail(t.repo,t.number).catch(()=>null);if(i){if(i.mergeable==="CONFLICTING")await Bs(t.repo,t.number).catch(()=>{});else if(i.mergeable==="MERGEABLE"){Do(r,t,"reconciled",null);let c=t.reconcileAwait.since||0;a&&!await Po(a,"reconciled",t.number,l=>(l.payload?.ts||0)>=c)&&await No(a,"reconciled",{...Bo(t,e),base:i.baseRefName||null}).catch(()=>{}),await Bs(t.repo,t.number).catch(()=>{})}}}}async function xI(t,e,n,r){let o=Do(r,t,"closed",Date.parse(e?.closedAt||"")||null),s=n.get(t.trackId);s&&!await Po(s,"closed",t.number)&&await No(s,"closed",{...Bo(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 SI(t,e,n){Do(n,t,"lost",null);let r=e.get(t.trackId);r&&!await Po(r,"lost",t.number)&&await No(r,"lost",Bo(t,null)).catch(()=>{}),await n.notifyIfResolved(t.repo,t.number,"lost").catch(()=>{}),await n.mark(t.repo,t.number,"lost").catch(()=>{})}async function jg(t,e,n,r,o={}){let s=Do(r,t,o.change||"merged",o.githubAt??(Date.parse(e.mergedAt||"")||null)),a=n.get(t.trackId);a&&((await F(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 vI(t={}){let{now:e=Date.now(),auth:n=il,view:r=lI,list:o=uI,links:s=ap,trackMap:a=dI,emitMerged:i=fI,mark:c=Pe,archiveIfArmed:l=We,notifyIfArmed:u=qe,notifyIfResolved:f=Ol,batchThreshold:d=rI,detail:m=pI,merge:h=mI,updateBranch:w=hI,lookup:y=Tt,disarm:b=Qr,emitDisarmed:A=wI,localHasCommit:_=bI}=t;if(!(await n().catch(()=>null))?.authenticated||Qs()>0)return 0;let I=(await s().catch(()=>[])).filter(p=>e-(Oa.get(ut(p.repo,p.number))||0)>=cI(p,e));if(!I.length)return 0;let L=Date.now(),v=new Map,$=new Map;for(let p of I){let S=Dg(p,e);v.set(ut(p.repo,p.number),{lane:S,intervalMs:Bg[S]}),Oa.set(ut(p.repo,p.number),e),$.has(p.repo)||$.set(p.repo,[]),$.get(p.repo).push(p)}let W=await a().catch(()=>new Map),nt={now:e,lanes:v,emitMerged:i,mark:c,archiveIfArmed:l,notifyIfArmed:u,notifyIfResolved:f,detail:m,merge:h,updateBranch:w,lookup:y,disarm:b,emitDisarmed:A,localHasCommit:_};for(let[p,S]of $){let T,J=new Set;if(S.length>d)T=await o(p).catch(()=>null);else{T=new Map;for(let R of S)try{let M=await r(p,R.number);M?.number!=null&&T.set(Number(M.number),M)}catch(M){aI(M)&&J.add(ut(R.repo,R.number))}}if(T)for(let R of S){let M=ut(R.repo,R.number),E=T.get(Number(R.number));if(!E){if(J.has(M)){let P=(Ca.get(M)||0)+1;Ca.set(M,P),P>=eI&&(await SI(R,W,nt),Ca.delete(M),Oa.delete(M))}continue}Ca.delete(M),E.state==="MERGED"||E.state==="CLOSED"?(E.state==="MERGED"?await jg(R,E,W,nt):await xI(R,E,W,nt),Oa.delete(M)):(await kI(R,E,W,nt).catch(()=>{}),R.autoMerge&&await yI(R,W,nt).catch(()=>{}))}}let g={};for(let{lane:p}of v.values())g[p]=(g[p]||0)+1;return console.log(`[pr-watch] probed ${I.length} link(s) (${Object.entries(g).map(([p,S])=>`${p} ${S}`).join(", ")}) in ${Date.now()-L}ms`),I.length}function Lg(){let t=!1;setInterval(async()=>{if(!t){t=!0;try{await vI()}catch{}finally{t=!1}}},Ng)}import Ae from"node:fs/promises";import Rr from"node:path";import Kg from"node:os";import Fg from"node:fs/promises";import Ma from"node:path";import{execFile as AI}from"node:child_process";import{promisify as TI}from"node:util";import{fileURLToPath as _I}from"node:url";var EI=TI(AI),Hg=Ma.dirname(_I(import.meta.url)),II=Ma.resolve(Hg,"..","..","client","dist"),RI="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",$I=new Map([...RI].map((t,e)=>[t,e]));function OI(t){let e=[],n=0,r=0;for(let o of t){let s=$I.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 CI(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=OI(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 MI(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 Ug=new Map;async function NI(t){let e;try{e=await Fg.stat(t)}catch{return null}let n=Ug.get(t);if(n&&n.mtimeMs===e.mtimeMs)return n;try{let r=JSON.parse(await Fg.readFile(t,"utf8")),o={mtimeMs:e.mtimeMs,sources:r.sources||[],lines:CI(r.mappings||"")};return Ug.set(t,o),o}catch{return null}}var su=null;function PI(){return su||(su=process.env.BOSTRAT_RELEASE?Promise.resolve(process.env.BOSTRAT_RELEASE):EI("git",["rev-parse","--short","HEAD"],{cwd:Ma.resolve(Hg,"..","..")}).then(t=>t.stdout.trim()).catch(()=>null)),su}var BI=/(?:https?:\/\/[^\s()]*)?\/assets\/([^\s()/]+\.js):(\d+):(\d+)/g;async function DI(t,{distDir:e=II}={}){let n=[],r=0,o=!1;for(let s of t.matchAll(BI)){let[a,i,c,l]=s,u=await NI(Ma.join(e,"assets",`${i}.map`)),f=u?MI(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 Wg(t,e={}){try{if(!t?.error?.stack||!t.release||t.release==="unknown")return t;let n=e.release??await PI();if(!n||t.release!==n)return t;let{stack:r,symbolicated:o}=await DI(t.error.stack,e);o&&(t.error.stack=r,t.error.symbolicated=!0)}catch{}return t}var jI=()=>process.env.BOSTRAT_STATE_DIR||Rr.join(process.env.HOME||Kg.tmpdir(),".bostrat"),tn=()=>Rr.join(jI(),"telemetry"),Vg=()=>Rr.join(tn(),".relay-offsets.json"),LI=50,FI=14,UI=3e4,qg=500,jt=(t,e)=>typeof t=="string"?t.slice(0,e):"";function HI(t,e){if(!t||typeof t!="object")return null;let n=jt(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:jt(t.anonId,64),sessionId:jt(t.sessionId,64),userId:jt(t.userId,64),release:jt(t.release,64),appVersion:jt(t.appVersion,32),route:jt(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:jt(t.error.kind,256),message:jt(t.error.message,8192),stack:jt(t.error.stack,32768),fingerprint:jt(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:jt(s?.n,64),r:jt(s?.r,128)}))),o}var zg=t=>new Date(t).toISOString().slice(0,10),Gg=0;async function Jg(t,e=Date.now()){let r=(Array.isArray(t?.events)?t.events.slice(0,LI):[]).map(s=>HI(s,e)).filter(Boolean);if(!r.length)return 0;for(let s of r)s.error?.stack&&await Wg(s);await Ae.mkdir(tn(),{recursive:!0});let o=Rr.join(tn(),`${zg(e)}.ndjson`);return await Ae.appendFile(o,r.map(s=>JSON.stringify(s)+`
|
|
315
|
-
`).join(""),"utf8"),e-
|
|
316
|
-
`).filter(Boolean),f=[];for(let d of u)try{f.push(JSON.parse(d))}catch{}for(let d=0;d<f.length;d+=qg){let m=f.slice(d,d+qg).map(w=>qI(w)),h=await t(`${e.url.replace(/\/$/,"")}/v1/logs`,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${e.ingestKey}`},body:JSON.stringify({events:m})}).catch(()=>null);if(!h||!h.ok)return{sent:o,disabled:!1};o+=m.length}r[s]=i+l+1,await Zg(r)}return{sent:o,disabled:!1}}var au=null,iu=!1;function Qg(){au||(au=setInterval(async()=>{if(!iu){iu=!0;try{await GI()}catch(t){console.error("[telemetry] relay failed:",t?.message||t)}finally{iu=!1}}},UI),au.unref?.())}import Ba from"node:fs/promises";import oy from"node:path";import KI from"node:os";var sy=()=>process.env.BOSTRAT_STATE_DIR||oy.join(process.env.HOME||KI.tmpdir(),".bostrat"),ay=()=>oy.join(sy(),"dictation-glossary.json"),VI=100,zI=500,JI=["bostrat","globopi","codex","tmux","worktree","Deepgram","Linear","Firecracker","Tigris","WireGuard","misous","postyx","cedar","gerbil","Anthropic","Opus","Haiku","Sonnet","keyterm","monorepo","dogfood"],Da=null,cu=null,lu=Promise.resolve();function ty(){let t={},e=Date.now();for(let n of JI)t[n.toLowerCase()]={term:n,count:1,lastUsed:e,source:"seed"};return t}async function iy(){return Da||(cu??=Ba.readFile(ay(),"utf8").then(t=>JSON.parse(t)?.terms??ty()).catch(()=>ty()).then(t=>Da=t).finally(()=>cu=null),cu)}async function ey(){let t=JSON.stringify({terms:Da},null,2),e=ay(),n=`${e}.${process.pid}.tmp`;await Ba.mkdir(sy(),{recursive:!0}),await Ba.writeFile(n,t),await Ba.rename(n,e)}function XI(){return lu=lu.then(ey,ey),lu.catch(()=>{})}async function uu(){return Object.values(await iy()).sort((e,n)=>n.count-e.count||n.lastUsed-e.lastUsed)}var YI=t=>Math.max(1,Math.ceil(t.length/4));async function ZI(){let t=[],e=0;for(let{term:n}of await uu()){let r=YI(n);if(!(e+r>zI)&&(t.push(n),e+=r,t.length>=VI))break}return t}async function QI(t,e){let n=await iy(),r=t.toLowerCase(),o=n[r];n[r]={term:t,count:(o?.count??0)+1,lastUsed:Date.now(),source:o?.source??e}}var ny=t=>(t||"").split(/\s+/).map(e=>e.replace(/^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu,"")).filter(Boolean),Pa=t=>t.toLowerCase();function ry(t){return!new RegExp("\\p{L}","u").test(t)||t.length<3?!1:/^[a-z]+$/.test(t)?t.length>=4:!0}async function cy(t,e){let n=ny(t),r=ny(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]=Pa(n[u])===Pa(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;)Pa(n[c])===Pa(r[l])?(c++,l++):a[c+1][l]>=a[c][l+1]?c++:(ry(r[l])&&i.add(r[l]),l++);for(;l<s;l++)ry(r[l])&&i.add(r[l]);if(!i.size)return[];for(let u of i)await QI(u,"learned");return await XI(),[...i]}var jo=null;function tR(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 ly(){if(!jo)return null;let t=await Vr();if(!t||t.apiKey.slice(-6)!==jo.keyTail)return null;let{keyTail:e,...n}=jo;return n}async function uy(t,e){let n=await Vr();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 ZI())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=tR(o.status,i);c.persistent&&(jo={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 jo=null,{transcript:(await o.json())?.results?.channels?.[0]?.alternatives?.[0]?.transcript??""}}var du={"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 eR(t){if(!t)return null;let e=String(t).trim();if(du[e])return e;let n=null;for(let r of Object.keys(du))e.startsWith(r)&&(!n||r.length>n.length)&&(n=r);return n}function nR(t,e={}){let n=du[eR(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 rR(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 Lo=null;async function oR(){if(!Lo)return null;let t=await Ts();if(!t||t.apiKey.slice(-6)!==Lo.keyTail)return null;let{keyTail:e,...n}=Lo;return n}async function sR(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=rR(e.status,o);s.persistent&&(Lo={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}Lo=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 aR={openai:sR};async function dy(){let t=await Ts();return t?{configured:!0,provider:t.provider,model:t.model,voice:t.voice,problem:await oR()}:{configured:!1}}async function fy(){let t=await Ts();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 aR[t.provider](t);return{provider:t.provider,model:t.model,voice:t.voice,...e}}async function py(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=nR(e,s),i=await O(t,{type:"cost.recorded",actor:"user",provider:"openai-realtime",payload:{model:e||"unknown",usd:a,tokens:{input:s.textIn+s.audioIn,output:s.textOut+s.audioOut,cacheRead:s.textInCached+s.audioInCached,cacheWrite:0},voice:s,seconds:Math.max(0,Math.round(Number(r)||0)),source:"voice-chat"}});return{usd:a,seq:i.seq}}var by=ct.dirname(cR(import.meta.url)),my=Number(process.env.PORT)||3362,ja=Ww(process.env),ky=Number(process.env.BOSTRAT_AUTO_TRIGGER_PCT)||80,$r=gg(process.env.BOSTRAT_ALLOW_IPS),xy=bg(),Sy=process.env.BOSTRAT_ALLOW_LAN_TOKENLESS!=="1",x=ue();x.disable("x-powered-by");x.use((t,e,n)=>{if(Mo(t)||$r.allowed($r.clientIp(t)))return n();e.status(403).type("text/plain").send("bostrat: access restricted to the allowlist")});x.use(_g);x.use(Sg({token:xy,enforce:Sy}));x.use("/api/mocks",ue.json({limit:"1mb"}));x.use("/api/mocks",ue.raw({type:"image/png",limit:"24mb"}));x.use("/api/runbooks",ue.json({limit:"1mb"}));x.use("/api/telemetry",ue.json({limit:"256kb"}));x.use("/api/hook",ue.json({limit:"2mb"}));x.use("/api/dictation",ue.raw({type:["audio/*","video/mp4","application/octet-stream"],limit:"32mb"}));x.use("/api/tracks/:id/upload",ue.raw({type:()=>!0,limit:"25mb"}));x.use(ue.json({limit:"64kb"}));x.get("/api/health",async(t,e)=>{e.json({ok:!0,tmuxSocket:Pt,hostTmuxAlive:await _e(),autoTriggerPct:ky})});x.get("/api/sessions",async(t,e)=>{e.json({sessions:await nn()})});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 rn(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(!mt(n))return e.status(400).json({error:"invalid session name"});if(!await X(n))return e.status(404).json({error:"session not found"});try{await Jt(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(!mt(n))return e.status(400).json({error:"invalid session name"});let r=await X(n);if(!r)return e.status(404).json({error:"session not found"});let o=await Dr(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(!mt(n))return e.status(400).json({error:"invalid session name"});try{let r=await bd(n);e.json({ok:!0,step:r.step,file:r.file,startedAt:r.startedAt})}catch(r){e.status(409).json({error:r?.message||String(r)})}});x.get("/api/handoff/:name",(t,e)=>{let n=decodeURIComponent(t.params.name),r=cs(n);if(!r)return e.json({running:!1});e.json({running:!r.finishedAt,step:r.step,error:r.error,file:r.file,startedAt:r.startedAt,finishedAt:r.finishedAt})});var hy=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>hy){let s=new Set([...r].sort((a,i)=>(i.archivedAt||0)-(a.archivedAt||0)).slice(0,hy).map(a=>a.id));o=n.filter(a=>!a.archivedAt||s.has(a.id))}e.json({tracks:o,archivedTotal:r.length,agents:ha,agentAvailability:await Tl(),tickets:await Rs()})});x.get("/api/cost",async(t,e)=>{try{let n=await Xw(await at());if(n.cursorUsage=rl(),t.query.format==="text")return e.type("text/plain").send(Yw(n));e.json(n)}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/usage/roundup",async(t,e)=>{try{e.json(await eg(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 ag(await at(),{window:t.query.window,from:t.query.from,to:t.query.to});n.cursorUsage=rl(),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 fg(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 dp(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 fp(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 bw()})}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 Su(t.get(qo),{byToken:Qh,legacy:()=>Zh(t.body?.session_id,t.body?.cwd)});if(n==="refused")return Go("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&&Go("mismatch",{track:r.track.id,box:r.box.name}),Go(n,{track:r?.track?.id,hook:t.body?.hook_event_name});let o=await Uw(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(!Xg($r.clientIp(t)))return e.json({ok:!0,dropped:!0});let n=await Jg(t.body||{});e.json({ok:!0,events:n})}catch(n){e.json({ok:!1,error:n?.message||String(n)})}});x.get("/api/repos",async(t,e)=>{try{let n=await ye(),r=new Set((await Promise.all(n.map(a=>Z(a.path)))).filter(Boolean).map(a=>a.toLowerCase())),s=((await no()).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 Us(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:Fs(),calls:Xm(),latency:Sh()})});x.post("/api/github/refresh",(t,e)=>{vp(),e.json({ok:!0})});x.get("/api/gmail/search",async(t,e)=>{try{let n=await mg(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 hg(String(t.params.id))})}catch(n){e.status(n?.status||502).json({error:n?.message||String(n)})}});function gu(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 La({repo:t=null,number:e=null,branch:n=null}){let r=t?await Z(t):null;if(r&&e!=null){let s=await Tt(r,e),a=s&&await Q(s.trackId);if(a)return a}return n?(await at()).find(s=>s.workspace?.branch===n&&gu(s,t,r))??null:null}async function vy({repo:t=null,number:e=null,branch:n=null}){let r=await La({repo:t,number:e,branch:n}).catch(()=>null);if(r&&!r.archivedAt)return r;if(!n)return null;let o=t?await Z(t).catch(()=>null):null;return(await at()).find(a=>!a.archivedAt&&a.workspace?.branch===n&&gu(a,t,o))??null}async function zt(t,{number:e,url:n=null,title:r=null,branch:o=null,repo:s=null,track:a=null,extra:i={}}){let c=a||await La({repo:s,number:e,branch:o}).catch(()=>null);if(!c)return;let l=i.repo??(s?await Z(s).catch(()=>null):null);await O({id:c.id,cwd:c.cwd},{type:"pr.state.changed",actor:"user",source:"orchestrator",payload:{ts:Date.now(),change:t,number:e,url:n,title:r,branch:o??c.workspace?.branch??null,...l?{repo:l}:{},...i}}).catch(()=>{})}async function fR(t){let e=(await at()).filter(n=>!n.archivedAt);for(let n of t??[]){if(!n.prs?.length)continue;let r=await Z(n.path);if(r)for(let o of n.prs){if(await Tt(r,o.number))continue;let s=e.find(a=>a.workspace?.branch===o.headRefName&&gu(a,n.path,r));s&&await Bt({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 Rh();await fR(n.repos).catch(()=>{}),e.json(n)}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.post("/api/git/prs/merge",async(t,e)=>{let{repo:n,number:r,method:o,auto:s,update:a,archive:i,notify:c,headRefName:l,title:u,url:f}=t.body||{};try{if(s){let w=o||"merge";if(!po.has(w))throw new Error(`invalid merge method: ${w}`);let y=Number(r);if(!Number.isInteger(y)||y<=0)throw new Error("invalid PR number");let b=await Z((await xe(n)).path);if(!b)throw new Error("repo has no GitHub remote");let A=await La({repo:n,number:y,branch:l}).catch(()=>null);if(!A)throw new Error("auto-merge needs a Track to watch this PR \u2014 none owns it");if(a&&await mo(n,y),await Bt({repo:b,number:y,trackId:A.id,url:f,headRef:l,title:u,source:"matched"}).catch(()=>{}),!await Zr(b,y,{merge:{method:w}}))throw new Error("couldn't record the auto-merge arm");return await Zn(b,y,i===!0),await Ds(b,y,c===!0&&i!==!0),ce(),await zt("auto-armed",{number:y,url:f,title:u,branch:l,repo:n,track:A,...a?{extra:{updated:!0}}:{}}),e.json({ok:!0,number:y,auto:!0})}let d=await mr(n,r,{method:o||"squash"});await zt("merged",{number:d.number,url:f,title:u,branch:l,repo:n});let m=await Z(n).catch(()=>null),h=!1;if(m&&(i===!0&&await Zn(m,d.number,!0).catch(()=>{}),c===!0&&i!==!0&&await Ds(m,d.number,!0).catch(()=>{}),await qe(m,d.number).catch(()=>{}),h=await We(m,d.number).catch(()=>!1),await Pe(m,d.number,"merged").catch(()=>{})),i===!0&&!h){let w=await La({repo:n,number:d.number,branch:l}).catch(()=>null);w&&!w.archivedAt&&await kr(w.id).catch(()=>{})}e.json(d)}catch(d){let m=d?.message||String(d),h=/invalid|unknown repo|required|needs a Track|no GitHub remote/i.test(m)?400:502;e.status(h).json({error:m})}});x.post("/api/git/prs/disarm-auto",async(t,e)=>{let{repo:n,number:r,headRefName:o,title:s,url:a}=t.body||{};try{let i=Number(r);if(!Number.isInteger(i)||i<=0)throw new Error("invalid PR number");let c=await Z((await xe(n)).path).catch(()=>null),l=c?await Qr(c,i).catch(()=>null):null;try{await ul(n,i)}catch(u){if(!l)throw u}ce(),await zt("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 Z(n).catch(()=>null),s=await Mh(n,{method:r||"merge",onMerged:a=>{zt("merged",{number:a.number,url:a.url,title:a.title,branch:a.headRefName,repo:n}),o&&qe(o,a.number).catch(()=>{}).then(()=>We(o,a.number)).catch(()=>{}).then(()=>Pe(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:Ch()})});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(!po.has(i))throw new Error(`invalid merge method: ${i}`);if(!n)return e.status(400).json({error:"trackId required"});let c=await Q(n);if(!c)return e.status(404).json({error:`Track ${n} isn't on this node`});let l=(await se()).filter(d=>d.trackId===c.id&&d.state!=="merged"&&d.state!=="closed");if(!l.length)return e.status(409).json({error:"no open PRs on this Track"});for(let d of l)await Zn(d.repo,d.number,o===!0).catch(()=>{}),await Ds(d.repo,d.number,s===!0&&o!==!0).catch(()=>{});let u=[],f=[];for(let d of l){let m={repo:d.repo,number:d.number,ok:!0,action:"armed"};u.push(m);try{let h=await An(d.repo);if(!h)throw new Error(`no local checkout for ${d.repo}`);let w=await pr(h,d.number);f.push({r:m,l:d,repoPath:h,pr:w,plan:$h(w,{update:r!==!1})})}catch(h){m.ok=!1,m.action="failed",m.reason=h?.message||String(h)}}for(let{r:d,l:m,repoPath:h,pr:w,plan:y}of Oh(f))try{if(y.action==="skip")d.action="skipped",d.reason=y.reason;else if(y.action==="merge")await mr(h,m.number,{method:i}),await zt("merged",{number:m.number,url:w.url,title:w.title,branch:w.headRefName,repo:h,track:c}),await qe(m.repo,m.number).catch(()=>{}),await We(m.repo,m.number).catch(()=>{}),await Pe(m.repo,m.number,"merged").catch(()=>{}),d.action="merged";else{if(y.action==="update-arm"&&(await mo(h,m.number).catch(()=>{}),d.action="updated-armed"),!await Zr(m.repo,m.number,{merge:{method:i}}))throw new Error("couldn't record the auto-merge arm");await zt("auto-armed",{number:m.number,url:w.url,title:w.title,branch:w.headRefName,repo:h,track:c,...d.action==="updated-armed"?{extra:{updated:!0}}:{}})}}catch(b){d.ok=!1,d.action="failed",d.reason=b?.message||String(b)}if(ce(),!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 se()).filter(a=>a.trackId===r.id&&a.state!=="merged"&&a.state!=="closed"),s=[];for(let a of o){let i=await Qr(a.repo,a.number).catch(()=>null),c=await An(a.repo).catch(()=>null);c&&await ul(c,a.number).catch(()=>{}),i&&(await zt("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}))}ce(),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=Ot(i.workspace),f,d=Array.isArray(a)?a.filter(Boolean):[];if(d.length)f=d.map(w=>{let y=u.find(b=>b.name===w||b.repo===w||b.base===w||b.dir===w);if(!y)throw new Error(`invalid repo "${w}" \u2014 not tagged on this Track`);return y});else if(f=await lo(i),!f.length)return e.status(409).json({error:"no tagged repo has work to ship"});let m=[];for(let w of f)try{let y=await fl(w.dir,{title:r,body:o,base:s}),b=w.repo||await Z(w.dir).catch(()=>null);b&&y.number&&await Bt({repo:b,number:y.number,trackId:i.id,url:y.url,headRef:i.workspace?.branch??null,title:r,source:"api"}),await zt("opened",{number:y.number,url:y.url,title:r,track:i,branch:i.workspace?.branch,extra:{repo:b??w.name}}),m.push({repo:w.name,slug:b??null,ok:!0,url:y.url,number:y.number})}catch(y){m.push({repo:w.name,slug:w.repo??null,ok:!1,error:y?.message||String(y)})}let h=m.filter(w=>w.ok);return h.length?e.json({ok:!0,multi:!0,results:m,url:h[0].url,number:h[0].number}):e.status(502).json({error:m.map(w=>`${w.repo}: ${w.error}`).join("; "),results:m})}let c=await fl(i.cwd,{title:r,body:o,base:s}),l=await Z(i.cwd).catch(()=>null);l&&c.number&&await Bt({repo:l,number:c.number,trackId:i.id,url:c.url,headRef:i.workspace?.branch??null,title:r,source:"api"}),await zt("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 lo(n),o=new Map(r.map(a=>[a.base,a])),s=Ot(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 dw(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 fw(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 pr(n,r),s=await Z(n).catch(()=>null),a=await vy({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=Ot(a.workspace).find(b=>b.base===n||s&&b.repo===s)??null,c=a.workspace?.kind==="multi",l=await Nh(n,o.number);if(!l.length)return e.status(409).json({error:"no failing checks on this PR"});let u=c&&i?ct.join("ci",i.name):"ci",f=ct.join(a.cwd,".bostrat",u),d=ct.join(".bostrat",u,`pr-${o.number}-fail.log`);await Mt.promises.mkdir(f,{recursive:!0}),await Mt.promises.writeFile(ct.join(a.cwd,d),l.map(b=>`\u2550\u2550\u2550\u2550 ${b.name}${b.link?` \xB7 ${b.link}`:""} \u2550\u2550\u2550\u2550
|
|
382
|
+
`),n?.length&&a.write(n),e.pipe(a),a.pipe(e)}),i=()=>{a.destroy(),e.destroy()};return a.on("error",i),e.on("error",i),!0}function Cy(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}.${ER(n.host)}/`}function ER(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 Ry(e)&&oc(n[0])?n.slice(1).join("."):e}var Ny=10,Py=200,IR=120,RR=24,$R=/^\d{4}-\d{2}-\d{2}$/;function Ou(t=process.env){return(t.CHANGELOG_REPOS||"globocodes/bostrat").split(",").map(e=>e.trim()).filter(Boolean)}var OR=(t=Date.now())=>new Date(t).toISOString().slice(0,10);function By(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 MR(t){if(typeof t!="string"||!t.trim())throw new Error("md (markdown string) required");let e=lo(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(`
|
|
383
|
+
`)){let s=o.trim();if(!s)continue;if(s.startsWith("#")){n==null&&(n=s.replace(/^#+\s*/,"").trim().slice(0,IR)||null);continue}let a=/^[-*]\s+(.*)$/.exec(s);if(!a)continue;let i=a[1].trim(),c=null,l=new RegExp(`^\\[([^\\]]{1,${RR}})\\]\\s+(.*)$`).exec(i);if(l&&(c=l[1].trim(),i=l[2].trim()),!!i){if(i.length>Py)throw new Error(`bullet ${r.length+1} is ${i.length} chars \u2014 over the ${Py}-char cap; a changelog bullet is one line`);r.push({text:i,...c?{area:c}:{}})}}if(!r.length)throw new Error("no `- ` bullets found \u2014 an entry is a bullet list, optionally under a `# headline`");if(r.length>Ny)throw new Error(`${r.length} bullets \u2014 over the ${Ny}-bullet cap; group related changes into one line`);return{title:n||null,bullets:r}}async function Dy(t,{repos:e=Ou()}={}){let n=new Set(e);return(await pe()).filter(r=>r.state==="merged"&&(r.resolvedAt??0)>=t&&n.has(r.repo)).sort((r,o)=>(r.resolvedAt??0)-(o.resolvedAt??0)).map(r=>({repo:r.repo,number:r.number,title:r.title||null,url:r.url||null,mergedAt:r.resolvedAt??null}))}async function jy(t,{md:e,day:n=null}){let r=MR(e),o=n==null||n===""?OR():String(n);if(!$R.test(o)||Number.isNaN(Date.parse(o)))throw new Error(`invalid day "${n}" \u2014 expected YYYY-MM-DD`);let s=await O({id:t.id,cwd:t.cwd},{type:"changelog.published",actor:"agent",source:"orchestrator",boxId:t.leadBox||null,payload:{ts:Date.now(),day:o,title:r.title,bullets:r.bullets}});return{day:o,title:r.title,bullets:r.bullets,eventId:s.id}}import{execFile as CR}from"node:child_process";import{promisify as NR}from"node:util";var Cu=NR(CR),Ly=3e4,PR=30*6e4,BR=Ly,DR=10*6e4,jR=10080*6e4,LR=60*6e4,FR=8,UR=1440*6e4,HR=3,Fy="number,url,title,headRefName,headRefOid,state,mergedAt,closedAt",Mu=60*6e4,WR=15*6e4,Va=new Map,Ja=new Map,qR=/could not resolve|not found|no pull requests?/i,KR=t=>qR.test(String(t?.message||t||""));function GR(t,e){return[t.ciFix?.delegatedAt,t.reconcile?.delegatedAt,t.ciFixAwait?.since,t.reconcileAwait?.since].some(n=>n&&e-n<Mu)}var Uy={fast:BR,slow:DR,decay:LR};function Hy(t,e){return t.armedAt&&e-t.armedAt<PR||GR(t,e)?"fast":e-(t.firstSeen||0)>jR?"decay":"slow"}function zR(t,e){return Uy[Hy(t,e)]}function Zo(t,e,n,r){let o=t.lanes?.get(dt(e.repo,e.number))||{};return Aa({change:n,repo:e.repo,number:Number(e.number),githubAt:r||null,lane:o.lane||null,intervalMs:o.intervalMs||null,source:"watch"})}async function VR(t,e){Re("watch");try{let{stdout:n}=await Cu("gh",["pr","view",String(e),"--repo",t,"--json",Fy],{timeout:3e4,maxBuffer:4194304,env:{...process.env,...await ee({slug:t})}});return JSON.parse(n||"null")}catch(n){throw Ye(n)&&Ze(n),n}}async function JR(t){Re("watch");try{let{stdout:e}=await Cu("gh",["pr","list","--repo",t,"--state","all","--limit","100","--json",Fy],{timeout:3e4,maxBuffer:8388608,env:{...process.env,...await ee({slug:t})}}),n=new Map;for(let r of JSON.parse(e||"[]"))n.set(Number(r.number),r);return n}catch(e){throw Ye(e)&&Ze(e),e}}async function XR(){let t=new Map;for(let e of await ct().catch(()=>[]))t.set(e.id,e);return t}async function YR(t,e){await O({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"user",source:"orchestrator",boxId:null,payload:{ts:Date.now(),change:"merged",...e}})}async function ZR(t,e){let n=await Nn(t);return n?Sr(n,e):null}async function QR(t,e,n){let r=await Nn(t);if(!r)throw new Error(`no local checkout for ${t}`);return vr(r,e,{method:n})}async function t$(t,e){let n=await Nn(t);if(!n)throw new Error(`no local checkout for ${t}`);return Oo(n,e)}async function e$(t,e){await O({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"user",source:"orchestrator",boxId:null,payload:{ts:Date.now(),change:"auto-canceled",...e}})}async function Lr(t,e,n,r,o){if(!await r.disarm(t.repo,t.number).catch(()=>null))return;let a=n.get(t.trackId);a&&await r.emitDisarmed(a,{number:Number(t.number),url:e?.url||t.url,title:e?.title||t.title,branch:e?.headRefName||t.headRef,repo:t.repo,reason:o}).catch(()=>{})}var n$=10*6e4;async function r$(t,e,n){let r=t.autoMerge?.armedAt||0;if(r&&(n.now??Date.now())-r>UR){await Lr(t,null,e,n,"auto-merge expired \u2014 checks never settled within 24h");return}let o=await n.detail(t.repo,t.number).catch(()=>null);if(!o)return;if(o.checks.failed>0){let a=o.checks.failing.map(i=>i.name).join(", ");await Lr(t,o,e,n,`check${o.checks.failed===1?"":"s"} failed: ${a}`);return}if(o.mergeable==="CONFLICTING"){await Lr(t,o,e,n,`conflicts with ${o.baseRefName}`);return}if(!o.checks.total){let a=Math.max(t.autoMerge?.armedAt||0,Date.parse(o.updatedAt||"")||0);if((n.now??Date.now())-a<n$)return;await Lr(t,o,e,n,o.checks.unavailable?"this token can't read the checks":"no CI checks appeared");return}if(o.checks.pending>0||o.mergeable==="UNKNOWN"||o.mergeStateStatus==="UNKNOWN"||o.isDraft)return;let s=await n.lookup(t.repo,t.number).catch(()=>null);if(s?.autoMerge){if(o.mergeStateStatus==="BEHIND"){try{await n.updateBranch(t.repo,t.number)}catch(a){let i=String(a?.message||a).trim();/no new commits|already up.to.date/i.test(i)||await Lr(t,o,e,n,i||`couldn't update with ${o.baseRefName}`)}return}try{await n.merge(t.repo,t.number,s.autoMerge.method||"merge")}catch(a){if(/already merged|not open|is closed/i.test(String(a?.message||a)))return;await Lr(t,o,e,n,String(a?.message||a).trim()||"merge failed");return}await Wy(t,{...o,state:"MERGED"},e,n,{change:"auto-merged",githubAt:o.checks.settledAt||null})}}async function Jo(t,e,n){await O({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"user",source:"orchestrator",boxId:null,payload:{ts:Date.now(),change:e,...n}})}async function o$(t,e){try{return await Cu("git",["-C",t.cwd,"cat-file","-e",`${e}^{commit}`],{timeout:1e4}),!0}catch{return!1}}async function Xo(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 Yo=(t,e)=>({number:Number(t.number),url:e?.url||t.url,title:e?.title||t.title,branch:e?.headRefName||t.headRef,repo:t.repo});async function s$(t,e,n,r){let o=r.now??Date.now(),s=e.headRefOid||null,a=n.get(t.trackId)||null;if(s&&(t.ciFix?.headSha&&s!==t.ciFix.headSha&&await ea(t.repo,t.number,{headSha:s}).catch(()=>{}),t.reconcile?.headSha&&s!==t.reconcile.headSha&&await ra(t.repo,t.number,{headSha:s}).catch(()=>{})),s&&s!==t.lastHeadSha){let i=!t.lastHeadSha,c=!!(t.ciFix||t.reconcile||t.ciFixAwait||t.reconcileAwait),l=!!t.baseFoldAt&&o-t.baseFoldAt<WR;await dm(t.repo,t.number,s).catch(()=>{}),t.baseFoldAt&&await um(t.repo,t.number).catch(()=>{}),!i&&!c&&!l&&a&&!await r.localHasCommit(a,s).catch(()=>!1)&&!await Xo(a,"pushed",t.number,f=>f.payload?.sha===s)&&await Jo(a,"pushed",{...Yo(t,e),sha:s}).catch(()=>{})}if(t.ciFixAwait)if(o-(t.ciFixAwait.since||0)>Mu)await na(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 na(t.repo,t.number).catch(()=>{});else if(i.checks.total>0&&i.checks.pending===0){let c=Zo(r,t,"ci-fixed",i.checks.settledAt||null),l=t.ciFixAwait.since||0;a&&!await Xo(a,"ci-fixed",t.number,u=>(u.payload?.ts||0)>=l)&&await Jo(a,"ci-fixed",{...Yo(t,e),passed:i.checks.passed,githubAt:c.githubAt,latencyMs:c.latencyMs,lane:c.lane}).catch(()=>{}),await na(t.repo,t.number).catch(()=>{})}}}if(t.reconcileAwait)if(o-(t.reconcileAwait.since||0)>Mu)await oa(t.repo,t.number).catch(()=>{});else{let i=await r.detail(t.repo,t.number).catch(()=>null);if(i){if(i.mergeable==="CONFLICTING")await oa(t.repo,t.number).catch(()=>{});else if(i.mergeable==="MERGEABLE"){Zo(r,t,"reconciled",null);let c=t.reconcileAwait.since||0;a&&!await Xo(a,"reconciled",t.number,l=>(l.payload?.ts||0)>=c)&&await Jo(a,"reconciled",{...Yo(t,e),base:i.baseRefName||null}).catch(()=>{}),await oa(t.repo,t.number).catch(()=>{})}}}}async function a$(t,e,n,r){let o=Zo(r,t,"closed",Date.parse(e?.closedAt||"")||null),s=n.get(t.trackId);s&&!await Xo(s,"closed",t.number)&&await Jo(s,"closed",{...Yo(t,e),githubAt:o.githubAt,latencyMs:o.latencyMs,lane:o.lane}).catch(()=>{}),await r.notifyIfResolved(t.repo,t.number,"closed").catch(()=>{}),await r.mark(t.repo,t.number,"closed").catch(()=>{})}async function i$(t,e,n){Zo(n,t,"lost",null);let r=e.get(t.trackId);r&&!await Xo(r,"lost",t.number)&&await Jo(r,"lost",Yo(t,null)).catch(()=>{}),await n.notifyIfResolved(t.repo,t.number,"lost").catch(()=>{}),await n.mark(t.repo,t.number,"lost").catch(()=>{})}async function Wy(t,e,n,r,o={}){let s=Zo(r,t,o.change||"merged",o.githubAt??(Date.parse(e.mergedAt||"")||null)),a=n.get(t.trackId);a&&((await L(a).catch(()=>[])).some(l=>l.type==="pr.state.changed"&&l.payload?.change==="merged"&&l.payload?.number===Number(t.number))||await r.emitMerged(a,{number:Number(t.number),url:e.url||t.url,title:e.title||t.title,branch:e.headRefName||t.headRef,repo:t.repo,githubAt:s.githubAt,latencyMs:s.latencyMs,lane:s.lane}).catch(()=>{})),await r.notifyIfArmed(t.repo,t.number).catch(()=>{}),await r.archiveIfArmed(t.repo,t.number).catch(()=>{}),await r.mark(t.repo,t.number,"merged").catch(()=>{})}async function c$(t={}){let{now:e=Date.now(),auth:n=Bl,view:r=VR,list:o=JR,links:s=pm,trackMap:a=XR,emitMerged:i=YR,mark:c=ze,archiveIfArmed:l=tn,notifyIfArmed:u=en,notifyIfResolved:f=iu,batchThreshold:d=HR,detail:m=ZR,merge:h=QR,updateBranch:w=t$,lookup:g=Et,disarm:b=go,emitDisarmed:v=e$,localHasCommit:A=o$}=t;if(!(await n().catch(()=>null))?.authenticated||ya()>0)return 0;let $=(await s().catch(()=>[])).filter(p=>e-(Va.get(dt(p.repo,p.number))||0)>=zR(p,e));if(!$.length)return 0;let F=Date.now(),T=new Map,C=new Map;for(let p of $){let S=Hy(p,e);T.set(dt(p.repo,p.number),{lane:S,intervalMs:Uy[S]}),Va.set(dt(p.repo,p.number),e),C.has(p.repo)||C.set(p.repo,[]),C.get(p.repo).push(p)}let W=await a().catch(()=>new Map),ot={now:e,lanes:T,emitMerged:i,mark:c,archiveIfArmed:l,notifyIfArmed:u,notifyIfResolved:f,detail:m,merge:h,updateBranch:w,lookup:g,disarm:b,emitDisarmed:v,localHasCommit:A};for(let[p,S]of C){let _,rt=new Set;if(S.length>d)_=await o(p).catch(()=>null);else{_=new Map;for(let I of S)try{let R=await r(p,I.number);R?.number!=null&&_.set(Number(R.number),R)}catch(R){KR(R)&&rt.add(dt(I.repo,I.number))}}if(_)for(let I of S){let R=dt(I.repo,I.number),E=_.get(Number(I.number));if(!E){if(rt.has(R)){let P=(Ja.get(R)||0)+1;Ja.set(R,P),P>=FR&&(await i$(I,W,ot),Ja.delete(R),Va.delete(R))}continue}Ja.delete(R),E.state==="MERGED"||E.state==="CLOSED"?(E.state==="MERGED"?await Wy(I,E,W,ot):await a$(I,E,W,ot),Va.delete(R)):(await s$(I,E,W,ot).catch(()=>{}),I.autoMerge&&await r$(I,W,ot).catch(()=>{}))}}let y={};for(let{lane:p}of T.values())y[p]=(y[p]||0)+1;return console.log(`[pr-watch] probed ${$.length} link(s) (${Object.entries(y).map(([p,S])=>`${p} ${S}`).join(", ")}) in ${Date.now()-F}ms`),$.length}function qy(){let t=!1;setInterval(async()=>{if(!t){t=!0;try{await c$()}catch{}finally{t=!1}}},Ly)}import Ce from"node:fs/promises";import Fr from"node:path";import Yy from"node:os";import Ky from"node:fs/promises";import Xa from"node:path";import{execFile as l$}from"node:child_process";import{promisify as u$}from"node:util";import{fileURLToPath as d$}from"node:url";var f$=u$(l$),zy=Xa.dirname(d$(import.meta.url)),p$=Xa.resolve(zy,"..","..","client","dist"),m$="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",h$=new Map([...m$].map((t,e)=>[t,e]));function w$(t){let e=[],n=0,r=0;for(let o of t){let s=h$.get(o);if(s===void 0)return e;n+=(s&31)<<r,s&32?r+=5:(e.push(n&1?-(n>>>1):n>>>1),n=0,r=0)}return e}function g$(t){let e=[],n=0,r=0,o=0;for(let s of t.split(";")){let a=[],i=0;if(s)for(let c of s.split(",")){let l=w$(c);l.length!==0&&(i+=l[0],l.length>=4&&(n+=l[1],r+=l[2],o+=l[3],a.push([i,n,r,o])))}e.push(a)}return e}function y$(t,e){if(!t||t.length===0||t[0][0]>e)return null;let n=0,r=t.length-1;for(;n<r;){let o=n+r+1>>1;t[o][0]<=e?n=o:r=o-1}return t[n]}var Gy=new Map;async function b$(t){let e;try{e=await Ky.stat(t)}catch{return null}let n=Gy.get(t);if(n&&n.mtimeMs===e.mtimeMs)return n;try{let r=JSON.parse(await Ky.readFile(t,"utf8")),o={mtimeMs:e.mtimeMs,sources:r.sources||[],lines:g$(r.mappings||"")};return Gy.set(t,o),o}catch{return null}}var Nu=null;function k$(){return Nu||(Nu=process.env.BOSTRAT_RELEASE?Promise.resolve(process.env.BOSTRAT_RELEASE):f$("git",["rev-parse","--short","HEAD"],{cwd:Xa.resolve(zy,"..","..")}).then(t=>t.stdout.trim()).catch(()=>null)),Nu}var x$=/(?:https?:\/\/[^\s()]*)?\/assets\/([^\s()/]+\.js):(\d+):(\d+)/g;async function S$(t,{distDir:e=p$}={}){let n=[],r=0,o=!1;for(let s of t.matchAll(x$)){let[a,i,c,l]=s,u=await b$(Xa.join(e,"assets",`${i}.map`)),f=u?y$(u.lines[Number(c)-1],Number(l)-1):null;if(n.push(t.slice(r,s.index)),f&&u.sources[f[1]]){let d=u.sources[f[1]].replace(/^(\.\.\/)+/,"");n.push(`${d}:${f[2]+1}:${f[3]+1}`),o=!0}else n.push(a);r=s.index+a.length}return n.push(t.slice(r)),{stack:n.join(""),symbolicated:o}}async function Vy(t,e={}){try{if(!t?.error?.stack||!t.release||t.release==="unknown")return t;let n=e.release??await k$();if(!n||t.release!==n)return t;let{stack:r,symbolicated:o}=await S$(t.error.stack,e);o&&(t.error.stack=r,t.error.symbolicated=!0)}catch{}return t}var v$=()=>process.env.BOSTRAT_STATE_DIR||Fr.join(process.env.HOME||Yy.tmpdir(),".bostrat"),dn=()=>Fr.join(v$(),"telemetry"),Zy=()=>Fr.join(dn(),".relay-offsets.json"),T$=50,A$=14,_$=3e4,Jy=500,Ut=(t,e)=>typeof t=="string"?t.slice(0,e):"";function E$(t,e){if(!t||typeof t!="object")return null;let n=Ut(t.name,64);if(!n)return null;let r=Number.isFinite(t.ts)?t.ts:e,o={name:n,ts:Math.abs(r-e)<168*3600*1e3?r:e,receivedAt:e,anonId:Ut(t.anonId,64),sessionId:Ut(t.sessionId,64),userId:Ut(t.userId,64),release:Ut(t.release,64),appVersion:Ut(t.appVersion,32),route:Ut(t.route,128),deviceClass:t.deviceClass==="phone"||t.deviceClass==="desktop"?t.deviceClass:""};return t.props&&typeof t.props=="object"&&!Array.isArray(t.props)&&(JSON.stringify(t.props).length<=8192?o.props=t.props:o.props={clamped:!0}),t.error&&typeof t.error=="object"&&(o.error={kind:Ut(t.error.kind,256),message:Ut(t.error.message,8192),stack:Ut(t.error.stack,32768),fingerprint:Ut(t.error.fingerprint,64)}),Array.isArray(t.breadcrumbs)&&(o.breadcrumbs=t.breadcrumbs.slice(-50).map(s=>({t:Number.isFinite(s?.t)?s.t:0,n:Ut(s?.n,64),r:Ut(s?.r,128)}))),o}var Qy=t=>new Date(t).toISOString().slice(0,10),Xy=0;async function tb(t,e=Date.now()){let r=(Array.isArray(t?.events)?t.events.slice(0,T$):[]).map(s=>E$(s,e)).filter(Boolean);if(!r.length)return 0;for(let s of r)s.error?.stack&&await Vy(s);await Ce.mkdir(dn(),{recursive:!0});let o=Fr.join(dn(),`${Qy(e)}.ndjson`);return await Ce.appendFile(o,r.map(s=>JSON.stringify(s)+`
|
|
384
|
+
`).join(""),"utf8"),e-Xy>6*3600*1e3&&(Xy=e,I$(e).catch(()=>{})),r.length}async function I$(t=Date.now()){let e=Qy(t-A$*24*3600*1e3),n=[];try{n=await Ce.readdir(dn())}catch{return}let r=n.filter(s=>s.endsWith(".ndjson")&&s.slice(0,10)<e);if(!r.length)return;for(let s of r)await Ce.rm(Fr.join(dn(),s),{force:!0});let o=await nb();for(let s of r)delete o[s];await rb(o)}var Ya=new Map;function eb(t,e=Date.now()){let n=t||"unknown",r=Ya.get(n);return r||(Ya.size>512&&Ya.clear(),r={tokens:40,at:e},Ya.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 nb(){try{return JSON.parse(await Ce.readFile(Zy(),"utf8"))||{}}catch{return{}}}async function rb(t){await Ce.mkdir(dn(),{recursive:!0}),await Ce.writeFile(Zy(),JSON.stringify(t),"utf8")}function R$(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||Yy.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 $$({fetchImpl:t=fetch}={}){let e=await Cp();if(!e)return{sent:0,disabled:!0};let n;try{n=(await Ce.readdir(dn())).filter(s=>s.endsWith(".ndjson")).sort()}catch{return{sent:0,disabled:!1}}let r=await nb(),o=0;for(let s of n){let a=Fr.join(dn(),s),i=r[s]||0,c;try{let d=await Ce.open(a,"r");try{let m=(await d.stat()).size;if(m<=i)continue;c=Buffer.alloc(m-i),await d.read(c,0,c.length,i)}finally{await d.close()}}catch{continue}let l=c.lastIndexOf(10);if(l<0)continue;let u=c.toString("utf8",0,l+1).split(`
|
|
385
|
+
`).filter(Boolean),f=[];for(let d of u)try{f.push(JSON.parse(d))}catch{}for(let d=0;d<f.length;d+=Jy){let m=f.slice(d,d+Jy).map(w=>R$(w)),h=await t(`${e.url.replace(/\/$/,"")}/v1/logs`,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${e.ingestKey}`},body:JSON.stringify({events:m})}).catch(()=>null);if(!h||!h.ok)return{sent:o,disabled:!1};o+=m.length}r[s]=i+l+1,await rb(r)}return{sent:o,disabled:!1}}var Pu=null,Bu=!1;function ob(){Pu||(Pu=setInterval(async()=>{if(!Bu){Bu=!0;try{await $$()}catch(t){console.error("[telemetry] relay failed:",t?.message||t)}finally{Bu=!1}}},_$),Pu.unref?.())}import Qa from"node:fs/promises";import lb from"node:path";import O$ from"node:os";var ub=()=>process.env.BOSTRAT_STATE_DIR||lb.join(process.env.HOME||O$.tmpdir(),".bostrat"),db=()=>lb.join(ub(),"dictation-glossary.json"),M$=100,C$=500,N$=["bostrat","globopi","codex","tmux","worktree","Deepgram","Linear","Firecracker","Tigris","WireGuard","misous","postyx","cedar","gerbil","Anthropic","Opus","Haiku","Sonnet","keyterm","monorepo","dogfood"],ti=null,Du=null,ju=Promise.resolve();function sb(){let t={},e=Date.now();for(let n of N$)t[n.toLowerCase()]={term:n,count:1,lastUsed:e,source:"seed"};return t}async function fb(){return ti||(Du??=Qa.readFile(db(),"utf8").then(t=>JSON.parse(t)?.terms??sb()).catch(()=>sb()).then(t=>ti=t).finally(()=>Du=null),Du)}async function ab(){let t=JSON.stringify({terms:ti},null,2),e=db(),n=`${e}.${process.pid}.tmp`;await Qa.mkdir(ub(),{recursive:!0}),await Qa.writeFile(n,t),await Qa.rename(n,e)}function P$(){return ju=ju.then(ab,ab),ju.catch(()=>{})}async function Lu(){return Object.values(await fb()).sort((e,n)=>n.count-e.count||n.lastUsed-e.lastUsed)}var B$=t=>Math.max(1,Math.ceil(t.length/4));async function D$(){let t=[],e=0;for(let{term:n}of await Lu()){let r=B$(n);if(!(e+r>C$)&&(t.push(n),e+=r,t.length>=M$))break}return t}async function j$(t,e){let n=await fb(),r=t.toLowerCase(),o=n[r];n[r]={term:t,count:(o?.count??0)+1,lastUsed:Date.now(),source:o?.source??e}}var ib=t=>(t||"").split(/\s+/).map(e=>e.replace(/^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu,"")).filter(Boolean),Za=t=>t.toLowerCase();function cb(t){return!new RegExp("\\p{L}","u").test(t)||t.length<3?!1:/^[a-z]+$/.test(t)?t.length>=4:!0}async function pb(t,e){let n=ib(t),r=ib(e);if(!n.length||!r.length)return[];let o=n.length,s=r.length,a=Array.from({length:o+1},()=>new Array(s+1).fill(0));for(let u=o-1;u>=0;u--)for(let f=s-1;f>=0;f--)a[u][f]=Za(n[u])===Za(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;)Za(n[c])===Za(r[l])?(c++,l++):a[c+1][l]>=a[c][l+1]?c++:(cb(r[l])&&i.add(r[l]),l++);for(;l<s;l++)cb(r[l])&&i.add(r[l]);if(!i.size)return[];for(let u of i)await j$(u,"learned");return await P$(),[...i]}var Qo=null;function L$(t,e){let n=null;try{n=JSON.parse(e)}catch{}let r=n?.err_code||n?.error_code||n?.category||"";return t===402||/PAYMENT_REQUIRED/i.test(r)?{status:402,code:"credits_exhausted",persistent:!0,message:"Deepgram credits are exhausted \u2014 dictation is down until you top up at console.deepgram.com or drop a new key into ~/.bostrat/integrations.json."}:t===401||t===403?{status:502,code:"key_invalid",persistent:!0,message:"Deepgram rejected the API key (revoked, or missing ASR scope) \u2014 replace it in ~/.bostrat/integrations.json."}:{status:502,code:"transcription_failed",persistent:!1,message:`Deepgram error ${t}: ${e.slice(0,300)}`}}async function mb(){if(!Qo)return null;let t=await uo();if(!t||t.apiKey.slice(-6)!==Qo.keyTail)return null;let{keyTail:e,...n}=Qo;return n}async function hb(t,e){let n=await uo();if(!n){let i=new Error("Deepgram is not configured \u2014 add deepgram.apiKey to ~/.bostrat/integrations.json");throw i.status=503,i.code="not_configured",i}let r=new URL("https://api.deepgram.com/v1/listen");r.searchParams.set("model","nova-3"),r.searchParams.set("smart_format","true");for(let i of await D$())r.searchParams.append("keyterm",i);let o=await fetch(r,{method:"POST",headers:{Authorization:`Token ${n.apiKey}`,"Content-Type":e||"application/octet-stream"},body:t});if(!o.ok){let i=await o.text().catch(()=>""),c=L$(o.status,i);c.persistent&&(Qo={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 Qo=null,{transcript:(await o.json())?.results?.channels?.[0]?.alternatives?.[0]?.transcript??""}}var Fu={"gpt-realtime-2.1":{textIn:4,textInCached:.4,textOut:24,audioIn:32,audioInCached:.4,audioOut:64},"gpt-realtime-2":{textIn:4,textInCached:.4,textOut:24,audioIn:32,audioInCached:.4,audioOut:64},"gpt-realtime":{textIn:4,textInCached:.4,textOut:16,audioIn:32,audioInCached:.4,audioOut:64},"gpt-realtime-2.1-mini":{textIn:.6,textInCached:.06,textOut:2.4,audioIn:10,audioInCached:.3,audioOut:20},"gpt-realtime-mini":{textIn:.6,textInCached:.06,textOut:2.4,audioIn:10,audioInCached:.3,audioOut:20}};function F$(t){if(!t)return null;let e=String(t).trim();if(Fu[e])return e;let n=null;for(let r of Object.keys(Fu))e.startsWith(r)&&(!n||r.length>n.length)&&(n=r);return n}function U$(t,e={}){let n=Fu[F$(t)];if(!n)return 0;let r=0;for(let o of Object.keys(n))r+=(Number(e[o])||0)*n[o];return r/1e6}function H$(t,e){let n=null;try{n=JSON.parse(e)}catch{}let r=n?.error?.code||n?.error?.type||"";return t===429&&/insufficient_quota/i.test(r)?{status:402,code:"credits_exhausted",persistent:!0,message:"The OpenAI account is out of credit \u2014 voice chat is down until you top up at platform.openai.com or drop a new key into ~/.bostrat/integrations.json."}:t===401||t===403?{status:502,code:"key_invalid",persistent:!0,message:"OpenAI rejected the API key (revoked, or missing Realtime access) \u2014 replace it in ~/.bostrat/integrations.json."}:{status:502,code:"mint_failed",persistent:!1,message:`OpenAI realtime session mint failed (${t}): ${e.slice(0,300)}`}}var ts=null;async function W$(){if(!ts)return null;let t=await zs();if(!t||t.apiKey.slice(-6)!==ts.keyTail)return null;let{keyTail:e,...n}=ts;return n}async function q$(t){let e=await fetch("https://api.openai.com/v1/realtime/client_secrets",{method:"POST",headers:{Authorization:`Bearer ${t.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify({session:{type:"realtime",model:t.model,audio:{output:{voice:t.voice}}}})});if(!e.ok){let o=await e.text().catch(()=>""),s=H$(e.status,o);s.persistent&&(ts={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}ts=null;let n=await e.json(),r=n?.value??n?.client_secret?.value;if(!r){let o=new Error("OpenAI returned no client secret");throw o.status=502,o.code="mint_failed",o}return{clientSecret:{value:r,expiresAt:n?.expires_at??n?.client_secret?.expires_at??null}}}var K$={openai:q$};async function wb(){let t=await zs();return t?{configured:!0,provider:t.provider,model:t.model,voice:t.voice,problem:await W$()}:{configured:!1}}async function gb(){let t=await zs();if(!t){let n=new Error("Voice chat is not configured \u2014 add openai.apiKey to ~/.bostrat/integrations.json");throw n.status=503,n.code="not_configured",n}let e=await K$[t.provider](t);return{provider:t.provider,model:t.model,voice:t.voice,...e}}async function yb(t,{model:e,usage:n={},seconds:r}={}){let o=c=>Math.max(0,Math.round(Number(c)||0)),s={textIn:o(n.textIn),textInCached:o(n.textInCached),textOut:o(n.textOut),audioIn:o(n.audioIn),audioInCached:o(n.audioInCached),audioOut:o(n.audioOut)},a=U$(e,s),i=await O(t,{type:"cost.recorded",actor:"user",provider:"openai-realtime",payload:{model:e||"unknown",usd:a,tokens:{input:s.textIn+s.audioIn,output:s.textOut+s.audioOut,cacheRead:s.textInCached+s.audioInCached,cacheWrite:0},voice:s,seconds:Math.max(0,Math.round(Number(r)||0)),source:"voice-chat"}});return{usd:a,seq:i.seq}}var Tb=ut.dirname(z$(import.meta.url)),bb=Number(process.env.PORT)||3362,ei=Jg(process.env),Ab=Number(process.env.BOSTRAT_AUTO_TRIGGER_PCT)||80,Ur=Sy(process.env.BOSTRAT_ALLOW_IPS),_b=Ty(),Eb=process.env.BOSTRAT_ALLOW_LAN_TOKENLESS!=="1",x=ge();x.disable("x-powered-by");x.use((t,e,n)=>{if(Vo(t)||Ur.allowed(Ur.clientIp(t)))return n();e.status(403).type("text/plain").send("bostrat: access restricted to the allowlist")});x.use(Oy);x.use(Ey({token:_b,enforce:Eb}));x.use("/api/mocks",ge.json({limit:"1mb"}));x.use("/api/mocks",ge.raw({type:"image/png",limit:"24mb"}));x.use("/api/runbooks",ge.json({limit:"1mb"}));x.use("/api/telemetry",ge.json({limit:"256kb"}));x.use("/api/hook",ge.json({limit:"2mb"}));x.use("/api/dictation",ge.raw({type:["audio/*","video/mp4","application/octet-stream"],limit:"32mb"}));x.use("/api/tracks/:id/upload",ge.raw({type:()=>!0,limit:"25mb"}));x.use(ge.json({limit:"64kb"}));x.get("/api/health",async(t,e)=>{e.json({ok:!0,tmuxSocket:jt,hostTmuxAlive:await De(),autoTriggerPct:Ab})});x.get("/api/sessions",async(t,e)=>{e.json({sessions:await mn()})});x.post("/api/sessions",async(t,e)=>{let{displayName:n,cwd:r}=t.body||{};if(typeof n!="string")return e.status(400).json({error:"displayName (string) required"});try{let o=await hn(n,r||void 0);e.json({ok:!0,...o})}catch(o){let s=o?.message||String(o),a=/already exists/i.test(s)?409:/must be|invalid/i.test(s)?400:500;e.status(a).json({error:s})}});x.delete("/api/sessions/:name",async(t,e)=>{let n=decodeURIComponent(t.params.name);if(!yt(n))return e.status(400).json({error:"invalid session name"});if(!await Y(n))return e.status(404).json({error:"session not found"});try{await re(n),e.json({ok:!0})}catch(r){e.status(500).json({error:`failed to kill: ${r?.message||r}`})}});x.get("/api/context/:name",async(t,e)=>{let n=decodeURIComponent(t.params.name);if(!yt(n))return e.status(400).json({error:"invalid session name"});let r=await Y(n);if(!r)return e.status(404).json({error:"session not found"});let o=await Zr(r.cwd,r.sessionId);if(!o)return e.json({available:!1,cwd:r.cwd,sessionId:r.sessionId||null});e.json({available:!0,...o})});x.post("/api/handoff/:name",async(t,e)=>{let n=decodeURIComponent(t.params.name);if(!yt(n))return e.status(400).json({error:"invalid session name"});try{let r=await Rf(n);e.json({ok:!0,step:r.step,file:r.file,startedAt:r.startedAt})}catch(r){e.status(409).json({error:r?.message||String(r)})}});x.get("/api/handoff/:name",(t,e)=>{let n=decodeURIComponent(t.params.name),r=Ms(n);if(!r)return e.json({running:!1});e.json({running:!r.finishedAt,step:r.step,error:r.error,file:r.file,startedAt:r.startedAt,finishedAt:r.finishedAt})});var kb=10;x.get("/api/tracks",async(t,e)=>{let n=await ct(),r=n.filter(s=>s.archivedAt),o=n;if(t.query.archived!=="all"&&r.length>kb){let s=new Set([...r].sort((a,i)=>(i.archivedAt||0)-(a.archivedAt||0)).slice(0,kb).map(a=>a.id));o=n.filter(a=>!a.archivedAt||s.has(a.id))}e.json({tracks:o,archivedTotal:r.length,agents:Ca,agentAvailability:await eu(),tickets:await Ys()})});x.get("/api/cost",async(t,e)=>{try{let n=await ct();t.query.track&&(n=n.filter(o=>o.id===t.query.track));let r=await ny(n);if(r.cursorUsage=Ml(),t.query.format==="text")return e.type("text/plain").send(ry(r));e.json(r)}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/usage/roundup",async(t,e)=>{try{e.json(await ay(await ct(),{window:t.query.window,from:t.query.from,to:t.query.to}))}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/usage/spend",async(t,e)=>{try{let n=await dy(await ct(),{window:t.query.window,from:t.query.from,to:t.query.to});n.cursorUsage=Ml(),n.subscriptionMonthlyUsd=Number(process.env.BOSTRAT_SUBSCRIPTION_MONTHLY_USD)||null,e.json(n)}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/usage/limits",async(t,e)=>{try{let n=t.query.fresh==="1"||t.query.fresh==="true";e.json(await gy(await ct(),{fresh:n}))}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/arcs",async(t,e)=>{try{e.json(await ym(await ct()))}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/arcs/:key",async(t,e)=>{try{let n=await bm(await ct(),t.params.key);if(!n)return e.status(404).json({error:"unknown arc"});e.json(n)}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.post("/api/arcs/backfill-beats",async(t,e)=>{try{e.json({ok:!0,...await Ag()})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.post("/api/hook",async(t,e)=>{try{let{mode:n,target:r}=await Xu(t.get(ss),{byToken:rg,legacy:()=>ng(t.body?.session_id,t.body?.cwd)});if(n==="refused")return as("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&&as("mismatch",{track:r.track.id,box:r.box.name}),as(n,{track:r?.track?.id,hook:t.body?.hook_event_name});let o=await zg(t.body||{},()=>r);e.json({ok:!0,events:o.length})}catch(n){e.json({ok:!1,error:n?.message||String(n)})}});x.post("/api/telemetry",async(t,e)=>{try{if(!eb(Ur.clientIp(t)))return e.json({ok:!0,dropped:!0});let n=await tb(t.body||{});e.json({ok:!0,events:n})}catch(n){e.json({ok:!1,error:n?.message||String(n)})}});x.get("/api/repos",async(t,e)=>{try{let n=await ye(),r=new Set((await Promise.all(n.map(a=>tt(a.path)))).filter(Boolean).map(a=>a.toLowerCase())),s=((await Gr()).repos||[]).filter(a=>!r.has(String(a.slug).toLowerCase())).map(a=>({slug:a.slug,name:a.slug.split("/")[1],branch:a.defaultBranch||null,private:!!a.private,remote:!0}));e.json({repos:[...n,...s]})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/github/token",async(t,e)=>{try{e.json(await us(String(t.query.repo||"")))}catch(n){e.status(n?.status||502).json({error:n?.message||String(n)})}});x.get("/api/github/status",(t,e)=>{e.json({configured:ls(),calls:Qh(),latency:Aw()})});x.post("/api/github/refresh",(t,e)=>{ad(),e.json({ok:!0})});x.get("/api/gmail/search",async(t,e)=>{try{let n=await by(String(t.query.q||""),{max:Number(t.query.max)||10});e.json({messages:n})}catch(n){e.status(n?.status||502).json({error:n?.message||String(n)})}});x.get("/api/gmail/messages/:id",async(t,e)=>{try{e.json({message:await ky(String(t.params.id))})}catch(n){e.status(n?.status||502).json({error:n?.message||String(n)})}});function Gu(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 tt(t):null;if(r&&e!=null){let s=await Et(r,e),a=s&&await Q(s.trackId);if(a)return a}return n?(await ct()).find(s=>s.workspace?.branch===n&&Gu(s,t,r))??null:null}async function Ib({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 tt(t).catch(()=>null):null;return(await ct()).find(a=>!a.archivedAt&&a.workspace?.branch===n&&Gu(a,t,o))??null}async function te(t,{number:e,url:n=null,title:r=null,branch:o=null,repo:s=null,track:a=null,extra:i={}}){let c=a||await ni({repo:s,number:e,branch:o}).catch(()=>null);if(!c)return;let l=i.repo??(s?await tt(s).catch(()=>null):null);await O({id:c.id,cwd:c.cwd},{type:"pr.state.changed",actor:"user",source:"orchestrator",payload:{ts:Date.now(),change:t,number:e,url:n,title:r,branch:o??c.workspace?.branch??null,...l?{repo:l}:{},...i}}).catch(()=>{})}async function Y$(t){let e=(await ct()).filter(n=>!n.archivedAt);for(let n of t??[]){if(!n.prs?.length)continue;let r=await tt(n.path);if(r)for(let o of n.prs){if(await Et(r,o.number))continue;let s=e.find(a=>a.workspace?.branch===o.headRefName&&Gu(a,n.path,r));s&&await Lt({repo:r,number:o.number,trackId:s.id,url:o.url,headRef:o.headRefName,title:o.title,source:"matched"})}}}x.get("/api/git/prs",async(t,e)=>{try{let n=await Mw();await Y$(n.repos).catch(()=>{}),e.json(n)}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.post("/api/git/prs/merge",async(t,e)=>{let{repo:n,number:r,method:o,auto:s,update:a,archive:i,notify:c,headRefName:l,title:u,url:f}=t.body||{};try{if(s){let w=o||"merge";if(!$o.has(w))throw new Error(`invalid merge method: ${w}`);let g=Number(r);if(!Number.isInteger(g)||g<=0)throw new Error("invalid PR number");let b=await tt((await $e(n)).path);if(!b)throw new Error("repo has no GitHub remote");let v=await ni({repo:n,number:g,branch:l}).catch(()=>null);if(!v)throw new Error("auto-merge needs a Track to watch this PR \u2014 none owns it");if(a&&await Oo(n,g),await Lt({repo:b,number:g,trackId:v.id,url:f,headRef:l,title:u,source:"matched"}).catch(()=>{}),!await wo(b,g,{merge:{method:w}}))throw new Error("couldn't record the auto-merge arm");return await lr(b,g,i===!0),await sa(b,g,c===!0&&i!==!0),he(),await te("auto-armed",{number:g,url:f,title:u,branch:l,repo:n,track:v,...a?{extra:{updated:!0}}:{}}),e.json({ok:!0,number:g,auto:!0})}let d=await vr(n,r,{method:o||"squash"});await te("merged",{number:d.number,url:f,title:u,branch:l,repo:n});let m=await tt(n).catch(()=>null),h=!1;if(m&&(i===!0&&await lr(m,d.number,!0).catch(()=>{}),c===!0&&i!==!0&&await sa(m,d.number,!0).catch(()=>{}),await en(m,d.number).catch(()=>{}),h=await tn(m,d.number).catch(()=>!1),await ze(m,d.number,"merged").catch(()=>{})),i===!0&&!h){let w=await ni({repo:n,number:d.number,branch:l}).catch(()=>null);w&&!w.archivedAt&&await Rr(w.id).catch(()=>{})}e.json(d)}catch(d){let m=d?.message||String(d),h=/invalid|unknown repo|required|needs a Track|no GitHub remote/i.test(m)?400:502;e.status(h).json({error:m})}});x.post("/api/git/prs/disarm-auto",async(t,e)=>{let{repo:n,number:r,headRefName:o,title:s,url:a}=t.body||{};try{let i=Number(r);if(!Number.isInteger(i)||i<=0)throw new Error("invalid PR number");let c=await tt((await $e(n)).path).catch(()=>null),l=c?await go(c,i).catch(()=>null):null;try{await Ll(n,i)}catch(u){if(!l)throw u}he(),await te("auto-canceled",{number:i,url:a,title:s,branch:o,repo:n}),e.json({ok:!0,number:i})}catch(i){let c=i?.message||String(i),l=/invalid|unknown repo|required/i.test(c)?400:502;e.status(l).json({error:c})}});x.post("/api/git/prs/merge-all",async(t,e)=>{let{repo:n,method:r}=t.body||{};try{let o=await tt(n).catch(()=>null),s=await Bw(n,{method:r||"merge",onMerged:a=>{te("merged",{number:a.number,url:a.url,title:a.title,branch:a.headRefName,repo:n}),o&&en(o,a.number).catch(()=>{}).then(()=>tn(o,a.number)).catch(()=>{}).then(()=>ze(o,a.number,"merged")).catch(()=>{})}});e.json(s)}catch(o){let s=o?.message||String(o),a=/already running/i.test(s)?409:/invalid|unknown repo|no PRs ready/i.test(s)?400:502;e.status(a).json({error:s})}});x.get("/api/git/prs/merge-all",(t,e)=>{e.json({jobs:Pw()})});x.post("/api/git/prs/merge-track",async(t,e)=>{let{trackId:n,update:r,archive:o,notify:s,method:a}=t.body||{};try{let i=a||"merge";if(!$o.has(i))throw new Error(`invalid merge method: ${i}`);if(!n)return e.status(400).json({error:"trackId required"});let c=await Q(n);if(!c)return e.status(404).json({error:`Track ${n} isn't on this node`});let l=(await pe()).filter(d=>d.trackId===c.id&&d.state!=="merged"&&d.state!=="closed");if(!l.length)return e.status(409).json({error:"no open PRs on this Track"});for(let d of l)await lr(d.repo,d.number,o===!0).catch(()=>{}),await sa(d.repo,d.number,s===!0&&o!==!0).catch(()=>{});let u=[],f=[];for(let d of l){let m={repo:d.repo,number:d.number,ok:!0,action:"armed"};u.push(m);try{let h=await Nn(d.repo);if(!h)throw new Error(`no local checkout for ${d.repo}`);let w=await Sr(h,d.number);f.push({r:m,l:d,repoPath:h,pr:w,plan:Cw(w,{update:r!==!1})})}catch(h){m.ok=!1,m.action="failed",m.reason=h?.message||String(h)}}for(let{r:d,l:m,repoPath:h,pr:w,plan:g}of Nw(f))try{if(g.action==="skip")d.action="skipped",d.reason=g.reason;else if(g.action==="merge")await vr(h,m.number,{method:i}),await te("merged",{number:m.number,url:w.url,title:w.title,branch:w.headRefName,repo:h,track:c}),await en(m.repo,m.number).catch(()=>{}),await tn(m.repo,m.number).catch(()=>{}),await ze(m.repo,m.number,"merged").catch(()=>{}),d.action="merged";else{if(g.action==="update-arm"&&(await Oo(h,m.number).catch(()=>{}),d.action="updated-armed"),!await wo(m.repo,m.number,{merge:{method:i}}))throw new Error("couldn't record the auto-merge arm");await te("auto-armed",{number:m.number,url:w.url,title:w.title,branch:w.headRefName,repo:h,track:c,...d.action==="updated-armed"?{extra:{updated:!0}}:{}})}}catch(b){d.ok=!1,d.action="failed",d.reason=b?.message||String(b)}if(he(),!u.some(d=>d.ok))return e.status(502).json({error:u.map(d=>`${d.repo}#${d.number}: ${d.reason}`).join("; "),results:u});e.json({ok:!0,trackId:c.id,results:u})}catch(i){let c=i?.message||String(i),l=/invalid|required/i.test(c)?400:502;e.status(l).json({error:c})}});x.post("/api/git/prs/disarm-track",async(t,e)=>{let{trackId:n}=t.body||{};try{if(!n)return e.status(400).json({error:"trackId required"});let r=await Q(n);if(!r)return e.status(404).json({error:`Track ${n} isn't on this node`});let o=(await pe()).filter(a=>a.trackId===r.id&&a.state!=="merged"&&a.state!=="closed"),s=[];for(let a of o){let i=await go(a.repo,a.number).catch(()=>null),c=await Nn(a.repo).catch(()=>null);c&&await Ll(c,a.number).catch(()=>{}),i&&(await te("auto-canceled",{number:a.number,url:a.url,title:a.title,branch:a.headRef,repo:c,track:r}),s.push({repo:a.repo,number:a.number}))}he(),e.json({ok:!0,trackId:r.id,canceled:s})}catch(r){let o=r?.message||String(r),s=/invalid|required/i.test(o)?400:502;e.status(s).json({error:o})}});x.post("/api/git/prs/open",async(t,e)=>{let{trackId:n,title:r,body:o,base:s,repos:a}=t.body||{};try{if(!n)return e.status(400).json({error:"trackId required"});let i=await Q(n);if(!i)return e.status(404).json({error:`Track ${n} isn't on this node`});if(i.workspace?.kind==="multi"){let u=Nt(i.workspace),f,d=Array.isArray(a)?a.filter(Boolean):[];if(d.length)f=d.map(w=>{let g=u.find(b=>b.name===w||b.repo===w||b.base===w||b.dir===w);if(!g)throw new Error(`invalid repo "${w}" \u2014 not tagged on this Track`);return g});else if(f=await _o(i),!f.length)return e.status(409).json({error:"no tagged repo has work to ship"});let m=[];for(let w of f)try{let g=await Ul(w.dir,{title:r,body:o,base:s}),b=w.repo||await tt(w.dir).catch(()=>null);b&&g.number&&await Lt({repo:b,number:g.number,trackId:i.id,url:g.url,headRef:i.workspace?.branch??null,title:r,source:"api"}),await te("opened",{number:g.number,url:g.url,title:r,track:i,branch:i.workspace?.branch,extra:{repo:b??w.name}}),m.push({repo:w.name,slug:b??null,ok:!0,url:g.url,number:g.number})}catch(g){m.push({repo:w.name,slug:w.repo??null,ok:!1,error:g?.message||String(g)})}let h=m.filter(w=>w.ok);return h.length?e.json({ok:!0,multi:!0,results:m,url:h[0].url,number:h[0].number}):e.status(502).json({error:m.map(w=>`${w.repo}: ${w.error}`).join("; "),results:m})}let c=await Ul(i.cwd,{title:r,body:o,base:s}),l=await tt(i.cwd).catch(()=>null);l&&c.number&&await Lt({repo:l,number:c.number,trackId:i.id,url:c.url,headRef:i.workspace?.branch??null,title:r,source:"api"}),await te("opened",{number:c.number,url:c.url,title:r,track:i,branch:i.workspace?.branch,extra:{...l?{repo:l}:{}}}),e.json(c)}catch(i){let c=i?.message||String(i),l=/required|invalid/i.test(c)?400:502;e.status(l).json({error:c})}});x.get("/api/tracks/:id/repos",async(t,e)=>{try{let n=await Q(t.params.id);if(!n)return e.status(404).json({error:"track not found"});let r=await _o(n),o=new Map(r.map(a=>[a.base,a])),s=Nt(n.workspace).map(a=>{let i=o.get(a.base);return{name:a.name,base:a.base,baseRef:a.baseRef??null,repo:a.repo??null,dirty:i?.dirty??!1,ahead:i?.ahead??0,changed:!!i}});e.json({repos:s,branch:n.workspace?.branch??null,kind:n.workspace?.kind??null})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.post("/api/tracks/:id/repos",async(t,e)=>{let{repo:n}=t.body||{};try{if(!n||typeof n!="string")return e.status(400).json({error:"repo (path or owner/name) required"});let r=await wg(t.params.id,n);e.json({ok:!0,added:{name:r.name,base:r.base,baseRef:r.baseRef??null,repo:r.repo??null}})}catch(r){let o=r?.message||String(r),s=/not found/i.test(o)?404:/already tagged|needs a multi|not a usable|archived|required/i.test(o)?400:502;e.status(s).json({error:o})}});x.delete("/api/tracks/:id/repos/:name",async(t,e)=>{try{let n=await gg(t.params.id,t.params.name);e.json({ok:!0,...n})}catch(n){let r=n?.message||String(n),o=/track .* not found/i.test(r)?404:/has work|still running|last repo/i.test(r)?409:/needs a multi|not tagged/i.test(r)?400:502;e.status(o).json({error:r})}});x.post("/api/git/prs/fix",async(t,e)=>{let{repo:n,number:r}=t.body||{};try{let o=await Sr(n,r),s=await tt(n).catch(()=>null),a=await Ib({repo:n,number:r,branch:o.headRefName});if(!a)return e.status(404).json({error:`no active Track owns PR #${o.number} (branch "${o.headRefName}")`});let i=Nt(a.workspace).find(b=>b.base===n||s&&b.repo===s)??null,c=a.workspace?.kind==="multi",l=await Dw(n,o.number);if(!l.length)return e.status(409).json({error:"no failing checks on this PR"});let u=c&&i?ut.join("ci",i.name):"ci",f=ut.join(a.cwd,".bostrat",u),d=ut.join(".bostrat",u,`pr-${o.number}-fail.log`);await Bt.promises.mkdir(f,{recursive:!0}),await Bt.promises.writeFile(ut.join(a.cwd,d),l.map(b=>`\u2550\u2550\u2550\u2550 ${b.name}${b.link?` \xB7 ${b.link}`:""} \u2550\u2550\u2550\u2550
|
|
317
386
|
${b.log??"(no fetchable log \u2014 see link)"}
|
|
318
387
|
`).join(`
|
|
319
|
-
`));let m=l.map(b=>b.name).join(", "),h=c&&i?` in the ${i.name}/ checkout`:"",w=await Se(a.id,`CI is red on PR #${o.number} "${o.title}" (${o.url}) \u2014 branch ${o.headRefName}${h}. Failing check${l.length===1?"":"s"}: ${m}. The failure log is at ${d} in this ${c?"workspace":"worktree"} \u2014 read it, fix the root cause${c&&i?` in ${i.name}/`:""}, run the relevant tests locally, then commit and push to update the PR.`,{kind:"ci-fix"}),y=s;y&&(await Bt({repo:y,number:o.number,trackId:a.id,url:o.url,headRef:o.headRefName,title:o.title,source:"matched"}),await tp(y,o.number,{headSha:o.headSha,trackId:a.id}).catch(()=>{})),await zt("fix-delegated",{number:o.number,url:o.url,title:o.title,branch:o.headRefName,repo:n,track:a,extra:{checks:l.map(b=>b.name)}}),e.json({ok:!0,leadBox:w.leadBox,queued:!!w.queued,checks:l.map(b=>b.name),log:d})}catch(o){let s=o?.message||String(o),a=/unknown repo|invalid|required/i.test(s)?400:/not found|no active Track/i.test(s)?404:502;e.status(a).json({error:s})}});x.post("/api/git/prs/reconcile",async(t,e)=>{let{repo:n,number:r}=t.body||{};try{let o=await pr(n,r);if(o.mergeable!=="CONFLICTING")return e.status(409).json({error:"this PR does not conflict with its base branch"});let s=await Z(n).catch(()=>null),a=await vy({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=Ot(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 Se(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 Bt({repo:u,number:o.number,trackId:a.id,url:o.url,headRef:o.headRefName,title:o.title,source:"matched"}),await ep(u,o.number,{headSha:o.headSha,trackId:a.id}).catch(()=>{})),await zt("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=Dm.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 F(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 Se(a.id,Xs(o,{branch:c.payload.branch,base:c.payload.base,toPr:c.payload.toPr||null,repos:c.payload.repos||null,offeredRebase:Array.isArray(c.payload.offered)&&c.payload.offered.includes("rebase")}),{kind:o.includes("pr")?"pr-go":"push-go"}));let u=o?o.includes("pr")?"pr":o.includes("push")?"push":o.includes("commit")?"commit":"rebase":"dismiss";await O({id:a.id,cwd:a.cwd},{type:"pr.offer.answered",actor:"user",source:"orchestrator",boxId:c.boxId,payload:{ts:Date.now(),offerId:n,action:u,actions:o,...s?{notify:!0}:{}}}),s&&ow(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 Fo=t=>e=>({...e,url:Ig(e,t.headers)});x.get("/api/views",async(t,e)=>{try{let n=await ks({trackId:t.query.track||void 0});e.json({views:n.map(Fo(t)),maxLive:Oe})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/views/:id",async(t,e)=>{let n=await of(t.params.id);if(!n)return e.status(404).json({error:"view not found"});e.json({view:Fo(t)(n),session:qn(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 at()).find(u=>u.cwd&&(s===u.cwd||s.startsWith(u.cwd+ct.sep)))||null),!i)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});let c=await Ri({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:Fo(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:Fo(t)(await sf(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:Fo(t)(await af(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 cf(t.params.id))}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});var pR=[["linear",un,Di],["jira",As,Pi]],fu=new Map;x.get("/api/integrations/ticketing",async(t,e)=>{try{let n=await Ni(),r={};for(let[o,s,a]of pR){let i=await s();if(!i){fu.delete(o),r[o]={configured:!1};continue}let c=fu.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},fu.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 Ni()})}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 Tf(o),i=n?await Q(n):null;!i&&r&&(i=(await at()).find(l=>l.cwd&&(r===l.cwd||r.startsWith(l.cwd+ct.sep)))||null),i&&await O({id:i.id,cwd:i.cwd},{type:"ticket.flow.changed",actor:s==="user"?"user":"agent",source:"orchestrator",payload:{ts:Date.now(),set:{...o},flow:a}}).catch(()=>{}),e.json({ok:!0,flow:a})}catch(a){let i=a?.message||String(a);e.status(/unknown|nothing to set/i.test(i)?400:500).json({error:i})}});x.post("/api/tickets/link",async(t,e)=>{let{trackId:n,cwd:r,ref:o}=t.body||{};try{let s=n?await Q(n):null;if(!s&&r&&(s=(await at()).find(c=>c.cwd&&(r===c.cwd||r.startsWith(c.cwd+ct.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 Yn(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 at()).find(a=>a.cwd&&(n===a.cwd||String(n).startsWith(a.cwd+ct.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 Uf(),ticket:await oe(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 oe(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 Yn(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 zi(t.params.id)})});var Fa=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 Gf(t.params.id))}catch(n){let r=n?.message||String(n);e.status(Fa(r)).json({error:r})}});x.post("/api/tracks/:id/ticket/state",async(t,e)=>{try{e.json({ok:!0,...await Ji(t.params.id,(t.body||{}).stateId)})}catch(n){let r=n?.message||String(n);e.status(Fa(r)).json({error:r})}});x.post("/api/tracks/:id/ticket/comment",async(t,e)=>{try{e.json({ok:!0,...await Kf(t.params.id,(t.body||{}).body)})}catch(n){let r=n?.message||String(n);e.status(Fa(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 Vf(o,n,r===!0)})}catch(o){let s=o?.message||String(o);e.status(Fa(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 at()).find(i=>i.cwd&&(r===i.cwd||r.startsWith(i.cwd+ct.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 Jf(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 zf()})}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 Xf(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 hs({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 $e(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 $e(t.params.id);if(!n)return e.status(404).json({error:"mock not found"});let r=an(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(Ft(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 $e(t.params.id);if(!n)return e.status(404).json({error:"mock not found"});let r=an(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(Ft(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 at()).find(m=>m.cwd&&(a===m.cwd||a.startsWith(m.cwd+ct.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 Ld({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 Fd(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 Hd(t.params.id),o=r.length?` over ${r.map(s=>`"${s.name}"`).join(", ")}`:"";await Se(n.trackId,`Design decision: go with "${n.name}" (mock:${n.id})${o} \u2014 proceed with this design.`,{kind:"mock-picked"}).catch(()=>{}),e.json({ok:!0,mock:n,siblings:r})}catch(n){let r=n?.message||String(n),o=/not found/i.test(r)?404:/nothing to pick/i.test(r)?409:500;e.status(o).json({error:r})}});x.delete("/api/mocks/:id",async(t,e)=>{try{e.json(await Wd(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+ct.sep)))?.id,!n)return e.json({runbooks:[]})}e.json({runbooks:await xs({trackId:n,status:t.query.status||void 0})})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/runbooks/:id",async(t,e)=>{let n=await Ci(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 Ci(t.params.id);if(!n)return e.status(404).json({error:"runbook not found"});let r=pf(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(Gn(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 at()).find(u=>u.cwd&&(s===u.cwd||s.startsWith(u.cwd+ct.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 wf({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 gf(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 yf(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 bf(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=Og(t.query.since||null);e.json({since:n,repos:nu(),prs:await Cg(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 at()).find(c=>c.cwd&&(s===c.cwd||String(s).startsWith(c.cwd+ct.sep)))||null),!a)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});e.json({ok:!0,entry:await Mg(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 mR=120,pu=400,wu=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>pu)return e.status(400).json({error:`message is ${i.length} chars \u2014 over the ${pu} cap; a notification is a nudge, put the detail in chat`});let c=Kr(`${n||""}
|
|
320
|
-
${i}`);if(c.length)return e.status(400).json({error:`notification appears to contain a secret VALUE (${c.join(", ")}) \u2014 notifications ship off-box; never put a credential in one`});let l=a?await Q(a):null;if(!l&&s&&(l=(await
|
|
388
|
+
`));let m=l.map(b=>b.name).join(", "),h=c&&i?` in the ${i.name}/ checkout`:"",w=await Qt(a.id,`CI is red on PR #${o.number} "${o.title}" (${o.url}) \u2014 branch ${o.headRefName}${h}. Failing check${l.length===1?"":"s"}: ${m}. The failure log is at ${d} in this ${c?"workspace":"worktree"} \u2014 read it, fix the root cause${c&&i?` in ${i.name}/`:""}, run the relevant tests locally, then commit and push to update the PR.`,{kind:"ci-fix"}),g=s;g&&(await Lt({repo:g,number:o.number,trackId:a.id,url:o.url,headRef:o.headRefName,title:o.title,source:"matched"}),await im(g,o.number,{headSha:o.headSha,trackId:a.id}).catch(()=>{})),await te("fix-delegated",{number:o.number,url:o.url,title:o.title,branch:o.headRefName,repo:n,track:a,extra:{checks:l.map(b=>b.name)}}),e.json({ok:!0,leadBox:w.leadBox,queued:!!w.queued,checks:l.map(b=>b.name),log:d})}catch(o){let s=o?.message||String(o),a=/unknown repo|invalid|required/i.test(s)?400:/not found|no active Track/i.test(s)?404:502;e.status(a).json({error:s})}});x.post("/api/git/prs/reconcile",async(t,e)=>{let{repo:n,number:r}=t.body||{};try{let o=await Sr(n,r);if(o.mergeable!=="CONFLICTING")return e.status(409).json({error:"this PR does not conflict with its base branch"});let s=await tt(n).catch(()=>null),a=await Ib({repo:n,number:r,branch:o.headRefName});if(!a)return e.status(404).json({error:`no active Track owns PR #${o.number} (branch "${o.headRefName}")`});let i=Nt(a.workspace).find(f=>f.base===n||s&&f.repo===s)??null,c=a.workspace?.kind==="multi"&&i?` In the ${i.name}/ checkout:`:"",l=await Qt(a.id,`PR #${o.number} "${o.title}" (${o.url}) \u2014 branch ${o.headRefName} \u2014 has merge conflicts with ${o.baseRefName}.${c} Bring ${o.baseRefName} into this branch (fetch origin, then merge origin/${o.baseRefName} \u2014 don't rebase this pushed PR branch), and resolve every conflict so that BOTH sides' intent is preserved. Run the relevant tests, then commit and push to update the PR. If any conflict is a genuine ambiguity \u2014 where you can't confidently tell which side's behavior should win, or reconciling would drop work from either side \u2014 STOP and ask me here rather than guessing, laying out the specific choice and your recommendation.`,{kind:"reconcile"}),u=s;u&&(await Lt({repo:u,number:o.number,trackId:a.id,url:o.url,headRef:o.headRefName,title:o.title,source:"matched"}),await cm(u,o.number,{headSha:o.headSha,trackId:a.id}).catch(()=>{})),await te("reconcile-delegated",{number:o.number,url:o.url,title:o.title,branch:o.headRefName,repo:n,track:a,extra:{base:o.baseRefName}}),e.json({ok:!0,leadBox:l.leadBox,queued:!!l.queued,base:o.baseRefName})}catch(o){let s=o?.message||String(o),a=/unknown repo|invalid|required/i.test(s)?400:/not found|no active Track/i.test(s)?404:502;e.status(a).json({error:s})}});x.post("/api/tracks/:id/pr-offer",async(t,e)=>{let{offerId:n,action:r}=t.body||{};try{let o=null;if(Array.isArray(t.body?.actions)){if(o=Fh.filter(f=>t.body.actions.includes(f)),!o.length)return e.status(400).json({error:"actions must include commit, rebase, push and/or pr"})}else if(r==="pr")o=["commit","push","pr"];else if(r==="push")o=["commit","push"];else if(r!=="dismiss")return e.status(400).json({error:'offerId and actions ["commit"|"rebase"|"push"|"pr"] (or action "dismiss") required'});if(!n)return e.status(400).json({error:"offerId required"});let s=o!=null&&t.body?.notify===!0,a=await Q(t.params.id);if(!a)return e.status(404).json({error:"track not found"});let i=await L(a),c=i.find(f=>f.type==="pr.offered"&&f.payload?.offerId===n);if(!c)return e.status(404).json({error:"unknown offer"});if(i.some(f=>f.type==="pr.offer.answered"&&f.payload?.offerId===n))return e.status(409).json({error:"offer already answered"});let l=null;o&&(l=await Qt(a.id,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 O({id:a.id,cwd:a.cwd},{type:"pr.offer.answered",actor:"user",source:"orchestrator",boxId:c.boxId,payload:{ts:Date.now(),offerId:n,action:u,actions:o,...s?{notify:!0}:{}}}),s&&cg(a.id,{actions:o}),e.json({ok:!0,offerId:n,action:u,actions:o,notify:s,queued:!!l?.queued})}catch(o){let s=o?.message||String(o),a=/required|invalid/i.test(s)?400:/not found|not running|archived/i.test(s)?409:502;e.status(a).json({error:s})}});var es=t=>e=>({...e,url:Cy(e,t.headers)});x.get("/api/views",async(t,e)=>{try{let n=await Hs({trackId:t.query.track||void 0});e.json({views:n.map(es(t)),maxLive:We})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/views/:id",async(t,e)=>{let n=await fp(t.params.id);if(!n)return e.status(404).json({error:"view not found"});e.json({view:es(t)(n),session:er(n.slug)})});x.post("/api/views",async(t,e)=>{let{trackId:n,name:r,cmd:o,cwd:s,port:a}=t.body||{};try{let i=n?await Q(n):null;if(!i&&s&&(i=(await ct()).find(u=>u.cwd&&(s===u.cwd||s.startsWith(u.cwd+ut.sep)))||null),!i)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});let c=await sc({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:es(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:es(t)(await pp(t.params.id))})}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});x.post("/api/views/:id/restart",async(t,e)=>{try{e.json({ok:!0,view:es(t)(await mp(t.params.id))})}catch(n){let r=n?.message||String(n),o=/not found/i.test(r)?404:/cap reached|no known command/i.test(r)?409:500;e.status(o).json({error:r})}});x.delete("/api/views/:id",async(t,e)=>{try{e.json(await hp(t.params.id))}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});var Z$=[["linear",xn,pc],["jira",Gs,dc]],Uu=new Map;x.get("/api/integrations/ticketing",async(t,e)=>{try{let n=await uc(),r={};for(let[o,s,a]of Z$){let i=await s();if(!i){Uu.delete(o),r[o]={configured:!1};continue}let c=Uu.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},Uu.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 Mp(o),i=n?await Q(n):null;!i&&r&&(i=(await ct()).find(l=>l.cwd&&(r===l.cwd||r.startsWith(l.cwd+ut.sep)))||null),i&&await O({id:i.id,cwd:i.cwd},{type:"ticket.flow.changed",actor:s==="user"?"user":"agent",source:"orchestrator",payload:{ts:Date.now(),set:{...o},flow:a}}).catch(()=>{}),e.json({ok:!0,flow:a})}catch(a){let i=a?.message||String(a);e.status(/unknown|nothing to set/i.test(i)?400:500).json({error:i})}});x.post("/api/tickets/link",async(t,e)=>{let{trackId:n,cwd:r,ref:o}=t.body||{};try{let s=n?await Q(n):null;if(!s&&r&&(s=(await ct()).find(c=>c.cwd&&(r===c.cwd||r.startsWith(c.cwd+ut.sep)))||null),!s)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});if(!o)return e.status(400).json({error:"ref (issue URL or key) required"});let a=await cr(s,o,{source:n?"api":"cli"});e.json({ok:!0,trackId:s.id,ticket:a})}catch(s){let a=s?.message||String(s);e.status(/not a Linear|not found|no ticketing|required/i.test(a)?400:502).json({error:a})}});x.get("/api/tickets/status",async(t,e)=>{let{cwd:n,track:r}=t.query;try{let o=r?await Q(r):null;if(!o&&n&&(o=(await ct()).find(a=>a.cwd&&(n===a.cwd||String(n).startsWith(a.cwd+ut.sep)))||null),!o)return e.status(400).json({error:"track (or a cwd inside a Track workspace) required"});e.json({trackId:o.id,configured:await Vp(),ticket:await fe(o.id)})}catch(o){e.status(500).json({error:o?.message||String(o)})}});x.get("/api/tracks/:id/ticket",async(t,e)=>{e.json({ticket:await fe(t.params.id)})});x.post("/api/tracks/:id/ticket",async(t,e)=>{try{let n=await Q(t.params.id);if(!n)return e.status(404).json({error:"track not found"});let r=await cr(n,(t.body||{}).ref,{source:"api"});e.json({ok:!0,ticket:r})}catch(n){let r=n?.message||String(n);e.status(/not a Linear|not found|no ticketing|required/i.test(r)?400:502).json({error:r})}});x.delete("/api/tracks/:id/ticket",async(t,e)=>{e.json({ok:!0,removed:await Tc(t.params.id)})});var ri=t=>/no ticket linked|unknown offer|unknown state/i.test(t)?404:/already answered/i.test(t)?409:/required|no ticketing/i.test(t)?400:502;x.get("/api/tracks/:id/ticket/states",async(t,e)=>{try{e.json(await Zp(t.params.id))}catch(n){let r=n?.message||String(n);e.status(ri(r)).json({error:r})}});x.post("/api/tracks/:id/ticket/state",async(t,e)=>{try{e.json({ok:!0,...await Ac(t.params.id,(t.body||{}).stateId)})}catch(n){let r=n?.message||String(n);e.status(ri(r)).json({error:r})}});x.post("/api/tracks/:id/ticket/comment",async(t,e)=>{try{e.json({ok:!0,...await Qp(t.params.id,(t.body||{}).body)})}catch(n){let r=n?.message||String(n);e.status(ri(r)).json({error:r})}});x.post("/api/tracks/:id/ticket-offer",async(t,e)=>{let{offerId:n,accept:r}=t.body||{};try{let o=await Q(t.params.id);if(!o)return e.status(404).json({error:"track not found"});e.json({ok:!0,...await tm(o,n,r===!0)})}catch(o){let s=o?.message||String(o);e.status(ri(s)).json({error:s})}});x.post("/api/tickets/propose",async(t,e)=>{let{trackId:n,cwd:r,plan:o}=t.body||{};try{let s=n?await Q(n):null;if(!s&&r&&(s=(await ct()).find(i=>i.cwd&&(r===i.cwd||r.startsWith(i.cwd+ut.sep)))||null),!s)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});e.json({ok:!0,trackId:s.id,proposal:await nm(s,o)})}catch(s){let a=s?.message||String(s);e.status(/no ticketing|are off|needs an epic|too many|required/i.test(a)?400:502).json({error:a})}});x.get("/api/tickets/teams",async(t,e)=>{try{e.json({teams:await em()})}catch(n){let r=n?.message||String(n);e.status(/no ticketing/i.test(r)?400:502).json({error:r})}});x.post("/api/tracks/:id/ticket-create-offer",async(t,e)=>{let{proposalId:n,accept:r,teamId:o}=t.body||{};try{let s=await Q(t.params.id);if(!s)return e.status(404).json({error:"track not found"});e.json({ok:!0,...await rm(s,n,r===!0,{teamId:o||null})})}catch(s){let a=s?.message||String(s);e.status(/unknown proposal|unknown team/i.test(a)?404:/already answered/i.test(a)?409:/required|no ticketing|pick one/i.test(a)?400:502).json({error:a})}});x.get("/api/mocks",async(t,e)=>{try{e.json({mocks:await Ds({trackId:t.query.track||void 0,group:t.query.group||void 0})})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/mocks/:id",async(t,e)=>{let n=await He(t.params.id);if(!n)return e.status(404).json({error:"mock not found"});e.json({mock:n})});x.get("/api/mocks/:id/png",async(t,e)=>{let n=await He(t.params.id);if(!n)return e.status(404).json({error:"mock not found"});let r=yn(n,t.query.v??null);if(!r)return e.status(409).json({error:`mock is ${n.state} \u2014 no captured version yet`});e.set("cache-control","private, max-age=31536000, immutable"),e.sendFile(qt(n,r.v).png,o=>{o&&!e.headersSent&&e.status(404).json({error:"rendered artifact missing"})})});x.get("/api/mocks/:id/html",async(t,e)=>{let n=await He(t.params.id);if(!n)return e.status(404).json({error:"mock not found"});let r=yn(n,t.query.v??null);if(!r?.htmlRel)return e.status(404).json({error:"this version has no HTML source (image-sourced)"});e.set("content-security-policy","sandbox"),e.sendFile(qt(n,r.v).html,o=>{o&&!e.headersSent&&e.status(404).json({error:"source artifact missing"})})});x.post("/api/mocks",async(t,e)=>{let n=Buffer.isBuffer(t.body)?t.body:null,r=n?t.query:t.body||{},{trackId:o,name:s,cwd:a,width:i,group:c}=r;try{let l=o?await Q(o):null;if(!l&&a&&(l=(await ct()).find(m=>m.cwd&&(a===m.cwd||a.startsWith(m.cwd+ut.sep)))||null),!l)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});let u=(l.boxes||[]).find(d=>d.name===l.leadBox),f=await zf({trackId:l.id,name:s,html:n?null:r.html,image:n,cwd:l.cwd,width:i,group:c,box:l.leadBox,agent:u?.agent||null});e.json({ok:!0,mock:f})}catch(l){let u=l?.message||String(l);e.status(/required|invalid|must be/i.test(u)?400:500).json({error:u})}});x.post("/api/mocks/:id/update",async(t,e)=>{let n=Buffer.isBuffer(t.body)?t.body:null;try{let r=await Vf(t.params.id,{html:n?null:(t.body||{}).html,image:n,width:n?t.query.width:(t.body||{}).width});e.json({ok:!0,mock:r})}catch(r){let o=r?.message||String(r),s=/not found/i.test(o)?404:/required|invalid|must be/i.test(o)?400:500;e.status(s).json({error:o})}});x.post("/api/mocks/:id/pick",async(t,e)=>{try{let{mock:n,siblings:r}=await Xf(t.params.id),o=r.length?` over ${r.map(s=>`"${s.name}"`).join(", ")}`:"";await Qt(n.trackId,`Design decision: go with "${n.name}" (mock:${n.id})${o} \u2014 proceed with this design.`,{kind:"mock-picked"}).catch(()=>{}),e.json({ok:!0,mock:n,siblings:r})}catch(n){let r=n?.message||String(n),o=/not found/i.test(r)?404:/nothing to pick/i.test(r)?409:500;e.status(o).json({error:r})}});x.delete("/api/mocks/:id",async(t,e)=>{try{e.json(await Yf(t.params.id))}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});x.get("/api/runbooks",async(t,e)=>{try{let n=t.query.track||void 0;if(!n&&t.query.cwd){let r=await ct(),o=String(t.query.cwd);if(n=r.find(s=>s.cwd&&(o===s.cwd||o.startsWith(s.cwd+ut.sep)))?.id,!n)return e.json({runbooks:[]})}e.json({runbooks:await Ws({trackId:n,status:t.query.status||void 0})})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/runbooks/:id",async(t,e)=>{let n=await cc(t.params.id);if(!n)return e.status(404).json({error:"runbook not found"});e.json({runbook:n})});x.get("/api/runbooks/:id/md",async(t,e)=>{let n=await cc(t.params.id);if(!n)return e.status(404).json({error:"runbook not found"});let r=kp(n,t.query.v??null);if(!r)return e.status(404).json({error:"no such version"});e.set("content-security-policy","sandbox"),e.set("cache-control","private, max-age=31536000, immutable"),e.type("text/markdown; charset=utf-8"),e.sendFile(nr(n,r.v),o=>{o&&!e.headersSent&&e.status(404).json({error:"runbook artifact missing"})})});x.post("/api/runbooks",async(t,e)=>{let{trackId:n,name:r,md:o,cwd:s}=t.body||{};try{let a=n?await Q(n):null;if(!a&&s&&(a=(await ct()).find(u=>u.cwd&&(s===u.cwd||s.startsWith(u.cwd+ut.sep)))||null),!a)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});let i=(a.boxes||[]).find(l=>l.name===a.leadBox),c=await vp({trackId:a.id,trackName:a.name,name:r,md:o,cwd:a.cwd,box:a.leadBox,agent:i?.agent||null});e.json({ok:!0,runbook:c})}catch(a){let i=a?.message||String(a);e.status(/required|invalid|over the|secret VALUE/i.test(i)?400:500).json({error:i})}});x.post("/api/runbooks/:id/update",async(t,e)=>{try{let{md:n,note:r}=t.body||{};e.json({ok:!0,runbook:await Tp(t.params.id,{md:n,note:r})})}catch(n){let r=n?.message||String(n),o=/not found/i.test(r)?404:/orphaned/i.test(r)?410:/required|invalid|over the|secret VALUE/i.test(r)?400:500;e.status(o).json({error:r})}});x.post("/api/runbooks/:id/status",async(t,e)=>{try{e.json({ok:!0,runbook:await Ap(t.params.id,(t.body||{}).status)})}catch(n){let r=n?.message||String(n),o=/not found/i.test(r)?404:/orphaned/i.test(r)?410:/must be/i.test(r)?400:500;e.status(o).json({error:r})}});x.delete("/api/runbooks/:id",async(t,e)=>{try{e.json(await _p(t.params.id))}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});x.get("/api/changelog/draft",async(t,e)=>{try{let n=By(t.query.since||null);e.json({since:n,repos:Ou(),prs:await Dy(n)})}catch(n){let r=n?.message||String(n);e.status(/unparseable/i.test(r)?400:500).json({error:r})}});x.post("/api/changelog/publish",async(t,e)=>{let{md:n,day:r,trackId:o,cwd:s}=t.body||{};try{let a=o?await Q(o):null;if(!a&&s&&(a=(await ct()).find(c=>c.cwd&&(s===c.cwd||String(s).startsWith(c.cwd+ut.sep)))||null),!a)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});e.json({ok:!0,entry:await jy(a,{md:n,day:r})})}catch(a){let i=a?.message||String(a);e.status(/required|invalid|no `- `|over the|secret VALUE/i.test(i)?400:500).json({error:i})}});var Q$=120,Hu=400,Ku=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>Hu)return e.status(400).json({error:`message is ${i.length} chars \u2014 over the ${Hu} cap; a notification is a nudge, put the detail in chat`});let c=lo(`${n||""}
|
|
389
|
+
${i}`);if(c.length)return e.status(400).json({error:`notification appears to contain a secret VALUE (${c.join(", ")}) \u2014 notifications ship off-box; never put a credential in one`});let l=a?await Q(a):null;if(!l&&s&&(l=(await ct()).find(h=>h.cwd&&(s===h.cwd||s.startsWith(h.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(m=>m.name===l.leadBox),f=(m,h)=>String(m).replace(/\s+/g," ").trim().slice(0,h),d={title:n?f(n,Q$):l.name,body:f(i,Hu),urgency:o==="high"?"high":"normal",trackName:l.name,ts:Date.now()};await O(l,{type:"notify.raised",actor:"agent",boxId:l.leadBox??null,provider:u?.agent||null,payload:d}),e.json({ok:!0,notification:d,delivered:Ku?Ku.status().connected:null})}catch(i){e.status(500).json({error:i?.message||String(i)})}});x.post("/api/dictation",async(t,e)=>{if(!Buffer.isBuffer(t.body)||t.body.length===0)return e.status(400).json({error:"expected a raw audio body"});try{e.json(await hb(t.body,t.headers["content-type"]))}catch(n){e.status(n?.status||500).json({error:n?.message||String(n),code:n?.code})}});x.get("/api/dictation/glossary",async(t,e)=>{e.json({configured:!!await uo(),terms:await Lu(),problem:await mb()})});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 pb(n,r)})});x.get("/api/tracks/:id/voice-chat",async(t,e)=>{if(!await Q(t.params.id))return e.status(404).json({error:"track not found"});e.json(await wb())});x.post("/api/tracks/:id/voice-chat/session",async(t,e)=>{try{let n=await Q(t.params.id);if(!n)return e.status(404).json({error:"track not found"});if(n.archivedAt)return e.status(409).json({error:"track is archived"});e.json(await gb())}catch(n){e.status(n?.status||500).json({error:n?.message||String(n),code:n?.code})}});x.post("/api/tracks/:id/voice-chat/usage",async(t,e)=>{try{let n=await Q(t.params.id);if(!n)return e.status(404).json({error:"track not found"});let{model:r,usage:o,seconds:s}=t.body||{};e.json({ok:!0,...await yb(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 au({name:n,cwd:r,repo:o,repos:s,agent:a,skipPermissions:i!==!1}),u=null;if(c)try{await cr(l,c,{source:"create"})}catch(f){u=f?.message||String(f)}e.json({ok:!0,track:l,...u?{ticketWarning:u}:{}})}catch(l){let u=l?.message||String(l);e.status(/required|unknown agent|invalid|must be/i.test(u)?400:500).json({error:u})}});x.get("/api/tracks/:id",async(t,e)=>{let n=await Q(t.params.id);if(!n)return e.status(404).json({error:"track not found"});e.json({track:n,handoff:Rb(Uo(t.params.id)),subtasks:$r(t.params.id).map(ns)})});x.delete("/api/tracks/:id",async(t,e)=>{try{let n=await vg(t.params.id,{cascadeMocks:t.query.mocks!=="0",cascadeRunbooks:t.query.runbooks==="1"});await Tc(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 Rr(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 Eg(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 nu(t.params.id);if(!n)return e.status(404).json({error:"track not found"});e.json(n)});x.get("/api/tracks/:id/events",async(t,e)=>{let n=await Q(t.params.id);if(!n)return e.status(404).json({error:"track not found"});e.set({"content-type":"text/event-stream","cache-control":"no-cache",connection:"keep-alive","x-accel-buffering":"no"}),e.flushHeaders();let r=i=>e.write(`id: ${i.seq}
|
|
321
390
|
data: ${JSON.stringify(i)}
|
|
322
391
|
|
|
323
|
-
`),o=Number(t.query.since??t.headers["last-event-id"])||0;for(let i of await
|
|
392
|
+
`),o=Number(t.query.since??t.headers["last-event-id"])||0;for(let i of await L(n))i.seq>o&&r(i);let s=ff(n.id,r),a=setInterval(()=>e.write(`: hb
|
|
324
393
|
|
|
325
|
-
`),25e3);t.on("close",()=>{clearInterval(a),s()})});x.
|
|
394
|
+
`),25e3);t.on("close",()=>{clearInterval(a),s()})});x.get("/api/tracks/:id/search",async(t,e)=>{let n=await Q(t.params.id);if(!n)return e.status(404).json({error:"track not found"});let r=String(t.query.q||"").trim();if(!r)return e.status(400).json({error:"q (query) is required"});let o=Math.min(Math.max(Number(t.query.k)||8,1),50),s=eo(await L(n)),a=_s(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 xd()})});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 Sd(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 tO=["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(`
|
|
395
|
+
`);x.post("/api/tracks/:id/memory/consolidate",async(t,e)=>{try{e.json({ok:!0,...await Qt(t.params.id,tO)})}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 Rg(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 Qt(t.params.id,n,{attachments:r}))}catch(o){let s=o?.message||String(o);e.status(/not found/i.test(s)?404:/archived/i.test(s)?409:/required|not running/i.test(s)?400:500).json({error:s})}});x.post("/api/tracks/:id/upload",async(t,e)=>{try{let n=await Q(t.params.id);if(!n)return e.status(404).json({error:`track "${t.params.id}" not found`});if(n.archivedAt)return e.status(409).json({error:"track is archived"});if(!n.cwd)return e.status(409).json({error:"track has no workspace yet"});let r=Buffer.isBuffer(t.body)?t.body:null,o=await Uw(n.cwd,{filename:t.query.filename,buffer:r});e.json({ok:!0,attachment:o})}catch(n){let r=n?.message||String(n);e.status(/too large|unsupported|empty|required/i.test(r)?400:500).json({error:r})}});x.get("/api/tracks/:id/uploads/:file",async(t,e)=>{try{let n=await Q(t.params.id);if(!n)return e.status(404).json({error:`track "${t.params.id}" not found`});if(!n.cwd)return e.status(404).json({error:"track has no workspace"});let r=ut.basename(String(t.params.file||""));if(!r||!Kl(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(ql(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 eO=/^[0-9a-fA-F-]{8,64}$/;x.get("/api/tracks/:id/chat-images/:box/:uuid/:idx",async(t,e)=>{try{let n=await Q(t.params.id);if(!n)return e.status(404).json({error:`track "${t.params.id}" not found`});let{box:r,uuid:o}=t.params,s=Number(t.params.idx);if(!eO.test(o)||!Number.isInteger(s)||s<0)return e.status(400).json({error:"bad image ref"});let a=(n.boxes||[]).find(d=>d.name===r);if(!a||a.agent!=="claude"||!a.cwd||!a.sessionId)return e.status(404).json({error:"image source not available"});let i=xe(a.cwd,a.sessionId);if(!i)return e.status(404).json({error:"transcript not found"});let c=`"${o}"`,l=null,u=G$.createInterface({input:Bt.createReadStream(i,{encoding:"utf8"}),crlfDelay:1/0});for await(let d of u){if(!d.includes(c))continue;let m;try{m=JSON.parse(d)}catch{continue}if(m.uuid===o){l=m,u.close();break}}let f=l?Ci(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 su(t.params.id))}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:/archived/i.test(r)?409:500).json({error:r})}});x.delete("/api/tracks/:id/queue",(t,e)=>{e.json({ok:!0,removed:Ba(t.params.id).filter(n=>n.kind!=="ship-notify")})});x.post("/api/tracks/:id/answer-prompt",async(t,e)=>{let{promptId:n,answers:r,text:o}=t.body||{};try{e.json(await ru(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 xg({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 sg(t.params.id,{promptId:n,questionIndex:r}))}catch(o){let s=o?.message||String(o),a=/not found/i.test(s)?404:/no pending prompt|changed since|not running|not supported|out of range|review screen/i.test(s)?409:500;e.status(a).json({error:s})}});x.put("/api/tracks/:id/draft",async(t,e)=>{let{text:n}=t.body||{};try{e.json(await dg(t.params.id,typeof n=="string"?n:""))}catch(r){let o=r?.message||String(r),s=/not found/i.test(o)?404:/required/i.test(o)?400:500;e.status(s).json({error:o})}});x.get("/api/tracks/:id/usage",async(t,e)=>{let n=await Q(t.params.id);if(!n)return e.status(404).json({error:"track not found"});let r=n.boxes?.find(s=>s.name===n.leadBox)||null,o=t.query.fresh==="1"||t.query.fresh==="true";e.json(await Ka(r,{fresh:o}))});x.post("/api/tracks/:id/model",async(t,e)=>{let{model:n}=t.body||{};try{e.json(await fg(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 Ng(t.params.id,{agent:n,task:r,skipPermissions:o!==!1,reason:s});e.json({ok:!0,subtask:ns(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:$r(t.params.id).map(ns)})});x.get("/api/tracks/:id/subtask/:sid/messages",async(t,e)=>{let n=await og(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:ns(fu(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:ns(Bg(t.params.sid))})}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:409).json({error:r})}});function ns(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 lu(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 lg(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(Rb(Uo(t.params.id))??{running:!1})});function Rb(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:ms(),paths:Ed,trace:await _d({limit:n})})});var Wu=ut.resolve(Tb,"..","..","client","dist");Bt.existsSync(Wu)&&(x.use(ge.static(Wu)),x.get(/^(?!\/api\/).*/,(t,e)=>e.sendFile(ut.join(Wu,"index.html"))));var $b=V$.createServer(x),nO=new X$({noServer:!0}),Ob=(t,e,n)=>{if(!(Vo(t)||Ur.allowed(Ur.clientIp(t)))){e.write(`HTTP/1.1 403 Forbidden\r
|
|
326
396
|
\r
|
|
327
|
-
`),e.destroy();return}if(
|
|
397
|
+
`),e.destroy();return}if(My(t,e,n))return;if(!Iy(t,_b,{enforce:Eb})){e.write(`HTTP/1.1 401 Unauthorized\r
|
|
328
398
|
\r
|
|
329
|
-
`),e.destroy();return}let r=
|
|
399
|
+
`),e.destroy();return}let r=Bd(t.url);if(!r.ok){e.write(`HTTP/1.1 ${r.status} ${r.status===400?"Bad Request":"Not Found"}\r
|
|
330
400
|
\r
|
|
331
|
-
`),e.destroy();return}let o=r.sessionName;if(!
|
|
401
|
+
`),e.destroy();return}let o=r.sessionName;if(!yt(o)){e.write(`HTTP/1.1 400 Bad Request\r
|
|
332
402
|
\r
|
|
333
|
-
`),e.destroy();return}let{cols:s,rows:a}=r;
|
|
403
|
+
`),e.destroy();return}let{cols:s,rows:a}=r;nO.handleUpgrade(t,e,n,i=>Cd(i,o,{cols:s,rows:a}))};$b.on("upgrade",Ob);var Mb=process.env.BOSTRAT_CERT_DIR||ut.join(process.env.HOME||qu.tmpdir(),".bostrat","certs"),xb=Number(process.env.BOSTRAT_TLS_PORT)||3443,Sb=ut.join(Mb,"bostrat.globopi.key"),vb=ut.join(Mb,"bostrat.globopi.crt");if(Bt.existsSync(Sb)&&Bt.existsSync(vb)){let t=J$.createServer({key:Bt.readFileSync(Sb),cert:Bt.readFileSync(vb)},x);t.on("upgrade",Ob),t.on("error",e=>{console.error(`[bostrat-agent] https listener failed (${e?.code||e?.message}) \u2014 continuing HTTP-only`)}),t.listen(xb,ei,()=>{console.log(`[bostrat-agent] https listening on ${ei||"0.0.0.0"}:${xb}`)})}$b.listen(bb,ei,()=>{console.log(`[bostrat-agent] listening on ${ei||"0.0.0.0"}:${bb}`),console.log(`[bostrat-agent] tmux socket: ${jt}`),console.log(`[bostrat-agent] auto-trigger default: ${Ab}%`),console.log(`[bostrat-agent] ip allowlist: ${Ur.open?"OPEN (BOSTRAT_ALLOW_IPS unset)":process.env.BOSTRAT_ALLOW_IPS}`),Mg().then(t=>t&&console.log("[bostrat-agent] reconciled interrupted handoff(s)")).catch(t=>console.error("[bostrat-agent] handoff reconcile failed:",t?.message||t)),Cg().then(t=>t&&console.log("[bostrat-agent] reconciled interrupted subtask(s)")).catch(t=>console.error("[bostrat-agent] subtask reconcile failed:",t?.message||t)),Ig().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)),kg().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)),aO(),sO(),qy(),om({resolveTrack:Q}),Gg({listTracks:ct,createTrack:au,archiveTrack:Rr,listRepos:ye,resolveTrack:Q,sayToLead:Qt,answerPrompt:ru,startLeadHandoff:lu,interruptLead:su,listSubtaskJobs:$r,cancelSubtask:fu,readEvents:L,appendEvent:O,knownAgents:Ca}),rO(),Xr().then(t=>{t||console.warn(`[boot] no host tmux server on ${jt} and auto-start failed \u2014 boxes cannot spawn until one runs`)}),ob(),process.env.BOSTRAT_PLANE_URL&&process.env.BOSTRAT_PLANE_TOKEN&&(Ku=Qg({url:process.env.BOSTRAT_PLANE_URL,token:process.env.BOSTRAT_PLANE_TOKEN,nodeId:process.env.BOSTRAT_NODE_ID||qu.hostname(),name:process.env.BOSTRAT_NODE_NAME||qu.hostname(),listTracks:ct,detectAgents:eu}),console.log(`[bostrat-agent] plane link \u2192 ${process.env.BOSTRAT_PLANE_URL}`))});function rO(){oO(),Us().catch(()=>{}),setInterval(()=>Us().catch(()=>{}),1e4)}function oO(){for(let t of["bostrat-view","bostrat-mock","bostrat-ticket","bostrat-runbook","bostrat-notify","bostrat-gmail","bostrat-changelog","gh"])try{let e=ut.resolve(Tb,"..","bin",t),n=ut.join(process.env.HOME||"",".local","bin");if(!process.env.HOME||!Bt.existsSync(e))continue;Bt.mkdirSync(n,{recursive:!0});let r=ut.join(n,t);(Bt.existsSync(r)?Bt.readlinkSync(r):null)!==e&&(Bt.rmSync(r,{force:!0}),Bt.symlinkSync(e,r)),console.log(`[bostrat-agent] ${t} CLI linked at ${r}`)}catch(e){console.error(`[bostrat-agent] ${t} CLI link failed:`,e?.message||e)}}function sO(){let t=!1,e=async()=>{if(!t){t=!0;try{let n=await ct(),r=await py({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 aO(){let t=!1;setInterval(async()=>{if(!t){t=!0;try{let e=await ct();for(let n of e)n.archivedAt||!n.boxes?.some(r=>r.state==="live")||(await nu(n.id).catch(()=>{}),await jw(n).catch(()=>{}))}catch{}finally{t=!1}}},4e3)}
|