bostrat-node 0.2.7 → 0.2.9
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 +42 -42
- package/dist/index.js +167 -125
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,86 +1,117 @@
|
|
|
1
|
-
var
|
|
1
|
+
var Nb=Object.defineProperty;var Vu=(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 Ku 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 vi from"node:fs";import{randomUUID as Hk}from"node:crypto";import Dt from"node:fs/promises";import Zu 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 ss="BOSTRAT_HOOK_TOKEN",as="x-bostrat-hook";function Ju(){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 Xu(t,e){return typeof t!="string"||typeof e!="string"||t.length!==e.length||!t?!1:Db(Buffer.from(t),Buffer.from(e))}var ai=new Map,ii=new Map,jb=6e4;function is(t,e={},{now:n=Date.now(),log:r=console.warn}={}){if(ai.set(t,(ai.get(t)||0)+1),ii.has(t)&&n-ii.get(t)<jb)return!1;ii.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=${ai.get(t)}${o?` ${o}`:""}`),!0}async function Yu(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 cs(){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(Zu.homedir(),".claude.json")}function Hb(){return process.env.CLAUDE_CONFIG_DIR?$t.join(process.env.CLAUDE_CONFIG_DIR,"settings.json"):$t.join(Zu.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 "${as}: $${ss}" --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 ci from"node:fs/promises";import Kb from"node:os";import ls from"node:path";function Gb(){let t=process.env.CODEX_HOME||ls.join(Kb.homedir(),".codex");return ls.join(t,"config.toml")}function Qu(t){return`[projects."${t}"]`}function zb(t,e){let n=Qu(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=ls.resolve(t),n=Gb(),r="";try{r=await ci.readFile(n,"utf8")}catch{}if(zb(r,e))return;let o=Qu(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 ci.mkdir(ls.dirname(n),{recursive:!0}),await ci.writeFile(n,r)}catch(s){console.warn(`[codex-config] could not seed folder trust for ${e}: ${s?.message||s}`)}}import td 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 td.mkdir(n,{recursive:!0}),await td.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 nd 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 ui 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),ed=process.env.HOME||"/home/globo",Qb=Pe.join(process.env.BOSTRAT_STATE_DIR||Pe.join(ed,".bostrat"),"repos"),tk=[...(process.env.BOSTRAT_REPOS_ROOT||Pe.join(ed,"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 ui.stat(Pe.join(t,".git"))).isDirectory()?"repo":"worktree"}catch{return null}}async function ok(t){let e=i=>ui.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 ui.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 li=new Map;async function tt(t){if(!t)return null;if(li.has(t))return li.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 li.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 rd=uk(lk),dk=process.env.BOSTRAT_STATE_DIR||Kr.join(process.env.HOME||ik.tmpdir(),".bostrat"),fk=Kr.join(dk,"repos"),od=/^[^/\s]+\/[^/\s]+$/,pk=5*6e4,mk=6e4,hk=10*6e4,sd=fetch,di=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 us(t=process.env){return!!(be(t)&&t.BOSTRAT_PLANE_TOKEN)}var ad=t=>({authorization:`Bearer ${t.BOSTRAT_PLANE_TOKEN||""}`}),Wn=(t,e)=>Object.assign(new Error(t),{status:e});async function ds(t,{env:e=process.env}={}){let n=String(t||"").toLowerCase();if(!od.test(n))throw Wn("repo must be owner/name",400);let r=di.get(n);if(r&&r.expiresAt-Date.now()>pk)return r;if(!us(e))throw Wn("node is not plane-linked \u2014 GitHub auth unavailable",503);let o;try{o=await sd(`${be(e)}/v1/github/token?repo=${encodeURIComponent(t)}`,{headers:ad(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 di.set(n,a),a}async function Gr({env:t=process.env,force:e=!1}={}){if(!us(t))return{configured:!1,repos:[],username:null};if(!e&&fn&&Date.now()-fn.at<mk)return fn.data;try{let n=await sd(`${be(t)}/v1/github/repos`,{headers:ad(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 id(){di.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 ds(r,{env:n});return o?{GH_TOKEN:o}:{}}catch{return{}}}async function fi({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 pi(t,{env:e=process.env}={}){let n=(...r)=>rd("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 mi(t,{env:e=process.env,baseUrl:n="https://github.com"}={}){if(!od.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 ds(t,{env:e});await nd.mkdir(Kr.dirname(r),{recursive:!0});let s="!f() { echo username=x-access-token; echo password=$BOSTRAT_GIT_TOKEN; }; f";try{await rd("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 nd.rm(r,{recursive:!0,force:!0}).catch(()=>{}),Wn(`clone of ${t} failed: ${a?.stderr?.trim?.()||a?.message||a}`,502)}return await pi(r,{env:e}),r}var bk=6e4,kk=5e3,hi=new Map,xk=(...t)=>fetch(...t);function cd(t=process.env){return!!(be(t)&&t.BOSTRAT_PLANE_TOKEN)}var ld=t=>({authorization:`Bearer ${t.BOSTRAT_PLANE_TOKEN||""}`});async function ud(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 dd(t,{env:e=process.env,force:n=!1}={}){if(!cd(e))return{facts:[],source:"unconfigured"};let r=hi.get(t);if(!n&&r&&Date.now()-r.at<bk)return{facts:r.facts,source:"cache"};try{let o=await ud(`/v1/memory/${encodeURIComponent(t)}`,{headers:ld(e)},e);if(!o.ok)throw new Error(`plane ${o.status}`);let s=await o.json(),a=Array.isArray(s?.facts)?s.facts:[];return hi.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 fs(t,e,{env:n=process.env}={}){if(!cd(n))return{pushed:0,source:"unconfigured"};if(!e?.length)return{pushed:0,source:"empty"};try{let r=await ud(`/v1/memory/${encodeURIComponent(t)}`,{method:"POST",headers:{...ld(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 hi.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"),fd=200,_k=25*1024;function ps(){return J.join(wi,"memory","repos")}var ke="operator";function pn(t){return t===ke?J.join(wi,"memory",ke):J.join(ps(),t)}var wd=".tombstones.json";async function zr(t){try{return JSON.parse(await j.readFile(J.join(t,wd),"utf8"))}catch{return{}}}async function gi(t,e){let n=await zr(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,wd),JSON.stringify(n))}function pd(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 md(t){try{let{stdout:e}=await Ak("git",["-C",t,"config","--get","remote.origin.url"]);return e.trim()||null}catch{return null}}async function yi(t){if(!t)return[];let e=pd(await md(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=pd(await md(J.join(t,o.name)));s&&!n.includes(s)&&n.push(s)}return n}function ms(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}=ms(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 bi(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 Vr(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 Vr(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}=ms(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 zr(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,bi(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 yi(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=await zr(n),o=new Set,s=new Map,a=[],i=0;for(let c of e||[]){let l=String(c?.file||"");if(!Ik.test(l)||l==="MEMORY.md")continue;let u=J.join(n,l),f=await j.access(u).then(()=>!0).catch(()=>!1),d=f?await ne(u):0,m=Number(c.modified)||0;if(c.deleted){m>d&&(f&&await j.rm(u,{force:!0}),await gi(n,[[l,m]]),f&&(a.push(l),i++));continue}f&&d>=m||(r[l]||0)>=m||(await j.mkdir(n,{recursive:!0}),await j.writeFile(u,String(c.content??"")),o.add(l),typeof c.indexLine=="string"&&c.indexLine.trim()&&s.set(l,c.indexLine),i++)}if(o.size||a.length){let c=J.join(n,"MEMORY.md"),l=await j.readFile(c,"utf8").catch(()=>""),u=bi(l,[...s.values()].join(`
|
|
21
|
+
`),o);if(a.length){let f=new Set(a);u=u.split(`
|
|
22
|
+
`).filter(d=>!f.has(qn(d))).join(`
|
|
23
|
+
`)}await j.mkdir(n,{recursive:!0}),await j.writeFile(c,u)}return{changed:i,tombstoned:a.length}}async function ki(t,e=null){let n=pn(t),r=e?.length?e:await Vr(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 fs(t,s)}async function Ok(t){let{facts:e,source:n}=await dd(t);e.length?await $k(t,e):n==="plane"&&await ki(t)}function Mk(t){let e=t.split(`
|
|
24
|
+
`);e.length>fd&&(e=e.slice(0,fd));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 zr(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 Vr(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 hd=0,Pk=3e4;async function kd(t){try{let e=await yi(t);if(!e.length)return{materialized:!1,reason:"no-repo-key"};let n=[...e,ke];if(Date.now()-hd>Pk){hd=Date.now(),await Bk();let{changedByKey:r}=await Ek();for(let[o,s]of r)await ki(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(ps(),{withFileTypes:!0})}catch{return{moved:t}}for(let n of e){if(!n.isDirectory())continue;let r=J.join(ps(),n.name),o=pn(ke),s=new Set,a=new Map;for(let m of await Vr(r)){let h=J.join(r,m),w;try{w=await j.readFile(h,"utf8")}catch{continue}let{fm:g}=ms(w);if(gd(g,n.name)!==ke)continue;let b=J.join(o,m),v=await ne(b).catch(()=>0),A=await ne(h,w);await j.mkdir(o,{recursive:!0}),A>v&&await j.writeFile(b,w),await j.rm(h,{force:!0}),s.add(m),a.set(m,yd(m,g)),t++}if(!s.size)continue;let i=Date.now();await gi(r,[...s].map(m=>[m,i])),await fs(n.name,[...s].map(m=>({file:m,deleted:!0,modified:i})));let c=J.join(r,"MEMORY.md"),l=await j.readFile(c,"utf8").catch(()=>""),u=l.split(`
|
|
37
|
+
`).filter(m=>!s.has(qn(m))).join(`
|
|
38
|
+
`);await j.writeFile(c,u).catch(()=>{});let f=J.join(o,"MEMORY.md"),d=await j.readFile(f,"utf8").catch(()=>"");await j.writeFile(f,bi(d,l,s,a))}return{moved:t}}async function xd(){let t=[],e=[ke];try{for(let n of await j.readdir(ps(),{withFileTypes:!0}))n.isDirectory()&&e.push(n.name)}catch{}for(let n of e){let r=pn(n),o=await Vr(r),s=await zr(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}=ms(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 gi(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 fs(t,[{file:e,deleted:!0,modified:s}]);return{deleted:e,key:t,plane:c.source}}async function vd(t){try{let e=await yi(t),{harvested:n,changedByKey:r}=await bd(t,e[0]||null);for(let[o,s]of r)await ki(o,[...s]);return{harvested:n}}catch(e){return{harvested:0,error:e?.message||String(e)}}}import Jr 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 xi(t){let e=String(t??"");for(let n of jk)e=e.replace(n,"\u2039redacted\u203A");return e}var Si=process.env.BOSTRAT_DEBUG_DIR||Ad.join(Dk.homedir(),".bostrat","debug"),Kn=Ad.join(Si,"handoff.jsonl"),Td=Number(process.env.BOSTRAT_HANDOFF_DEBUG_MAX)||500;function hs(){let t=process.env.BOSTRAT_HANDOFF_DEBUG;return t==="1"||t==="true"}async function Lk(t){try{await Jr.mkdir(Si,{recursive:!0}),await Jr.appendFile(Kn,`${JSON.stringify(t)}
|
|
41
|
+
`,"utf8");let n=(await Jr.readFile(Kn,"utf8")).split(`
|
|
42
|
+
`).filter(Boolean);n.length>Td&&await Jr.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
|
-
`))
|
|
31
|
-
`))if(a.trim())try{r.push(JSON.parse(a))}catch{}}catch{}return
|
|
32
|
-
${o}`.trim()||e.slice(0,2e3)}function
|
|
33
|
-
`):""}async function
|
|
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&&hs())try{e.paneTail=xi((await gt(e.sessionName,{lines:35})).slice(-4e3))}catch{e.paneTail=null}(hs()||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 Jr.readFile(Kn,"utf8")).split(`
|
|
45
|
+
`).filter(Boolean).slice(-t).map(r=>{try{let o=JSON.parse(r);return typeof o?.paneTail=="string"&&(o.paneTail=xi(o.paneTail)),o}catch{return{raw:xi(r)}}})}catch{return[]}}var Ed={DEBUG_DIR:Si,LOG_FILE:Kn};var lt=Uk(Fk);function Xr(t){return new Promise(e=>setTimeout(e,t))}function ws(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||"/",Ti=process.env.BOSTRAT_AGENT_CMD||"claude --dangerously-skip-permissions",Ai="bostrat-usage-probe",qk=new Set([Ai]);function _i(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 Ei(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 gs(t){return Ei(String(t||"").trim().split(/\s+/)[0])}var Ii={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(!vi.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 Yr(){if(await De())return!0;try{vi.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(!vi.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 Ri(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=Ri(r);if(!yt(o))throw new Error("resulting tmux session name is invalid");if(!await Yr())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||Ti,a=n.agent||zn(s),i=!!n.resumeSessionId,c=n.resumeSessionId||Hk(),l=i?s:_i(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?cs():null,h=Ju(),w=["-e",`${ss}=${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 ws(m?ws(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 Xr(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 ys(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 Xr(80))}catch{}}async function je(t){return await ys(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 Xr(n);return G({flow:"tmux",phase:"send-keys",sessionName:t,kind:r||"keys",keys:o.join(" ")}).catch(()=>{}),o.length}async function bs(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 ks(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 Xr(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 Xr(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)){xs(t,`invalid session name: ${e}`),t.close(1008,"invalid session name");return}if(!await De()){xs(t,`host tmux server not running on ${jt}`),t.close(1011,"tmux server unavailable");return}if(!await Y(e)){xs(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){xs(t,`failed to spawn tmux attach: ${a.message}`),t.close(1011,"spawn failed");return}s.onData(a=>{t.readyState===t.OPEN&&$i(t,{type:"data",data:a})}),s.onExit(({exitCode:a,signal:i})=>{t.readyState===t.OPEN&&($i(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 $i(t,e){try{t.send(JSON.stringify(e))}catch{}}function xs(t,e){$i(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 Zr 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,Oi=process.env.BOSTRAT_CLAUDE_PROJECTS||Vn.join(ox.homedir(),".claude","projects");function Mi(t){return t.replace(/[^A-Za-z0-9]/g,"-")}function Ci(t){let e=Vn.join(Oi,Mi(t)),n;try{n=Zr.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}=Zr.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(Oi,Mi(t),`${e}.jsonl`);return Zr.existsSync(n)?n:null}function jd(t){if(!t)return[];let e=Vn.join(Oi,Mi(t)),n;try{n=Zr.readdirSync(e).filter(o=>o.endsWith(".jsonl"))}catch{return[]}let r=[];for(let o of n)try{let s=Zr.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 Qr(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=Ci(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 Ni(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=Ni(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 Pi(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 Pi(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 ro 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 Bi=hx(mx),wx=Number(process.env.BOSTRAT_CODEX_UPDATE_TIMEOUT_MS)||5*6e4,Ud=new Set,Hd=Promise.resolve(),Di=null;function qd(t){Di=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 Bi("/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 Bi("npm",["install","-g","@openai/codex"],{env:a,timeout:wx});let i=(await Bi(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(Di)try{await Di(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 Ss=/(^|\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(Ss.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(Ss.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 Ss.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 Ss.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 ji=/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)||ji.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)||ji.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)||ji.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,Li=Number(process.env.BOSTRAT_AGENT_READY_POLL_MS)||500,to=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,vs=new Map;function Zd(t,e){let n=Date.now(),r=Bx();for(let[s,a]of vs)n-a>=r&&vs.delete(s);let o=`${t}::${e}`;return vs.has(o)?!1:(vs.set(o,n),!0)}var 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 Fi(t,e){let n=At[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=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 Fi(t,e)==="busy"}function Ts(t,e){return Fi(t,e)!=="idle"}function As(t,e){let n=At[t];return n?.up?n.up.test(e):!0}function Ux(t,e){let n=At[t];return n?Ui(t,e)?!1:n.up.test(e)&&Fi(t,e)==="idle":!0}function Ui(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(to);return}if(e==="cursor"&&Cx.test(s)&&!At.cursor.up.test(s)&&o<3){if(!Zd(t,"cursor-trust")){await Le(Li);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(to);continue}if(e==="codex"&&o<3){let a=Px(s);if(a){if(!Zd(t,`codex-update:${a.from}->${a.to}`)){await Le(Li);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(to);continue}}if(Date.now()>r){if(Ui(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(to);return}await Le(Li)}}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 _s({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(to)}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 Is 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 eo(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=eo(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 no(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 Es(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 Is.readdir(e)}catch{return no([])}let r=[];for(let o of n)if(o.endsWith(".ndjson"))try{let s=await Is.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 no(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=Es(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 Is.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 Is.appendFile(e,`
|
|
34
65
|
`+c),{appended:!0,chars:c.length,chunks:c.split(`
|
|
35
|
-
- `).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)}}}
|
|
36
|
-
`),
|
|
37
|
-
`))if(a.trim())try{s.push(JSON.parse(a))}catch{}return s}function
|
|
38
|
-
`,n!=null?{mode:n}:void 0),await
|
|
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 Hi 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 Wi=process.env.BOSTRAT_STATE_DIR||Yn.join(process.env.HOME||Qx.tmpdir(),".bostrat");function $s(t){return t?.cwd?Yn.join(t.cwd,".bostrat","events"):Yn.join(Wi,"events")}var df=(t,e)=>Yn.join(t,`${e}.ndjson`),Rs=new Map;async function s0(t,e){if(!Rs.has(e)){let o=(await L(t,e)).reduce((s,a)=>a.seq>s?a.seq:s,0);Rs.set(e,o)}let n=Rs.get(e)+1;return Rs.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(Wi,"events"):$s(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 Hi.mkdir(r,{recursive:!0}),await Hi.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(Wi,"events"):$s(t));let o;try{o=await Hi.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 ro.mkdir(r,{recursive:!0});let s=await Os(o),a=await Y(t);if(!a)throw new Error(`session "${t}" not found`);return await Ms(t,a,m0(o)),await Mf(o,s,n),w0(await ro.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,qi=new Map;function Cs(t){return qi.get(t)||null}async function Rf(t){let e=qi.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 qi.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 Ms(t.name,n,"/clear"),await Cf(y0),t.step="resume",await Ms(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 ro.mkdir(n,{recursive:!0});let o=await Os(r),s=await Y(t);await G({flow:"checkpoint",phase:"summarize-start",sessionName:t,cwd:e,file:r,beforeMtime:o}),await Ms(t,s,l0(r)),await Mf(r,o,b0),await G({flow:"checkpoint",phase:"summarize-done",sessionName:t,file:r,afterMtime:await Os(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 Os(t){try{return(await ro.stat(t)).mtimeMs}catch{return 0}}async function Mf(t,e,n){let r=Date.now()+n;for(;;){if(await Os(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 ro.copyFile(e,Zn.join(t,`${n}.md`))}catch{}}function Cf(t){return new Promise(e=>setTimeout(e,t))}async function Ms(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 Fo}from"node:crypto";import{execFile as __}from"node:child_process";import{promisify as E_}from"node:util";import mm from"node:path";var Ki={"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 Ki[e]?e:Nf[e]?Nf[e]:Object.keys(Ki).find(r=>e.startsWith(r))||null}var Ns=t=>Pf(t)!==null;function oe(t,e={}){let n=Ki[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 Bs(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&&!Ns(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}=Bs(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:oo(a),totalTokens:u,turns:n.length,repricedTurns:i,unpricedTurns:c,unpricedModels:[...l],byModel:Ps(r,f=>({...f,usd:oo(f.usd)})),byProvider:Ps(o,f=>({...f,usd:oo(f.usd)})),byBox:Ps(s,f=>({usd:oo(f.usd),events:f.events,provider:f.provider,label:f.label,models:[...f.models],...f.epochs?{epochs:Ps(f.epochs,d=>({usd:oo(d.usd),events:d.events,provider:d.provider,models:[...d.models]}))}:{}}))}}var oo=t=>Math.round(t*1e4)/1e4,Ps=(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 so 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 Gi from"node:fs/promises";import v0 from"node:path";var T0=0;async function ae(t,e,{mode:n}={}){await Gi.mkdir(v0.dirname(t),{recursive:!0});let r=`${t}.${process.pid}.${++T0}.tmp`;await Gi.writeFile(r,JSON.stringify(e,null,2)+`
|
|
69
|
+
`,n!=null?{mode:n}:void 0),await Gi.rename(r,t)}var Lf=E0(_0),Ff=process.env.BOSTRAT_STATE_DIR||Mt.join(process.env.HOME||so.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 so.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:so.freemem()}catch{return so.freemem()}}var Vt=class extends Error{constructor(e,n=""){super(n?`${e}
|
|
39
70
|
${n.trim().split(`
|
|
40
71
|
`).slice(-6).join(`
|
|
41
|
-
`)}`:e),this.name="MockRenderError"}},cn=null,vi=null;async function he(){return cn||(vi||=(async()=>{try{cn=JSON.parse(await kt.readFile(Qd,"utf8"))}catch{cn={mocks:[]}}finally{vi=null}return cn.mocks||=[],cn})(),vi)}async function te(){await Qt(Qd,cn)}function Gt(t,e,n={}){return C({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 vs=t=>Rt.join(t,".bostrat","mocks");function Ft(t,e){let n=vs(t.cwd);return{html:Rt.join(n,`${t.id}.v${e}.html`),png:Rt.join(n,`${t.id}.v${e}.png`)}}function ln(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 Ti(t){return t.length<24||t.readUInt32BE(12)!==1229472850?null:{width:t.readUInt32BE(16),height:t.readUInt32BE(20)}}var dx=Buffer.from([137,80,78,71,13,10,26,10]);function ef(t){return Buffer.isBuffer(t)&&t.length>24&&t.subarray(0,8).equals(dx)}async function fx(t){let e=Buffer.alloc(24),n=await kt.open(t,"r");try{await n.read(e,0,24,0)}finally{await n.close()}return e}function px({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 mx(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 hx(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 wx({htmlPath:t,outPath:e,width:n}){let r=Date.now(),o;try{o=await rx.launch({executablePath:sx,args:["--disable-gpu"],timeout:3e4})}catch(s){throw new qt("chromium failed to launch",s?.message||"")}try{let a=await(await o.newContext({viewport:{width:n,height:800},deviceScaleFactor:qn})).newPage();a.setDefaultTimeout(Gn),await a.goto(`file://${t}`,{waitUntil:"load",timeout:Gn}),await a.waitForTimeout(250);let i;for(let l=1;;l++)try{i=await a.screenshot({fullPage:!0,timeout:Gn});break}catch(u){if(l>=3||!/captureScreenshot|Unable to capture/i.test(u?.message||""))throw u;await a.waitForTimeout(500)}try{let l=await mx(a,i),u=l&&px({...l,pad:cx*qn,align:qn});u&&(i=await hx(a,i,{bg:l.bg,crop:u}))}catch{}if(i.length<tf)throw new qt(`capture produced a ${i.length}-byte image`);let c=Ti(i);if(!c)throw new qt("output is not a PNG");return await kt.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 qt?s:new qt(/timeout/i.test(s?.message||"")?`render timed out after ${Gn}ms`:"chromium render failed",s?.message||"")}finally{await o.close().catch(()=>{})}}async function Xd({htmlPath:t,outPath:e,width:n}){let r=await kt.mkdtemp(Rt.join(Kr.tmpdir(),"bostrat-mock-profile-")),o=Date.now(),s="";try{try{s=(await Yd(ax,["--headless","--no-remote","--profile",r,`--window-size=${n}`,"--screenshot",e,`file://${t}`],{env:{...process.env,MOZ_HEADLESS:"1"},timeout:Gn,killSignal:"SIGKILL",maxBuffer:4194304})).stderr||""}catch(c){throw s=c?.stderr||"",new qt(c?.killed?`render timed out after ${Gn}ms`:"firefox exited non-zero",s)}let a=await kt.stat(e).catch(()=>null);if(!a||a.size<tf)throw new qt(`render produced ${a?`a ${a.size}-byte file`:"no file"} (silent no-op \u2014 another firefox holding the profile?)`,s);let i=Ti(await fx(e));if(!i)throw new qt("output is not a PNG",s);return{width:i.width,height:i.height,dpr:1,renderMs:Date.now()-o}}finally{await kt.rm(r,{recursive:!0,force:!0}).catch(()=>{})}}async function gx(t){if(ox==="firefox")return Xd(t);try{return await wx(t)}catch(e){if(/failed to launch/i.test(e?.message||""))return Xd(t);throw e}}var yx=gx;var Ai=Promise.resolve();function nf(t){return Ai=Ai.then(t,t),Ai}async function rf(t,e){let n=await he(),r=n.mocks.find(o=>o.id===t);if(r){r.state="rendering",await te(),await Gt(r,"rendering",{version:e});try{let o=await ux();if(o<lx)throw new qt(`box is low on memory (${Math.round(o/1e6)} MB available) \u2014 not spawning a browser`);let s=Ft(r,e),a=await yx({htmlPath:s.html,outPath:s.png,width:r.width});if(!n.mocks.some(i=>i.id===t)){await kt.rm(s.png,{force:!0}).catch(()=>{});return}of(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 te(),await Gt(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 te(),await Gt(r,"error",{version:e,error:r.error})}}}function of(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 bx({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?ix:Number(r);if(!Number.isInteger(o)||o<320||o>4e3)throw new Error("invalid width (320-4000)");return o}async function As({trackId:t,group:e}={}){let n=await he(),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 Me(t){let n=(await he()).mocks.find(r=>r.id===t);return n?{...n}:null}async function sf({trackId:t,name:e,html:n,image:r,cwd:o,width:s,group:a,box:i,agent:c,prompt:l}){let u=bx({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&&!ef(r))throw new Error("image must be a PNG");let d=await he(),m={id:tx().slice(0,8),trackId:t,box:i||null,agent:c||null,name:e.trim(),prompt:l?String(l).slice(0,500):null,group:a?String(a).trim():null,cwd:o,width:u,height:null,dpr:null,state:"queued",source:f?"html":"image",version:1,readyVersion:null,versions:[],error:null,renderMs:null,createdAt:Date.now(),updatedAt:Date.now()};return await kt.mkdir(vs(o),{recursive:!0}),d.mocks.push(m),f?(await kt.writeFile(Ft(m,1).html,n),await te(),await Gt(m,"created"),nf(()=>rf(m.id,1))):(await cf(m,1,r),await te(),await Gt(m,"created"),await Gt(m,"ready",{version:1,width:m.width,height:m.height,dpr:m.dpr})),{...m}}async function af(t,{html:e,image:n,width:r}={}){let s=(await he()).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&&!ef(n))throw new Error("image must be a PNG");if(r!=null){let c=Number(r);if(!Number.isInteger(c)||c<320||c>4e3)throw new Error("invalid width (320-4000)");s.width=c}let i=s.version+1;return s.version=i,s.updatedAt=Date.now(),s.source=a?"html":"image",await kt.mkdir(vs(s.cwd),{recursive:!0}),a?(s.state="queued",await kt.writeFile(Ft(s,i).html,e),await te(),await Gt(s,"updated",{version:i}),nf(()=>rf(s.id,i))):(await cf(s,i,n),await te(),await Gt(s,"updated",{version:i}),await Gt(s,"ready",{version:i,width:s.width,height:s.height,dpr:s.dpr})),{...s}}async function cf(t,e,n){let r=Ti(n);if(!r)throw new Error("image must be a PNG");let o=Ft(t,e);await kt.writeFile(o.png,n),of(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 lf(t){let e=await he(),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 Gt(n,"picked",{version:n.readyVersion,siblings:r}),{mock:{...n},siblings:r}}async function uf(t){let e=await he(),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 kt.rm(s.html,{force:!0}).catch(()=>{}),await kt.rm(s.png,{force:!0}).catch(()=>{})}return await te(),await Gt(r,"deleted"),{ok:!0,id:t,name:r.name}}async function df(t){let n=(await he()).mocks.filter(i=>i.trackId===t.id);if(!n.length)return 0;let r=Rt.join(Zd,"mock-archive",t.id),o=vs(t.cwd),s=Rt.join(r,".bostrat","mocks");await kt.mkdir(s,{recursive:!0});let a=await kt.readdir(o).catch(()=>[]);for(let i of a)await kt.copyFile(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 kt.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 te(),n.length}async function ff(t){let e=await he(),n=e.mocks.filter(r=>r.trackId===t);return n.length&&(e.mocks=e.mocks.filter(r=>r.trackId!==t),await te()),n.length}import zr from"node:fs/promises";import $t from"node:path";import kx from"node:os";import xx from"node:net";import{randomUUID as Sx}from"node:crypto";import{execFile as vx}from"node:child_process";import{promisify as Ax}from"node:util";var ne=Ax(vx),Tx=process.env.BOSTRAT_STATE_DIR||$t.join(process.env.HOME||kx.tmpdir(),".bostrat"),gf=$t.join(Tx,"views.json"),[pf,mf]=(process.env.BOSTRAT_VIEW_PORTS||"4300-4349").split("-").map(t=>Number(t)||0),Ne=Number(process.env.BOSTRAT_VIEW_MAX)||3,_x="bostrat-view-",Kn=t=>`${_x}${t}`,hf=new Set(["bostrat","www","api","views","app"]),Ts=t=>Object.assign(new Error(t),{status:400}),Ex=new Set(["npm","pnpm","yarn","bun"]),Ii="package.json",Ix=/[|;&`]|\$\(/,Rx=new Set(["sh","bash","zsh","dash","env","exec","nohup","time"]),$x=/(^|\s)(-C|--cwd|--manifest-path|--dir)(=|\s)/,yf=t=>String(t).trim().split(/\s+/).filter(Boolean),Ox=/^[A-Za-z_][A-Za-z0-9_]*=/;function Cx(t){for(let e of yf(t))if(!Ox.test(e))return e;return null}function bf(t){let e=yf(t),n=e.indexOf("--");return n===-1?e:e.slice(0,n)}function Mx(t,e){let n=bf(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 Nx=t=>zr.stat(t).then(e=>e.isDirectory(),()=>!1),kf=t=>zr.stat(t).then(e=>e.isFile(),()=>!1),_s=t=>zr.realpath(t).catch(()=>$t.resolve(t));async function Px(t,e){if($t.resolve(t)===$t.resolve(e))return!0;let[n,r]=await Promise.all([_s(t),_s(e)]);return n===r}async function Bx(t){let n=(await zr.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 kf($t.join(t,o.name,Ii))?`./${o.name}`:null))).filter(Boolean)}async function Dx(t,e){let[n,r]=await Promise.all([_s(t),_s(e)]),o=$t.relative(n,r);if(o&&(o.startsWith("..")||$t.isAbsolute(o)))throw Ts(`serve directory escapes the Track workspace: ${e}`)}async function jx(t,e){if(!await Nx(t))throw Ts(`serve directory does not exist: ${t}`);let n=Cx(e);if(!n)return;if(!n.includes("/")&&!await ri(n,{loginShell:!0,failOpen:!0}))throw Ts(`\`${n}\` is not on the box's PATH \u2014 install it in the box first`);if(Ix.test(e)||Rx.has(n)||!Ex.has(n)||!bf(e).includes("run")||$x.test(e))return;let r=Mx(t,e);if(await kf($t.join(r,Ii)))return;let o=await Bx(r),s=o.length?` \u2014 found one in ${o.join(", ")}; run bostrat-view from there`:"";throw Ts(`no ${Ii} in ${r}${s}`)}var ee=null,_i=null;async function ye(){return ee||(_i||=(async()=>{try{ee=JSON.parse(await zr.readFile(gf,"utf8"))}catch{ee={views:[],adoptWatermarks:{}}}finally{_i=null}return ee.views||=[],ee.adoptWatermarks||={},ee})(),_i)}async function re(){await Qt(gf,ee)}function ge(t,e,n={}){return C({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 Vr=new Map,Oi=Number(process.env.BOSTRAT_VIEW_JOB_TIMEOUT)||3e5,xf=12e3;function Ci(t){return Vr.get(t)||null}function Sf(t,{name:e,box:n}={}){let r=Vr.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 Vr.set(t,o),o}function Ri(t,e){let n=Vr.get(t);n&&!n.finishedAt&&!n.viewId&&(n.viewId=e,n.step="booting")}function Es(t,{error:e=null}={}){let n=Vr.get(t);return!n||n.finishedAt?null:(n.finishedAt=Date.now(),e&&(n.error=e),n)}function vf(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 $i(t){return String(t||"").trim().toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,40).replace(/-+$/g,"")||"view"}function Lx(t,e){let n=$i(e),r=new Set(t.views.map(o=>o.slug));if(!r.has(n)&&!hf.has(n))return n;for(let o=2;;o++){let s=`${n}-${o}`;if(!r.has(s)&&!hf.has(s))return s}}function Jr(t,{timeoutMs:e=1200}={}){return new Promise(n=>{let r=xx.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 Mi(t){if(process.platform!=="darwin"){await ne("fuser",["-k",`${t}/tcp`]).catch(()=>{});return}let e="";try{({stdout:e}=await ne("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 Af(t=process.env){return new Set([Number(t.PORT)||3362,Number(t.BOSTRAT_TLS_PORT)||3443])}async function Fx(t,e){let n=new Set(t.views.map(o=>o.port)),r=Af();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=pf;o<=mf;o++)if(!(n.has(o)||r.has(o))&&!await Jr(o,{timeoutMs:250}))return o;throw new Error(`no free view port in ${pf}-${mf}`)}async function Ux(t){try{let{stdout:e}=await ne(N,B("list-sessions","-F","#{session_name}"));return e.split(`
|
|
42
|
-
`).some(n=>n.trim()===
|
|
72
|
+
`)}`:e),this.name="MockRenderError"}},gn=null,zi=null;async function ve(){return gn||(zi||=(async()=>{try{gn=JSON.parse(await St.readFile(Uf,"utf8"))}catch{gn={mocks:[]}}finally{zi=null}return gn.mocks||=[],gn})(),zi)}async function ie(){await ae(Uf,gn)}function Jt(t,e,n={}){return O({id:t.trackId,cwd:t.cwd},{type:"mock.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box||null,provider:t.agent||null,payload:{ts:Date.now(),change:e,mockId:t.id,name:t.name,...t.group?{group:t.group}:{},...n}}).catch(()=>{})}var Ds=t=>Mt.join(t,".bostrat","mocks");function qt(t,e){let n=Ds(t.cwd);return{html:Mt.join(n,`${t.id}.v${e}.html`),png:Mt.join(n,`${t.id}.v${e}.png`)}}function yn(t,e=null){let n=t.versions||[];return e!=null?n.find(r=>r.v===Number(e))||null:n.length?n[n.length-1]:null}function Ji(t){return t.length<24||t.readUInt32BE(12)!==1229472850?null:{width:t.readUInt32BE(16),height:t.readUInt32BE(20)}}var B0=Buffer.from([137,80,78,71,13,10,26,10]);function Wf(t){return Buffer.isBuffer(t)&&t.length>24&&t.subarray(0,8).equals(B0)}async function D0(t){let e=Buffer.alloc(24),n=await St.open(t,"r");try{await n.read(e,0,24,0)}finally{await n.close()}return e}function j0({imgW:t,imgH:e,bbox:n,pad:r,align:o=1}){if(!n||!(t>0)||!(e>0))return null;let s=n.right-n.left+1,a=n.bottom-n.top+1;if(s<=0||a<=0||s>=t*.9&&a>=e*.9)return null;let i=s+2*r,c=a+2*r;return i+=(o-i%o)%o,c+=(o-c%o)%o,{sx:n.left,sy:n.top,sw:s,sh:a,outW:i,outH:c,dx:Math.floor((i-s)/2),dy:Math.floor((c-a)/2)}}async function L0(t,e){return t.evaluate(async n=>{let r=new Image;await new Promise((h,w)=>{r.onload=h,r.onerror=()=>w(new Error("decode failed")),r.src=`data:image/png;base64,${n}`});let o=r.naturalWidth,s=r.naturalHeight;if(!o||!s||o*s>24e6)return null;let a=document.createElement("canvas");a.width=o,a.height=s;let i=a.getContext("2d",{willReadFrequently:!0});i.drawImage(r,0,0);let c=i.getImageData(0,0,o,s).data,l=[c[0],c[1],c[2]],u=o,f=s,d=-1,m=-1;for(let h=0;h<s;h++)for(let w=h*o*4,g=0;g<o;g++,w+=4)Math.abs(c[w]-l[0])+Math.abs(c[w+1]-l[1])+Math.abs(c[w+2]-l[2])>24&&(g<u&&(u=g),g>d&&(d=g),h<f&&(f=h),m=h);return d<0?null:{imgW:o,imgH:s,bg:l,bbox:{left:u,top:f,right:d,bottom:m}}},e.toString("base64"))}async function F0(t,e,{bg:n,crop:r}){let o=await t.evaluate(async({b64:s,bg:a,crop:i})=>{let c=new Image;await new Promise((f,d)=>{c.onload=f,c.onerror=()=>d(new Error("decode failed")),c.src=`data:image/png;base64,${s}`});let l=document.createElement("canvas");l.width=i.outW,l.height=i.outH;let u=l.getContext("2d");return u.fillStyle=`rgb(${a[0]},${a[1]},${a[2]})`,u.fillRect(0,0,i.outW,i.outH),u.drawImage(c,i.sx,i.sy,i.sw,i.sh,i.dx,i.dy,i.sw,i.sh),l.toDataURL("image/png").split(",")[1]},{b64:e.toString("base64"),bg:n,crop:r});return Buffer.from(o,"base64")}async function U0({htmlPath:t,outPath:e,width:n}){let r=Date.now(),o;try{o=await I0.launch({executablePath:$0,args:["--disable-gpu"],timeout:3e4})}catch(s){throw new Vt("chromium failed to launch",s?.message||"")}try{let a=await(await o.newContext({viewport:{width:n,height:800},deviceScaleFactor:Qn})).newPage();a.setDefaultTimeout(tr),await a.goto(`file://${t}`,{waitUntil:"load",timeout:tr}),await a.waitForTimeout(250);let i;for(let l=1;;l++)try{i=await a.screenshot({fullPage:!0,timeout:tr});break}catch(u){if(l>=3||!/captureScreenshot|Unable to capture/i.test(u?.message||""))throw u;await a.waitForTimeout(500)}try{let l=await L0(a,i),u=l&&j0({...l,pad:C0*Qn,align:Qn});u&&(i=await F0(a,i,{bg:l.bg,crop:u}))}catch{}if(i.length<Hf)throw new Vt(`capture produced a ${i.length}-byte image`);let c=Ji(i);if(!c)throw new Vt("output is not a PNG");return await St.writeFile(e,i),{width:Math.round(c.width/Qn),height:Math.round(c.height/Qn),dpr:Qn,renderMs:Date.now()-r}}catch(s){throw s instanceof Vt?s:new Vt(/timeout/i.test(s?.message||"")?`render timed out after ${tr}ms`:"chromium render failed",s?.message||"")}finally{await o.close().catch(()=>{})}}async function jf({htmlPath:t,outPath:e,width:n}){let r=await St.mkdtemp(Mt.join(so.tmpdir(),"bostrat-mock-profile-")),o=Date.now(),s="";try{try{s=(await Lf(O0,["--headless","--no-remote","--profile",r,`--window-size=${n}`,"--screenshot",e,`file://${t}`],{env:{...process.env,MOZ_HEADLESS:"1"},timeout:tr,killSignal:"SIGKILL",maxBuffer:4194304})).stderr||""}catch(c){throw s=c?.stderr||"",new Vt(c?.killed?`render timed out after ${tr}ms`:"firefox exited non-zero",s)}let a=await St.stat(e).catch(()=>null);if(!a||a.size<Hf)throw new Vt(`render produced ${a?`a ${a.size}-byte file`:"no file"} (silent no-op \u2014 another firefox holding the profile?)`,s);let i=Ji(await D0(e));if(!i)throw new Vt("output is not a PNG",s);return{width:i.width,height:i.height,dpr:1,renderMs:Date.now()-o}}finally{await St.rm(r,{recursive:!0,force:!0}).catch(()=>{})}}async function H0(t){if(R0==="firefox")return jf(t);try{return await U0(t)}catch(e){if(/failed to launch/i.test(e?.message||""))return jf(t);throw e}}var W0=H0;var Vi=Promise.resolve();function qf(t){return Vi=Vi.then(t,t),Vi}async function Kf(t,e){let n=await ve(),r=n.mocks.find(o=>o.id===t);if(r){r.state="rendering",await ie(),await Jt(r,"rendering",{version:e});try{let o=await P0();if(o<N0)throw new Vt(`box is low on memory (${Math.round(o/1e6)} MB available) \u2014 not spawning a browser`);let s=qt(r,e),a=await W0({htmlPath:s.html,outPath:s.png,width:r.width});if(!n.mocks.some(i=>i.id===t)){await St.rm(s.png,{force:!0}).catch(()=>{});return}Gf(r,{v:e,source:"html",htmlRel:Mt.relative(r.cwd,s.html),pngRel:Mt.relative(r.cwd,s.png),width:a.width,height:a.height,dpr:a.dpr,renderMs:a.renderMs,createdAt:Date.now()}),await ie(),await Jt(r,"ready",{version:e,width:a.width,height:a.height,dpr:a.dpr})}catch(o){if(!n.mocks.some(s=>s.id===t))return;r.state="error",r.error=o?.message||String(o),await ie(),await Jt(r,"error",{version:e,error:r.error})}}}function Gf(t,e){t.versions=(t.versions||[]).filter(r=>r.v!==e.v).concat(e),t.versions.sort((r,o)=>r.v-o.v);let n=t.versions[t.versions.length-1];t.readyVersion=n.v,t.height=n.height,t.dpr=n.dpr,t.renderMs=n.renderMs??null,n.v===t.version&&(t.state="ready",t.error=null)}function q0({trackId:t,name:e,cwd:n,width:r}){if(!t)throw new Error("trackId required");if(typeof e!="string"||!e.trim())throw new Error("name (string) required");if(!n)throw new Error("cwd required");let o=r==null?M0:Number(r);if(!Number.isInteger(o)||o<320||o>4e3)throw new Error("invalid width (320-4000)");return o}async function js({trackId:t,group:e}={}){let n=await ve(),r=t?n.mocks.filter(o=>o.trackId===t):n.mocks;return e&&(r=r.filter(o=>o.group===e)),r.map(o=>({...o}))}async function He(t){let n=(await ve()).mocks.find(r=>r.id===t);return n?{...n}:null}async function zf({trackId:t,name:e,html:n,image:r,cwd:o,width:s,group:a,box:i,agent:c,prompt:l}){let u=q0({trackId:t,name:e,cwd:o,width:s}),f=typeof n=="string"&&n.trim();if(!f&&!r)throw new Error("html (string) or image (png) required");if(r&&!Wf(r))throw new Error("image must be a PNG");let d=await ve(),m={id:A0().slice(0,8),trackId:t,box:i||null,agent:c||null,name:e.trim(),prompt:l?String(l).slice(0,500):null,group:a?String(a).trim():null,cwd:o,width:u,height:null,dpr:null,state:"queued",source:f?"html":"image",version:1,readyVersion:null,versions:[],error:null,renderMs:null,createdAt:Date.now(),updatedAt:Date.now()};return await St.mkdir(Ds(o),{recursive:!0}),d.mocks.push(m),f?(await St.writeFile(qt(m,1).html,n),await ie(),await Jt(m,"created"),qf(()=>Kf(m.id,1))):(await Jf(m,1,r),await ie(),await Jt(m,"created"),await Jt(m,"ready",{version:1,width:m.width,height:m.height,dpr:m.dpr})),{...m}}async function Vf(t,{html:e,image:n,width:r}={}){let s=(await ve()).mocks.find(c=>c.id===t);if(!s)throw new Error("mock not found");let a=typeof e=="string"&&e.trim();if(!a&&!n)throw new Error("html (string) or image (png) required");if(n&&!Wf(n))throw new Error("image must be a PNG");if(r!=null){let c=Number(r);if(!Number.isInteger(c)||c<320||c>4e3)throw new Error("invalid width (320-4000)");s.width=c}let i=s.version+1;return s.version=i,s.updatedAt=Date.now(),s.source=a?"html":"image",await St.mkdir(Ds(s.cwd),{recursive:!0}),a?(s.state="queued",await St.writeFile(qt(s,i).html,e),await ie(),await Jt(s,"updated",{version:i}),qf(()=>Kf(s.id,i))):(await Jf(s,i,n),await ie(),await Jt(s,"updated",{version:i}),await Jt(s,"ready",{version:i,width:s.width,height:s.height,dpr:s.dpr})),{...s}}async function Jf(t,e,n){let r=Ji(n);if(!r)throw new Error("image must be a PNG");let o=qt(t,e);await St.writeFile(o.png,n),Gf(t,{v:e,source:"image",htmlRel:null,pngRel:Mt.relative(t.cwd,o.png),width:r.width,height:r.height,dpr:1,renderMs:null,createdAt:Date.now()}),t.width=r.width}async function Xf(t){let e=await ve(),n=e.mocks.find(o=>o.id===t);if(!n)throw new Error("mock not found");if(n.readyVersion==null)throw new Error(`mock is ${n.state} \u2014 nothing to pick yet`);let r=n.group?e.mocks.filter(o=>o.trackId===n.trackId&&o.group===n.group&&o.id!==t&&o.readyVersion!=null).map(o=>({mockId:o.id,name:o.name})):[];return await Jt(n,"picked",{version:n.readyVersion,siblings:r}),{mock:{...n},siblings:r}}async function Yf(t){let e=await ve(),n=e.mocks.findIndex(o=>o.id===t);if(n===-1)throw new Error("mock not found");let r=e.mocks[n];e.mocks.splice(n,1);for(let o=1;o<=r.version;o++){let s=qt(r,o);await St.rm(s.html,{force:!0}).catch(()=>{}),await St.rm(s.png,{force:!0}).catch(()=>{})}return await ie(),await Jt(r,"deleted"),{ok:!0,id:t,name:r.name}}async function Zf(t){let n=(await ve()).mocks.filter(i=>i.trackId===t.id);if(!n.length)return 0;let r=Mt.join(Ff,"mock-archive",t.id),o=Ds(t.cwd),s=Mt.join(r,".bostrat","mocks");await St.mkdir(s,{recursive:!0});let a=await St.readdir(o).catch(()=>[]);for(let i of a)await St.copyFile(Mt.join(o,i),Mt.join(s,i));for(let i of n){for(let c of i.versions||[])for(let l of[c.htmlRel,c.pngRel]){if(!l)continue;if(!(await St.stat(Mt.join(r,l)).catch(()=>null))?.isFile())throw new Error(`mock evacuation incomplete: ${l} missing from the archive`)}i.cwd=r,i.trackDeletedAt=Date.now(),i.trackName=t.name||t.id}return await ie(),n.length}async function Qf(t){let e=await ve(),n=e.mocks.filter(r=>r.trackId===t);return n.length&&(e.mocks=e.mocks.filter(r=>r.trackId!==t),await ie()),n.length}import io from"node:fs/promises";import Ct from"node:path";import K0 from"node:os";import G0 from"node:net";import{randomUUID as z0}from"node:crypto";import{execFile as V0}from"node:child_process";import{promisify as J0}from"node:util";var le=J0(V0),X0=process.env.BOSTRAT_STATE_DIR||Ct.join(process.env.HOME||K0.tmpdir(),".bostrat"),op=Ct.join(X0,"views.json"),[tp,ep]=(process.env.BOSTRAT_VIEW_PORTS||"4300-4349").split("-").map(t=>Number(t)||0),We=Number(process.env.BOSTRAT_VIEW_MAX)||3,Y0="bostrat-view-",er=t=>`${Y0}${t}`,np=new Set(["bostrat","www","api","views","app"]),Ls=t=>Object.assign(new Error(t),{status:400}),Z0=new Set(["npm","pnpm","yarn","bun"]),Zi="package.json",Q0=/[|;&`]|\$\(/,tS=new Set(["sh","bash","zsh","dash","env","exec","nohup","time"]),eS=/(^|\s)(-C|--cwd|--manifest-path|--dir)(=|\s)/,sp=t=>String(t).trim().split(/\s+/).filter(Boolean),nS=/^[A-Za-z_][A-Za-z0-9_]*=/;function rS(t){for(let e of sp(t))if(!nS.test(e))return e;return null}function ap(t){let e=sp(t),n=e.indexOf("--");return n===-1?e:e.slice(0,n)}function oS(t,e){let n=ap(e);for(let r=0;r<n.length;r++){let o=/^--prefix=(.+)$/.exec(n[r]);if(o)return Ct.resolve(t,o[1]);if(n[r]==="--prefix"&&n[r+1])return Ct.resolve(t,n[r+1])}return t}var sS=t=>io.stat(t).then(e=>e.isDirectory(),()=>!1),ip=t=>io.stat(t).then(e=>e.isFile(),()=>!1),Fs=t=>io.realpath(t).catch(()=>Ct.resolve(t));async function aS(t,e){if(Ct.resolve(t)===Ct.resolve(e))return!0;let[n,r]=await Promise.all([Fs(t),Fs(e)]);return n===r}async function iS(t){let n=(await io.readdir(t,{withFileTypes:!0}).catch(()=>[])).filter(o=>o.isDirectory()&&!o.name.startsWith(".")&&o.name!=="node_modules");return(await Promise.all(n.map(async o=>await ip(Ct.join(t,o.name,Zi))?`./${o.name}`:null))).filter(Boolean)}async function cS(t,e){let[n,r]=await Promise.all([Fs(t),Fs(e)]),o=Ct.relative(n,r);if(o&&(o.startsWith("..")||Ct.isAbsolute(o)))throw Ls(`serve directory escapes the Track workspace: ${e}`)}async function lS(t,e){if(!await sS(t))throw Ls(`serve directory does not exist: ${t}`);let n=rS(e);if(!n)return;if(!n.includes("/")&&!await Ei(n,{loginShell:!0,failOpen:!0}))throw Ls(`\`${n}\` is not on the box's PATH \u2014 install it in the box first`);if(Q0.test(e)||tS.has(n)||!Z0.has(n)||!ap(e).includes("run")||eS.test(e))return;let r=oS(t,e);if(await ip(Ct.join(r,Zi)))return;let o=await iS(r),s=o.length?` \u2014 found one in ${o.join(", ")}; run bostrat-view from there`:"";throw Ls(`no ${Zi} in ${r}${s}`)}var ce=null,Xi=null;async function _e(){return ce||(Xi||=(async()=>{try{ce=JSON.parse(await io.readFile(op,"utf8"))}catch{ce={views:[],adoptWatermarks:{}}}finally{Xi=null}return ce.views||=[],ce.adoptWatermarks||={},ce})(),Xi)}async function ue(){await ae(op,ce)}function Ae(t,e,n={}){return O({id:t.trackId,cwd:t.cwd},{type:"view.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box||null,payload:{ts:Date.now(),change:e,viewId:t.id,name:t.name,slug:t.slug,port:t.port,...n}}).catch(()=>{})}var ao=new Map,ec=Number(process.env.BOSTRAT_VIEW_JOB_TIMEOUT)||3e5,cp=12e3;function nc(t){return ao.get(t)||null}function lp(t,{name:e,box:n}={}){let r=ao.get(t);if(r&&!r.finishedAt)throw new Error("a view is already being spun up for this Track");let o={trackId:t,name:e||"preview",box:n||null,step:"spinning-up",startedAt:Date.now(),finishedAt:null,viewId:null,error:null};return ao.set(t,o),o}function Qi(t,e){let n=ao.get(t);n&&!n.finishedAt&&!n.viewId&&(n.viewId=e,n.step="booting")}function Us(t,{error:e=null}={}){let n=ao.get(t);return!n||n.finishedAt?null:(n.finishedAt=Date.now(),e&&(n.error=e),n)}function up(t){let e=[];for(let n of t?.events||[])n.type!=="view-spinup"||!n.box||!n.startedAt||!n.endedAt||e.push({box:n.box,from:n.startedAt,to:n.endedAt});return e}function tc(t){return String(t||"").trim().toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,40).replace(/-+$/g,"")||"view"}function uS(t,e){let n=tc(e),r=new Set(t.views.map(o=>o.slug));if(!r.has(n)&&!np.has(n))return n;for(let o=2;;o++){let s=`${n}-${o}`;if(!r.has(s)&&!np.has(s))return s}}function co(t,{timeoutMs:e=1200}={}){return new Promise(n=>{let r=G0.connect({port:t,host:"127.0.0.1"}),o=s=>{r.destroy(),n(s)};r.setTimeout(e,()=>o(!1)),r.once("connect",()=>o(!0)),r.once("error",()=>o(!1))})}async function rc(t){if(process.platform!=="darwin"){await le("fuser",["-k",`${t}/tcp`]).catch(()=>{});return}let e="";try{({stdout:e}=await le("lsof",["-t","-sTCP:LISTEN","-i",`tcp:${t}`]))}catch{return}for(let n of e.split(/\s+/).filter(Boolean)){let r=Number(n);if(!(!r||r===process.pid))try{process.kill(r,"SIGKILL")}catch{}}}function dp(t=process.env){return new Set([Number(t.PORT)||3362,Number(t.BOSTRAT_TLS_PORT)||3443])}async function dS(t,e){let n=new Set(t.views.map(o=>o.port)),r=dp();if(e){let o=Number(e);if(!Number.isInteger(o)||o<1024||o>65535)throw new Error("invalid port");if(r.has(o))throw new Error(`port ${o} is the node's own API port`);if(n.has(o))throw new Error(`port ${o} is already owned by a view`);return o}for(let o=tp;o<=ep;o++)if(!(n.has(o)||r.has(o))&&!await co(o,{timeoutMs:250}))return o;throw new Error(`no free view port in ${tp}-${ep}`)}async function fS(t){try{let{stdout:e}=await le(N,B("list-sessions","-F","#{session_name}"));return e.split(`
|
|
73
|
+
`).some(n=>n.trim()===er(t))}catch{return!1}}async function lo(t){try{await le(N,B("kill-session","-t",er(t)))}catch{}}async function oc(t){if(!await Yr())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(`
|
|
43
74
|
`).filter(Boolean).slice(-e).join(`
|
|
44
|
-
`)}catch{return""}}var we=new Map,Hx=12e4;async function Wx(t){if(t.state==="stopped"||t.state==="error")return!1;let e=t.origin!=="detected",n=e?await Ux(t.slug):!0;if(e&&!n){let s=t.state==="starting";return t.state=s?"error":"stopped",s&&(t.error=await wf(t.slug)||"server exited before listening"),we.delete(t.id),await ge(t,t.state,t.error?{error:t.error}:{}),!0}if(await Jr(t.port))return we.delete(t.id),t.lastHealthyAt=Date.now(),t.state!=="live"?(t.state="live",t.error=null,await ge(t,"live"),!0):!1;if(t.state==="starting")return Date.now()-t.createdAt>Hx?(t.state="error",t.error=await wf(t.slug)||"never started listening",await ge(t,"error",{error:t.error}),!0):!1;let o=(we.get(t.id)||0)+1;return we.set(t.id,o),o>=3&&t.state==="live"?(t.state="unhealthy",!0):!1}var Ei=!1;async function Is(){if(!Ei){Ei=!0;try{let t=await ye(),e=!1;for(let n of t.views)await Wx(n)&&(e=!0);e&&await re()}finally{Ei=!1}}}async function Rs({trackId:t}={}){await Is();let e=await ye();return(t?e.views.filter(r=>r.trackId===t):e.views).map(r=>({...r}))}async function Tf(t){let n=(await ye()).views.find(r=>r.id===t);return n?{...n}:null}function Pi(t){return ee&&ee.views.find(e=>e.slug===t)||null}async function Bi({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 Dx(r,u),await jx(u,n)},d=await ye();if(i!=="detected"){let w=$i(e),g=String(n).trim(),k=d.views.find(v=>v.trackId===t&&(v.slug===w||$i(v.name)===w||v.cmd===g&&$t.resolve(v.serveCwd??v.cwd)===u));if(k)return qx(d,k,{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>=Ne)throw new Error(`view cap reached (${Ne} live) \u2014 stop one first`);let h={id:Sx().slice(0,8),trackId:t,box:a||null,name:e.trim(),slug:Lx(d,e),cmd:n?String(n).trim():null,cwd:r,serveCwd:u,branch:c,port:await Fx(d,s),origin:i,state:"starting",error:null,createdAt:Date.now(),lastHealthyAt:null};return i!=="detected"&&await Ni(h),d.views.push(h),await re(),await ge(h,"created",{origin:i}),Ri(t,h.id),i==="detected"&&await Jr(h.port)&&(h.state="live",h.lastHealthyAt=Date.now(),await re(),await ge(h,"live")),{...h}}async function qx(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 Px(e.serveCwd??e.cwd,s);if((e.state==="live"||e.state==="starting")&&!i&&!c&&await Jr(e.port))return await re(),Ri(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>=Ne)throw new Error(`view cap reached (${Ne} live) \u2014 stop one first`);return e.origin==="detected"?(await Mi(e.port),e.origin="agent"):await Xr(e.slug),e.cmd=n,s!==void 0&&(e.serveCwd=s),e.state="starting",e.error=null,e.createdAt=Date.now(),we.delete(e.id),await Ni(e),await re(),await ge(e,"created",{restarted:!0,reused:!0}),Ri(e.trackId,e.id),{...e,reused:!0}}async function _f(t){let n=(await ye()).views.find(r=>r.id===t);if(!n)throw new Error("view not found");return n.origin==="detected"?await Mi(n.port):await Xr(n.slug),n.state!=="stopped"&&(n.state="stopped",we.delete(n.id),await re(),await ge(n,"stopped")),{...n}}async function Ef(t){let e=await ye(),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>=Ne)throw new Error(`view cap reached (${Ne} live) \u2014 stop one first`);return await Xr(n.slug),n.state="starting",n.error=null,n.createdAt=Date.now(),we.delete(n.id),await Ni(n),await re(),await ge(n,"created",{restarted:!0}),{...n}}async function If(t){let e=await ye(),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 Mi(r.port):await Xr(r.slug),e.views.splice(n,1),we.delete(r.id),await re(),await ge(r,"deleted"),{ok:!0,id:t,slug:r.slug}}async function Rf(t){let e=await ye(),n=e.views.filter(r=>r.trackId===t);for(let r of n)r.origin!=="detected"&&await Xr(r.slug),we.delete(r.id);return n.length&&(e.views=e.views.filter(r=>r.trackId!==t),await re()),n.length}var Gx=/https?:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0)(?::(\d{2,5}))?(?:\/|\b)/g;async function $f(t,e){if(!t?.id||!Array.isArray(e)||!e.length)return[];let n=await ye(),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 re()),!o.length)return[];let a=new Set;for(let c of o)for(let l of String(c.text||"").matchAll(Gx)){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(!Af().has(c)&&!n.views.some(l=>l.port===c)&&await Jr(c))try{i.push(await Bi({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 dn from"node:fs/promises";import Pe from"node:path";import Kx from"node:os";import{randomUUID as Vx}from"node:crypto";var zn=process.env.BOSTRAT_STATE_DIR||Pe.join(process.env.HOME||Kx.tmpdir(),".bostrat"),Cf=Pe.join(zn,"runbooks.json"),ji=Pe.join(zn,"runbooks"),Of=256*1024,zx=[{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 Yr(t){return zx.filter(({re:e})=>e.test(t)).map(({label:e})=>e)}var un=null,Di=null;async function Be(){return un||(Di||=(async()=>{try{un=JSON.parse(await dn.readFile(Cf,"utf8"))}catch{un={runbooks:[]}}finally{Di=null}return un.runbooks||=[],un})(),Di)}async function Jn(){await Qt(Cf,un)}function Xn(t,e,n={}){return C({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 Vn(t,e){return Pe.join(ji,`${t.id}.v${e}.md`)}function Mf(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 Nf(t){if(typeof t!="string"||!t.trim())throw new Error("md (markdown string) required");let e=Buffer.byteLength(t,"utf8");if(e>Of)throw new Error(`runbook is ${Math.round(e/1024)} KB \u2014 over the ${Of/1024} KB cap; a doc that size belongs in the repo, not a runbook`);let n=Yr(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 Pf(t,e){if(t.trackDeletedAt)throw new Error(`runbook is orphaned (its Track was deleted) \u2014 cannot ${e} it`)}async function $s({trackId:t,status:e}={}){let n=await Be(),r=t?n.runbooks.filter(o=>o.trackId===t):n.runbooks;return e&&(r=r.filter(o=>o.status===e)),r.map(o=>({...o}))}async function Li(t){let n=(await Be()).runbooks.find(r=>r.id===t);return n?{...n}:null}async function Bf({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=Nf(r),c=await Be(),l={id:Vx().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 dn.mkdir(ji,{recursive:!0}),await dn.writeFile(Vn(l,1),r),l.versions.push({v:1,mdRel:Pe.relative(zn,Vn(l,1)),bytes:i,note:null,createdAt:Date.now()}),c.runbooks.push(l),await Jn(),await Xn(l,"created",{version:1}),{...l}}async function Df(t,{md:e,note:n}={}){let o=(await Be()).runbooks.find(i=>i.id===t);if(!o)throw new Error("runbook not found");Pf(o,"update");let s=Nf(e),a=o.version+1;return o.version=a,o.updatedAt=Date.now(),await dn.mkdir(ji,{recursive:!0}),await dn.writeFile(Vn(o,a),e),o.versions.push({v:a,mdRel:Pe.relative(zn,Vn(o,a)),bytes:s,note:n?String(n).slice(0,200):null,createdAt:Date.now()}),await Jn(),await Xn(o,"updated",{version:a,...n?{note:String(n).slice(0,200)}:{}}),{...o}}async function jf(t,e){if(e!=="open"&&e!=="done")throw new Error('status must be "open" or "done"');let r=(await Be()).runbooks.find(o=>o.id===t);if(!r)throw new Error("runbook not found");return Pf(r,e==="done"?"complete":"reopen"),r.status===e?{...r}:(r.status=e,r.updatedAt=Date.now(),await Jn(),await Xn(r,e==="done"?"done":"reopened"),{...r})}async function Lf(t){let e=await Be(),n=e.runbooks.findIndex(o=>o.id===t);if(n===-1)throw new Error("runbook not found");let r=e.runbooks[n];e.runbooks.splice(n,1);for(let o of r.versions||[])await dn.rm(Pe.join(zn,o.mdRel),{force:!0}).catch(()=>{});return await Jn(),r.trackDeletedAt||await Xn(r,"deleted"),{ok:!0,id:t,name:r.name}}async function Ff(t){let n=(await Be()).runbooks.filter(r=>r.trackId===t&&!r.trackDeletedAt);for(let r of n)r.trackDeletedAt=Date.now(),await Xn(r,"orphaned"),r.cwd=null;return n.length&&await Jn(),n.length}async function Uf(t){let e=await Be(),n=e.runbooks.filter(r=>r.trackId===t);for(let r of n){await Xn(r,"deleted");for(let o of r.versions||[])await dn.rm(Pe.join(zn,o.mdRel),{force:!0}).catch(()=>{})}return n.length&&(e.runbooks=e.runbooks.filter(r=>r.trackId!==t),await Jn()),n.length}import to from"node:fs/promises";import np from"node:path";import A0 from"node:os";import{randomUUID as rp}from"node:crypto";import Jx from"node:fs/promises";import Hf from"node:path";import Xx from"node:os";var Wf=()=>Hf.join(process.env.BOSTRAT_STATE_DIR||Hf.join(process.env.HOME||Xx.tmpdir(),".bostrat"),"integrations.json");async function be(){try{return JSON.parse(await Jx.readFile(Wf(),"utf8"))||{}}catch{return{}}}var Os=["linked","prOpened","prMerged","create"],Fi=["prompt","auto","off"],Yx={create:["prompt","off"]},qf=t=>Yx[t]??Fi;function Cs(t){let e=t.linear||{},n=e.transitions===!0?"auto":"prompt",r={};for(let o of Os){let s=qf(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 fn(){let t=await be(),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:Cs(t),defaultTeam:n}:null}async function Ms(){let t=await be(),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:Cs(t)}}async function Ui(){return Cs(await be())}async function Gf(t){let e=Object.entries(t||{});if(!e.length)throw new Error(`nothing to set \u2014 phases: ${Os.join(", ")}`);for(let[r,o]of e){if(!Os.includes(r))throw new Error(`unknown phase "${r}" \u2014 configurable phases: ${Os.join(", ")}`);let s=qf(r);if(!s.includes(o))throw new Error(s.length<Fi.length?`mode "${o}" not allowed for "${r}" \u2014 ${r} is always confirmed (modes: ${s.join(", ")})`:`unknown mode "${o}" \u2014 modes: ${Fi.join(", ")}`)}let n=await be();return n.ticketFlow={...Cs(n),...Object.fromEntries(e)},n.linear&&(delete n.linear.ticketFlow,delete n.linear.transitions),await Qt(Wf(),n,{mode:384}),n.ticketFlow}async function Zr(){let t=(await be()).deepgram||{},e=process.env.DEEPGRAM_API_KEY||t.apiKey;return e?{apiKey:e}:null}async function Zx(){let t=(await be()).openai||{},e=process.env.BOSTRAT_OPENAI_API_KEY||t.apiKey;return e?{apiKey:e}:null}var Qx={openai:{model:"gpt-realtime-2.1-mini",voice:"marin"}};async function Ns(){let e=(await be()).voiceChat||{},n=e.provider||"openai",r=Qx[n];if(!r)return null;let o=n==="openai"?await Zx():null;return o?{provider:n,model:e.model||r.model,voice:e.voice||r.voice,apiKey:o.apiKey}:null}async function Kf(){let t=(await be()).cedar||{},e=process.env.BOSTRAT_CEDAR_INGEST_KEY||t.ingestKey,n=process.env.BOSTRAT_CEDAR_URL||t.url||"https://cedar-api.fly.dev";return e?{url:n,ingestKey:e}:null}async function pn(){let t=(await be()).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 Wi={};$u(Wi,{createComment:()=>c0,findIssueUrl:()=>r0,issueStates:()=>d0,mdToAdf:()=>Jf,moveIssue:()=>f0,parseIssueRef:()=>n0,resolveIssue:()=>o0,uploadImage:()=>l0,upsertCard:()=>s0,whoami:()=>Hi});var Yn=t=>String(t.baseUrl||"").replace(/\/$/,""),zf=t=>`Basic ${Buffer.from(`${t.email}:${t.apiToken}`).toString("base64")}`;async function mn(t,e,n,r){let o=await fetch(`${Yn(t)}${n}`,{method:e,headers:{authorization:zf(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 t0=/\/browse\/([A-Za-z][A-Za-z0-9]{0,9}-\d+)/,e0=/^([A-Z][A-Z0-9]{0,9}-\d+)$/;function n0(t){let e=String(t||"").trim();if(/^https?:\/\//i.test(e)){let r=e.match(t0);return r?r[1].toUpperCase():null}let n=e.match(e0);return n?n[1]:null}function r0(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 o0(t,e){let n;try{n=await mn(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:`${Yn(t)}/browse/${n.key}`,state:n.fields?.status?.name??null}}async function s0(t,e,{url:n,title:r,subtitle:o}){await mn(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 a0=/(!\[[^\]]*\]\([^)\s]+\)|\*\*[^*\n]+\*\*|`[^`\n]+`|_[^_\n]+_|https?:\/\/[^\s)]+)/g;function i0(t){let e=[],n=0;for(let r of t.matchAll(a0)){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 Jf(t){let e=String(t??"").split(/\n{2,}/).map(n=>n.trim()).filter(Boolean).map(n=>{let r=[];return n.split(`
|
|
45
|
-
`).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 co(t.port))return Te.delete(t.id),t.lastHealthyAt=Date.now(),t.state!=="live"?(t.state="live",t.error=null,await Ae(t,"live"),!0):!1;if(t.state==="starting")return Date.now()-t.createdAt>pS?(t.state="error",t.error=await rp(t.slug)||"never started listening",await Ae(t,"error",{error:t.error}),!0):!1;let o=(Te.get(t.id)||0)+1;return Te.set(t.id,o),o>=3&&t.state==="live"?(t.state="unhealthy",!0):!1}var Yi=!1;async function Hs(){if(!Yi){Yi=!0;try{let t=await _e(),e=!1;for(let n of t.views)await mS(n)&&(e=!0);e&&await ue()}finally{Yi=!1}}}async function Ws({trackId:t}={}){await Hs();let e=await _e();return(t?e.views.filter(r=>r.trackId===t):e.views).map(r=>({...r}))}async function fp(t){let n=(await _e()).views.find(r=>r.id===t);return n?{...n}:null}function sc(t){return ce&&ce.views.find(e=>e.slug===t)||null}async function ac({trackId:t,name:e,cmd:n,cwd:r,serveCwd:o,port:s,box:a,origin:i="agent",branch:c=null}){if(!t)throw new Error("trackId required");if(typeof e!="string"||!e.trim())throw new Error("name (string) required");if(i!=="detected"&&(typeof n!="string"||!n.trim()))throw new Error("cmd (string) required");if(!r)throw new Error("cwd required");let l=o!=null,u=Ct.resolve(r,o??"."),f=i==="detected"?null:async()=>{await cS(r,u),await lS(u,n)},d=await _e();if(i!=="detected"){let w=tc(e),g=String(n).trim(),b=d.views.find(v=>v.trackId===t&&(v.slug===w||tc(v.name)===w||v.cmd===g&&Ct.resolve(v.serveCwd??v.cwd)===u));if(b)return hS(d,b,{cmd:g,box:a,branch:c,serveCwd:l?u:void 0,validate:f})}if(f&&await f(),d.views.filter(w=>w.state==="live"||w.state==="starting").length>=We)throw new Error(`view cap reached (${We} live) \u2014 stop one first`);let h={id:z0().slice(0,8),trackId:t,box:a||null,name:e.trim(),slug:uS(d,e),cmd:n?String(n).trim():null,cwd:r,serveCwd:u,branch:c,port:await dS(d,s),origin:i,state:"starting",error:null,createdAt:Date.now(),lastHealthyAt:null};return i!=="detected"&&await oc(h),d.views.push(h),await ue(),await Ae(h,"created",{origin:i}),Qi(t,h.id),i==="detected"&&await co(h.port)&&(h.state="live",h.lastHealthyAt=Date.now(),await ue(),await Ae(h,"live")),{...h}}async function hS(t,e,{cmd:n,box:r,branch:o,serveCwd:s,validate:a}){r&&(e.box=r),o&&(e.branch=o);let i=e.cmd!==n,c=s!==void 0&&!await aS(e.serveCwd??e.cwd,s);if((e.state==="live"||e.state==="starting")&&!i&&!c&&await co(e.port))return await ue(),Qi(e.trackId,e.id),{...e,reused:!0};if(a&&await a(),t.views.filter(f=>f.id!==e.id&&(f.state==="live"||f.state==="starting")).length>=We)throw new Error(`view cap reached (${We} live) \u2014 stop one first`);return e.origin==="detected"?(await rc(e.port),e.origin="agent"):await lo(e.slug),e.cmd=n,s!==void 0&&(e.serveCwd=s),e.state="starting",e.error=null,e.createdAt=Date.now(),Te.delete(e.id),await oc(e),await ue(),await Ae(e,"created",{restarted:!0,reused:!0}),Qi(e.trackId,e.id),{...e,reused:!0}}async function pp(t){let n=(await _e()).views.find(r=>r.id===t);if(!n)throw new Error("view not found");return n.origin==="detected"?await rc(n.port):await lo(n.slug),n.state!=="stopped"&&(n.state="stopped",Te.delete(n.id),await ue(),await Ae(n,"stopped")),{...n}}async function mp(t){let e=await _e(),n=e.views.find(o=>o.id===t);if(!n)throw new Error("view not found");if(!n.cmd)throw new Error("a detected view has no known command \u2014 recreate it as a managed view");if(e.views.filter(o=>o.id!==t&&(o.state==="live"||o.state==="starting")).length>=We)throw new Error(`view cap reached (${We} live) \u2014 stop one first`);return await lo(n.slug),n.state="starting",n.error=null,n.createdAt=Date.now(),Te.delete(n.id),await oc(n),await ue(),await Ae(n,"created",{restarted:!0}),{...n}}async function hp(t){let e=await _e(),n=e.views.findIndex(o=>o.id===t);if(n===-1)throw new Error("view not found");let r=e.views[n];return r.origin==="detected"?await rc(r.port):await lo(r.slug),e.views.splice(n,1),Te.delete(r.id),await ue(),await Ae(r,"deleted"),{ok:!0,id:t,slug:r.slug}}async function wp(t){let e=await _e(),n=e.views.filter(r=>r.trackId===t);for(let r of n)r.origin!=="detected"&&await lo(r.slug),Te.delete(r.id);return n.length&&(e.views=e.views.filter(r=>r.trackId!==t),await ue()),n.length}var wS=/https?:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0)(?::(\d{2,5}))?(?:\/|\b)/g;async function gp(t,e){if(!t?.id||!Array.isArray(e)||!e.length)return[];let n=await _e(),r=n.adoptWatermarks[t.id]??null,o=e.filter(c=>c.role==="assistant"&&c.ts!=null&&(r==null?!1:c.ts>r)),s=e.reduce((c,l)=>l.ts>c?l.ts:c,r??0);if(n.adoptWatermarks[t.id]!==s&&(n.adoptWatermarks[t.id]=s,await ue()),!o.length)return[];let a=new Set;for(let c of o)for(let l of String(c.text||"").matchAll(wS)){let u=Number(l[1]||80);u>=1024&&u<=65535&&a.add(u)}if(!a.size)return[];let i=[];for(let c of a)if(!dp().has(c)&&!n.views.some(l=>l.port===c)&&await co(c))try{i.push(await ac({trackId:t.id,name:`${t.name} preview`,cmd:null,cwd:t.cwd,port:c,origin:"detected",branch:t.workspace?.branch??null}))}catch{}return i}import kn from"node:fs/promises";import qe from"node:path";import gS from"node:os";import{randomUUID as yS}from"node:crypto";var rr=process.env.BOSTRAT_STATE_DIR||qe.join(process.env.HOME||gS.tmpdir(),".bostrat"),bp=qe.join(rr,"runbooks.json"),cc=qe.join(rr,"runbooks"),yp=256*1024,bS=[{label:"Anthropic API key",re:/\bsk-ant-[A-Za-z0-9_-]{20,}/},{label:"OpenAI-style API key",re:/\bsk-(?:proj-|svcacct-)?[A-Za-z0-9_-]{32,}\b/},{label:"Stripe secret key",re:/\bsk_(?:live|test)_[A-Za-z0-9]{16,}/},{label:"GitHub token",re:/\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{36,}|\bgithub_pat_[A-Za-z0-9_]{22,}/},{label:"AWS access key id",re:/\bAKIA[0-9A-Z]{16}\b/},{label:"Slack token",re:/\bxox[baprs]-[A-Za-z0-9-]{10,}/},{label:"private key block",re:/-----BEGIN [A-Z ]*PRIVATE KEY-----/},{label:"JWT",re:/\beyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/}];function uo(t){return bS.filter(({re:e})=>e.test(t)).map(({label:e})=>e)}var bn=null,ic=null;async function Ke(){return bn||(ic||=(async()=>{try{bn=JSON.parse(await kn.readFile(bp,"utf8"))}catch{bn={runbooks:[]}}finally{ic=null}return bn.runbooks||=[],bn})(),ic)}async function or(){await ae(bp,bn)}function sr(t,e,n={}){return O({id:t.trackId,cwd:t.cwd},{type:"runbook.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box||null,provider:t.agent||null,payload:{ts:Date.now(),change:e,runbookId:t.id,name:t.name,status:t.status,...n}}).catch(()=>{})}function nr(t,e){return qe.join(cc,`${t.id}.v${e}.md`)}function kp(t,e=null){let n=t.versions||[];return e!=null?n.find(r=>r.v===Number(e))||null:n.length?n[n.length-1]:null}function xp(t){if(typeof t!="string"||!t.trim())throw new Error("md (markdown string) required");let e=Buffer.byteLength(t,"utf8");if(e>yp)throw new Error(`runbook is ${Math.round(e/1024)} KB \u2014 over the ${yp/1024} KB cap; a doc that size belongs in the repo, not a runbook`);let n=uo(t);if(n.length)throw new Error(`runbook appears to contain a secret VALUE (${n.join(", ")}) \u2014 runbooks ship off-box; name the secret and where it goes, never its value. Replace it with a placeholder like <your key> and retry.`);return e}function Sp(t,e){if(t.trackDeletedAt)throw new Error(`runbook is orphaned (its Track was deleted) \u2014 cannot ${e} it`)}async function qs({trackId:t,status:e}={}){let n=await Ke(),r=t?n.runbooks.filter(o=>o.trackId===t):n.runbooks;return e&&(r=r.filter(o=>o.status===e)),r.map(o=>({...o}))}async function lc(t){let n=(await Ke()).runbooks.find(r=>r.id===t);return n?{...n}:null}async function vp({trackId:t,trackName:e,name:n,md:r,cwd:o,box:s,agent:a}){if(!t)throw new Error("trackId required");if(typeof n!="string"||!n.trim())throw new Error("name (string) required");let i=xp(r),c=await Ke(),l={id:yS().slice(0,8),trackId:t,trackName:e||t,box:s||null,agent:a||null,name:n.trim(),status:"open",version:1,versions:[],trackDeletedAt:null,cwd:o||null,createdAt:Date.now(),updatedAt:Date.now()};return await kn.mkdir(cc,{recursive:!0}),await kn.writeFile(nr(l,1),r),l.versions.push({v:1,mdRel:qe.relative(rr,nr(l,1)),bytes:i,note:null,createdAt:Date.now()}),c.runbooks.push(l),await or(),await sr(l,"created",{version:1}),{...l}}async function Tp(t,{md:e,note:n}={}){let o=(await Ke()).runbooks.find(i=>i.id===t);if(!o)throw new Error("runbook not found");Sp(o,"update");let s=xp(e),a=o.version+1;return o.version=a,o.updatedAt=Date.now(),await kn.mkdir(cc,{recursive:!0}),await kn.writeFile(nr(o,a),e),o.versions.push({v:a,mdRel:qe.relative(rr,nr(o,a)),bytes:s,note:n?String(n).slice(0,200):null,createdAt:Date.now()}),await or(),await sr(o,"updated",{version:a,...n?{note:String(n).slice(0,200)}:{}}),{...o}}async function Ap(t,e){if(e!=="open"&&e!=="done")throw new Error('status must be "open" or "done"');let r=(await Ke()).runbooks.find(o=>o.id===t);if(!r)throw new Error("runbook not found");return Sp(r,e==="done"?"complete":"reopen"),r.status===e?{...r}:(r.status=e,r.updatedAt=Date.now(),await or(),await sr(r,e==="done"?"done":"reopened"),{...r})}async function _p(t){let e=await Ke(),n=e.runbooks.findIndex(o=>o.id===t);if(n===-1)throw new Error("runbook not found");let r=e.runbooks[n];e.runbooks.splice(n,1);for(let o of r.versions||[])await kn.rm(qe.join(rr,o.mdRel),{force:!0}).catch(()=>{});return await or(),r.trackDeletedAt||await sr(r,"deleted"),{ok:!0,id:t,name:r.name}}async function Ep(t){let n=(await Ke()).runbooks.filter(r=>r.trackId===t&&!r.trackDeletedAt);for(let r of n)r.trackDeletedAt=Date.now(),await sr(r,"orphaned"),r.cwd=null;return n.length&&await or(),n.length}async function Ip(t){let e=await Ke(),n=e.runbooks.filter(r=>r.trackId===t);for(let r of n){await sr(r,"deleted");for(let o of r.versions||[])await kn.rm(qe.join(rr,o.mdRel),{force:!0}).catch(()=>{})}return n.length&&(e.runbooks=e.runbooks.filter(r=>r.trackId!==t),await or()),n.length}import mo from"node:fs/promises";import Wp from"node:path";import JS from"node:os";import{randomUUID as qp}from"node:crypto";import kS from"node:fs/promises";import Rp from"node:path";import xS from"node:os";var $p=()=>Rp.join(process.env.BOSTRAT_STATE_DIR||Rp.join(process.env.HOME||xS.tmpdir(),".bostrat"),"integrations.json");async function Ee(){try{return JSON.parse(await kS.readFile($p(),"utf8"))||{}}catch{return{}}}var Ks=["linked","prOpened","prMerged","create"],uc=["prompt","auto","off"],SS={create:["prompt","off"]},Op=t=>SS[t]??uc;function Gs(t){let e=t.linear||{},n=e.transitions===!0?"auto":"prompt",r={};for(let o of Ks){let s=Op(o),a=s.includes(n)?n:"prompt",i=t.ticketFlow?.[o]??e.ticketFlow?.[o];r[o]=s.includes(i)?i:a}return r}async function xn(){let t=await Ee(),e=process.env.BOSTRAT_LINEAR_API_KEY||t.linear?.apiKey,n=process.env.BOSTRAT_LINEAR_DEFAULT_TEAM||t.linear?.defaultTeam||null;return e?{apiKey:e,ticketFlow:Gs(t),defaultTeam:n}:null}async function zs(){let t=await Ee(),e=t.jira||{},n=process.env.BOSTRAT_JIRA_BASE_URL||e.baseUrl,r=process.env.BOSTRAT_JIRA_EMAIL||e.email,o=process.env.BOSTRAT_JIRA_API_TOKEN||e.apiToken;return!n||!r||!o?null:{baseUrl:String(n).replace(/\/$/,""),email:r,apiToken:o,tokenExpiresAt:e.tokenExpiresAt??null,ticketFlow:Gs(t)}}async function dc(){return Gs(await Ee())}async function Mp(t){let e=Object.entries(t||{});if(!e.length)throw new Error(`nothing to set \u2014 phases: ${Ks.join(", ")}`);for(let[r,o]of e){if(!Ks.includes(r))throw new Error(`unknown phase "${r}" \u2014 configurable phases: ${Ks.join(", ")}`);let s=Op(r);if(!s.includes(o))throw new Error(s.length<uc.length?`mode "${o}" not allowed for "${r}" \u2014 ${r} is always confirmed (modes: ${s.join(", ")})`:`unknown mode "${o}" \u2014 modes: ${uc.join(", ")}`)}let n=await Ee();return n.ticketFlow={...Gs(n),...Object.fromEntries(e)},n.linear&&(delete n.linear.ticketFlow,delete n.linear.transitions),await ae($p(),n,{mode:384}),n.ticketFlow}async function fo(){let t=(await Ee()).deepgram||{},e=process.env.DEEPGRAM_API_KEY||t.apiKey;return e?{apiKey:e}:null}async function vS(){let t=(await Ee()).openai||{},e=process.env.BOSTRAT_OPENAI_API_KEY||t.apiKey;return e?{apiKey:e}:null}var TS={openai:{model:"gpt-realtime-2.1-mini",voice:"marin"}};async function Vs(){let e=(await Ee()).voiceChat||{},n=e.provider||"openai",r=TS[n];if(!r)return null;let o=n==="openai"?await vS():null;return o?{provider:n,model:e.model||r.model,voice:e.voice||r.voice,apiKey:o.apiKey}:null}async function Cp(){let t=(await Ee()).cedar||{},e=process.env.BOSTRAT_CEDAR_INGEST_KEY||t.ingestKey,n=process.env.BOSTRAT_CEDAR_URL||t.url||"https://cedar-api.fly.dev";return e?{url:n,ingestKey:e}:null}async function Sn(){let t=(await Ee()).slack||{},e=process.env.BOSTRAT_SLACK_BOT_TOKEN||t.botToken,n=process.env.BOSTRAT_SLACK_APP_TOKEN||t.appToken;return!e||!n?null:{botToken:e,appToken:n,channel:process.env.BOSTRAT_SLACK_CHANNEL||t.channel||null,maxConcurrent:Number.isInteger(t.maxConcurrent)&&t.maxConcurrent>0?t.maxConcurrent:2}}var pc={};Vu(pc,{createComment:()=>CS,findIssueUrl:()=>IS,issueStates:()=>BS,mdToAdf:()=>Bp,moveIssue:()=>DS,parseIssueRef:()=>ES,resolveIssue:()=>RS,uploadImage:()=>NS,upsertCard:()=>$S,whoami:()=>fc});var ar=t=>String(t.baseUrl||"").replace(/\/$/,""),Pp=t=>`Basic ${Buffer.from(`${t.email}:${t.apiToken}`).toString("base64")}`;async function vn(t,e,n,r){let o=await fetch(`${ar(t)}${n}`,{method:e,headers:{authorization:Pp(t),accept:"application/json",...r!==void 0?{"content-type":"application/json"}:{}},body:r!==void 0?JSON.stringify(r):void 0}),s=await o.text(),a=null;try{a=s?JSON.parse(s):null}catch{a=null}if(!o.ok){let i=a?.errorMessages?.[0]??Object.values(a?.errors??{})[0]??`HTTP ${o.status}`,c=new Error(`jira: ${i}`);throw c.status=o.status,c}return a}var AS=/\/browse\/([A-Za-z][A-Za-z0-9]{0,9}-\d+)/,_S=/^([A-Z][A-Z0-9]{0,9}-\d+)$/;function ES(t){let e=String(t||"").trim();if(/^https?:\/\//i.test(e)){let r=e.match(AS);return r?r[1].toUpperCase():null}let n=e.match(_S);return n?n[1]:null}function IS(t,e=null){let n=null;try{n=e?.baseUrl?new URL(e.baseUrl).host:null}catch{n=null}let r=/https?:\/\/[^\s)]+\/browse\/[A-Za-z][A-Za-z0-9]{0,9}-\d+[^\s)]*/g;for(let o of String(t||"").matchAll(r))try{let s=new URL(o[0]).host;if(s===n||/\.atlassian\.net$/i.test(s))return o[0]}catch{}return null}async function RS(t,e){let n;try{n=await vn(t,"GET",`/rest/api/3/issue/${encodeURIComponent(e)}?fields=summary,status`)}catch(r){throw r.status===404?new Error(`jira: issue "${e}" not found`):r}return{issueId:n.id,key:n.key,title:n.fields?.summary??null,url:`${ar(t)}/browse/${n.key}`,state:n.fields?.status?.name??null}}async function $S(t,e,{url:n,title:r,subtitle:o}){await vn(t,"POST",`/rest/api/3/issue/${encodeURIComponent(e.key)}/remotelink`,{globalId:n,application:{type:"com.bostrat.tracks",name:"Bostrat"},object:{url:n,title:r,summary:o,icon:{title:"Bostrat"}}})}var OS=/(!\[[^\]]*\]\([^)\s]+\)|\*\*[^*\n]+\*\*|`[^`\n]+`|_[^_\n]+_|https?:\/\/[^\s)]+)/g;function MS(t){let e=[],n=0;for(let r of t.matchAll(OS)){r.index>n&&e.push({type:"text",text:t.slice(n,r.index)});let o=r[0];if(o.startsWith("![")){let[,s,a]=o.match(/^!\[([^\]]*)\]\(([^)\s]+)\)$/);e.push({type:"text",text:s||a,marks:[{type:"link",attrs:{href:a}}]})}else o.startsWith("**")?e.push({type:"text",text:o.slice(2,-2),marks:[{type:"strong"}]}):o.startsWith("`")?e.push({type:"text",text:o.slice(1,-1),marks:[{type:"code"}]}):o.startsWith("_")?e.push({type:"text",text:o.slice(1,-1),marks:[{type:"em"}]}):e.push({type:"text",text:o,marks:[{type:"link",attrs:{href:o}}]});n=r.index+o.length}return n<t.length&&e.push({type:"text",text:t.slice(n)}),e}function Bp(t){let e=String(t??"").split(/\n{2,}/).map(n=>n.trim()).filter(Boolean).map(n=>{let r=[];return n.split(`
|
|
76
|
+
`).forEach((o,s)=>{s>0&&r.push({type:"hardBreak"}),r.push(...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 fc(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 gc={};Vu(gc,{createComment:()=>KS,createIssue:()=>wc,createProject:()=>hc,findIssueUrl:()=>HS,issueStates:()=>zS,moveIssue:()=>VS,parseIssueRef:()=>US,resolveIssue:()=>WS,teams:()=>ir,uploadImage:()=>GS,upsertCard:()=>qS,whoami:()=>mc});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 mc(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!) {
|
|
46
77
|
fileUpload(contentType: $contentType, filename: $filename, size: $size) {
|
|
47
78
|
success
|
|
48
79
|
uploadFile { uploadUrl assetUrl headers { key value } }
|
|
49
80
|
}
|
|
50
|
-
}`,{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 S0(t,e){let n=await oe(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 v0(t,e,n){if(!(await oe(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 Zn(t){return((await oe(t,"query { teams(first: 100) { nodes { id key name } } }")).teams?.nodes??[]).map(n=>({id:n.id,key:n.key,name:n.name}))}var Xf=255;async function Gi(t,{teamId:e,name:n,description:r}){let o=r||null,s=!!o&&o.length>Xf,a=s?`${o.slice(0,Xf-1).trimEnd()}\u2026`:o,i=await oe(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 Ki(t,{teamId:e,title:n,description:r,projectId:o,priority:s}){let a=await oe(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 op={linear:{api:Vi,config:fn},jira:{api:Wi,config:Ms}};async function Qi(){let t=[];for(let[e,{api:n,config:r}]of Object.entries(op)){let o=await r();o&&t.push({name:e,api:n,cfg:o})}return t}async function hn(t){let e=op[t?.provider];if(!e)return null;let n=await e.config();return n?{name:t.provider,api:e.api,cfg:n}:null}function tc(t,e){if(!t||t.name)return t;let n=e.find(r=>r.id===t.id);return n?{...t,name:n.name}:t}var sp=()=>process.env.BOSTRAT_STATE_DIR||np.join(process.env.HOME||A0.tmpdir(),".bostrat"),Yi=()=>np.join(sp(),"tickets.json"),ap=()=>(process.env.BOSTRAT_PUBLIC_URL||"http://bostrat.globopi").replace(/\/$/,""),Bs=t=>`${ap()}/track/${t}`,Ps=null,zi=null,Ji=Promise.resolve();async function Ds(){return Ps||(zi??=to.readFile(Yi(),"utf8").then(t=>JSON.parse(t)?.tickets??{}).catch(()=>({})).then(t=>Ps=t).finally(()=>zi=null),zi)}async function Yf(){let t=JSON.stringify({tickets:Ps},null,2),e=`${Yi()}.${process.pid}.tmp`;await to.mkdir(sp(),{recursive:!0}),await to.writeFile(e,t),await to.rename(e,Yi())}async function De(){Ji=Ji.then(Yf,Yf),await Ji.catch(()=>{})}async function se(t){return(await Ds())[t]??null}async function js(){return{...await Ds()}}async function ip(){return(await Qi()).length>0}async function Qn(t,e,{source:n="api"}={}){let r=await Qi();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 Ds())[t.id]=c,await De(),await C({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 eo(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 Qr(c,`**Bostrat** \u2014 Track **${c.track.name}** linked${l.length?` (${l.join(", ")})`:""}.`),await up(t.id,c,"linked"),c}async function ec(t){let e=await Ds();return e[t]?(delete e[t],await De(),!0):!1}var Zi=t=>`$${t>=100?Math.round(t):t.toFixed(2)}`;async function cp(t,e){if(!t.track?.cwd)return null;let n=Zt(await j({id:e,cwd:t.track.cwd}));return!n.totalUsd||n.totalUsd<.01?null:n}function T0(t){let e=Object.entries(t.byProvider).sort((r,o)=>o[1].usd-r[1].usd).map(([r,o])=>`${r} ${Zi(o.usd)}`).join(" \xB7 "),n=Object.keys(t.byBox).length;return`${Zi(t.totalUsd)}${e?` (${e})`:""}${n>1?` across ${n} boxes`:""}`}function _0(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(Zi(e.totalUsd)),n.join(" \xB7 ")||"linked"}async function eo(t,e){let n=await hn(e);if(!n)return;let r=await cp(e,t).catch(()=>null);await n.api.upsertCard(n.cfg,e,{url:Bs(t),title:`Bostrat Track: ${e.track.name}`,subtitle:_0(e,r)})}function Ls(t){let e=new URL(ap());return`${e.protocol}//${t}.${e.host}/`}var E0={linked:"linked",opened:"prOpened",merged:"prMerged"};function I0(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 lp(t,e,{from:n,to:r,mode:o,phase:s=null,actor:a="user"}){e.state=r,await De(),await C({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 up(t,e,n){let r=await hn(e);if(!r)return;let o=r.cfg.ticketFlow?.[E0[n]]??"prompt";if(o==="off")return;let{current:s,states:a}=await r.api.issueStates(r.cfg,e),i=tc(s,a),c=I0(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 lp(t,e,{from:l,to:c.name,mode:"auto",phase:n,actor:"system"});return}await C({id:t,cwd:e.track?.cwd},{type:"ticket.move.offered",actor:"system",source:"orchestrator",payload:{ts:Date.now(),offerId:rp(),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 dp(t){let e=await se(t);if(!e)throw new Error("no ticket linked to this Track");let n=await hn(e);if(!n)throw new Error("no ticketing integration configured");let{current:r,states:o}=await n.api.issueStates(n.cfg,e),s=tc(r,o);return s?.name&&e.state!==s.name&&(e.state=s.name,await De()),{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 nc(t,e,{mode:n="manual"}={}){if(!e)throw new Error("stateId required");let r=await se(t);if(!r)throw new Error("no ticket linked to this Track");let o=await hn(r);if(!o)throw new Error("no ticketing integration configured");let{current:s,states:a}=await o.api.issueStates(o.cfg,r),i=tc(s,a),c=a.find(u=>u.id===e);if(!c)throw new Error("unknown state for this ticket's team");let l=i?.name??r.state??null;return i?.id===c.id?(r.state!==c.name&&(r.state=c.name,await De()),{key:r.key,from:l,to:c.name,moved:!1}):(await o.api.moveIssue(o.cfg,r,c.id),await lp(t,r,{from:l,to:c.name,mode:n}),{key:r.key,from:l,to:c.name,moved:!0})}async function fp(t,e){let n=await se(t);if(!n)throw new Error("no ticket linked to this Track");let r=await hn(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 C({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 pp(t,e,n){if(!e)throw new Error("offerId required");let r=await j({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 nc(t.id,o.payload.to?.id,{mode:"offer"})),await C({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 Zf=50;async function mp(){let t=await fn();if(!t)throw new Error("no ticketing integration configured");return Zn(t)}function R0(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>Zf)throw new Error(`too many issues (${n.length}); cap is ${Zf} per proposal`);return{epic:r,issues:n,team:e.team?String(e.team).trim():null,linkCurrentTrack:e.linkCurrentTrack===!0}}async function $0(t,e,n,r){let o=r??await Zn(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 se(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 hp(t,e){let n=await fn();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=R0(e),o=await Zn(n),s=await $0(n,r,t,o),a=s?{id:s.id,key:s.key,name:s.name}:null,i=rp();return await C({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 wp(t,e,n,{teamId:r=null}={}){if(!e)throw new Error("proposalId required");let o=await j({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 C({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 fn();if(!a)throw new Error("no ticketing integration configured");let i=s.payload,c=i.team??null;if(!c&&r){let h=(await Zn(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 Gi(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 Ki(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 C({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 C({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 se(t.id)&&(m=await Qn(t,d[0].key,{source:"created"}).catch(()=>null)),{proposalId:e,action:"created",created:l,failed:u,linked:m?m.key:null}}async function Qr(t,e){let n=await hn(t);n&&await n.api.createComment(n.cfg,t,e)}async function O0(t,e,n){let r=await hn(t);if(!r)return null;let o=await Me(e);if(!o)return null;let s=ln(o,n);if(!s)return null;let a=await to.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 Qf=()=>Number(process.env.BOSTRAT_TICKET_COST_SYNC_MS??12e4),Xi=new Map;function C0(t,e){if(Qf()<=0)return eo(t,e);if(Xi.has(t))return;let n=setTimeout(()=>{Xi.delete(t),se(t).then(r=>r?eo(t,r):null).catch(()=>{})},Qf());n.unref?.(),Xi.set(t,n)}var tp=new Map;async function ep(t,e,n){let r=tp.get(t)??0;Date.now()-r<600*1e3||(tp.set(t,Date.now()),await C({id:t,cwd:e},{type:"ticket.sync.failed",actor:"system",source:"orchestrator",payload:{ts:Date.now(),message:n?.message||String(n)}}).catch(()=>{}))}async function M0(t,{resolveTrack:e=null}={}){let n=await se(t.trackId),r=t.payload||{};if(!n){if(t.type!=="turn.user"||t.actor!=="user")return;let o=null;for(let a of await Qi())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 Qn(s,o,{source:"chat"})}catch(a){await ep(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 De())}try{switch(t.type){case"handoff.done":n.track.leadAgent=r.toAgent??n.track.leadAgent,await De(),await eo(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 De(),await eo(t.trackId,n),r.change==="opened"||r.change==="merged"){let o=r.change==="merged"?await cp(n,t.trackId).catch(()=>null):null;await Qr(n,`**Bostrat** \u2014 PR ${r.change} for **${n.track.name}**${r.url?`: ${r.url}`:r.number?` (#${r.number})`:""}`+(o?`
|
|
51
|
-
|
|
52
|
-
Agent cost for this Track: ${
|
|
53
|
-
|
|
54
|
-
`)}else if(r.change==="picked"){let o=(r.siblings??[]).map(s=>`**${s.name}**`).join(", ");await
|
|
55
|
-
_(runs on the Track's branch; reachable on the operator's private network only)_`);break}case"cost.recorded":await C0(t.trackId,n);break;default:break}}catch(o){let s=e?await e(t.trackId).catch(()=>null):null;await ep(t.trackId,s?.cwd??null,o)}}function gp({resolveTrack:t}){Lt.on("event",e=>{M0(e,{resolveTrack:t}).catch(()=>{})})}import Fs from"node:fs/promises";import yp from"node:path";import N0 from"node:os";var bp=process.env.BOSTRAT_STATE_DIR||yp.join(process.env.HOME||N0.tmpdir(),".bostrat"),oc=yp.join(bp,"pr-links.json"),lt=(t,e)=>`${t}#${Number(e)}`,Us=null,rc=null,D=Promise.resolve();async function yt(){return Us||(rc??=Fs.readFile(oc,"utf8").then(t=>JSON.parse(t)?.links??{}).catch(()=>({})).then(t=>Us=t).finally(()=>rc=null),rc)}async function X(){let t=JSON.stringify({links:Us},null,2),e=`${oc}.${process.pid}.tmp`;await Fs.mkdir(bp,{recursive:!0}),await Fs.writeFile(e,t),await Fs.rename(e,oc)}var P0=new Set(["api","observed","reconciled"]);async function Pt({repo:t,number:e,trackId:n,url:r=null,headRef:o=null,title:s=null,source:a="matched"}){if(!t||!Number.isInteger(Number(e))||!n)return{link:null,created:!1};let i=await yt(),c=lt(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:P0.has(a),firstSeen:Date.now()},D=D.then(X,X),await D.catch(()=>{}),{link:i[c],created:!0})}async function no(t,e){if(!t||e==null)return null;let n=(await yt())[lt(t,e)];return!n||n.announced?null:(n.announced=!0,D=D.then(X,X),await D.catch(()=>{}),n)}async function Tt(t,e){return!t||e==null?null:(await yt())[lt(t,e)]??null}async function je(t,e,n){if(!t||e==null)return null;let r=(await yt())[lt(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(X,X),await D.catch(()=>{}),r):null}async function kp(t,e,{headSha:n=null,trackId:r=null}={}){if(!t||e==null)return null;let o=(await yt())[lt(t,e)];return o?(o.ciFix={delegatedAt:Date.now(),headSha:n,trackId:r},D=D.then(X,X),await D.catch(()=>{}),o):null}async function xp(t,e,{headSha:n=null,trackId:r=null}={}){if(!t||e==null)return null;let o=(await yt())[lt(t,e)];return o?(o.reconcile={delegatedAt:Date.now(),headSha:n,trackId:r},D=D.then(X,X),await D.catch(()=>{}),o):null}async function Hs(t,e,{headSha:n=null}={}){if(!t||e==null)return null;let r=(await yt())[lt(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(X,X),await D.catch(()=>{}),r)}async function Ws(t,e){if(!t||e==null)return null;let n=(await yt())[lt(t,e)];return!n||!n.ciFixAwait?null:(delete n.ciFixAwait,D=D.then(X,X),await D.catch(()=>{}),n)}async function qs(t,e,{headSha:n=null}={}){if(!t||e==null)return null;let r=(await yt())[lt(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(X,X),await D.catch(()=>{}),r)}async function Gs(t,e){if(!t||e==null)return null;let n=(await yt())[lt(t,e)];return!n||!n.reconcileAwait?null:(delete n.reconcileAwait,D=D.then(X,X),await D.catch(()=>{}),n)}async function Sp(t,e,{at:n=Date.now()}={}){if(!t||e==null)return null;let r=(await yt())[lt(t,e)];return r?(r.baseFoldAt=n,D=D.then(X,X),await D.catch(()=>{}),r):null}async function vp(t,e){if(!t||e==null)return null;let n=(await yt())[lt(t,e)];return!n||!n.baseFoldAt?null:(delete n.baseFoldAt,D=D.then(X,X),await D.catch(()=>{}),n)}async function Ap(t,e,n){if(!t||e==null||!n)return null;let r=(await yt())[lt(t,e)];return r?(r.lastHeadSha=n,D=D.then(X,X),await D.catch(()=>{}),r):null}async function ro(t,e,{merge:n=null}={}){if(!t||e==null)return null;let r=(await yt())[lt(t,e)];return r?(r.armedAt=Date.now(),n&&(r.autoMerge={method:n.method||"merge",armedAt:r.armedAt}),D=D.then(X,X),await D.catch(()=>{}),r):null}async function tr(t,e,n=!0){if(!t||e==null)return null;let r=(await yt())[lt(t,e)];return r?(n?r.archiveOnMerge=!0:delete r.archiveOnMerge,D=D.then(X,X),await D.catch(()=>{}),r):null}async function Ks(t,e,n=!0){if(!t||e==null)return null;let r=(await yt())[lt(t,e)];return r?(n?r.notifyOnMerge=!0:delete r.notifyOnMerge,D=D.then(X,X),await D.catch(()=>{}),r):null}async function Tp(t,e,n){if(!t||e==null)return null;let r=(await yt())[lt(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(X,X),await D.catch(()=>{}),r}async function oo(t,e){if(!t||e==null)return null;let n=(await yt())[lt(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(X,X),await D.catch(()=>{}),n)}var B0=new Set(["merged","closed","lost"]);async function _p(){return Object.values(await yt()).filter(t=>!B0.has(t.state))}async function ae(){return Object.values(await yt()).sort((t,e)=>e.firstSeen-t.firstSeen)}var Ip=t=>Math.round(t*100)/100;function D0(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():Ep.basename(e.base)}))}function Rp(t){let e=new Map;for(let n of t||[])for(let r of D0(n)){let o=e.get(r.key);o||e.set(r.key,o={...r,tracks:[]}),o.tracks.push(n)}return e}function Vs(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 sc=(t,e)=>`${t} ${e}${t===1?"":"s"}`;function j0(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 ac(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(sc(s,"PR")+" opened");let a=Object.keys(t?.subtasks||{}).length||r.filter(u=>u.type==="subtask-delegated").length;a&&n.push(sc(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(`${sc(i.length,"handoff")} ${u}${i.length>3?"\u2026":""}`)}let c=Zt(e);c.totalUsd>=.01&&n.push(`$${c.totalUsd.toFixed(2)}`);let l=Number.isFinite(t?.archivedAt)&&Number.isFinite(t?.createdAt)?j0(t.archivedAt-t.createdAt):null;return n.length&&l&&n.push(l),n.length?n.join(" \xB7 "):null}function L0(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 $p(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=Vs(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:Ip(l),firstActivityAt:c,lastActivityAt:i}}async function Op(t){let e=Rp(t),n=await ae().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 j(i).catch(()=>[]);r.set(i.id,Zt(c).totalUsd)}let s=[...e.values()].map(a=>$p(a,r,n)).sort((a,i)=>(i.lastActivityAt??0)-(a.lastActivityAt??0));return{generatedAt:Date.now(),arcs:s}}async function Cp(t,e){let r=Rp(t).get(e);if(!r)return null;let[o,s,a,i,c]=await Promise.all([ae().catch(()=>[]),As().catch(()=>[]),Rs().catch(()=>[]),$s().catch(()=>[]),js().catch(()=>({}))]),l=new Map,u=[],f=(d,m)=>(Vs(m)||m.createdAt||0)-(Vs(d)||d.createdAt||0);for(let d of[...r.tracks].sort(f)){let m=await j(d).catch(()=>[]),h=Zt(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:L0(d),handoffs:(d.events||[]).filter(g=>g.type==="lead-handoff").length,subtasks:Object.keys(d.subtasks||{}).length,repos:Ot(d.workspace).map(g=>g.repo||Ep.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:Ip(h.totalUsd),lastActivityAt:Vs(d)})}return{...$p(r,l,o),generatedAt:Date.now(),segments:u}}import{execFile as F0}from"node:child_process";import{promisify as U0}from"node:util";import{randomUUID as H0}from"node:crypto";var wn=U0(F0),zs=t=>new Promise(e=>setTimeout(e,t)),W0=Number(process.env.BOSTRAT_SWAP_EXIT_TIMEOUT_MS)||15e3,Mp=Number(process.env.BOSTRAT_SWAP_SHELL_TIMEOUT_MS)||8e3,q0=250,G0=400,Np={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 Bp(t){let e=await Wu(t);if(!e)return!1;try{return await wn("pgrep",["-P",String(e)]),!1}catch(n){return n?.code===1}}async function ic(t,e){let n=Date.now()+e;for(;;){if(await Bp(t))return!0;if(Date.now()>n)return!1;await zs(q0)}}async function K0(t,e){let n=Np[e]||Np.default;n.escape&&(await bt(t,["Escape"],{kind:"swap-exit"}),await zs(150)),n.wipe&&await ss(t,400,{kind:"swap-exit"}),await as(t,n.text,{perCharDelayMs:90,kind:"swap-exit"});let r=me(e).submitDelayMs||0;await zs(Math.max(r,250)),await bt(t,["Enter"],{kind:"swap-exit"})}async function V0(t,e){try{let{stdout:n}=await wn(N,B("show-environment","-t",t,e));return!String(n||"").trimStart().startsWith("-")}catch{return!1}}async function Pp(t){await wn(N,B("respawn-pane","-k","-t",t))}async function cc(t,e={}){if(!ht(t))throw new Error("invalid session name");let n=String(e.agentCmd||"").trim();if(!n)throw new Error("agentCmd is required");let r=await J(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||jn(n),a=Number(e.exitTimeoutMs)||W0;await G({flow:"swap",phase:"start",sessionName:t,from:o,to:s,cwd:r.cwd});let i=!1;if(!await Bp(t)&&(await K0(t,o),!await ic(t,a)&&(i=!0,await G({flow:"swap",phase:"respawn-forced",sessionName:t,from:o}),await Pp(t),!await ic(t,Mp))))throw new Error(`pane shell did not come back after respawn-pane in "${t}"`);let c=s==="claude"&&Dn(n),l=c?Qo():null,u=await V0(t,"ANTHROPIC_API_KEY"),f=!1;try{l?(await wn(N,B("set-environment","-t",t,"ANTHROPIC_API_KEY",l)),f=!0):u&&(await wn(N,B("set-environment","-u","-t",t,"ANTHROPIC_API_KEY")),f=!0)}catch(g){throw l?ns(g,l):g}if(f&&!i&&(await Pp(t),!await ic(t,Mp)))throw new Error(`pane shell did not come back after env respawn in "${t}"`);let d=!!e.resumeSessionId,m=e.resumeSessionId||H0(),h=d?n:ni(n,m),w=d?e.pinned===!0:/(^|\s)--session-id(\s|=)/.test(h);return c&&await Ee(r.cwd),s==="codex"&&await Nn(r.cwd),s==="cursor"&&await Pn(r.cwd),await an(t),await wn(N,B("set-option","-t",t,"@bostrat-sid",m)),await wn(N,B("set-option","-t",t,"@bostrat-agent",s)),await zs(G0),await xt(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 oS from"node:fs";import jp from"node:fs/promises";import ao from"node:path";import sS from"node:os";import{fileURLToPath as aS}from"node:url";import{execFile as iS}from"node:child_process";import{promisify as cS}from"node:util";import uc from"node:fs/promises";import Le from"node:path";import{execFile as z0}from"node:child_process";import{promisify as J0}from"node:util";var X0=J0(z0),Dp=process.env.HOME||"/home/globo",Y0=Le.join(process.env.BOSTRAT_STATE_DIR||Le.join(Dp,".bostrat"),"repos"),Z0=[...(process.env.BOSTRAT_REPOS_ROOT||Le.join(Dp,"Documents")).split(","),Y0].map(t=>t.trim()).filter(Boolean),Q0=Number(process.env.BOSTRAT_REPOS_DEPTH)||3,tS=new Set(["node_modules",".git",".cache","dist","build",".next","vendor","__pycache__","venv",".venv","target"]);async function eS(t){try{return(await uc.stat(Le.join(t,".git"))).isDirectory()?"repo":"worktree"}catch{return null}}async function nS(t){let e=i=>uc.readFile(Le.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 ke(){let t=new Map;async function e(o,s){if(t.has(o))return;let a=await eS(o);if(a==="worktree")return;if(a==="repo"){t.set(o,{path:o,name:Le.basename(o),branch:await nS(o)});return}if(s>=Q0)return;let i;try{i=await uc.readdir(o,{withFileTypes:!0})}catch{return}for(let c of i)!c.isDirectory()||c.name.startsWith(".")||tS.has(c.name)||await e(Le.join(o,c.name),s+1)}for(let o of Z0)await e(Le.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 rS(t){let e=String(t||"").trim().match(/github\.com[:/]+([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/i);return e?`${e[1]}/${e[2]}`:null}var lc=new Map;async function Z(t){if(!t)return null;if(lc.has(t))return lc.get(t);let e=null;try{let{stdout:n}=await X0("git",["-C",t,"config","--get","remote.origin.url"],{timeout:5e3});e=rS(n)}catch{e=null}return lc.set(t,e),e}async function so(t){let e=String(t||"").toLowerCase();if(!e)return null;for(let n of await ke())if((await Z(n.path))?.toLowerCase()===e)return n.path;return null}var Lp=cS(iS),lS=process.env.BOSTRAT_STATE_DIR||ao.join(process.env.HOME||sS.tmpdir(),".bostrat"),uS=ao.join(lS,"repos"),Fp=/^[^/\s]+\/[^/\s]+$/,dS=5*6e4,fS=6e4,pS=10*6e4,Up=fetch,dc=new Map,gn=null;function nr(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 Js(t=process.env){return!!(nr(t)&&t.BOSTRAT_PLANE_TOKEN)}var Hp=t=>({authorization:`Bearer ${t.BOSTRAT_PLANE_TOKEN||""}`}),er=(t,e)=>Object.assign(new Error(t),{status:e});async function Xs(t,{env:e=process.env}={}){let n=String(t||"").toLowerCase();if(!Fp.test(n))throw er("repo must be owner/name",400);let r=dc.get(n);if(r&&r.expiresAt-Date.now()>dS)return r;if(!Js(e))throw er("node is not plane-linked \u2014 GitHub auth unavailable",503);let o;try{o=await Up(`${nr(e)}/v1/github/token?repo=${encodeURIComponent(t)}`,{headers:Hp(e)})}catch(i){if(r&&r.expiresAt>Date.now())return r;throw er(`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 er(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 dc.set(n,a),a}async function io({env:t=process.env,force:e=!1}={}){if(!Js(t))return{configured:!1,repos:[],username:null};if(!e&&gn&&Date.now()-gn.at<fS)return gn.data;try{let n=await Up(`${nr(t)}/v1/github/repos`,{headers:Hp(t)});if(!n.ok)throw new Error(`plane replied ${n.status}`);let r=await n.json();return gn={at:Date.now(),data:r},r}catch(n){return gn?gn.data:{configured:!0,repos:[],username:null,error:n?.message||String(n)}}}function Wp(){dc.clear(),gn=null}async function ie({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 Xs(r,{env:n});return o?{GH_TOKEN:o}:{}}catch{return{}}}async function fc({env:t=process.env}={}){return(await io({env:t})).username??null}var mS=()=>aS(new URL("../bin/bostrat-git-credential",import.meta.url)),hS=(t=process.env)=>`http://127.0.0.1:${Number(t.PORT)||3362}`;function wS(t){let[e,n]=String(t).split("/");return ao.join(uS,e,n)}async function pc(t,{env:e=process.env}={}){let n=(...r)=>Lp("git",["-C",t,...r],{timeout:1e4});await n("config","--replace-all","credential.helper",""),await n("config","--add","credential.helper",`!"${mS()}" ${hS(e)}`),await n("config","credential.useHttpPath","true")}async function mc(t,{env:e=process.env,baseUrl:n="https://github.com"}={}){if(!Fp.test(String(t||"")))throw er(`bad repo slug "${t}"`,400);let r=wS(t);if(oS.existsSync(ao.join(r,".git")))return r;let{token:o}=await Xs(t,{env:e});await jp.mkdir(ao.dirname(r),{recursive:!0});let s="!f() { echo username=x-access-token; echo password=$BOSTRAT_GIT_TOKEN; }; f";try{await Lp("git",["-c","credential.helper=","-c",`credential.helper=${s}`,"clone","--no-tags","-q",`${n}/${t}.git`,r],{timeout:pS,env:{...e,BOSTRAT_GIT_TOKEN:o,GIT_TERMINAL_PROMPT:"0"}})}catch(a){throw await jp.rm(r,{recursive:!0,force:!0}).catch(()=>{}),er(`clone of ${t} failed: ${a?.stderr?.trim?.()||a?.message||a}`,502)}return await pc(r,{env:e}),r}import JS from"node:fs/promises";function gS(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===`
|
|
56
|
-
`){e.push(n),n="";continue}n+=s}return e.push(n),e.map(o=>o.trim()).filter(Boolean)}function
|
|
57
|
-
`,n);i!==-1&&(e=e.slice(0,i))}let r=
|
|
58
|
-
`).map(n=>n.trim()).find(Boolean)||t;return e.length>88?`${e.slice(0,88).trimEnd()}\u2026`:e}function
|
|
59
|
-
`)){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(
|
|
60
|
-
`)){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?
|
|
61
|
-
`).trim():"",
|
|
62
|
-
`);if(i!==-1)return o.slice(0,i);if(s+=a,o.length>=e)break}return o}finally{await n.close()}}async function
|
|
63
|
-
`).trim():"";async function
|
|
64
|
-
`)){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:
|
|
65
|
-
`;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
|
|
66
|
-
`)){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"&&
|
|
67
|
-
`),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(
|
|
68
|
-
`),r=n.length-1;for(;r>=0&&n[r].trim()==="";)r--;if(r<0||!
|
|
69
|
-
`),o=[];for(let c=0;c<r.length;c++){let l=r[c].match(
|
|
70
|
-
`),o=-1;for(let u=r.length-1;u>=0;u--)if(
|
|
71
|
-
`),o=
|
|
72
|
-
`),o=
|
|
73
|
-
`),o="";for(let s=0;s<r.length;s++)if(/^\s*https:\/\//.test(r[s])){o=
|
|
74
|
-
`)].reverse().find(a=>
|
|
75
|
-
`),o=
|
|
76
|
-
`),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(
|
|
77
|
-
`),o=
|
|
78
|
-
`).pop()||"")?!0:
|
|
79
|
-
`).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
|
|
80
|
-
`).map(c=>c.replace(/\s+$/,"")),r=-1;for(let c=n.length-1;c>=0;c--)if(
|
|
81
|
+
}`,{contentType:n,filename:e,size:r.length}),s=o.fileUpload?.uploadFile;if(!o.fileUpload?.success||!s?.uploadUrl)throw new Error("linear: fileUpload did not succeed");let a={"content-type":n};for(let c of s.headers||[])a[c.key]=c.value;let i=await fetch(s.uploadUrl,{method:"PUT",headers:a,body:r});if(!i.ok)throw new Error(`linear: asset PUT failed (HTTP ${i.status})`);return s.assetUrl}async function zS(t,e){let n=await de(t,"query($id: String!) { issue(id: $id) { state { id name type } team { states { nodes { id name type position } } } } }",{id:e.key});if(!n.issue)throw new Error(`linear: issue "${e.key}" not found`);return{current:n.issue.state??null,states:n.issue.team?.states?.nodes??[]}}async function VS(t,e,n){if(!(await de(t,"mutation($id: String!, $input: IssueUpdateInput!) { issueUpdate(id: $id, input: $input) { success } }",{id:e.issueId,input:{stateId:n}})).issueUpdate?.success)throw new Error("linear: issueUpdate did not succeed")}async function ir(t){return((await de(t,"query { teams(first: 100) { nodes { id key name } } }")).teams?.nodes??[]).map(n=>({id:n.id,key:n.key,name:n.name}))}var Dp=255;async function hc(t,{teamId:e,name:n,description:r}){let o=r||null,s=!!o&&o.length>Dp,a=s?`${o.slice(0,Dp-1).trimEnd()}\u2026`:o,i=await de(t,"mutation($input: ProjectCreateInput!) { projectCreate(input: $input) { success project { id url name } } }",{input:{teamIds:[e],name:n,...a?{description:a}:{},...s?{content:o}:{}}}),c=i.projectCreate?.project;if(!i.projectCreate?.success||!c)throw new Error("linear: projectCreate did not succeed");return{id:c.id,url:c.url,name:c.name}}async function wc(t,{teamId:e,title:n,description:r,projectId:o,priority:s}){let a=await de(t,"mutation($input: IssueCreateInput!) { issueCreate(input: $input) { success issue { id identifier url title } } }",{input:{teamId:e,title:n,...r?{description:r}:{},...o?{projectId:o}:{},...Number.isInteger(s)?{priority:s}:{}}}),i=a.issueCreate?.issue;if(!a.issueCreate?.success||!i)throw new Error("linear: issueCreate did not succeed");return{id:i.id,key:i.identifier,url:i.url,title:i.title}}var Kp={linear:{api:gc,config:xn},jira:{api:pc,config:zs}};async function vc(){let t=[];for(let[e,{api:n,config:r}]of Object.entries(Kp)){let o=await r();o&&t.push({name:e,api:n,cfg:o})}return t}async function Tn(t){let e=Kp[t?.provider];if(!e)return null;let n=await e.config();return n?{name:t.provider,api:e.api,cfg:n}:null}function Tc(t,e){if(!t||t.name)return t;let n=e.find(r=>r.id===t.id);return n?{...t,name:n.name}:t}var Gp=()=>process.env.BOSTRAT_STATE_DIR||Wp.join(process.env.HOME||JS.tmpdir(),".bostrat"),xc=()=>Wp.join(Gp(),"tickets.json"),zp=()=>(process.env.BOSTRAT_PUBLIC_URL||"http://bostrat.globopi").replace(/\/$/,""),Xs=t=>`${zp()}/track/${t}`,Js=null,yc=null,bc=Promise.resolve();async function Ys(){return Js||(yc??=mo.readFile(xc(),"utf8").then(t=>JSON.parse(t)?.tickets??{}).catch(()=>({})).then(t=>Js=t).finally(()=>yc=null),yc)}async function jp(){let t=JSON.stringify({tickets:Js},null,2),e=`${xc()}.${process.pid}.tmp`;await mo.mkdir(Gp(),{recursive:!0}),await mo.writeFile(e,t),await mo.rename(e,xc())}async function Ge(){bc=bc.then(jp,jp),await bc.catch(()=>{})}async function fe(t){return(await Ys())[t]??null}async function Zs(){return{...await Ys()}}async function Vp(){return(await vc()).length>0}async function cr(t,e,{source:n="api"}={}){let r=await vc();if(!r.length)throw new Error("no ticketing integration configured \u2014 add a linear or jira entry to ~/.bostrat/integrations.json");let o=null,s=null,a=null;for(let u of r){let f=u.api.parseIssueRef(e);if(f)try{s=await u.api.resolveIssue(u.cfg,f),o=u;break}catch(d){a=d}}if(!o)throw a||new Error(`"${e}" is not a Linear or Jira issue URL or key (ABC-123)`);let i=(t.boxes||[]).find(u=>u.name===t.leadBox),c={provider:o.name,key:s.key,issueId:s.issueId,url:s.url,title:s.title,state:s.state,source:n,linkedAt:Date.now(),track:{name:t.name,branch:t.workspace?.branch??null,leadAgent:i?.agent??null,cwd:t.cwd??null,...t.workspace?.kind==="multi"?{repos:(t.workspace.repos??[]).map(u=>u?.name).filter(Boolean)}:{}},pr:null};(await Ys())[t.id]=c,await Ge(),await O({id:t.id,cwd:t.cwd},{type:"ticket.linked",actor:"user",source:"orchestrator",payload:{ts:Date.now(),provider:c.provider,key:c.key,url:c.url,title:c.title,linkSource:n}}).catch(()=>{}),await ho(t.id,c);let l=[];return c.track.branch&&l.push(`\`${c.track.branch}\``),c.track.repos?.length&&l.push(`repos ${c.track.repos.join(" + ")}`),c.track.leadAgent&&l.push(`lead ${c.track.leadAgent}`),await po(c,`**Bostrat** \u2014 Track **${c.track.name}** linked${l.length?` (${l.join(", ")})`:""}.`),await Yp(t.id,c,"linked"),c}async function Ac(t){let e=await Ys();return e[t]?(delete e[t],await Ge(),!0):!1}var Sc=t=>`$${t>=100?Math.round(t):t.toFixed(2)}`;async function Jp(t,e){if(!t.track?.cwd)return null;let n=se(await L({id:e,cwd:t.track.cwd}));return!n.totalUsd||n.totalUsd<.01?null:n}function XS(t){let e=Object.entries(t.byProvider).sort((r,o)=>o[1].usd-r[1].usd).map(([r,o])=>`${r} ${Sc(o.usd)}`).join(" \xB7 "),n=Object.keys(t.byBox).length;return`${Sc(t.totalUsd)}${e?` (${e})`:""}${n>1?` across ${n} boxes`:""}`}function YS(t,e){let n=[];return t.track.leadAgent&&n.push(`lead ${t.track.leadAgent}`),t.track.branch&&n.push(t.track.branch),t.track.repos?.length&&n.push(t.track.repos.join(" + ")),t.pr?.number&&n.push(`PR #${t.pr.number} ${t.pr.change==="merged"?"merged":"open"}`),e&&n.push(Sc(e.totalUsd)),n.join(" \xB7 ")||"linked"}async function ho(t,e){let n=await Tn(e);if(!n)return;let r=await Jp(e,t).catch(()=>null);await n.api.upsertCard(n.cfg,e,{url:Xs(t),title:`Bostrat Track: ${e.track.name}`,subtitle:YS(e,r)})}function Qs(t){let e=new URL(zp());return`${e.protocol}//${t}.${e.host}/`}var ZS={linked:"linked",opened:"prOpened",merged:"prMerged"};function QS(t,e,n){let r=o=>e.filter(s=>s.type===o).sort((s,a)=>s.position-a.position);if(n==="linked"){if(["triage","backlog","unstarted"].includes(t?.type))return r("started")[0]??null}else if(n==="opened"){if(t?.type!=="completed")return r("started").find(o=>/review/i.test(o.name))??null}else if(n==="merged"&&t?.type!=="completed")return r("completed")[0]??null;return null}async function Xp(t,e,{from:n,to:r,mode:o,phase:s=null,actor:a="user"}){e.state=r,await Ge(),await O({id:t,cwd:e.track?.cwd},{type:"ticket.state.changed",actor:a,source:"orchestrator",payload:{ts:Date.now(),key:e.key,url:e.url,from:n??null,to:r,mode:o,...s?{phase:s}:{}}}).catch(()=>{})}async function Yp(t,e,n){let r=await Tn(e);if(!r)return;let o=r.cfg.ticketFlow?.[ZS[n]]??"prompt";if(o==="off")return;let{current:s,states:a}=await r.api.issueStates(r.cfg,e),i=Tc(s,a),c=QS(i,a,n);if(!c||c.id===i?.id)return;let l=i?.name??e.state??null;if(o==="auto"){await r.api.moveIssue(r.cfg,e,c.id),await Xp(t,e,{from:l,to:c.name,mode:"auto",phase:n,actor:"system"});return}await O({id:t,cwd:e.track?.cwd},{type:"ticket.move.offered",actor:"system",source:"orchestrator",payload:{ts:Date.now(),offerId:qp(),provider:e.provider,key:e.key,url:e.url,phase:n,from:l,to:{id:c.id,name:c.name,type:c.type},...e.pr?.number?{prNumber:e.pr.number}:{}}}).catch(()=>{})}async function Zp(t){let e=await fe(t);if(!e)throw new Error("no ticket linked to this Track");let n=await Tn(e);if(!n)throw new Error("no ticketing integration configured");let{current:r,states:o}=await n.api.issueStates(n.cfg,e),s=Tc(r,o);return s?.name&&e.state!==s.name&&(e.state=s.name,await Ge()),{key:e.key,url:e.url,current:s?{id:s.id,name:s.name??null,type:s.type??null}:null,states:[...o].sort((a,i)=>a.position-i.position).map(({id:a,name:i,type:c,position:l})=>({id:a,name:i,type:c,position:l}))}}async function _c(t,e,{mode:n="manual"}={}){if(!e)throw new Error("stateId required");let r=await fe(t);if(!r)throw new Error("no ticket linked to this Track");let o=await Tn(r);if(!o)throw new Error("no ticketing integration configured");let{current:s,states:a}=await o.api.issueStates(o.cfg,r),i=Tc(s,a),c=a.find(u=>u.id===e);if(!c)throw new Error("unknown state for this ticket's team");let l=i?.name??r.state??null;return i?.id===c.id?(r.state!==c.name&&(r.state=c.name,await Ge()),{key:r.key,from:l,to:c.name,moved:!1}):(await o.api.moveIssue(o.cfg,r,c.id),await Xp(t,r,{from:l,to:c.name,mode:n}),{key:r.key,from:l,to:c.name,moved:!0})}async function Qp(t,e){let n=await fe(t);if(!n)throw new Error("no ticket linked to this Track");let r=await Tn(n);if(!r)throw new Error("no ticketing integration configured");let o=String(e||"").trim();if(!o)throw new Error("comment body required");let s=await r.api.createComment(r.cfg,n,o);return await O({id:t,cwd:n.track?.cwd},{type:"ticket.commented",actor:"user",source:"orchestrator",payload:{ts:Date.now(),key:n.key,url:s??n.url,excerpt:o.length>280?`${o.slice(0,279)}\u2026`:o}}).catch(()=>{}),{key:n.key,url:s??n.url}}async function tm(t,e,n){if(!e)throw new Error("offerId required");let r=await L({id:t.id,cwd:t.cwd}),o=r.find(a=>a.type==="ticket.move.offered"&&a.payload?.offerId===e);if(!o)throw new Error("unknown offer");if(r.some(a=>a.type==="ticket.move.answered"&&a.payload?.offerId===e))throw new Error("offer already answered");let s=null;return n&&(s=await _c(t.id,o.payload.to?.id,{mode:"offer"})),await O({id:t.id,cwd:t.cwd},{type:"ticket.move.answered",actor:"user",source:"orchestrator",payload:{ts:Date.now(),offerId:e,action:n?"moved":"dismiss",...s?{from:s.from,to:s.to}:{}}}),{offerId:e,action:n?"moved":"dismiss",...s??{}}}var Lp=50;async function em(){let t=await xn();if(!t)throw new Error("no ticketing integration configured");return ir(t)}function tv(t){let e=t&&typeof t=="object"?t:{},n=(Array.isArray(e.issues)?e.issues:[]).map(o=>o&&typeof o=="object"?o:{title:String(o||"")}).map(o=>({title:String(o.title||"").trim(),description:o.description?String(o.description):null,priority:Number.isInteger(o.priority)&&o.priority>=0&&o.priority<=4?o.priority:null})).filter(o=>o.title),r=e.epic&&String(e.epic.name||"").trim()?{name:String(e.epic.name).trim(),description:e.epic.description?String(e.epic.description):null}:null;if(!n.length&&!r)throw new Error("a plan needs an epic or at least one issue");if(n.length>Lp)throw new Error(`too many issues (${n.length}); cap is ${Lp} per proposal`);return{epic:r,issues:n,team:e.team?String(e.team).trim():null,linkCurrentTrack:e.linkCurrentTrack===!0}}async function ev(t,e,n,r){let o=r??await ir(t),s=i=>o.find(c=>c.id===i||c.key.toLowerCase()===String(i).toLowerCase()||c.name.toLowerCase()===String(i).toLowerCase())??null;if(e.team){let i=s(e.team);if(i)return i}let a=await fe(n.id);if(a?.key){let i=a.key.split("-")[0],c=o.find(l=>l.key===i);if(c)return c}if(t.defaultTeam){let i=s(t.defaultTeam);if(i)return i}return o.length===1?o[0]:null}async function nm(t,e){let n=await xn();if(!n)throw new Error("no ticketing integration configured \u2014 add a linear apiKey to ~/.bostrat/integrations.json");if(n.ticketFlow?.create==="off")throw new Error("ticket creation offers are off (bostrat-ticket flow set create=prompt to re-enable)");let r=tv(e),o=await ir(n),s=await ev(n,r,t,o),a=s?{id:s.id,key:s.key,name:s.name}:null,i=qp();return await O({id:t.id,cwd:t.cwd},{type:"ticket.create.offered",actor:"system",source:"orchestrator",payload:{ts:Date.now(),proposalId:i,provider:"linear",epic:r.epic,issues:r.issues,team:a,needsTeam:!s,linkCurrentTrack:r.linkCurrentTrack}}),{proposalId:i,epic:r.epic,issues:r.issues,team:a,needsTeam:!s}}async function rm(t,e,n,{teamId:r=null}={}){if(!e)throw new Error("proposalId required");let o=await L({id:t.id,cwd:t.cwd}),s=o.find(h=>h.type==="ticket.create.offered"&&h.payload?.proposalId===e);if(!s)throw new Error("unknown proposal");if(o.some(h=>h.type==="ticket.create.answered"&&h.payload?.proposalId===e))throw new Error("proposal already answered");if(!n)return await O({id:t.id,cwd:t.cwd},{type:"ticket.create.answered",actor:"user",source:"orchestrator",payload:{ts:Date.now(),proposalId:e,action:"dismiss"}}),{proposalId:e,action:"dismiss"};let a=await xn();if(!a)throw new Error("no ticketing integration configured");let i=s.payload,c=i.team??null;if(!c&&r){let h=(await ir(a)).find(w=>w.id===r);if(!h)throw new Error("unknown team");c={id:h.id,key:h.key,name:h.name}}if(!c)throw new Error("a team is required to create \u2014 pick one");let l=[],u=[],f=null;if(i.epic){let h=await hc(a,{teamId:c.id,name:i.epic.name,description:i.epic.description});f=h.id,l.push({kind:"epic",title:h.name,url:h.url})}for(let h of i.issues||[])try{let w=await wc(a,{teamId:c.id,title:h.title,description:h.description,projectId:f,priority:h.priority});l.push({kind:"issue",key:w.key,title:w.title,url:w.url})}catch(w){u.push({title:h.title,message:w?.message||String(w)})}await O({id:t.id,cwd:t.cwd},{type:"ticket.created",actor:"user",source:"orchestrator",payload:{ts:Date.now(),proposalId:e,team:{key:c.key,name:c.name},items:l,failed:u}}),await O({id:t.id,cwd:t.cwd},{type:"ticket.create.answered",actor:"user",source:"orchestrator",payload:{ts:Date.now(),proposalId:e,action:"created",createdCount:l.length,failedCount:u.length}});let d=l.filter(h=>h.kind==="issue"),m=null;return i.linkCurrentTrack&&d.length===1&&!await fe(t.id)&&(m=await cr(t,d[0].key,{source:"created"}).catch(()=>null)),{proposalId:e,action:"created",created:l,failed:u,linked:m?m.key:null}}async function po(t,e){let n=await Tn(t);n&&await n.api.createComment(n.cfg,t,e)}async function nv(t,e,n){let r=await Tn(t);if(!r)return null;let o=await He(e);if(!o)return null;let s=yn(o,n);if(!s)return null;let a=await mo.readFile(qt(o,s.v).png).catch(()=>null);return a?r.api.uploadImage(r.cfg,{issue:t.key,filename:`${o.name.replace(/[^\w.-]+/g,"-")}.v${s.v}.png`,contentType:"image/png",buffer:a}):null}var Fp=()=>Number(process.env.BOSTRAT_TICKET_COST_SYNC_MS??12e4),kc=new Map;function rv(t,e){if(Fp()<=0)return ho(t,e);if(kc.has(t))return;let n=setTimeout(()=>{kc.delete(t),fe(t).then(r=>r?ho(t,r):null).catch(()=>{})},Fp());n.unref?.(),kc.set(t,n)}var Up=new Map;async function Hp(t,e,n){let r=Up.get(t)??0;Date.now()-r<600*1e3||(Up.set(t,Date.now()),await O({id:t,cwd:e},{type:"ticket.sync.failed",actor:"system",source:"orchestrator",payload:{ts:Date.now(),message:n?.message||String(n)}}).catch(()=>{}))}async function ov(t,{resolveTrack:e=null}={}){let n=await fe(t.trackId),r=t.payload||{};if(!n){if(t.type!=="turn.user"||t.actor!=="user")return;let o=null;for(let a of await vc())if(o=a.api.findIssueUrl(r.text,a.cfg),o)break;if(!o)return;let s=e?await e(t.trackId):null;if(!s)return;try{await cr(s,o,{source:"chat"})}catch(a){await Hp(t.trackId,s.cwd,a)}return}if(!n.track.cwd&&e){let o=await e(t.trackId).catch(()=>null);o?.cwd&&(n.track.cwd=o.cwd,await Ge())}try{switch(t.type){case"handoff.done":n.track.leadAgent=r.toAgent??n.track.leadAgent,await Ge(),await ho(t.trackId,n);break;case"pr.state.changed":{if(!r.change)break;if(r.number&&(n.pr={number:r.number,url:r.url??n.pr?.url??null,change:r.change}),await Ge(),await ho(t.trackId,n),r.change==="opened"||r.change==="merged"){let o=r.change==="merged"?await Jp(n,t.trackId).catch(()=>null):null;await po(n,`**Bostrat** \u2014 PR ${r.change} for **${n.track.name}**${r.url?`: ${r.url}`:r.number?` (#${r.number})`:""}`+(o?`
|
|
82
|
+
|
|
83
|
+
Agent cost for this Track: ${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 po(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 po(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 po(n,`**Bostrat** \u2014 live preview **${r.name}**: ${Qs(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 ta from"node:fs/promises";import sm from"node:path";import sv from"node:os";var am=process.env.BOSTRAT_STATE_DIR||sm.join(process.env.HOME||sv.tmpdir(),".bostrat"),Ic=sm.join(am,"pr-links.json"),dt=(t,e)=>`${t}#${Number(e)}`,ea=null,Ec=null,D=Promise.resolve();async function kt(){return ea||(Ec??=ta.readFile(Ic,"utf8").then(t=>JSON.parse(t)?.links??{}).catch(()=>({})).then(t=>ea=t).finally(()=>Ec=null),Ec)}async function Z(){let t=JSON.stringify({links:ea},null,2),e=`${Ic}.${process.pid}.tmp`;await ta.mkdir(am,{recursive:!0}),await ta.writeFile(e,t),await ta.rename(e,Ic)}var av=new Set(["api","observed","reconciled"]);async function Lt({repo:t,number:e,trackId:n,url:r=null,headRef:o=null,title:s=null,source:a="matched"}){if(!t||!Number.isInteger(Number(e))||!n)return{link:null,created:!1};let i=await kt(),c=dt(t,e);return i[c]?{link:i[c],created:!1}:(i[c]={repo:t,number:Number(e),trackId:n,url:r,headRef:o,title:s,source:a,announced:av.has(a),firstSeen:Date.now()},D=D.then(Z,Z),await D.catch(()=>{}),{link:i[c],created:!0})}async function wo(t,e){if(!t||e==null)return null;let n=(await kt())[dt(t,e)];return!n||n.announced?null:(n.announced=!0,D=D.then(Z,Z),await D.catch(()=>{}),n)}async function Et(t,e){return!t||e==null?null:(await kt())[dt(t,e)]??null}async function ze(t,e,n){if(!t||e==null)return null;let r=(await kt())[dt(t,e)];return r?(r.state=n,r.resolvedAt=Date.now(),delete r.armedAt,delete r.autoMerge,delete r.archiveOnMerge,delete r.notifyOnMerge,delete r.notifyPending,D=D.then(Z,Z),await D.catch(()=>{}),r):null}async function im(t,e,{headSha:n=null,trackId:r=null}={}){if(!t||e==null)return null;let o=(await kt())[dt(t,e)];return o?(o.ciFix={delegatedAt:Date.now(),headSha:n,trackId:r},D=D.then(Z,Z),await D.catch(()=>{}),o):null}async function cm(t,e,{headSha:n=null,trackId:r=null}={}){if(!t||e==null)return null;let o=(await kt())[dt(t,e)];return o?(o.reconcile={delegatedAt:Date.now(),headSha:n,trackId:r},D=D.then(Z,Z),await D.catch(()=>{}),o):null}async function na(t,e,{headSha:n=null}={}){if(!t||e==null)return null;let r=(await kt())[dt(t,e)];return!r||!r.ciFix?null:(r.ciFixAwait={since:Date.now(),trackId:r.ciFix.trackId??null,headSha:n},delete r.ciFix,D=D.then(Z,Z),await D.catch(()=>{}),r)}async function ra(t,e){if(!t||e==null)return null;let n=(await kt())[dt(t,e)];return!n||!n.ciFixAwait?null:(delete n.ciFixAwait,D=D.then(Z,Z),await D.catch(()=>{}),n)}async function oa(t,e,{headSha:n=null}={}){if(!t||e==null)return null;let r=(await kt())[dt(t,e)];return!r||!r.reconcile?null:(r.reconcileAwait={since:Date.now(),trackId:r.reconcile.trackId??null,headSha:n},delete r.reconcile,D=D.then(Z,Z),await D.catch(()=>{}),r)}async function sa(t,e){if(!t||e==null)return null;let n=(await kt())[dt(t,e)];return!n||!n.reconcileAwait?null:(delete n.reconcileAwait,D=D.then(Z,Z),await D.catch(()=>{}),n)}async function lm(t,e,{at:n=Date.now()}={}){if(!t||e==null)return null;let r=(await kt())[dt(t,e)];return r?(r.baseFoldAt=n,D=D.then(Z,Z),await D.catch(()=>{}),r):null}async function um(t,e){if(!t||e==null)return null;let n=(await kt())[dt(t,e)];return!n||!n.baseFoldAt?null:(delete n.baseFoldAt,D=D.then(Z,Z),await D.catch(()=>{}),n)}async function dm(t,e,n){if(!t||e==null||!n)return null;let r=(await kt())[dt(t,e)];return r?(r.lastHeadSha=n,D=D.then(Z,Z),await D.catch(()=>{}),r):null}async function go(t,e,{merge:n=null}={}){if(!t||e==null)return null;let r=(await kt())[dt(t,e)];return r?(r.armedAt=Date.now(),n&&(r.autoMerge={method:n.method||"merge",armedAt:r.armedAt}),D=D.then(Z,Z),await D.catch(()=>{}),r):null}async function lr(t,e,n=!0){if(!t||e==null)return null;let r=(await kt())[dt(t,e)];return r?(n?r.archiveOnMerge=!0:delete r.archiveOnMerge,D=D.then(Z,Z),await D.catch(()=>{}),r):null}async function aa(t,e,n=!0){if(!t||e==null)return null;let r=(await kt())[dt(t,e)];return r?(n?r.notifyOnMerge=!0:delete r.notifyOnMerge,D=D.then(Z,Z),await D.catch(()=>{}),r):null}async function fm(t,e,n){if(!t||e==null)return null;let r=(await kt())[dt(t,e)];if(!r)return null;r.notifyOnMerge=!0;let o=new Map((r.notifyPending||[]).map(s=>[`${s.repo}#${s.number}`,s]));for(let s of n||[])s&&s.number!=null&&o.set(`${s.repo}#${s.number}`,s);return r.notifyPending=[...o.values()],D=D.then(Z,Z),await D.catch(()=>{}),r}async function yo(t,e){if(!t||e==null)return null;let n=(await kt())[dt(t,e)];return!n||!n.armedAt&&!n.autoMerge?null:(delete n.armedAt,delete n.autoMerge,delete n.archiveOnMerge,delete n.notifyOnMerge,delete n.notifyPending,D=D.then(Z,Z),await D.catch(()=>{}),n)}var iv=new Set(["merged","closed","lost"]);async function pm(){return Object.values(await kt()).filter(t=>!iv.has(t.state))}async function pe(){return Object.values(await kt()).sort((t,e)=>e.firstSeen-t.firstSeen)}var hm=t=>Math.round(t*100)/100;function cv(t){return!t||t.kind==="setup"?[]:Nt(t.workspace).filter(e=>e&&(e.repo||e.base)).map(e=>({key:e.repo||e.base,slug:e.repo??null,base:e.base??null,name:e.repo?e.repo.split("/").pop():mm.basename(e.base)}))}function wm(t){let e=new Map;for(let n of t||[])for(let r of cv(n)){let o=e.get(r.key);o||e.set(r.key,o={...r,tracks:[]}),o.tracks.push(n)}return e}function ia(t){let e=t.events?.length?t.events[t.events.length-1].at:0;return Math.max(t.createdAt||0,t.archivedAt||0,t.lastResponseAt||0,e||0)||null}var Rc=(t,e)=>`${t} ${e}${t===1?"":"s"}`;function lv(t){if(!Number.isFinite(t)||t<=0)return null;let e=t/864e5;if(e>=1)return`${Math.round(e)}d`;let n=t/36e5;return n>=1?`${Math.round(n)}h`:`${Math.max(1,Math.round(t/6e4))}m`}function $c(t,e=[]){let n=[],r=t?.events||[],o=new Map,s=0;for(let u of e){if(u.type!=="pr.state.changed")continue;let f=u.payload||{};!f.repo||f.number==null||(f.change==="merged"?o.set(`${f.repo}#${f.number}`,f):f.change==="opened"&&s++)}if(o.size){let u=[...o.keys()].map(f=>{let[d,m]=f.split("#");return`${d.split("/").pop()}#${m}`});n.push(`Merged ${u.slice(0,4).join(", ")}${u.length>4?` +${u.length-4}`:""}`)}else s&&n.push(Rc(s,"PR")+" opened");let a=Object.keys(t?.subtasks||{}).length||r.filter(u=>u.type==="subtask-delegated").length;a&&n.push(Rc(a,"subtask"));let i=r.filter(u=>u.type==="lead-handoff"&&u.fromAgent&&u.toAgent);if(i.length){let u=i.slice(0,3).map(f=>`${f.fromAgent}\u2192${f.toAgent}`).join(", ");n.push(`${Rc(i.length,"handoff")} ${u}${i.length>3?"\u2026":""}`)}let c=se(e);c.totalUsd>=.01&&n.push(`$${c.totalUsd.toFixed(2)}`);let l=Number.isFinite(t?.archivedAt)&&Number.isFinite(t?.createdAt)?lv(t.archivedAt-t.createdAt):null;return n.length&&l&&n.push(l),n.length?n.join(" \xB7 "):null}function uv(t){let e=[],n=[...t?.boxes||[]].sort((o,s)=>(o.createdAt||0)-(s.createdAt||0));for(let o of n){if(o.role!=="lead"&&o.role!=="handed-off")continue;let s=Array.isArray(o.epochs)&&o.epochs.length?o.epochs.map(a=>a.agent):[o.agent];for(let a of s)a&&e[e.length-1]!==a&&e.push(a)}let r=new Set(n.filter(o=>o.role==="subtask"&&o.agent&&!e.includes(o.agent)).map(o=>o.agent));return{lead:e,subtask:[...r]}}function gm(t,e,n){let r=new Set(t.tracks.map(d=>d.id)),o=0,s=0,a=0,i=null,c=null,l=0;for(let d of t.tracks){d.archivedAt?s++:o++,d.beat?.text&&a++;let m=ia(d);m&&(!i||m>i)&&(i=m),d.createdAt&&(!c||d.createdAt<c)&&(c=d.createdAt),l+=e.get(d.id)||0}let u=0,f=0;for(let d of n)r.has(d.trackId)&&(t.slug&&d.repo!==t.slug||t.slug&&(d.state==="merged"?f++:d.state!=="closed"&&u++));return{key:t.key,slug:t.slug,base:t.base,name:t.name,tracks:{total:t.tracks.length,live:o,archived:s,withBeat:a},prs:{open:u,merged:f},usd:hm(l),firstActivityAt:c,lastActivityAt:i}}async function ym(t){let e=wm(t),n=await pe().catch(()=>[]),r=new Map,o=new Set;for(let a of e.values())for(let i of a.tracks){if(o.has(i.id))continue;o.add(i.id);let c=await L(i).catch(()=>[]);r.set(i.id,se(c).totalUsd)}let s=[...e.values()].map(a=>gm(a,r,n)).sort((a,i)=>(i.lastActivityAt??0)-(a.lastActivityAt??0));return{generatedAt:Date.now(),arcs:s}}async function bm(t,e){let r=wm(t).get(e);if(!r)return null;let[o,s,a,i,c]=await Promise.all([pe().catch(()=>[]),js().catch(()=>[]),Ws().catch(()=>[]),qs().catch(()=>[]),Zs().catch(()=>({}))]),l=new Map,u=[],f=(d,m)=>(ia(m)||m.createdAt||0)-(ia(d)||d.createdAt||0);for(let d of[...r.tracks].sort(f)){let m=await L(d).catch(()=>[]),h=se(m);l.set(d.id,h.totalUsd);let w=c[d.id]||null;u.push({trackId:d.id,name:d.name,createdAt:d.createdAt??null,archivedAt:d.archivedAt??null,live:!d.archivedAt,beat:d.beat??null,agents:uv(d),handoffs:(d.events||[]).filter(g=>g.type==="lead-handoff").length,subtasks:Object.keys(d.subtasks||{}).length,repos:Nt(d.workspace).map(g=>g.repo||mm.basename(g.base||"")),prs:o.filter(g=>g.trackId===d.id&&(!r.slug||g.repo===r.slug)).map(g=>({repo:g.repo,number:g.number,state:g.state??null,title:g.title??null,url:g.url??null})),mocks:s.filter(g=>g.trackId===d.id).length,views:a.filter(g=>g.trackId===d.id).length,runbooks:i.filter(g=>g.trackId===d.id).map(g=>({name:g.name,status:g.status})),ticket:w?{provider:w.provider,key:w.key,url:w.url??null}:null,usd:hm(h.totalUsd),lastActivityAt:ia(d)})}return{...gm(r,l,o),generatedAt:Date.now(),segments:u}}import{execFile as dv}from"node:child_process";import{promisify as fv}from"node:util";import{randomUUID as pv}from"node:crypto";var An=fv(dv),ca=t=>new Promise(e=>setTimeout(e,t)),mv=Number(process.env.BOSTRAT_SWAP_EXIT_TIMEOUT_MS)||15e3,km=Number(process.env.BOSTRAT_SWAP_SHELL_TIMEOUT_MS)||8e3,hv=250,wv=400,xm={claude:{escape:!0,wipe:!0,text:"/exit"},codex:{escape:!0,wipe:!0,text:"/quit"},cursor:{escape:!0,wipe:!0,text:"/quit"},default:{escape:!1,wipe:!1,text:"exit"}};async function vm(t){let e=await $d(t);if(!e)return!1;try{return await An("pgrep",["-P",String(e)]),!1}catch(n){return n?.code===1}}async function Oc(t,e){let n=Date.now()+e;for(;;){if(await vm(t))return!0;if(Date.now()>n)return!1;await ca(hv)}}async function gv(t,e){let n=xm[e]||xm.default;n.escape&&(await xt(t,["Escape"],{kind:"swap-exit"}),await ca(150)),n.wipe&&await bs(t,400,{kind:"swap-exit"}),await ks(t,n.text,{perCharDelayMs:90,kind:"swap-exit"});let r=Se(e).submitDelayMs||0;await ca(Math.max(r,250)),await xt(t,["Enter"],{kind:"swap-exit"})}async function yv(t,e){try{let{stdout:n}=await An(N,B("show-environment","-t",t,e));return!String(n||"").trimStart().startsWith("-")}catch{return!1}}async function Sm(t){await An(N,B("respawn-pane","-k","-t",t))}async function Mc(t,e={}){if(!yt(t))throw new Error("invalid session name");let n=String(e.agentCmd||"").trim();if(!n)throw new Error("agentCmd is required");let r=await Y(t);if(!r)throw new Error(`session "${t}" not found`);if(!r.cwd)throw new Error(`can't resolve cwd for "${t}"`);let o=r.agent||"agent",s=e.agent||zn(n),a=Number(e.exitTimeoutMs)||mv;await G({flow:"swap",phase:"start",sessionName:t,from:o,to:s,cwd:r.cwd});let i=!1;if(!await vm(t)&&(await gv(t,o),!await Oc(t,a)&&(i=!0,await G({flow:"swap",phase:"respawn-forced",sessionName:t,from:o}),await Sm(t),!await Oc(t,km))))throw new Error(`pane shell did not come back after respawn-pane in "${t}"`);let c=s==="claude"&&Gn(n),l=c?cs():null,u=await yv(t,"ANTHROPIC_API_KEY"),f=!1;try{l?(await An(N,B("set-environment","-t",t,"ANTHROPIC_API_KEY",l)),f=!0):u&&(await An(N,B("set-environment","-u","-t",t,"ANTHROPIC_API_KEY")),f=!0)}catch(g){throw l?ws(g,l):g}if(f&&!i&&(await Sm(t),!await Oc(t,km)))throw new Error(`pane shell did not come back after env respawn in "${t}"`);let d=!!e.resumeSessionId,m=e.resumeSessionId||pv(),h=d?n:_i(n,m),w=d?e.pinned===!0:/(^|\s)--session-id(\s|=)/.test(h);return c&&await Ne(r.cwd),s==="codex"&&await Un(r.cwd),s==="cursor"&&await Hn(r.cwd),await wn(t),await An(N,B("set-option","-t",t,"@bostrat-sid",m)),await An(N,B("set-option","-t",t,"@bostrat-agent",s)),await ca(wv),await vt(t,h,{kind:"swap-launch"}),await G({flow:"swap",phase:"done",sessionName:t,from:o,to:s,sid:m,forced:i,envRespawned:f}),{name:t,cwd:r.cwd,sessionId:m,agent:s,pinned:w,forced:i,envRespawned:f}}import Yv from"node:fs/promises";function bv(t){let e=[],n="",r=null;for(let o=0;o<t.length;o++){let s=t[o];if(r){n+=s,s===r&&t[o-1]!=="\\"&&(r=null);continue}if(s==="'"||s==='"'||s==="`"){r=s,n+=s;continue}if(s==="&"&&t[o+1]==="&"){e.push(n),n="",o++;continue}if(s==="|"&&t[o+1]==="|"){e.push(n),n="",o++;continue}if(s==="|"||s===";"||s===`
|
|
87
|
+
`){e.push(n),n="";continue}n+=s}return e.push(n),e.map(o=>o.trim()).filter(Boolean)}function bo(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 ko(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 Cc(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 Nc(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 Pc(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:Cc,grep:Cc,egrep:Cc,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:Nc,pnpm:Nc,yarn:Nc,npx:t=>{let e=st(t)[0];return e?`Run ${e}`:null},node:t=>Pc(t,"Node"),python:t=>Pc(t,"Python"),python3:t=>Pc(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=ko(bo(t));if(!e.length)return null;if(/^(ba|z)?sh$/.test(mt(e[0]))&&(e[1]==="-lc"||e[1]==="-c")&&e[2])return Bc(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 Bc(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=ko(bo(r[i])),l=ko(bo(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(ko(bo(i))[0]||""),sub:st(ko(bo(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 jc=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||Bc(t)||fr(t)}function Dc(t,e){if(t){if(Array.isArray(t)){for(let n of t)Dc(n,e);return}if(typeof t=="object"){if(t.type==="tool_use"&&jc.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))Dc(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(Dc(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 xo 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 la=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 Lc=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 ${Lc(e.glob)}`:"",a=Xt(String(r),40);return{command:Xt(`grep ${Lc(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 ${Lc(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(xo.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=xo.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(!xo.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 ua(t,e){if(!t)return null;if(e){let n=_n.join(Nm(t),e),r=_n.join(n,"store.db");if(xo.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"||!la.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 Fc(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(xo.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=ua(t,e);if(!r)return[];let{turns:o,model:s}=await Fc(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=ua(t,e);if(!n)return null;let{turns:r,requestIds:o,model:s}=await Fc(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=ua(t,e);if(!r)return[];let{turns:o,trailingCommands:s}=await Fc(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=ua(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 Hc from"node:fs/promises";import Uc from"node:path";import Uv from"node:os";var Hv=process.env.BOSTRAT_CODEX_SESSIONS||Uc.join(process.env.CODEX_HOME||Uc.join(Uv.homedir(),".codex"),"sessions");async function Hm(t){try{return await Hc.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=Uc.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 Hc.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:qc,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,Wc.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 So=new Set(["exec_command","shell","exec"]),Wc=new Set(["wait","write_stdin"]),qc="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"&&So.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 Hc.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}},Kc=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?Pi(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}}},Gc=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)}},zc=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 Vc(t){return Gm.set(t.agent,t),t}function vo(t){return Gm.get(t)||Zv}Vc(new Kc("claude",jc));Vc(new Gc("cursor",la));Vc(new zc("codex",So));var Jc=t=>e=>({...e,box:t.name,agent:t.agent,boxRole:t.role});async function Xc(t,e={}){return(await vo(t.agent).readMessages(t,e)).map(Jc(t))}async function zm(t,e={}){return(await vo(t.agent).readCommands(t,e)).map(Jc(t))}async function Vm(t,e={}){return(await vo(t.agent).readJobs(t,e)).map(Jc(t))}var Ym=/to navigate\b.*\bEsc to cancel|Enter to select\b/,Qc=/Chat about this/i,In=/^\s*(❯)?\s*(\d+)\.\s+(?:\[([ xX✔])\]\s+)?(.+?)\s*$/,Jm=/^(type something|chat about this|submit)\b/i,Rn=/^[─—-]{6,}\s*$/,tl=/[\u2500-\u257F\u2550-\u256F┌┐└┘├┤│┬┴┼─✂]/;function $n(t){let e=t.search(tl);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)||tl.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 Yc=/Resume from summary/i,iT=/^\s*This session is\b/;function cT(t,e=""){let n=String(t);if(!Yc.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=>Yc.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 el(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=el(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=el(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 nl=/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)||nl.test(t[r]))break;n+=o}return n}function pT(t,e=""){let n=String(t);if(!nl.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 Zc=/OAuth error:/,mT=/Press Enter to retry\./;function hT(t,e=""){let n=String(t);if(!Zc.test(n)||!mT.test(n)||On.test(n))return null;let s=([...n.replace(/\r/g,"").split(`
|
|
105
|
+
`)].reverse().find(a=>Zc.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=el(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])||tl.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(Qc.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)||!Qc.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)&&Qc.test(n)||Zm.test(n)||Yc.test(n)||Qm.test(n.trimEnd().split(`
|
|
109
|
+
`).pop()||"")?!0:On.test(n)?!1:th.test(n)&&eh.test(n)||nh.test(n)||nl.test(n)||Zc.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 rl(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 To(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=To(t,n);return o&&ol(e,o)?"stuck":r||o?"submitted":"unknown"}function ol(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 sl=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,Ao=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=Ao(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=Ao(i?.label);if(!c)return null;let l=Ao(i?.description);a.push(l?{label:c,description:l}:{label:c})}if(a.length<vT)return null;n.push({question:o,header:Ao(r?.header).slice(0,TT),multiSelect:r?.multiSelect===!0,options:a})}return n.length?n:null}function al(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 il(t,{leadBoxId:e,provider:n,isAnswered:r,maxQuestions:o=dh}={}){if(!Array.isArray(t)||!e||!sl.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||al(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=Ao(n),s;return r.length?r.length===1?s=`${r[0].name}: ${r[0].picks}`:s=`Answers:
|
|
81
112
|
${r.map(a=>`- ${a.name}: ${a.picks}`).join(`
|
|
82
113
|
`)}`:s=o,r.length&&o&&(s+=`
|
|
83
|
-
${o}`),s||""}var
|
|
114
|
+
${o}`),s||""}var ph=`
|
|
84
115
|
When you are BLOCKED on a decision only the operator can make \u2014 an ambiguous
|
|
85
116
|
requirement, an irreversible choice, several valid approaches \u2014 do not bury the
|
|
86
117
|
question in prose. End your message with exactly ONE fenced code block tagged
|
|
@@ -100,40 +131,50 @@ your turn. The operator sees the questions as an interactive card in their chat
|
|
|
100
131
|
answer. Never use the block for rhetorical questions, progress updates, or
|
|
101
132
|
anything you can decide yourself; keep asking in prose for questions without
|
|
102
133
|
enumerable options.
|
|
103
|
-
`;import{execFile as
|
|
104
|
-
`).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
|
|
105
|
-
`).map(s=>s.trim()).filter(Boolean)}async function
|
|
106
|
-
`).map(n=>n.trim()).filter(Boolean)}catch{return[]}}import
|
|
107
|
-
`),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
|
|
108
|
-
`),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
|
|
109
|
-
`);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
|
|
110
|
-
`),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&&!qm.has(o))throw new Error(`unknown codex effort "${o}" (low | medium | high | xhigh)`);return{model:r,effort:o||null}}if(t==="cursor"){let r=Gm.find(o=>o.alias===n);return{model:n,filter:r?r.label:n}}if(!Nv.has(n))throw new Error(`unknown model "${n}"`);return{model:n}}async function wo(t,e,{timeoutMs:n=Cv}={}){let r=Date.now()+n;for(;;){let o=await mt(t,{lines:80}).catch(()=>""),s=e(o);if(s)return s;if(Date.now()>r)return null;await ea(Mv)}}async function Dv(t,e){return await xt(t,`/model ${e.model}`,me("claude")),{verified:!1,note:"confirmed when the next turn's transcript model flips"}}async function jv(t,e){await os(t),await xt(t,"/model",{enter:!1}),await ea(250),await bt(t,["Enter"],{kind:"model-switch"});let n=await wo(t,Fm);if(!n)throw new Error("codex model picker didn't open");let r=n.find(i=>i.id===e.model);if(!r)throw await bt(t,["Escape"],{kind:"model-switch"}).catch(()=>{}),new Error(`unknown model "${e.model}" \u2014 codex offers: ${n.map(i=>i.id).join(", ")}`);let o=i=>{let c=Bv(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 wo(t,i=>Um(i)||!Fm(i)&&o(i)&&{done:o(i)});if(!s)throw await bt(t,["Escape"],{kind:"model-switch"}).catch(()=>{}),new Error("codex reasoning-level picker didn't open");if(!s.done){let i=e.effort?qm.get(e.effort):null,c=i?s.find(l=>l.label.toLowerCase()===i.toLowerCase()):s.find(l=>l.isDefault)||s[0];if(!c)throw await bt(t,["Escape","Escape"],{kind:"model-switch"}).catch(()=>{}),new Error(`codex doesn't offer effort "${e.effort}" for ${e.model}`);await xt(t,String(c.n),{enter:!1,kind:"model-switch"})}let a=s.done||await wo(t,i=>!Um(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 Lv(){let t=Lm.join(process.env.CURSOR_CONFIG_DIR||Lm.join(Ov.homedir(),".cursor"),"cli-config.json");try{return JSON.parse(await $v.readFile(t,"utf8"))?.model||null}catch{return null}}async function Fv(t,e){await os(t),await as(t,`/model ${e.filter}`,{kind:"model-switch"});let n=await wo(t,Hm),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 wo(t,Hm,{timeoutMs:1500});if(s=l?.rows.find(u=>u.active)||null,s&&o(s))break;if(!l||!l.rows.some(o)){s=null;break}await bt(t,["Down"],{kind:"model-switch"}),await ea(250)}if(!n||!s||!o(s)){await bt(t,["Escape"],{kind:"model-switch"}).catch(()=>{});let c=Array(`/model ${e.filter}`.length).fill("BSpace");throw await bt(t,c,{settleMs:15,kind:"model-switch"}).catch(()=>{}),new Error(n?`cursor picker has no model matching "${e.filter}"`:"cursor model picker didn't open")}await bt(t,["Enter"],{kind:"model-switch"}),await ea(800);let a=await Lv();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 zm(t,e,n){return t==="codex"?jv(e,n):t==="cursor"?Fv(e,n):Dv(e,n)}var Bt=t=>t||null;function ra(t){let e=t||{},n=Uv(e);return n&&(n.payload={ts:e.at,...n.payload}),n}function Uv(t){switch(t.type){case"said":return{type:"turn.user",actor:"user",source:"orchestrator",boxId:t.box,provider:Bt(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:Bt(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:Bt(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:Bt(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:Bt(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:Bt(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:Bt(t.agent),payload:{...fr(t)}};case"subtask-failed":return{type:"error",actor:"system",source:"orchestrator",boxId:t.box||t.to,provider:Bt(t.agent||t.toAgent),payload:{kind:"subtask-failed",...fr(t)}};case"subtask-resumed":return{type:"box.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Bt(t.agent),payload:{change:"subtask-resumed",...fr(t)}};case"model-change":return{type:"box.state.changed",actor:"user",source:"orchestrator",boxId:t.box,payload:{change:"model",model:t.to,from:t.from??null,verified:t.verified??null}};case"codex-update":return{type:"box.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:"codex",payload:{change:"cli-update",from:t.from??null,to:t.to??null,status:t.status,...t.error?{error:t.error}:{}}};case"opened":return{type:"box.state.changed",actor:"user",source:"orchestrator",boxId:t.box,payload:{change:"opened",...fr(t)}};case"archived":case"unarchived":case"track-deleted":return{type:"box.state.changed",actor:"user",source:"orchestrator",payload:{change:t.type,...fr(t)}};case"rehydrated":return{type:"box.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Bt(t.agent),payload:{change:"rehydrated"}};case"retagged":return{type:"box.state.changed",actor:"user",source:"orchestrator",payload:{change:"retagged",...fr(t)}};case"rehydrate-failed":return{type:"error",actor:"system",source:"orchestrator",boxId:t.box,provider:Bt(t.agent),payload:{kind:"rehydrate-failed",reason:t.reason}};default:return null}}var na=Promise.resolve(),el=t=>(na=na.then(t).catch(()=>{}),na),Xm=()=>na;function Ym(t,e){let n=ra(e);if(!n)return;let r=e?.type==="track-deleted"?{id:t.id}:t;el(()=>C(r,n))}var Jm=new Set;async function Zm(t){if(!(!t?.id||Jm.has(t.id))){Jm.add(t.id);try{let e=await j(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=ra(r);!o||n.has(`${o.type}|${o.payload?.ts??"?"}`)||await C(t,o)}}catch{}}}function fr(t){let{type:e,at:n,...r}=t;return r}var tl=new Map;function Qm(t,e,n){!n?.promptId||tl.get(t.id)===n.promptId||(tl.set(t.id,n.promptId),el(()=>C(t,{type:"prompt.raised",actor:"agent",source:"pane",boxId:e?.name,provider:Bt(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 nl(t,e,n,r={}){tl.delete(t.id),el(()=>C(t,{type:"prompt.answered",actor:"user",source:"orchestrator",boxId:e?.name,provider:Bt(n?.provider||e?.agent),payload:{promptId:n?.promptId,...r}}))}import{randomUUID as bA}from"node:crypto";var eh=/(?<!\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,nh=/(?<!\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,rh=/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,oh=/(?<!\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,Hv=new RegExp(`\\b(?:${eh}|${nh}|${rh}|${oh})\\b`,"i"),Wv=/\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,qv=/^(?:commit|push|open|create|raise|rebase|ready|ok(?:ay)?\s+to|good\s+to)\b/i,Gv=90;function Kv(t){return String(t).split(/(?<=[.?!])\s+|\n+/).map(e=>e.trim()).filter(Boolean)}function sh(t){let e=String(t||"").trim();if(!e)return null;let n=e.slice(-600);for(let r of Kv(n).reverse()){if(!Hv.test(r))continue;let o=r.endsWith("?")&&r.length<=Gv&&qv.test(r);if(Wv.test(r)||o)return r.length>200?r.slice(0,197)+"\u2026":r}return null}var ah=["commit","rebase","push","pr"],Vv=new RegExp(`\\b(?:${eh})\\b`,"i"),zv=new RegExp(`\\b(?:${oh})\\b`,"i"),Jv=new RegExp(`\\b(?:${nh})\\b`,"i"),Xv=new RegExp(`\\b(?:${rh})\\b`,"i"),Yv=/\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 ih(t){let e=String(t||""),n=[];Vv.test(e)&&n.push("commit"),zv.test(e)&&n.push("rebase"),Jv.test(e)&&n.push("push");let r=Yv.test(e);!r&&Xv.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 oa(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 Zv=3e4;function ch(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<=Zv&&n.push({box:r.box,from:r.from,to:e+1e12}),n}function lh(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 th={commit:"commit",rebase:"rebase",push:"push",pr:"open a PR"};function uh(t){let e=(Array.isArray(t)?t:[]).filter(r=>th[r]).map(r=>th[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 rA}from"node:child_process";import{promisify as oA}from"node:util";import Qv from"node:fs";import rl from"node:fs/promises";import mh from"node:path";import tA from"node:os";var hh=process.env.BOSTRAT_STATE_DIR||mh.join(process.env.HOME||tA.tmpdir(),".bostrat"),sa=mh.join(hh,"github-metrics.json"),wh=60*6e4,dh=5*6e4,eA=6e4,pr=Date.now(),xe=new Map,He=null,Tn=0,fh=0,aa=t=>[...t.values()].reduce((e,n)=>e+n,0);try{let t=JSON.parse(Qv.readFileSync(sa,"utf8"));t?.windowStart&&(Date.now()-t.windowStart<wh?(pr=t.windowStart,xe=new Map(Object.entries(t.byCaller||{})),He=t.lastHour||null):aa(new Map(Object.entries(t.byCaller||{})))&&(He={start:t.windowStart,total:aa(new Map(Object.entries(t.byCaller||{}))),byCaller:t.byCaller||{}}))}catch{}var ph=Promise.resolve();function gh(t,e=!1){if(!e&&t-fh<eA)return;fh=t;let n=JSON.stringify({windowStart:pr,byCaller:Object.fromEntries(xe),lastHour:He});ph=ph.then(()=>rl.mkdir(hh,{recursive:!0})).then(()=>rl.writeFile(`${sa}.tmp`,n)).then(()=>rl.rename(`${sa}.tmp`,sa)).catch(()=>{})}function yh(t){t-pr<wh||(He={start:pr,total:aa(xe),byCaller:Object.fromEntries(xe)},He.total&&console.log(`[github] ${He.total} calls last hour`,He.byCaller),pr=t,xe=new Map,gh(t,!0))}function Se(t="other",e=Date.now()){yh(e),xe.set(t,(xe.get(t)||0)+1),gh(e)}function bh(t=Date.now()){return yh(t),{windowStart:pr,total:aa(xe),byCaller:Object.fromEntries(xe),lastHour:He,backoffUntil:Tn>t?Tn:null}}var nA=/rate limit|too quickly|abuse detection|HTTP 429/i;function We(t){return nA.test(String(t?.message||t||""))}function qe(t,e=Date.now()){return Tn<=e&&console.warn(`[github] rate limited \u2014 backing off ${dh/6e4} min:`,String(t?.message||t||"").slice(0,200)),Tn=Math.max(Tn,e+dh),Tn}function ia(t=Date.now()){return Math.max(0,Tn-t)}async function ol(t,e){let n={working:!1,prompted:!1};if(!t)return n;let r=await mt(t,{lines:St}).catch(()=>"");if(!Oe(e,r))return pd(e,r)?{working:!0,prompted:!1}:n;if(lr(e,r)){let o=await Re(t).catch(()=>r);if(An({name:t,agent:e},o))return{working:!1,prompted:!0}}return{working:!0,prompted:!1}}async function ca(t,e){return(await ol(t,e)).working}var kh=oA(rA),xh=[4e3,15e3,45e3],sA=2500,aA=5*6e4;async function sl(t,e,{midTurn:n=ca,pollMs:r=sA,capMs:o=aA}={}){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 iA=6e4,cA=9e4,lA=2e3,uA=/(?:^|[;&|]\s*|\)\s*)gh\s+(?:[^\s;&|"'`$]+\s+)*?pr\s+create\b/,dA=/(?:^|[;&|]\s*|\)\s*)gh\s+(?:[^\s;&|"'`$]+\s+)*?pr\s+merge\b/,fA=/\s--web\b/,Sh=/(?:--repo|-R)[\s=]+([\w.-]+\/[\w.-]+)/;function al(t,e){return t.slice(e).replace(/^[\s;&|)]+/,"").replace(/"[^"]*"|'[^']*'/g,'""').split(/[;&|]/)[0]}function vh(t){let e=String(t||""),n=e.match(uA);if(!n)return null;let r=al(e,n.index);return fA.test(r)?null:{repo:r.match(Sh)?.[1]??null}}var pA=new Set(["--subject","-t","--body","-b","--body-file","-F","--match-head-commit","--author-email","-A","--repo","-R"]);function Ah(t){let e=String(t||""),n=e.match(dA);if(!n)return null;let r=al(e,n.index),o=al(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("-")){pA.has(c)&&i++;continue}s=c.replace(/^["']+|["']+$/g,"")||null;break}return{repo:r.match(Sh)?.[1]??null,selector:s,auto:/\s--auto\b/.test(r)}}var mA="number,url,title,headRefName,createdAt";async function hA(t,e){let n=async s=>{Se("observer");let{stdout:a}=await kh("gh",["pr","list","--repo",t,"--author","@me","--state","all","--limit","10","--json",mA],{timeout:3e4,maxBuffer:4*1024*1024,env:{...process.env,...s}}).catch(i=>{throw We(i)&&qe(i),i});return JSON.parse(a||"[]")},r=await ie({slug:t}),o=await n(r);return!o.length&&r.GH_TOKEN&&(o=await n({}).catch(()=>[])),o.filter(s=>Date.parse(s.createdAt)>=e-iA).sort((s,a)=>Date.parse(a.createdAt)-Date.parse(s.createdAt))}async function wA(t,e){if(e)return[e];let n=[];for(let r of Th(t)){let o=await Z(r).catch(()=>null);o&&!n.includes(o)&&n.push(o)}for(let r of await ke().catch(()=>[])){let o=await Z(r.path).catch(()=>null);o&&!n.includes(o)&&n.push(o)}return n}function Th(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 gA(t,e,n){let r=t.workspace?.branch,o=[];for(let s of await wA(t,e)){let a;try{a=await hA(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 Ge=new Set;function _h(t,e,{delays:n=xh,turnEnd:r}={}){let o=vh(e.command);if(!o)return null;let s=!!Ah(e.command),a=`${t.id}|${e.id??e.ts??e.command}`;if(Ge.has(a))return null;Ge.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 gA(t,o.repo,i).catch(()=>null);if(!u)continue;let{slug:f,pr:d}=u,{link:m,created:h}=await Pt({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 no(f,d.number))?null:(await sl(e.box,e.agent,r),await C({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{Ge.delete(a)}})();return c.catch(()=>Ge.delete(a)),c}var yA="number,url,title,headRefName,state";function Eh(t,e,{delays:n=xh,turnEnd:r,openedWait:o}={}){let s=Ah(e.command);if(!s)return null;let a=`merge|${t.id}|${e.id??e.ts??e.command}`;if(Ge.has(a))return null;Ge.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=Th(t);if(h.length>1&&/^\d+$/.test(String(s.selector??"")))return null;for(let w of h){let g=await Z(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 Tt(c,h.number);return g&&g.trackId!==t.id?null:(g||await Pt({repo:c,number:h.number,trackId:t.id,url:h.url,headRef:h.headRefName,title:h.title,source:"merge-observed"}),await ro(c,h.number),w&&((await j(t).catch(()=>[])).some(_=>_.type==="pr.state.changed"&&(_.payload?.change==="auto-armed"||_.payload?.change==="merged")&&_.payload?.number===h.number)||(await sl(e.box,e.agent,r),await C({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{Se("observer");let{stdout:L}=await kh("gh",["pr","view",String(l),"--repo",$,"--json",yA],{timeout:3e4,maxBuffer:4*1024*1024,env:{...process.env,...await ie({slug:$})}}),A=JSON.parse(L||"null");A?.number&&w.push({slug:$,pr:A})}catch(L){We(L)&&qe(L)}let g=w.find($=>$.pr.state==="MERGED")??(w.length===1?w[0]:null);if(!g)continue;c=g.slug;let k=g.pr;if(!k?.number)continue;if(k.state==="CLOSED")return null;if(k.state!=="MERGED"){if(m=k,s.auto)return d(k,!0);continue}let v=await Tt(c,k.number);if(v&&v.trackId!==t.id)return null;v||await Pt({repo:c,number:k.number,trackId:t.id,url:k.url,headRef:k.headRefName,title:k.title,source:"merge-observed"}),await sl(e.box,e.agent,r);let _=await j(t).catch(()=>[]);if(_.some($=>$.type==="pr.state.changed"&&$.payload?.change==="merged"&&$.payload?.number===k.number))return null;let M=$=>$.some(L=>L.type==="pr.state.changed"&&L.payload?.change==="opened"&&L.payload?.number===k.number),V=0;if(!M(_)){if(vh(e.command)&&await no(c,k.number)){let $=Date.now();V=$+1,await C({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:k.number,url:k.url,title:k.title,branch:k.headRefName,repo:c,selfMerge:!0}})}else if((await Tt(c,k.number))?.announced){let $=o?.capMs??cA,L=o?.pollMs??lA,A=Date.now()+$;for(;Date.now()<A&&(await new Promise(O=>setTimeout(O,L)),!M(await j(t).catch(()=>[]))););}}return await C({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:Math.max(Date.now(),V),change:"merged",number:k.number,url:k.url,title:k.title,branch:k.headRefName,repo:c}}),await Ve(c,k.number).catch(()=>{}),await Ke(c,k.number).catch(()=>{}),await je(c,k.number,"merged"),k}return m?d(m,!1):null}finally{Ge.delete(a)}})();return i.catch(()=>Ge.delete(a)),i}function cl(t){return t.sourceId?`${t.role}|id:${t.sourceId}`:`${t.role}|${t.ts??"?"}|${String(t.text||"").slice(0,40)}`}function Ih(t){return t.id?`cmd|${t.box}|id:${t.id}`:`cmd|${t.box}|${t.ts??"?"}|${String(t.command||"").slice(0,40)}`}function Rh(t){return`job|${t.box}|id:${t.id}`}var il=new Map,la=new Map;async function ll(t){return il.has(t.id)?il.get(t.id):(la.has(t.id)||la.set(t.id,j(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(cl({role:o,sourceId:r.payload.sourceId})),n.keys.add(cl({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(Ih({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(Rh({id:r.payload.id,box:r.boxId}));return il.set(t.id,n),n}).finally(()=>la.delete(t.id))),la.get(t.id))}async function $h(t,e){try{let n=(await ll(t)).keys;for(let r of e||[]){if(!r||r.ts==null||!String(r.text||"").trim()&&!r.images?.length)continue;let o=cl(r);if(!n.has(o)&&(n.add(o),await C(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=sh(r.text);if(s){let{offered:a,toPr:i}=ih(s),c;if(t.workspace?.kind==="multi"){let l=await go(t).catch(()=>[]);c=l.length?l.map(u=>({name:u.name,slug:u.repo??null})):void 0}await C(t,{type:"pr.offered",actor:"agent",source:"transcript",boxId:r.box,provider:r.agent||null,payload:{offerId:bA(),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 Oh(t,e){try{let n=await ll(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=Ih(o);n.keys.has(s)||(n.keys.add(s),n.cmdN+=1,await C(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}}),_h(t,o),Eh(t,o))}}catch{}}async function Ch(t,e){try{let n=await ll(t),r=[...e||[]].filter(o=>o&&o.id).sort((o,s)=>(o.ts||0)-(s.ts||0));for(let o of r){let s=Rh(o);n.keys.has(s)||(n.keys.add(s),await C(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 kA=/^Base directory for this skill:\s*(\S+)/,xA=/^Run the "([^"]+)" workflow\./,SA=/<command-name>\s*\/?([a-z0-9:_-]+)\s*<\/command-name>/i,vA=/^<local-command-(?:stdout|caveat)>/i,AA=/^\[Image: original \d+x\d+, displayed at \d+x\d+\.\s*Multiply coordinates by [\d.]+ to map to original image\.\]$/,TA=/^Continue from where you left off\.$/,_A=/^<task-notification>/i,EA=/<summary>([\s\S]*?)<\/summary>/i,IA=/<task-id>([\s\S]*?)<\/task-id>/i,RA=/<tool-use-id>([\s\S]*?)<\/tool-use-id>/i,$A=/<status>([\s\S]*?)<\/status>/i,OA=/<result>([\s\S]*?)(?:<\/result>|$)/i;var ul=new Set(["model","clear","compact","login","exit","quit"]);function Mh(t){return ul.has(String(t||"").replace(/^\//,"").toLowerCase())}var CA=/^\/([a-z0-9:_-]+)$/i;function ua(t){let e=String(t||"").trim();if(!e)return null;let n=e.match(kA);if(n)return{kind:"skill",name:n[1].split("/").filter(Boolean).pop()||"skill"};let r=e.match(xA);if(r)return{kind:"skill",name:r[1]};let o=e.match(SA);if(o){let a=o[1].toLowerCase();return ul.has(a)?{kind:"suppress"}:{kind:"command",command:`/${a}`}}let s=e.match(CA);if(s&&ul.has(s[1].toLowerCase()))return{kind:"suppress"};if(vA.test(e))return{kind:"suppress"};if(TA.test(e))return{kind:"suppress"};if(AA.test(e))return{kind:"suppress"};if(_A.test(e)){let a=c=>MA(e.match(c)?.[1].trim()||"")||null,i=a(OA);return{kind:"task",summary:a(EA)||"",taskId:a(IA),toolUseId:a(RA),status:a($A)?.toLowerCase()||null,resultHead:i?i.length>600?`${i.slice(0,600).trimEnd()}\u2026`:i:null}}return null}function MA(t){return t.replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/'/g,"'").replace(/&/g,"&")}function Nh(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 Ph(t){return t?.kind==="skill"?"skill-run":t?.kind==="task"?"task-done":"command-run"}var st=t=>t.payload?.ts??t.ts,NA=new Set(["opened","ci-fixed","reconciled","pushed"]),Bh=t=>`${t.repo??""}#${t.number}`;function PA(t,e,n){let r=Bh(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 yo(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 T=S[0].split("/").pop().toLowerCase();f.has(T)||f.set(T,[]),f.get(T).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(T=>T>=p-d&&T<=p+m);for(let y of t||[])if(y.type==="turn.retracted"){r.add(`${y.boxId}|${y.payload?.saidTs}`);let p=Vt(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=Vt(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:st(y)};p.toolUseId&&i.set(p.toolUseId,S);let T=String(p.summary||"").match(u);T&&l.set(T[1],S)}let w=new Map,g=new Map,k=null,v=new Map,_=new Map,M=new Map,V=new Map,$=new Map,L=new Map,A=new Map,O=6e4,H=new Map,nt=(y,p)=>{let S=o.get(y);if(!S?.length)return!1;let T=Vt(p),et=s.get(y);return S.some(I=>{if(!T.startsWith(I))return!1;let R=Vt(T.slice(I.length));return!!R&&!!et?.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||ua(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"&&Mh(S.command))break;S.kind!=="suppress"&&e.push({id:y.id,kind:"event",type:Ph(S),name:S.name??null,command:S.command??null,summary:S.summary??null,ts:st(y),box:y.boxId,agent:y.provider});break}}if(r.has(`${y.boxId}|${st(y)}`)||y.source==="transcript"&&nt(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:st(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:st(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;A.clear();let S=(p.images||[]).filter(R=>!h(R,st(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 T=y.provider==="codex"?(p.tools||[]).filter(R=>!Tc.has(R)&&!fo.has(R)):p.tools||[],et=p.text,I=null;if(Uc.has(y.provider)){let R=Hc(String(p.text||""));R&&(et=R.text,I={questions:R.questions})}e.push({id:y.id,kind:"message",role:"assistant",text:et,ts:st(y),box:y.boxId,agent:y.provider,boxRole:p.boxRole,...I?{ask:I}:{},...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=st(y),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 et={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},I=H.get(y.boxId),R=I?.jobs[I.jobs.length-1]?.startedTs;if(I&&R!=null&&S-R<=O)I.jobs.push(et);else{let P={id:y.id,kind:"job-dock",dockId:p.id,ts:S,box:y.boxId,agent:y.provider,jobs:[et]};H.set(y.boxId,P),e.push(P)}break}case"handoff.done":e.push(ze(y,"lead-handoff"));break;case"subtask.delegated":e.push({...ze(y,"subtask-delegated"),to:y.boxId,toAgent:y.provider});break;case"subtask.merged":e.push(ze(y,"subtask-merged"));break;case"pr.state.changed":{if(!p.change)break;if(p.change==="opened"){for(let T of v.values())T.status="stale";v.clear()}let S=ze(y,`pr-${p.change}`);if(NA.has(p.change)&&p.number!=null){let T=PA(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 et=[S];for(let I of w.keys())g.set(I,et)}else{let et=g.get(T)??[];et.push(S),g.set(T,et)}}else if(p.change==="merged"||p.change==="closed"||p.change==="lost"){let T=Bh(p),et=k&&!k.complete||w.size>=2;if(w.has(T))w.delete(T),g.delete(T);else if(p.number!=null)for(let I of[...w.keys()])I.endsWith(`#${p.number}`)&&(w.delete(I),g.delete(I));if(et&&p.number!=null){(!k||k.complete)&&(k={id:y.id,kind:"event",type:"pr-group-settled",prs:[],remaining:[],complete:!1,at:st(y),ts:st(y),box:y.boxId,agent:y.provider},e.push(k)),k.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:st(y)}),k.remaining=[...w.values()],k.complete=w.size===0;break}}e.push(S);break}case"pr.offered":{if(!p.offerId)break;for(let T of v.values())T.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:st(y),box:y.boxId,agent:y.provider};v.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,v.delete(p.offerId));break}case"box.state.changed":{let S=p.change==="model"?"model-change":p.change;if(!S)break;let T=ze(y,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:y.id,kind:"view-card",viewId:p.viewId,name:p.name,slug:p.slug,port:p.port,origin:p.origin,ts:st(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:st(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:st(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:st(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:st(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:st(y),box:y.boxId,agent:y.provider});break}case"ticket.move.offered":{if(!p.offerId)break;for(let T of M.values())T.key===p.key&&(T.status="stale",M.delete(T.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:st(y),box:y.boxId,agent:y.provider};M.set(p.offerId,S),V.set(p.offerId,S),e.push(S);break}case"ticket.move.answered":{let S=V.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(ze(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:st(y),box:y.boxId,agent:y.provider});break}case"ticket.flow.changed":e.push(ze(y,"ticket-flow"));break;case"ticket.create.offered":{if(!p.proposalId)break;for(let T of $.values())T.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:st(y),box:y.boxId,agent:y.provider};$.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,$.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:y.id,kind:"ticket-created",...S,ts:st(y),box:y.boxId,agent:y.provider});break}case"error":{if(!p.kind)break;e.push(ze(y,p.kind));break}case"provider.connected":{e.push({id:y.id,kind:"event",type:"provider-connected",provider:p.provider||y.provider,ts:st(y),at:st(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=A.get(S);T||(T={id:y.id,kind:"prompt",groupId:S,provider:y.provider,box:y.boxId,agent:y.provider,questionTotal:p.questionTotal??1,questions:[],ts:st(y)},A.set(S,T));let et=p.questionIndex??0,I={index:et,header:p.header||"",question:p.question,multiSelect:!!p.multiSelect,chosen:Array.isArray(p.chosen)?p.chosen:[]},R=T.questions.findIndex(P=>P.index===et);R>=0?T.questions[R]=I:T.questions.push(I),T.questions.sort((P,Q)=>P.index-Q.index),T.questionTotal=Math.max(T.questionTotal,p.questionTotal??1),new Set(T.questions.map(P=>P.index)).size>=T.questionTotal&&(e.push(T),A.delete(S));break}default:break}}return DA(e.sort((y,p)=>(y.ts??0)-(p.ts??0)))}var BA=2*6e4;function DA(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<=BA){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 dl(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:jA(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??yo(t)).filter(o=>o.kind==="message");return Jp(n,r)}function jA(t){let e=String(t.summary||"").trim();return e&&e!==t.command&&e!==sr(t.command)?e:Fe(t.command,t.description||"")}function ze(t,e){let{ts:n,change:r,...o}=t.payload||{};return{id:t.id,kind:"event",type:e,...o,at:st(t),ts:st(t)}}function LA(t){let e=[],n=null;for(let r of String(t||"").split(`
|
|
111
|
-
`)){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
|
|
112
|
-
`)}catch{}}function Gh(){let t={};for(let[e,n]of wl)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:pa.slice(-20)}}var ga=nT(eT),rT={timeout:3e4,maxBuffer:8*1024*1024};async function Ht(t,e,n={}){let{caller:r="prs",...o}=n,s=await ie({cwd:t});Se(r);try{let{stdout:a}=await ga("gh",e,{cwd:t,...rT,...o,env:{...process.env,...s,...o.env||{}}});return String(a||"")}catch(a){throw We(a)&&qe(a),a}}var ha=null;async function bl(){if(ha&&Date.now()-ha.at<6e4)return ha.data;let t;try{Se("auth");let e=await ga("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 io();e.configured&&(t={installed:!0,authenticated:!0,login:e.username,via:"broker"})}catch{}return ha={at:Date.now(),data:t},t}async function oT(t){try{let{stdout:e}=await ga("git",["-C",t,"config","--get","remote.origin.url"],{timeout:5e3});return/github\.com/.test(e)}catch{return!1}}function Vh(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 gl(t){let e=(t||[]).map(Vh),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 kl="number,title,url,headRefName,headRefOid,baseRefName,isDraft,author,reviewDecision,mergeable,mergeStateStatus,statusCheckRollup,autoMergeRequest,additions,deletions,createdAt,updatedAt",zh=kl.replace("statusCheckRollup,","");function xl(t){return/Resource not accessible by personal access token/.test(String(t?.stderr||t?.message||""))}async function Jh(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 Xh(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 Yh(t,e){let n=await Jh(t,e);return n===null?{...gl([]),unavailable:!0}:gl(Xh(n))}function wa(t,e=gl(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 sT=9e4,mr=new Map;function aT(t,e,n){let r=new Set;for(let o of e){let s=`${t}#${o.number}`;if(o.mergeable!=="UNKNOWN"&&o.mergeStateStatus!=="UNKNOWN"){mr.delete(s);continue}r.add(s),mr.has(s)||mr.set(s,n),o.mergeabilityPending=n-mr.get(s)<sT}for(let o of mr.keys())o.startsWith(`${t}#`)&&!r.has(o)&&mr.delete(o);return e}async function hr(t,e){let n=await ve(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",kl]);return wa(JSON.parse(o))}catch(o){if(!xl(o))throw o;let s=await Ht(n.path,["pr","view",String(r),"--json",zh]),a=JSON.parse(s);return wa(a,await Yh(n.path,a.headRefOid))}}var Kh=(t,e)=>t.number-e.number;async function ko(t,{caller:e="prs"}={}){try{let n=await Ht(t,["pr","list","--json",kl,"--limit","50"],{caller:e});return JSON.parse(n||"[]").map(r=>wa(r)).sort(Kh)}catch(n){if(!xl(n))throw n;let r=await Ht(t,["pr","list","--json",zh,"--limit","50"],{caller:e});return(await Promise.all(JSON.parse(r||"[]").map(async o=>wa(o,await Yh(t,o.headRefOid))))).sort(Kh)}}var iT=3e4,cT=1500,bo=new Map;function le(){bo.clear()}async function lT(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 Hs(t,n.number,{headSha:n.headSha}).catch(()=>{}):n.ciFix={delegatedAt:r.ciFix.delegatedAt,trackId:r.ciFix.trackId??null}),r.reconcile&&(o(r.reconcile)?await qs(t,n.number,{headSha:n.headSha}).catch(()=>{}):n.reconcile={delegatedAt:r.reconcile.delegatedAt,trackId:r.reconcile.trackId??null})}return e}async function Zh(){let t=await bl();if(!t.authenticated)return{auth:t,repos:[]};let e=[];for(let a of await ke())await oT(a.path)&&e.push(a);let n=Date.now(),r=ia(n)>0,o=await Promise.all(e.map(async a=>{let i=bo.get(a.path),c=i?.pending?cT:iT;if(i&&(r||n-i.at<c))return i.entry;try{let l=await Z(a.path).catch(()=>null),u=aT(a.path,await ko(a.path,{caller:"tab"}),n);if(await lT(l,u),i?.entry?.prs)for(let m of u)i.entry.prs.find(w=>w.number===m.number)?.checks?.pending>0&&m.checks.total>0&&m.checks.pending===0&&ma({change:m.checks.failed>0?"checks-failed":"checks-green",repo:l||a.name,number:m.number,githubAt:m.checks.settledAt,source:"tab"});let f={name:a.name,path:a.path,prs:u},d=u.some(m=>m.mergeabilityPending||m.checks.pending>0||m.autoMergeArmed);return bo.set(a.path,{at:Date.now(),entry:f,pending:d}),f}catch(l){return i?i.entry:{name:a.name,path:a.path,prs:[],error:ce(l)}}})),s=new Set(e.map(a=>a.path));for(let a of bo.keys())s.has(a)||bo.delete(a);return{auth:t,repos:o}}async function ve(t){let e=(await ke()).find(n=>n.path===t);if(!e)throw new Error(`unknown repo: ${t}`);return e}async function _n(t){return so(t)}var xo=new Set(["squash","merge","rebase"]);async function wr(t,e,{method:n="squash"}={}){let r=await ve(t);if(!xo.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 ie({cwd:r.path})).GH_TOKEN){let a=await fc().catch(()=>null);a&&s.push("--body",`Merged by bostrat on behalf of @${a}`)}try{await Ht(r.path,s)}catch(a){throw new Error(ce(a))}finally{le()}return{ok:!0,number:o}}async function So(t,e){let n=await ve(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(ce(s))}finally{le()}let o=await Z(n.path).catch(()=>null);return o&&await Sp(o,r).catch(()=>{}),{ok:!0,number:r}}async function Sl(t,e){let n=await ve(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(ce(o))}finally{le()}return{ok:!0,number:r}}var uT=2500,dT=9e4,fT=t=>new Promise(e=>setTimeout(e,t));function vl(t){return t.green&&!t.isDraft}function pT(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 Qh(t,{update:e=!0}={}){return t.isDraft?{action:"skip",reason:"draft"}:vl(t)?{action:"merge"}:e&&t.mergeStateStatus==="BEHIND"&&t.mergeable!=="CONFLICTING"&&t.checks.failed===0?{action:"update-arm"}:{action:"arm"}}function tw(t){return[...t.filter(e=>e.plan.action!=="merge"),...t.filter(e=>e.plan.action==="merge")]}var yl=new Map;function ew(){let t={};for(let[e,n]of yl)t[e]={...n};return t}async function nw(t,{method:e="merge",onMerged:n=null}={}){let r=await ve(t);if(!xo.has(e))throw new Error(`invalid merge method: ${e}`);if(yl.get(r.path)?.running)throw new Error("a merge-all is already running for this repo");let o=(await ko(r.path)).filter(vl).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 yl.set(r.path,s),hT(s,o,{listPrs:()=>ko(r.path),merge:a=>wr(r.path,a,{method:e}),sleep:fT,onMerged:n}).catch(()=>{}),{ok:!0,repo:r.path,total:s.total,queue:o.map(a=>a.number)}}async function mT(t,e){let n=Date.now()+(e.settleMaxMs??dT),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(uT)}}async function hT(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 mT(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: ${ce(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(!vl(a)){t.skipped.push({number:a.number,title:a.title,reason:pT(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:ce(i)})}}}finally{t.current=null,t.running=!1,t.finishedAt=Date.now(),le()}}async function Al(t,{title:e,body:n="",base:r=null}={}){if(typeof e!="string"||!e.trim())throw new Error("title required");try{await ga("git",["-C",t,"push","-u","origin","HEAD"],{timeout:12e4,maxBuffer:4*1024*1024})}catch(l){throw new Error(`push failed: ${ce(l)}`)}let o=n||"";if((await ie({cwd:t})).GH_TOKEN){let l=await fc().catch(()=>null);l&&(o=`${o?`${o}
|
|
113
|
-
|
|
114
|
-
`:""}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
|
|
134
|
+
`;import{execFile as ET}from"node:child_process";import{promisify as IT}from"node:util";var ll=IT(ET),mh=64*1024*1024;async function ht(t,e,{input:n}={}){let r=ll("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 da(t){if(!t)return!1;try{let{stdout:e}=await ht(t,["rev-parse","--is-inside-work-tree"]);return e.trim()==="true"}catch{return!1}}async function 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 ul(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)=>ll("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 ll("rm",["-f",r])}catch{}}}async function dl(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 fl(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 pl(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 fa(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 ml(t,e){return!e||!e.length?t:[...t,"--",".",...e.map(n=>`:(exclude)${n}`)]}async function hl(t,e,n,r){let{stdout:o}=await ht(t,ml(["diff","--shortstat",e,n],r));return $T(o)}async function wl(t,e,n,r){let{stdout:o}=await ht(t,ml(["diff","--name-only",e,n],r));return o.split(`
|
|
136
|
+
`).map(s=>s.trim()).filter(Boolean)}async function gl(t,e,n,r){let{stdout:o}=await ht(t,ml(["diff","--binary",e,n],r));if(!o.trim())return{applied:!0,empty:!0,conflicts:[]};if((await cl(t,o,["--whitespace=nowarn"])).ok)return{applied:!0,empty:!1,conflicts:[]};let s=await cl(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 cl(t,o,["--reject","--whitespace=nowarn"]),{applied:!1,empty:!1,conflicts:[`patch did not apply cleanly: ${s.err}`]})}async function cl(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 pa=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 _o(t,e,{timeoutMs:n=NT}={}){let r=Date.now()+n;for(;;){let o=await gt(t,{lines:80}).catch(()=>""),s=e(o);if(s)return s;if(Date.now()>r)return null;await pa(PT)}}async function LT(t,e){return await vt(t,`/model ${e.model}`,Se("claude")),{verified:!1,note:"confirmed when the next turn's transcript model flips"}}async function FT(t,e){await ys(t),await vt(t,"/model",{enter:!1}),await pa(250),await xt(t,["Enter"],{kind:"model-switch"});let n=await _o(t,kh);if(!n)throw new Error("codex model picker didn't open");let r=n.find(i=>i.id===e.model);if(!r)throw await xt(t,["Escape"],{kind:"model-switch"}).catch(()=>{}),new Error(`unknown model "${e.model}" \u2014 codex offers: ${n.map(i=>i.id).join(", ")}`);let o=i=>{let c=jT(i);return!c||c.model!==e.model||e.effort&&c.effort&&c.effort.toLowerCase()!==e.effort.toLowerCase()?null:c};await vt(t,String(r.n),{enter:!1,kind:"model-switch"});let s=await _o(t,i=>xh(i)||!kh(i)&&o(i)&&{done:o(i)});if(!s)throw await xt(t,["Escape"],{kind:"model-switch"}).catch(()=>{}),new Error("codex reasoning-level picker didn't open");if(!s.done){let i=e.effort?Th.get(e.effort):null,c=i?s.find(l=>l.label.toLowerCase()===i.toLowerCase()):s.find(l=>l.isDefault)||s[0];if(!c)throw await xt(t,["Escape","Escape"],{kind:"model-switch"}).catch(()=>{}),new Error(`codex doesn't offer effort "${e.effort}" for ${e.model}`);await vt(t,String(c.n),{enter:!1,kind:"model-switch"})}let a=s.done||await _o(t,i=>!xh(i)&&o(i));if(!a)throw new Error("codex never confirmed the model change");return{verified:!0,model:a.model,effort:a.effort}}async function UT(){let t=bh.join(process.env.CURSOR_CONFIG_DIR||bh.join(CT.homedir(),".cursor"),"cli-config.json");try{return JSON.parse(await MT.readFile(t,"utf8"))?.model||null}catch{return null}}async function HT(t,e){await ys(t),await ks(t,`/model ${e.filter}`,{kind:"model-switch"});let n=await _o(t,Sh),r=c=>String(c||"").toLowerCase(),o=c=>r(c.label).startsWith(r(e.filter)),s=n?.rows.find(c=>c.active)||null;if(n&&!(s&&o(s)))for(let c=0;c<12;c++){let l=await _o(t,Sh,{timeoutMs:1500});if(s=l?.rows.find(u=>u.active)||null,s&&o(s))break;if(!l||!l.rows.some(o)){s=null;break}await xt(t,["Down"],{kind:"model-switch"}),await pa(250)}if(!n||!s||!o(s)){await xt(t,["Escape"],{kind:"model-switch"}).catch(()=>{});let c=Array(`/model ${e.filter}`.length).fill("BSpace");throw await xt(t,c,{settleMs:15,kind:"model-switch"}).catch(()=>{}),new Error(n?`cursor picker has no model matching "${e.filter}"`:"cursor model picker didn't open")}await xt(t,["Enter"],{kind:"model-switch"}),await pa(800);let a=await UT();return{verified:!!a&&(a.modelId===e.model||a.displayModelId===e.model||r(a.displayName).startsWith(r(e.filter))),model:a?.modelId||null,note:"cursor applies this as the global default for new sessions too"}}async function Ih(t,e,n){return t==="codex"?FT(e,n):t==="cursor"?HT(e,n):LT(e,n)}var Ft=t=>t||null;function ha(t){let e=t||{},n=WT(e);return n&&(n.payload={ts:e.at,...n.payload}),n}function WT(t){switch(t.type){case"said":return{type:"turn.user",actor:"user",source:"orchestrator",boxId:t.box,provider:Ft(t.agent),payload:{text:t.text,boxRole:t.boxRole,...t.kind?{kind:t.kind}:{},...t.attachments?.length?{attachments:t.attachments}:{}}};case"unsaid":return{type:"turn.retracted",actor:"user",source:"orchestrator",boxId:t.box,provider:Ft(t.agent),payload:{saidTs:t.saidTs,text:t.text,boxRole:t.boxRole}};case"orchestration-prompt":return{type:"turn.user",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Ft(t.agent),payload:{text:t.prompt,kind:t.kind,boxRole:t.boxRole}};case"lead-handoff":return{type:"handoff.done",actor:"orchestrator",source:"orchestrator",boxId:t.to,provider:Ft(t.toAgent),payload:{from:t.from,fromAgent:t.fromAgent,to:t.to,toAgent:t.toAgent,reason:t.reason,handoffStartedAt:t.handoffStartedAt,fromSessionId:t.fromSessionId,toSessionId:t.toSessionId,epoch:t.epoch,singleBox:t.singleBox,forced:t.forced,recovered:t.recovered}};case"lead-handoff-failed":return{type:"handoff.failed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Ft(t.toAgent),payload:{box:t.box,fromAgent:t.fromAgent,toAgent:t.toAgent,reason:t.reason,error:t.error,rolledBack:t.rolledBack,handoffStartedAt:t.handoffStartedAt}};case"subtask-delegated":return{type:"subtask.delegated",actor:"orchestrator",source:"orchestrator",boxId:t.to,provider:Ft(t.toAgent),payload:{task:t.task,subtaskId:t.subtaskId,reason:t.reason}};case"subtask-merged":return{type:"subtask.merged",actor:"orchestrator",source:"orchestrator",boxId:t.box||t.from,provider:Ft(t.agent),payload:{...br(t)}};case"subtask-failed":return{type:"error",actor:"system",source:"orchestrator",boxId:t.box||t.to,provider:Ft(t.agent||t.toAgent),payload:{kind:"subtask-failed",...br(t)}};case"subtask-resumed":return{type:"box.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Ft(t.agent),payload:{change:"subtask-resumed",...br(t)}};case"model-change":return{type:"box.state.changed",actor:"user",source:"orchestrator",boxId:t.box,payload:{change:"model",model:t.to,from:t.from??null,verified:t.verified??null}};case"codex-update":return{type:"box.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:"codex",payload:{change:"cli-update",from:t.from??null,to:t.to??null,status:t.status,...t.error?{error:t.error}:{}}};case"opened":return{type:"box.state.changed",actor:"user",source:"orchestrator",boxId:t.box,payload:{change:"opened",...br(t)}};case"archived":case"unarchived":case"track-deleted":return{type:"box.state.changed",actor:"user",source:"orchestrator",payload:{change:t.type,...br(t)}};case"rehydrated":return{type:"box.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Ft(t.agent),payload:{change:"rehydrated"}};case"retagged":return{type:"box.state.changed",actor:"user",source:"orchestrator",payload:{change:"retagged",...br(t)}};case"rehydrate-failed":return{type:"error",actor:"system",source:"orchestrator",boxId:t.box,provider:Ft(t.agent),payload:{kind:"rehydrate-failed",reason:t.reason}};default:return null}}var ma=Promise.resolve(),bl=t=>(ma=ma.then(t).catch(()=>{}),ma),$h=()=>ma;function Oh(t,e){let n=ha(e);if(!n)return;let r=e?.type==="track-deleted"?{id:t.id}:t;bl(()=>O(r,n))}var Rh=new Set;async function Mh(t){if(!(!t?.id||Rh.has(t.id))){Rh.add(t.id);try{let e=await L(t),n=new Set(e.filter(r=>r.source==="orchestrator").map(r=>`${r.type}|${r.payload?.ts??"?"}`));for(let r of t.events||[]){let o=ha(r);!o||n.has(`${o.type}|${o.payload?.ts??"?"}`)||await O(t,o)}}catch{}}}function br(t){let{type:e,at:n,...r}=t;return r}var yl=new Map;function Ch(t,e,n){!n?.promptId||yl.get(t.id)===n.promptId||(yl.set(t.id,n.promptId),bl(()=>O(t,{type:"prompt.raised",actor:"agent",source:"pane",boxId:e?.name,provider:Ft(n.provider||e?.agent),payload:{promptId:n.promptId,question:n.question||n.title||null,options:(n.options||[]).map(r=>r?.label??r).slice(0,12)}})))}function kl(t,e,n,r={}){yl.delete(t.id),bl(()=>O(t,{type:"prompt.answered",actor:"user",source:"orchestrator",boxId:e?.name,provider:Ft(n?.provider||e?.agent),payload:{promptId:n?.promptId,...r}}))}import{randomUUID as xA}from"node:crypto";var Ph=/(?<!\b(?:a|an|the|this|that|another|one|each|any|no|separate|single|new|initial|final|first|second|third|last|later|next|subsequent|extra|additional|amend(?:ed)?|squash|merge|follow[\s-]?up|clean[\s-]?up)\s)commit(?!s\b)(?!\s+(?:history|log|message|hash|sha)s?\b)/.source,Bh=/(?<!\b(?:a|an|the|this|that|another|one|each|any|no|separate|single|new|initial|final|first|second|third|last|later|next|subsequent|extra|additional)\s)push(?!\s+notifications?\b)(?:\s+(?:it|this|that|them|everything|the\s+\w+))?\s*(?:up)?/.source,Dh=/open(?:\s+up)?\s+(?:a|the|that)\s+(?:draft\s+)?(?:pr|pull\s+request)|creat(?:e|ing)\s+(?:a|the)\s+(?:draft\s+)?(?:pr|pull\s+request)|rais(?:e|ing)\s+(?:a|the)\s+(?:pr|pull\s+request)|put\s+up\s+(?:a|the)\s+(?:pr|pull\s+request)|send\s+(?:up\s+)?(?:a|the)\s+(?:pr|pull\s+request)/.source,jh=/(?<!\b(?:a|an|the|this|that|another|one|each|any|no|clean|interactive)\s)rebase(?!s\b)(?:\s+(?:it|this|that|them|(?:this|that|the)\s+branch))?(?:\s+(?:on(?:to)?|against|atop|over)\s+\S+)?/.source,qT=new RegExp(`\\b(?:${Ph}|${Bh}|${Dh}|${jh})\\b`,"i"),KT=/\b(?:want\s+me\s+to|would\s+you\s+like(?:\s+me)?(?:\s+to)?|should\s+i|shall\s+i|do\s+you\s+want(?:\s+me)?(?:\s+to)?|happy\s+to|ready\s+for\s+me\s+to|let\s+me\s+know\s+(?:if|when|whether|and)|say\s+the\s+word|give\s+me\s+the\s+(?:word|go[\s-]?ahead)|if\s+you(?:'d|\s+would)?\s+(?:like|want|prefer)|just\s+say\s+(?:so|yes|go))\b/i,GT=/^(?:commit|push|open|create|raise|rebase|ready|ok(?:ay)?\s+to|good\s+to)\b/i,zT=90;function VT(t){return String(t).split(/(?<=[.?!])\s+|\n+/).map(e=>e.trim()).filter(Boolean)}function Lh(t){let e=String(t||"").trim();if(!e)return null;let n=e.slice(-600);for(let r of VT(n).reverse()){if(!qT.test(r))continue;let o=r.endsWith("?")&&r.length<=zT&>.test(r);if(KT.test(r)||o)return r.length>200?r.slice(0,197)+"\u2026":r}return null}var Fh=["commit","rebase","push","pr"],JT=new RegExp(`\\b(?:${Ph})\\b`,"i"),XT=new RegExp(`\\b(?:${jh})\\b`,"i"),YT=new RegExp(`\\b(?:${Bh})\\b`,"i"),ZT=new RegExp(`\\b(?:${Dh})\\b`,"i"),QT=/\b(?:to|into|onto|on|updat(?:e|es|ing))\s+(?:the|that|this)\s+(?:open\s+|existing\s+|current\s+|same\s+)?(?:draft\s+)?(?:pr|pull\s+request)\b/i;function Uh(t){let e=String(t||""),n=[];JT.test(e)&&n.push("commit"),XT.test(e)&&n.push("rebase"),YT.test(e)&&n.push("push");let r=QT.test(e);!r&&ZT.test(e)&&n.push("pr");let o=null;if(r){let s=e.match(/(?:pr|pull\s+request)\s*\(?\s*#(\d+)\)?/i)||e.match(/#(\d+)/);o={number:s?Number(s[1]):null}}return{offered:n.length?n:null,toPr:o}}function wa(t,{branch:e=null,base:n=null,toPr:r=null,repos:o=null,offeredRebase:s=!1}={}){Array.isArray(t)||(t=t==="push"?["commit","push"]:["commit","push","pr"]);let a=new Set(t),i=Array.isArray(o)&&o.length>0,c=i?o.map(m=>`${m.name}/`).join(", "):null,l=e?` on this branch (${e})`:"",u=[];if(a.has("commit")&&u.push(i?`commit the work in each changed checkout (${c})${l} with a clear commit message per repo`:`commit the work${l} with a clear commit message`),a.has("rebase")){let m=a.has("commit")?"the branch":`this branch${e?` (${e})`:""}`;u.push(i?`rebase ${m} in each changed checkout (${c}) onto its own base branch (fetch origin first), resolving any conflicts so both sides' intent is preserved`:`rebase ${m} onto ${n?`the latest ${n}`:"its base branch"} (fetch origin first), resolving any conflicts so both sides' intent is preserved`)}if(a.has("push")||a.has("pr")){let m=r&&!a.has("pr")?` (the open PR${r.number?` #${r.number}`:""} picks it up)`:"",h=a.has("rebase")?" with `--force-with-lease` (the rebase rewrote history)":"";i?u.push(a.has("commit")||a.has("rebase")?`push each one to its remote${h}${m}`:`push this branch${e?` (${e})`:""} from each changed checkout (${c}) to its remote${m}`):u.push(a.has("commit")||a.has("rebase")?`push it to the remote${h}${m}`:`push this branch${l} to the remote${m}`)}a.has("pr")&&u.push(i?"open a pull request in each changed repo with a concise title and a description of what changed and why (run `gh pr create` inside each checkout)":`open a pull request${n?` against ${n}`:""} with a concise title and a description of what changed and why (use \`gh pr create\`)`);let f=[];a.has("commit")||f.push("don't create any new commits \u2014 ship what's already committed"),s&&!a.has("rebase")&&f.push("don't rebase \u2014 leave the branch's history as it is"),!a.has("push")&&!a.has("pr")&&f.push("don't push"),a.has("pr")||f.push("don't open a pull request");let d=`Yes \u2014 go ahead: ${u.join(", then ")}.`;return f.length&&(d+=` ${f.join(", and ").replace(/^./,m=>m.toUpperCase())}.`),a.has("pr")&&(d+=i?" If a repo's PR for this branch is already open, just push that repo's update instead.":" If a PR for this branch is already open, just push the update instead."),d}var tA=3e4;function Hh(t,e=Date.now()){let n=[],r=null;for(let o of t||[]){let s=o.payload||{},a=s.ts??o.ts;if(o.type==="pr.offer.answered"&&s.action==="pr"){r={box:o.boxId,from:a,lastActivity:a};continue}!r||a==null||a<r.from||(o.type==="pr.offer.answered"?r=null:o.type==="pr.state.changed"&&s.change==="opened"?(n.push({box:r.box,from:r.from,to:a,settled:!0}),r=null):o.type==="turn.user"&&o.source!=="hook"&&!s.kind&&String(s.text||"").trim()?r=null:o.boxId===r.box&&/^(?:turn|tool)\./.test(o.type)&&(r.lastActivity=Math.max(r.lastActivity,a)))}return r&&e-r.lastActivity<=tA&&n.push({box:r.box,from:r.from,to:e+1e12}),n}function Wh(t,e){let n=new Set;for(let r of e||[]){if(!r.settled)continue;let o=null;for(let s of t||[])s.kind!=="message"||s.role!=="assistant"||s.box!==r.box||s.ts==null||s.ts<r.from||s.ts>r.to||(!o||s.ts>=o.ts)&&(o=s);o&&n.add(o)}return n}var Nh={commit:"commit",rebase:"rebase",push:"push",pr:"open a PR"};function qh(t){let e=(Array.isArray(t)?t:[]).filter(r=>Nh[r]).map(r=>Nh[r]);return`The ship you were given the go-ahead for${e.length?` (${e.join(", then ")})`:""} should now be complete. This is an automated ship notification the operator armed on the offer: carry straight on with this Track's follow-up work (dependent tasks, cleanup, next steps) \u2014 don't wait for review or merge. If a ship step failed or is unfinished, finish the ship first. If nothing needs doing, say so briefly.`}import{execFile as sA}from"node:child_process";import{promisify as aA}from"node:util";import eA from"node:fs";import xl from"node:fs/promises";import Vh from"node:path";import nA from"node:os";var Jh=process.env.BOSTRAT_STATE_DIR||Vh.join(process.env.HOME||nA.tmpdir(),".bostrat"),ga=Vh.join(Jh,"github-metrics.json"),Xh=60*6e4,Kh=5*6e4,rA=6e4,kr=Date.now(),Ie=new Map,Xe=null,Cn=0,Gh=0,ya=t=>[...t.values()].reduce((e,n)=>e+n,0);try{let t=JSON.parse(eA.readFileSync(ga,"utf8"));t?.windowStart&&(Date.now()-t.windowStart<Xh?(kr=t.windowStart,Ie=new Map(Object.entries(t.byCaller||{})),Xe=t.lastHour||null):ya(new Map(Object.entries(t.byCaller||{})))&&(Xe={start:t.windowStart,total:ya(new Map(Object.entries(t.byCaller||{}))),byCaller:t.byCaller||{}}))}catch{}var zh=Promise.resolve();function Yh(t,e=!1){if(!e&&t-Gh<rA)return;Gh=t;let n=JSON.stringify({windowStart:kr,byCaller:Object.fromEntries(Ie),lastHour:Xe});zh=zh.then(()=>xl.mkdir(Jh,{recursive:!0})).then(()=>xl.writeFile(`${ga}.tmp`,n)).then(()=>xl.rename(`${ga}.tmp`,ga)).catch(()=>{})}function Zh(t){t-kr<Xh||(Xe={start:kr,total:ya(Ie),byCaller:Object.fromEntries(Ie)},Xe.total&&console.log(`[github] ${Xe.total} calls last hour`,Xe.byCaller),kr=t,Ie=new Map,Yh(t,!0))}function Re(t="other",e=Date.now()){Zh(e),Ie.set(t,(Ie.get(t)||0)+1),Yh(e)}function Qh(t=Date.now()){return Zh(t),{windowStart:kr,total:ya(Ie),byCaller:Object.fromEntries(Ie),lastHour:Xe,backoffUntil:Cn>t?Cn:null}}var oA=/rate limit|too quickly|abuse detection|HTTP 429/i;function Ye(t){return oA.test(String(t?.message||t||""))}function Ze(t,e=Date.now()){return Cn<=e&&console.warn(`[github] rate limited \u2014 backing off ${Kh/6e4} min:`,String(t?.message||t||"").slice(0,200)),Cn=Math.max(Cn,e+Kh),Cn}function ba(t=Date.now()){return Math.max(0,Cn-t)}async function Sl(t,e){let n={working:!1,prompted:!1};if(!t)return n;let r=await gt(t,{lines:Tt}).catch(()=>"");if(!Fe(e,r))return tf(e,r)?{working:!0,prompted:!1}:n;if(wr(e,r)){let o=await je(t).catch(()=>r);if(Mn({name:t,agent:e},o))return{working:!1,prompted:!0}}return{working:!0,prompted:!1}}async function ka(t,e){return(await Sl(t,e)).working}var tw=aA(sA),ew=[4e3,15e3,45e3],iA=2500,cA=5*6e4;async function vl(t,e,{midTurn:n=ka,pollMs:r=iA,capMs:o=cA}={}){let s=Date.now()+o;for(;await n(t,e).catch(()=>!1);){if(Date.now()>=s)return!1;await new Promise(a=>setTimeout(a,r))}return!0}var lA=6e4,uA=9e4,dA=2e3,fA=/(?:^|[;&|]\s*|\)\s*)gh\s+(?:[^\s;&|"'`$]+\s+)*?pr\s+create\b/,pA=/(?:^|[;&|]\s*|\)\s*)gh\s+(?:[^\s;&|"'`$]+\s+)*?pr\s+merge\b/,mA=/\s--web\b/,nw=/(?:--repo|-R)[\s=]+([\w.-]+\/[\w.-]+)/;function Tl(t,e){return t.slice(e).replace(/^[\s;&|)]+/,"").replace(/"[^"]*"|'[^']*'/g,'""').split(/[;&|]/)[0]}function rw(t){let e=String(t||""),n=e.match(fA);if(!n)return null;let r=Tl(e,n.index);return mA.test(r)?null:{repo:r.match(nw)?.[1]??null}}var hA=new Set(["--subject","-t","--body","-b","--body-file","-F","--match-head-commit","--author-email","-A","--repo","-R"]);function ow(t){let e=String(t||""),n=e.match(pA);if(!n)return null;let r=Tl(e,n.index),o=Tl(e,n.index+n[0].length),s=null,a=o.trim().split(/\s+/).filter(Boolean);for(let i=0;i<a.length;i++){let c=a[i];if(c.startsWith("-")){hA.has(c)&&i++;continue}s=c.replace(/^["']+|["']+$/g,"")||null;break}return{repo:r.match(nw)?.[1]??null,selector:s,auto:/\s--auto\b/.test(r)}}var wA="number,url,title,headRefName,createdAt";async function gA(t,e){let n=async s=>{Re("observer");let{stdout:a}=await tw("gh",["pr","list","--repo",t,"--author","@me","--state","all","--limit","10","--json",wA],{timeout:3e4,maxBuffer:4*1024*1024,env:{...process.env,...s}}).catch(i=>{throw Ye(i)&&Ze(i),i});return JSON.parse(a||"[]")},r=await ee({slug:t}),o=await n(r);return!o.length&&r.GH_TOKEN&&(o=await n({}).catch(()=>[])),o.filter(s=>Date.parse(s.createdAt)>=e-lA).sort((s,a)=>Date.parse(a.createdAt)-Date.parse(s.createdAt))}async function yA(t,e){if(e)return[e];let n=[];for(let r of sw(t)){let o=await tt(r).catch(()=>null);o&&!n.includes(o)&&n.push(o)}for(let r of await ye().catch(()=>[])){let o=await tt(r.path).catch(()=>null);o&&!n.includes(o)&&n.push(o)}return n}function sw(t){let e=t.workspace;return e?.kind==="multi"?(e.repos??[]).map(n=>n?.base).filter(Boolean):[e?.base||t.cwd].filter(Boolean)}async function bA(t,e,n){let r=t.workspace?.branch,o=[];for(let s of await yA(t,e)){let a;try{a=await gA(s,n)}catch{continue}for(let i of a){let c=await Et(s,i.number);if(!(c&&(c.trackId!==t.id||c.announced))){if(r&&i.headRefName===r)return{slug:s,pr:i};o.push({slug:s,pr:i})}}}return o.length?(o.sort((s,a)=>Date.parse(a.pr.createdAt)-Date.parse(s.pr.createdAt)),o[0]):null}var Qe=new Set;function aw(t,e,{delays:n=ew,turnEnd:r}={}){let o=rw(e.command);if(!o)return null;let s=!!ow(e.command),a=`${t.id}|${e.id??e.ts??e.command}`;if(Qe.has(a))return null;Qe.add(a);let i=e.ts??Date.now(),c=(async()=>{try{for(let l of n){await new Promise(w=>setTimeout(w,l));let u=await bA(t,o.repo,i).catch(()=>null);if(!u)continue;let{slug:f,pr:d}=u,{link:m,created:h}=await Lt({repo:f,number:d.number,trackId:t.id,url:d.url,headRef:d.headRefName,title:d.title,source:"observed"});return!h&&!(m?.trackId===t.id&&await wo(f,d.number))?null:(await vl(e.box,e.agent,r),await O({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:Date.now(),change:"opened",number:d.number,url:d.url,title:d.title,branch:d.headRefName,repo:f,...s?{selfMerge:!0}:{}}}),d)}return null}finally{Qe.delete(a)}})();return c.catch(()=>Qe.delete(a)),c}var kA="number,url,title,headRefName,state";function iw(t,e,{delays:n=ew,turnEnd:r,openedWait:o}={}){let s=ow(e.command);if(!s)return null;let a=`merge|${t.id}|${e.id??e.ts??e.command}`;if(Qe.has(a))return null;Qe.add(a);let i=(async()=>{try{let c=s.repo,l=s.selector,u=l?.match(/github\.com\/([\w.-]+\/[\w.-]+)\/pull\/(\d+)/);u&&([,c,l]=u);let f=[];if(c)f=[c];else{let h=sw(t);if(h.length>1&&/^\d+$/.test(String(s.selector??"")))return null;for(let w of h){let g=await tt(w).catch(()=>null);g&&!f.includes(g)&&f.push(g)}}if(l||=t.workspace?.branch,!f.length||!l)return null;c=f[0];let d=async(h,w)=>{let g=await Et(c,h.number);return g&&g.trackId!==t.id?null:(g||await Lt({repo:c,number:h.number,trackId:t.id,url:h.url,headRef:h.headRefName,title:h.title,source:"merge-observed"}),await go(c,h.number),w&&((await L(t).catch(()=>[])).some(A=>A.type==="pr.state.changed"&&(A.payload?.change==="auto-armed"||A.payload?.change==="merged")&&A.payload?.number===h.number)||(await vl(e.box,e.agent,r),await O({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:Date.now(),change:"auto-armed",number:h.number,url:h.url,title:h.title,branch:h.headRefName,repo:c}}))),h)},m=null;for(let h of n){await new Promise($=>setTimeout($,h));let w=[];for(let $ of f)try{Re("observer");let{stdout:F}=await tw("gh",["pr","view",String(l),"--repo",$,"--json",kA],{timeout:3e4,maxBuffer:4*1024*1024,env:{...process.env,...await ee({slug:$})}}),T=JSON.parse(F||"null");T?.number&&w.push({slug:$,pr:T})}catch(F){Ye(F)&&Ze(F)}let g=w.find($=>$.pr.state==="MERGED")??(w.length===1?w[0]:null);if(!g)continue;c=g.slug;let b=g.pr;if(!b?.number)continue;if(b.state==="CLOSED")return null;if(b.state!=="MERGED"){if(m=b,s.auto)return d(b,!0);continue}let v=await Et(c,b.number);if(v&&v.trackId!==t.id)return null;v||await Lt({repo:c,number:b.number,trackId:t.id,url:b.url,headRef:b.headRefName,title:b.title,source:"merge-observed"}),await vl(e.box,e.agent,r);let A=await L(t).catch(()=>[]);if(A.some($=>$.type==="pr.state.changed"&&$.payload?.change==="merged"&&$.payload?.number===b.number))return null;let M=$=>$.some(F=>F.type==="pr.state.changed"&&F.payload?.change==="opened"&&F.payload?.number===b.number),z=0;if(!M(A)){if(rw(e.command)&&await wo(c,b.number)){let $=Date.now();z=$+1,await O({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:$,change:"opened",number:b.number,url:b.url,title:b.title,branch:b.headRefName,repo:c,selfMerge:!0}})}else if((await Et(c,b.number))?.announced){let $=o?.capMs??uA,F=o?.pollMs??dA,T=Date.now()+$;for(;Date.now()<T&&(await new Promise(C=>setTimeout(C,F)),!M(await L(t).catch(()=>[]))););}}return await O({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:Math.max(Date.now(),z),change:"merged",number:b.number,url:b.url,title:b.title,branch:b.headRefName,repo:c}}),await en(c,b.number).catch(()=>{}),await tn(c,b.number).catch(()=>{}),await ze(c,b.number,"merged"),b}return m?d(m,!1):null}finally{Qe.delete(a)}})();return i.catch(()=>Qe.delete(a)),i}function _l(t){return t.sourceId?`${t.role}|id:${t.sourceId}`:`${t.role}|${t.ts??"?"}|${String(t.text||"").slice(0,40)}`}function cw(t){return t.id?`cmd|${t.box}|id:${t.id}`:`cmd|${t.box}|${t.ts??"?"}|${String(t.command||"").slice(0,40)}`}function lw(t){return`job|${t.box}|id:${t.id}`}var Al=new Map,xa=new Map;async function El(t){return Al.has(t.id)?Al.get(t.id):(xa.has(t.id)||xa.set(t.id,L(t).then(e=>{let n={keys:new Set,cmdN:0};for(let r of e)if((r.type==="turn.user"||r.type==="turn.agent")&&r.payload?.text!=null){let o=r.type==="turn.user"?"user":"assistant";r.payload.sourceId&&n.keys.add(_l({role:o,sourceId:r.payload.sourceId})),n.keys.add(_l({role:o,ts:r.payload.ts,text:r.payload.text}))}else if(r.type==="tool.called"&&r.source==="transcript"&&r.payload?.command){let o=r.payload;n.keys.add(cw({id:o.id,box:r.boxId,ts:o.ts,command:o.command})),Number(o.n)>n.cmdN&&(n.cmdN=Number(o.n))}else r.type==="job.started"&&r.payload?.id&&n.keys.add(lw({id:r.payload.id,box:r.boxId}));return Al.set(t.id,n),n}).finally(()=>xa.delete(t.id))),xa.get(t.id))}async function uw(t,e){try{let n=(await El(t)).keys;for(let r of e||[]){if(!r||r.ts==null||!String(r.text||"").trim()&&!r.images?.length)continue;let o=_l(r);if(!n.has(o)&&(n.add(o),await O(t,{type:r.role==="user"?"turn.user":"turn.agent",actor:r.role==="user"?"user":"agent",source:"transcript",boxId:r.box,provider:r.agent||null,payload:{text:r.text,ts:r.ts,model:r.model||null,tools:r.tools||void 0,boxRole:r.boxRole,sourceId:r.sourceId||void 0,note:r.note||void 0,images:r.images?.length?r.images:void 0}}),r.role==="assistant"&&r.box===t.leadBox&&t.workspace?.branch)){let s=Lh(r.text);if(s){let{offered:a,toPr:i}=Uh(s),c;if(t.workspace?.kind==="multi"){let l=await Eo(t).catch(()=>[]);c=l.length?l.map(u=>({name:u.name,slug:u.repo??null})):void 0}await O(t,{type:"pr.offered",actor:"agent",source:"transcript",boxId:r.box,provider:r.agent||null,payload:{offerId:xA(),ts:r.ts,excerpt:s,branch:t.workspace.branch,base:t.workspace.baseRef?.replace(/^origin\//,"")||null,boxRole:r.boxRole,offered:a||void 0,toPr:i||void 0,repos:c}})}}}}catch{}}async function dw(t,e){try{let n=await El(t),r=[...e||[]].filter(o=>o&&String(o.command||"").trim()).sort((o,s)=>(o.ts||0)-(s.ts||0));for(let o of r){let s=cw(o);n.keys.has(s)||(n.keys.add(s),n.cmdN+=1,await O(t,{type:"tool.called",actor:"agent",source:"transcript",boxId:o.box,provider:o.agent||null,payload:{id:o.id??null,tool:o.tool||null,command:o.command,description:o.description||"",summary:o.summary||o.command,ts:o.ts??null,boxRole:o.boxRole,n:n.cmdN}}),aw(t,o),iw(t,o))}}catch{}}async function fw(t,e){try{let n=await El(t),r=[...e||[]].filter(o=>o&&o.id).sort((o,s)=>(o.ts||0)-(s.ts||0));for(let o of r){let s=lw(o);n.keys.has(s)||(n.keys.add(s),await O(t,{type:"job.started",actor:"agent",source:"transcript",boxId:o.box,provider:o.agent||null,payload:{id:o.id,kind:o.kind||"agent",name:o.name||null,detail:o.detail||null,ts:o.ts??null,boxRole:o.boxRole}}))}}catch{}}var SA=/^Base directory for this skill:\s*(\S+)/,vA=/^Run the "([^"]+)" workflow\./,TA=/<command-name>\s*\/?([a-z0-9:_-]+)\s*<\/command-name>/i,AA=/^<local-command-(?:stdout|caveat)>/i,_A=/^\[Image: original \d+x\d+, displayed at \d+x\d+\.\s*Multiply coordinates by [\d.]+ to map to original image\.\]$/,EA=/^Continue from where you left off\.$/,IA=/^<task-notification>/i,RA=/<summary>([\s\S]*?)<\/summary>/i,$A=/<task-id>([\s\S]*?)<\/task-id>/i,OA=/<tool-use-id>([\s\S]*?)<\/tool-use-id>/i,MA=/<status>([\s\S]*?)<\/status>/i,CA=/<result>([\s\S]*?)(?:<\/result>|$)/i;var Il=new Set(["model","clear","compact","login","exit","quit"]);function pw(t){return Il.has(String(t||"").replace(/^\//,"").toLowerCase())}var NA=/^\/([a-z0-9:_-]+)$/i;function Sa(t){let e=String(t||"").trim();if(!e)return null;let n=e.match(SA);if(n)return{kind:"skill",name:n[1].split("/").filter(Boolean).pop()||"skill"};let r=e.match(vA);if(r)return{kind:"skill",name:r[1]};let o=e.match(TA);if(o){let a=o[1].toLowerCase();return Il.has(a)?{kind:"suppress"}:{kind:"command",command:`/${a}`}}let s=e.match(NA);if(s&&Il.has(s[1].toLowerCase()))return{kind:"suppress"};if(AA.test(e))return{kind:"suppress"};if(EA.test(e))return{kind:"suppress"};if(_A.test(e))return{kind:"suppress"};if(IA.test(e)){let a=c=>PA(e.match(c)?.[1].trim()||"")||null,i=a(CA);return{kind:"task",summary:a(RA)||"",taskId:a($A),toolUseId:a(OA),status:a(MA)?.toLowerCase()||null,resultHead:i?i.length>600?`${i.slice(0,600).trimEnd()}\u2026`:i:null}}return null}function PA(t){return t.replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/'/g,"'").replace(/&/g,"&")}function mw(t){return t?.kind==="skill"?`Ran the ${t.name} skill`:t?.kind==="command"?`Ran ${t.command}`:t?.kind==="task"?t.summary||"A background task finished":""}function hw(t){return t?.kind==="skill"?"skill-run":t?.kind==="task"?"task-done":"command-run"}var it=t=>t.payload?.ts??t.ts,BA=new Set(["opened","ci-fixed","reconciled","pushed"]),ww=t=>`${t.repo??""}#${t.number}`;function DA(t,e,n){let r=ww(n),o=t.has(r)?r:null;if(!o&&n.number!=null)for(let[i,c]of t){if(Number(c.number)!==Number(n.number))continue;let l=!!(n.url&&c.url&&n.url===c.url),u=!c.repo&&!!n.repo||!!c.repo&&!n.repo;if(l||u&&(!c.url||!n.url||c.url===n.url)){o=i;break}}let s=n.repo||!o?r:o,a=o?t.get(o):t.get(s);if(o&&o!==s){t.delete(o);let i=e.get(o);if(e.delete(o),i?.length){let c=e.get(s)??[];e.set(s,c.concat(i))}}return t.set(s,{number:n.number,url:n.url??a?.url??null,title:n.title??a?.title??null,repo:n.repo??a?.repo??null,branch:n.branch??a?.branch??null}),s}function Io(t){let e=[],n=new Set,r=new Set,o=new Map,s=new Map,a=new Set,i=new Map,c=new Set,l=new Map,u=/^Agent "([\s\S]+)" finished$/,f=new Map;for(let y of t||[]){if(y.type!=="tool.called"||y.source!=="transcript")continue;let p=String(y.payload?.command||"");if(/\bbostrat-mock\s+(?:add|update)\b/.test(p))for(let S of p.matchAll(/[\w./~-]+\.(?:png|jpe?g|gif|webp|bmp|svg)\b/gi)){let _=S[0].split("/").pop().toLowerCase();f.has(_)||f.set(_,[]),f.get(_).push(y.payload?.ts??y.ts)}}let d=3*6e4,m=20*6e4,h=(y,p)=>!!(y?.name&&f.get(String(y.name).toLowerCase()))?.some(_=>_>=p-d&&_<=p+m);for(let y of t||[])if(y.type==="turn.retracted"){r.add(`${y.boxId}|${y.payload?.saidTs}`);let p=Yt(y.payload?.text);p&&(o.has(y.boxId)||o.set(y.boxId,[]),o.get(y.boxId).push(p))}else if(y.type==="turn.user"&&y.source==="orchestrator"){let p=Yt(y.payload?.text);p&&(s.has(y.boxId)||s.set(y.boxId,new Set),s.get(y.boxId).add(p))}else if(y.type==="job.started"&&y.payload?.id)a.add(y.payload.id),(y.payload.kind||"agent")==="agent"&&y.payload.name&&c.add(y.payload.name);else if(y.type==="turn.user"&&y.payload?.note?.kind==="task"){let p=y.payload.note,S={status:p.status||null,summary:p.summary||null,resultHead:p.resultHead??null,ts:it(y)};p.toolUseId&&i.set(p.toolUseId,S);let _=String(p.summary||"").match(u);_&&l.set(_[1],S)}let w=new Map,g=new Map,b=null,v=new Map,A=new Map,M=new Map,z=new Map,$=new Map,F=new Map,T=new Map,C=6e4,W=new Map,ot=(y,p)=>{let S=o.get(y);if(!S?.length)return!1;let _=Yt(p),rt=s.get(y);return S.some(I=>{if(!_.startsWith(I))return!1;let R=Yt(_.slice(I.length));return!!R&&!!rt?.has(R)})};for(let y of t||[]){let p=y.payload||{};switch(y.type){case"turn.user":{if(y.source==="hook"||!String(p.text||"").trim()&&!p.images?.length)break;{let S=p.note||Sa(p.text);if(S){if(S.kind==="task"){if(S.toolUseId&&a.has(S.toolUseId))break;let _=String(S.summary||"").match(u);if(!S.toolUseId&&_&&c.has(_[1]))break}if(S.kind==="command"&&pw(S.command))break;S.kind!=="suppress"&&e.push({id:y.id,kind:"event",type:hw(S),name:S.name??null,command:S.command??null,summary:S.summary??null,ts:it(y),box:y.boxId,agent:y.provider});break}}if(r.has(`${y.boxId}|${it(y)}`)||y.source==="transcript"&&ot(y.boxId,p.text))break;if(p.kind==="merge-notify"||p.kind==="ship-notify"){e.push({id:y.id,kind:"event",type:p.kind==="ship-notify"?"ship-notified":"merge-notified",text:p.text,ts:it(y),box:y.boxId,agent:y.provider});break}if(p.kind==="mock-picked"||p.kind==="ci-fix"||p.kind==="reconcile"||p.kind==="pr-go"||p.kind==="push-go"||p.kind==="spin-up-view"||p.kind==="prompt-answer")break;for(let S of v.values())S.status="stale";v.clear(),e.push({id:y.id,kind:"message",role:"user",text:p.text,ts:it(y),box:y.boxId,agent:y.provider,boxRole:p.boxRole,...y.actor==="orchestrator"?{orchestration:p.kind||!0}:{},...p.sourceId?{sourceId:p.sourceId}:{},...p.attachments?.length?{attachments:p.attachments}:{},...p.images?.length?{images:p.images}:{}});break}case"turn.agent":{if(p.model==="<synthetic>"&&/^No response requested\.$/.test(String(p.text||"").trim()))break;T.clear();let S=(p.images||[]).filter(R=>!h(R,it(y)));if(!String(p.text||"").trim()&&!S.length)break;if(y.provider==="cursor"){let R=`${y.boxId}|${p.text}`;if(n.has(R))break;n.add(R)}let _=y.provider==="codex"?(p.tools||[]).filter(R=>!Wc.has(R)&&!So.has(R)):p.tools||[],rt=p.text,I=null;if(sl.has(y.provider)){let R=al(String(p.text||""));R&&(rt=R.text,I={questions:R.questions})}e.push({id:y.id,kind:"message",role:"assistant",text:rt,ts:it(y),box:y.boxId,agent:y.provider,boxRole:p.boxRole,...I?{ask:I}:{},..._.length?{tools:_}:{},...p.model?{model:p.model}:{},...p.sourceId?{sourceId:p.sourceId}:{},...S.length?{images:S}:{}});break}case"job.started":{if(!p.id)break;let S=it(y),_=i.get(p.id);if(!_&&(p.kind||"agent")==="agent"&&p.name){let P=l.get(p.name);P&&P.ts>=S&&(_=P)}let rt={jobId:p.id,jobKind:p.kind||"agent",name:p.name||null,detail:p.detail||null,status:_?_.status&&_.status!=="completed"?"failed":"done":"running",startedTs:S,finishedTs:_?.ts??null,summary:_?.summary??null,resultHead:_?.resultHead??null},I=W.get(y.boxId),R=I?.jobs[I.jobs.length-1]?.startedTs;if(I&&R!=null&&S-R<=C)I.jobs.push(rt);else{let P={id:y.id,kind:"job-dock",dockId:p.id,ts:S,box:y.boxId,agent:y.provider,jobs:[rt]};W.set(y.boxId,P),e.push(P)}break}case"handoff.done":e.push(nn(y,"lead-handoff"));break;case"subtask.delegated":e.push({...nn(y,"subtask-delegated"),to:y.boxId,toAgent:y.provider});break;case"subtask.merged":e.push(nn(y,"subtask-merged"));break;case"pr.state.changed":{if(!p.change)break;if(p.change==="opened"){for(let _ of v.values())_.status="stale";v.clear()}let S=nn(y,`pr-${p.change}`);if(BA.has(p.change)&&p.number!=null){let _=DA(w,g,p);if(w.size>=2){for(let I of g.values())for(let R of I)R.grouped=!0;S.groupPrs=[...w.values()];let rt=[S];for(let I of w.keys())g.set(I,rt)}else{let rt=g.get(_)??[];rt.push(S),g.set(_,rt)}}else if(p.change==="merged"||p.change==="closed"||p.change==="lost"){let _=ww(p),rt=b&&!b.complete||w.size>=2;if(w.has(_))w.delete(_),g.delete(_);else if(p.number!=null)for(let I of[...w.keys()])I.endsWith(`#${p.number}`)&&(w.delete(I),g.delete(I));if(rt&&p.number!=null){(!b||b.complete)&&(b={id:y.id,kind:"event",type:"pr-group-settled",prs:[],remaining:[],complete:!1,at:it(y),ts:it(y),box:y.boxId,agent:y.provider},e.push(b)),b.prs.push({number:p.number,url:p.url??null,title:p.title??null,repo:p.repo??null,branch:p.branch??null,change:p.change,ts:it(y)}),b.remaining=[...w.values()],b.complete=w.size===0;break}}e.push(S);break}case"pr.offered":{if(!p.offerId)break;for(let _ of v.values())_.status="stale";v.clear();let S={id:y.id,kind:"pr-offer",offerId:p.offerId,excerpt:p.excerpt||null,branch:p.branch||null,base:p.base||null,offered:Array.isArray(p.offered)&&p.offered.length?p.offered:null,toPr:p.toPr&&typeof p.toPr=="object"?{number:p.toPr.number??null}:null,repos:Array.isArray(p.repos)&&p.repos.length?p.repos:null,status:"open",ts:it(y),box:y.boxId,agent:y.provider};v.set(p.offerId,S),A.set(p.offerId,S),e.push(S);break}case"pr.offer.answered":{let S=A.get(p.offerId);S&&(S.status=p.action||"dismiss",S.actions=Array.isArray(p.actions)?p.actions:null,S.notify=p.notify===!0,v.delete(p.offerId));break}case"box.state.changed":{let S=p.change==="model"?"model-change":p.change;if(!S)break;let _=nn(y,S);S==="model-change"&&(_.to=p.model),e.push(_);break}case"view.state.changed":{if(p.change!=="created"||p.restarted)break;e.push({id:y.id,kind:"view-card",viewId:p.viewId,name:p.name,slug:p.slug,port:p.port,origin:p.origin,ts:it(y),box:y.boxId,agent:y.provider});break}case"view.spinup.failed":{e.push({id:y.id,kind:"view-spinup-failed",name:p.name||null,reason:p.reason||"no-view",ts:it(y),box:y.boxId,agent:y.provider});break}case"mock.state.changed":{if(p.change==="picked"){e.push({id:y.id,kind:"mock-picked",mockId:p.mockId,name:p.name,group:p.group??null,version:p.version??null,siblings:p.siblings??[],ts:it(y),box:y.boxId,agent:y.provider});break}if(p.change!=="created")break;e.push({id:y.id,kind:"mock-card",mockId:p.mockId,name:p.name,group:p.group??null,ts:it(y),box:y.boxId,agent:y.provider});break}case"runbook.state.changed":{if(p.change!=="created")break;e.push({id:y.id,kind:"runbook-card",runbookId:p.runbookId,name:p.name,ts:it(y),box:y.boxId,agent:y.provider});break}case"ticket.linked":{e.push({id:y.id,kind:"ticket-card",provider:p.provider||"linear",key:p.key,url:p.url,title:p.title??null,linkSource:p.linkSource??null,ts:it(y),box:y.boxId,agent:y.provider});break}case"ticket.move.offered":{if(!p.offerId)break;for(let _ of M.values())_.key===p.key&&(_.status="stale",M.delete(_.offerId));let S={id:y.id,kind:"ticket-offer",offerId:p.offerId,provider:p.provider||"linear",key:p.key,url:p.url??null,phase:p.phase??null,from:p.from??null,to:p.to?.name??null,prNumber:p.prNumber??null,status:"open",ts:it(y),box:y.boxId,agent:y.provider};M.set(p.offerId,S),z.set(p.offerId,S),e.push(S);break}case"ticket.move.answered":{let S=z.get(p.offerId);S&&(S.status=p.action==="moved"?"moved":"dismiss",M.delete(p.offerId));break}case"ticket.state.changed":{for(let S of M.values())S.key===p.key&&S.to===p.to&&(S.status="stale",M.delete(S.offerId));if(p.mode==="offer")break;e.push(nn(y,"ticket-state"));break}case"ticket.commented":{e.push({id:y.id,kind:"ticket-comment",key:p.key,url:p.url??null,excerpt:p.excerpt??null,ts:it(y),box:y.boxId,agent:y.provider});break}case"ticket.flow.changed":e.push(nn(y,"ticket-flow"));break;case"ticket.create.offered":{if(!p.proposalId)break;for(let _ of $.values())_.status="stale";$.clear();let S={id:y.id,kind:"ticket-create-offer",proposalId:p.proposalId,provider:p.provider||"linear",epic:p.epic??null,issues:Array.isArray(p.issues)?p.issues:[],team:p.team??null,needsTeam:!!p.needsTeam,linkCurrentTrack:!!p.linkCurrentTrack,status:"open",ts:it(y),box:y.boxId,agent:y.provider};$.set(p.proposalId,S),F.set(p.proposalId,S),e.push(S);break}case"ticket.create.answered":{let S=F.get(p.proposalId);S&&(S.status=p.action==="created"?"created":"dismiss",S.createdCount=p.createdCount??null,S.failedCount=p.failedCount??null,$.delete(p.proposalId));break}case"ticket.created":{let S={items:Array.isArray(p.items)?p.items:[],failed:Array.isArray(p.failed)?p.failed:[],team:p.team??null},_=p.proposalId?F.get(p.proposalId):null;if(_){_.created=S;break}e.push({id:y.id,kind:"ticket-created",...S,ts:it(y),box:y.boxId,agent:y.provider});break}case"error":{if(!p.kind)break;e.push(nn(y,p.kind));break}case"provider.connected":{e.push({id:y.id,kind:"event",type:"provider-connected",provider:p.provider||y.provider,ts:it(y),at:it(y),box:y.boxId,agent:y.provider});break}case"prompt.answered":{if(p.control||!p.question)break;let S=p.groupId||p.promptId;if(!S)break;let _=T.get(S);_||(_={id:y.id,kind:"prompt",groupId:S,provider:y.provider,box:y.boxId,agent:y.provider,questionTotal:p.questionTotal??1,questions:[],ts:it(y)},T.set(S,_));let rt=p.questionIndex??0,I={index:rt,header:p.header||"",question:p.question,multiSelect:!!p.multiSelect,chosen:Array.isArray(p.chosen)?p.chosen:[]},R=_.questions.findIndex(P=>P.index===rt);R>=0?_.questions[R]=I:_.questions.push(I),_.questions.sort((P,et)=>P.index-et.index),_.questionTotal=Math.max(_.questionTotal,p.questionTotal??1),new Set(_.questions.map(P=>P.index)).size>=_.questionTotal&&(e.push(_),T.delete(S));break}default:break}}return LA(e.sort((y,p)=>(y.ts??0)-(p.ts??0)))}var jA=2*6e4;function LA(t){let e=new Map,n=[];for(let r of t){if(r.kind!=="message"||!r.box){n.push(r);continue}let o=r.role==="assistant"&&!String(r.text||"").trim()&&r.images?.length,s=e.get(r.box);if(o&&s&&(r.ts??0)-s.lastTs<=jA){s.item.images.push(...r.images),s.lastTs=r.ts??s.lastTs;continue}if(o){let a={...r,images:[...r.images]};e.set(r.box,{item:a,lastTs:r.ts??0}),n.push(a)}else e.delete(r.box),n.push(r)}return n}function Rl(t,e=null){let n=[];for(let o of t||[]){if(o.type!=="tool.called"||o.source!=="transcript")continue;let s=o.payload||{};String(s.command||"").trim()&&n.push({id:s.id??null,tool:s.tool||null,command:s.command,description:s.description||"",summary:FA(s),ts:s.ts??o.ts,n:s.n,box:o.boxId,agent:o.provider,boxRole:s.boxRole})}if(!n.length)return[];let r=(e??Io(t)).filter(o=>o.kind==="message");return Rm(n,r)}function FA(t){let e=String(t.summary||"").trim();return e&&e!==t.command&&e!==fr(t.command)?e:Ve(t.command,t.description||"")}function nn(t,e){let{ts:n,change:r,...o}=t.payload||{};return{id:t.id,kind:"event",type:e,...o,at:it(t),ts:it(t)}}function UA(t){let e=[],n=null;for(let r of String(t||"").split(`
|
|
142
|
+
`)){let o=r.trim();if(!o||o[0]!=="{")continue;let s;try{s=JSON.parse(o)}catch{continue}let a=s.payload||{};if(s.type==="turn_context"&&a.model&&(n=a.model),s.type==="event_msg"&&a.type==="token_count"&&a.info?.last_token_usage){let i=a.info.last_token_usage;e.push({ts:s.timestamp?Date.parse(s.timestamp):null,model:n,last:{input:i.input_tokens||0,cachedInput:i.cached_input_tokens||0,output:i.output_tokens||0,reasoningOutput:i.reasoning_output_tokens||0},occupancy:i.total_tokens??null,contextWindow:a.info.model_context_window??null})}}return e}var $l=new Map;async function HA(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?.codexTs!=null&&o.add(s.payload.codexTs);$l.set(n,o)}return $l.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 va from"node:fs";import Ml from"node:path";import WA from"node:os";var qA=process.env.BOSTRAT_STATE_DIR||Ml.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},Ta=()=>Ml.join(qA,"cursor-usage.json");function kw(){try{return JSON.parse(va.readFileSync(Ta(),"utf8"))}catch{return{sinceMs:0,seen:[]}}}function JA(t){try{va.mkdirSync(Ml.dirname(Ta()),{recursive:!0});let e=Ta()+".tmp";va.writeFileSync(e,JSON.stringify(t)),va.renameSync(e,Ta())}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 Cl=()=>({enabled:bw(),lastPollAt:rn.at||null,error:rn.error}),Ol=new Map;async function ZA(t,e){let n=`${t.id}:${e.name}`;if(!Ol.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);Ol.set(n,o)}return Ol.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,Aa=[],Nl=new Map;function _a({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};Aa.push(l),Aa.length>e_&&Aa.shift();let u=Nl.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)),Nl.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 Nl)t[e]={count:n.count,measured:n.measured,avgMs:n.measured?Math.round(n.totalMs/n.measured):null,maxMs:n.measured?n.maxMs:null};return{byChange:t,recent:Aa.slice(-20)}}var Ra=o_(r_),s_={timeout:3e4,maxBuffer:8*1024*1024};async function Gt(t,e,n={}){let{caller:r="prs",...o}=n,s=await ee({cwd:t});Re(r);try{let{stdout:a}=await Ra("gh",e,{cwd:t,...s_,...o,env:{...process.env,...s,...o.env||{}}});return String(a||"")}catch(a){throw Ye(a)&&Ze(a),a}}var Ea=null;async function Dl(){if(Ea&&Date.now()-Ea.at<6e4)return Ea.data;let t;try{Re("auth");let e=await Ra("gh",["auth","status"],{timeout:1e4});t={installed:!0,authenticated:!0,login:`${e.stdout||""}${e.stderr||""}`.match(/account\s+(\S+)/)?.[1]??null}}catch(e){t=e?.code==="ENOENT"?{installed:!1,authenticated:!1,login:null}:{installed:!0,authenticated:!1,login:null}}if(t.installed&&!t.authenticated)try{let e=await Gr();e.configured&&(t={installed:!0,authenticated:!0,login:e.username,via:"broker"})}catch{}return Ea={at:Date.now(),data:t},t}async function a_(t){try{let{stdout:e}=await Ra("git",["-C",t,"config","--get","remote.origin.url"],{timeout:5e3});return/github\.com/.test(e)}catch{return!1}}function Ew(t){let e=t.name||t.context||"check",n=t.detailsUrl||t.targetUrl||null,r=t.completedAt||t.createdAt||null,o;return t.__typename==="StatusContext"?o=t.state==="SUCCESS"?"pass":t.state==="PENDING"||t.state==="EXPECTED"?"pending":"fail":t.status&&t.status!=="COMPLETED"?o="pending":["SUCCESS","NEUTRAL","SKIPPED"].includes(t.conclusion)?o="pass":o="fail",{name:e,state:o,url:n,completedAt:r}}function Pl(t){let e=(t||[]).map(Ew),n=e.filter(s=>s.state==="pending").length,r=e.map(s=>Date.parse(s.completedAt||"")).filter(s=>s>0),o=e.length&&!n&&r.length?Math.max(...r):null;return{total:e.length,passed:e.filter(s=>s.state==="pass").length,failed:e.filter(s=>s.state==="fail").length,pending:n,failing:e.filter(s=>s.state==="fail").map(({name:s,url:a})=>({name:s,url:a})),settledAt:o}}var jl="number,title,url,headRefName,headRefOid,baseRefName,isDraft,author,reviewDecision,mergeable,mergeStateStatus,statusCheckRollup,autoMergeRequest,additions,deletions,createdAt,updatedAt",Iw=jl.replace("statusCheckRollup,","");function Ll(t){return/Resource not accessible by personal access token/.test(String(t?.stderr||t?.message||""))}async function Rw(t,e){if(!e)return null;let n=await tt(t);if(!n)return null;try{let r=await Gt(t,["api",`repos/${n}/actions/runs?head_sha=${encodeURIComponent(e)}&per_page=100`,"--jq",".workflow_runs"]),o=new Map;for(let s of JSON.parse(r||"[]"))o.has(s.name)||o.set(s.name,s);return[...o.values()]}catch{return null}}function $w(t){return(t||[]).map(e=>({name:e.name,detailsUrl:e.html_url||null,status:String(e.status||"").toUpperCase(),conclusion:e.conclusion?String(e.conclusion).toUpperCase():null,completedAt:e.status==="completed"&&e.updated_at||null}))}async function Ow(t,e){let n=await Rw(t,e);return n===null?{...Pl([]),unavailable:!0}:Pl($w(n))}function Ia(t,e=Pl(t.statusCheckRollup)){return{number:t.number,title:t.title,url:t.url,author:t.author?.login??null,headRefName:t.headRefName,headSha:t.headRefOid??null,baseRefName:t.baseRefName,isDraft:!!t.isDraft,reviewDecision:t.reviewDecision||null,mergeable:t.mergeable||"UNKNOWN",mergeStateStatus:t.mergeStateStatus||"UNKNOWN",additions:t.additions??0,deletions:t.deletions??0,createdAt:t.createdAt,updatedAt:t.updatedAt,checks:e,green:e.failed===0&&e.pending===0&&t.mergeStateStatus==="CLEAN",autoMergeArmed:!!t.autoMergeRequest,mergeabilityPending:!1}}var i_=9e4,xr=new Map;function c_(t,e,n){let r=new Set;for(let o of e){let s=`${t}#${o.number}`;if(o.mergeable!=="UNKNOWN"&&o.mergeStateStatus!=="UNKNOWN"){xr.delete(s);continue}r.add(s),xr.has(s)||xr.set(s,n),o.mergeabilityPending=n-xr.get(s)<i_}for(let o of xr.keys())o.startsWith(`${t}#`)&&!r.has(o)&&xr.delete(o);return e}async function Sr(t,e){let n=await $e(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");try{let o=await Gt(n.path,["pr","view",String(r),"--json",jl]);return Ia(JSON.parse(o))}catch(o){if(!Ll(o))throw o;let s=await Gt(n.path,["pr","view",String(r),"--json",Iw]),a=JSON.parse(s);return Ia(a,await Ow(n.path,a.headRefOid))}}var _w=(t,e)=>t.number-e.number;async function $o(t,{caller:e="prs"}={}){try{let n=await Gt(t,["pr","list","--json",jl,"--limit","50"],{caller:e});return JSON.parse(n||"[]").map(r=>Ia(r)).sort(_w)}catch(n){if(!Ll(n))throw n;let r=await Gt(t,["pr","list","--json",Iw,"--limit","50"],{caller:e});return(await Promise.all(JSON.parse(r||"[]").map(async o=>Ia(o,await Ow(t,o.headRefOid))))).sort(_w)}}var l_=3e4,u_=1500,Ro=new Map;function he(){Ro.clear()}async function d_(t,e){if(!t)return e;for(let n of e){let r=await Et(t,n.number).catch(()=>null);if(!r)continue;r.trackId&&(n.trackId=r.trackId),r.autoMerge&&(n.autoMergeArmed=!0),r.archiveOnMerge&&(n.archiveOnMerge=!0),r.notifyOnMerge&&(n.notifyOnMerge=!0);let o=s=>n.headSha&&s.headSha&&n.headSha!==s.headSha;r.ciFix&&(o(r.ciFix)?await na(t,n.number,{headSha:n.headSha}).catch(()=>{}):n.ciFix={delegatedAt:r.ciFix.delegatedAt,trackId:r.ciFix.trackId??null}),r.reconcile&&(o(r.reconcile)?await oa(t,n.number,{headSha:n.headSha}).catch(()=>{}):n.reconcile={delegatedAt:r.reconcile.delegatedAt,trackId:r.reconcile.trackId??null})}return e}async function Mw(){let t=await Dl();if(!t.authenticated)return{auth:t,repos:[]};let e=[];for(let a of await ye())await a_(a.path)&&e.push(a);let n=Date.now(),r=ba(n)>0,o=await Promise.all(e.map(async a=>{let i=Ro.get(a.path),c=i?.pending?u_:l_;if(i&&(r||n-i.at<c))return i.entry;try{let l=await tt(a.path).catch(()=>null),u=c_(a.path,await $o(a.path,{caller:"tab"}),n);if(await d_(l,u),i?.entry?.prs)for(let m of u)i.entry.prs.find(w=>w.number===m.number)?.checks?.pending>0&&m.checks.total>0&&m.checks.pending===0&&_a({change:m.checks.failed>0?"checks-failed":"checks-green",repo:l||a.name,number:m.number,githubAt:m.checks.settledAt,source:"tab"});let f={name:a.name,path:a.path,prs:u},d=u.some(m=>m.mergeabilityPending||m.checks.pending>0||m.autoMergeArmed);return Ro.set(a.path,{at:Date.now(),entry:f,pending:d}),f}catch(l){return i?i.entry:{name:a.name,path:a.path,prs:[],error:me(l)}}})),s=new Set(e.map(a=>a.path));for(let a of Ro.keys())s.has(a)||Ro.delete(a);return{auth:t,repos:o}}async function $e(t){let e=(await ye()).find(n=>n.path===t);if(!e)throw new Error(`unknown repo: ${t}`);return e}async function Nn(t){return qr(t)}var Oo=new Set(["squash","merge","rebase"]);async function vr(t,e,{method:n="squash"}={}){let r=await $e(t);if(!Oo.has(n))throw new Error(`invalid merge method: ${n}`);let o=Number(e);if(!Number.isInteger(o)||o<=0)throw new Error("invalid PR number");let s=["pr","merge",String(o),`--${n}`];if(n!=="rebase"&&(await ee({cwd:r.path})).GH_TOKEN){let a=await fi().catch(()=>null);a&&s.push("--body",`Merged by bostrat on behalf of @${a}`)}try{await Gt(r.path,s)}catch(a){throw new Error(me(a))}finally{he()}return{ok:!0,number:o}}async function Mo(t,e){let n=await $e(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");try{await Gt(n.path,["api","-X","PUT",`repos/{owner}/{repo}/pulls/${r}/update-branch`])}catch(s){throw new Error(me(s))}finally{he()}let o=await tt(n.path).catch(()=>null);return o&&await lm(o,r).catch(()=>{}),{ok:!0,number:r}}async function Fl(t,e){let n=await $e(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");try{await Gt(n.path,["pr","merge",String(r),"--disable-auto"])}catch(o){throw new Error(me(o))}finally{he()}return{ok:!0,number:r}}var f_=2500,p_=9e4,m_=t=>new Promise(e=>setTimeout(e,t));function Ul(t){return t.green&&!t.isDraft}function h_(t){return t.isDraft?"draft":t.mergeable==="CONFLICTING"?`conflicts with ${t.baseRefName}`:t.checks.failed>0?"checks failing":t.checks.pending>0?"checks running":t.mergeable==="UNKNOWN"||t.mergeStateStatus==="UNKNOWN"?"mergeability unresolved":t.mergeStateStatus==="BEHIND"?`behind ${t.baseRefName}`:t.mergeStateStatus==="BLOCKED"?"blocked by branch protection":`not clean (${t.mergeStateStatus})`}function Cw(t,{update:e=!0}={}){return t.isDraft?{action:"skip",reason:"draft"}:Ul(t)?{action:"merge"}:e&&t.mergeStateStatus==="BEHIND"&&t.mergeable!=="CONFLICTING"&&t.checks.failed===0?{action:"update-arm"}:{action:"arm"}}function Nw(t){return[...t.filter(e=>e.plan.action!=="merge"),...t.filter(e=>e.plan.action==="merge")]}var Bl=new Map;function Pw(){let t={};for(let[e,n]of Bl)t[e]={...n};return t}async function Bw(t,{method:e="merge",onMerged:n=null}={}){let r=await $e(t);if(!Oo.has(e))throw new Error(`invalid merge method: ${e}`);if(Bl.get(r.path)?.running)throw new Error("a merge-all is already running for this repo");let o=(await $o(r.path)).filter(Ul).sort((a,i)=>Date.parse(a.createdAt)-Date.parse(i.createdAt));if(!o.length)throw new Error("no PRs ready to merge");let s={running:!0,startedAt:Date.now(),finishedAt:null,method:e,total:o.length,current:o[0].number,queue:o.map(a=>({number:a.number,title:a.title})),merged:[],skipped:[],failed:[]};return Bl.set(r.path,s),g_(s,o,{listPrs:()=>$o(r.path),merge:a=>vr(r.path,a,{method:e}),sleep:m_,onMerged:n}).catch(()=>{}),{ok:!0,repo:r.path,total:s.total,queue:o.map(a=>a.number)}}async function w_(t,e){let n=Date.now()+(e.settleMaxMs??p_),r=null;for(;;){let o=null;try{if(o=await e.listPrs(),r=null,!o.some(a=>t.includes(a.number)&&(a.mergeable==="UNKNOWN"||a.mergeStateStatus==="UNKNOWN")))return o}catch(s){r=s}if(Date.now()>=n){if(o)return o;if(r)throw r;return[]}await e.sleep(f_)}}async function g_(t,e,n){try{let r=e;for(let o=0;o<e.length;o++){let s=e[o];if(t.current=s.number,o>0)try{r=await w_(e.slice(o).map(i=>i.number),n)}catch(i){for(let c of e.slice(o))t.failed.push({number:c.number,title:c.title,reason:`couldn't re-check PR state: ${me(i)}`});break}let a=r.find(i=>i.number===s.number);if(!a){t.skipped.push({number:s.number,title:s.title,reason:"no longer open"});continue}if(!Ul(a)){t.skipped.push({number:a.number,title:a.title,reason:h_(a)});continue}try{await n.merge(a.number),t.merged.push({number:a.number,title:a.title});try{await n.onMerged?.(a)}catch{}}catch(i){t.failed.push({number:a.number,title:a.title,reason:me(i)})}}}finally{t.current=null,t.running=!1,t.finishedAt=Date.now(),he()}}async function Hl(t,{title:e,body:n="",base:r=null}={}){if(typeof e!="string"||!e.trim())throw new Error("title required");try{await Ra("git",["-C",t,"push","-u","origin","HEAD"],{timeout:12e4,maxBuffer:4*1024*1024})}catch(l){throw new Error(`push failed: ${me(l)}`)}let o=n||"";if((await ee({cwd:t})).GH_TOKEN){let l=await fi().catch(()=>null);l&&(o=`${o?`${o}
|
|
144
|
+
|
|
145
|
+
`:""}Opened by bostrat on behalf of @${l}`)}let s=["pr","create","--title",e.trim(),"--body",o];r&&s.push("--base",r);let a;try{a=await 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(!Ll(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(`
|
|
115
146
|
`);return o.length>e&&(r=`\u2026 (${o.length-e} earlier lines truncated)
|
|
116
147
|
`+o.slice(-e).join(`
|
|
117
148
|
`)),Buffer.byteLength(r)>n&&(r=`\u2026 (truncated)
|
|
118
|
-
`+r.slice(-n)),r}function
|
|
119
|
-
`).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,$a=new Map;function Wl(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(Wl(n)==null)return;let r=(t.boxes||[]).find(c=>c.name===t.leadBox);if(await ka(t.leadBox,r?.agent).catch(()=>!1))return;let o=Date.now();if(o-($a.get(t.id)||0)<k_)return;$a.set(t.id,o);let s;try{s=await $o(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 wo(i,a.number))){$a.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(()=>{}),$a.delete(t.id)}import ql from"node:fs/promises";import Co from"node:path";import{randomUUID as x_}from"node:crypto";var Fw=".bostrat/uploads",Kl=t=>Co.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 Gl(t){let e=String(t||"").toLowerCase().replace(/^\./,"");return S_.has(e)?"image":v_.has(e)?"doc":null}function T_(t){let r=Co.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=Co.extname(r).slice(1),s=Gl(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=Kl(t);await ql.mkdir(i,{recursive:!0});let c=`${a}-${r}`;return await ql.writeFile(Co.join(i,c),n,{mode:384}),await ql.chmod(Co.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 zl(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"),tu=H.join(Oe,"workspaces"),$_=process.env.BOSTRAT_DEFAULT_REPO||null,Ca={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"}},Na=Object.keys(Ca);function O_(t,{skipPermissions:e=!0}={}){let n=Ca[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=Ca[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 Uo(t){let e=Ca[t];if(e)return process.env[e.envVar]||e.base}var Pn={at:0,value:null};async function nu({maxAgeMs:t=6e4}={}){if(Pn.value&&Date.now()-Pn.at<t)return Pn.value;let e=await Promise.all(Na.map(async n=>[n,await gs(Uo(n))]));return Pn.value=Object.fromEntries(e),Pn.at=Date.now(),Pn.value}async function Pa(t,e=!0){let n=O_(t,{skipPermissions:e});if(!await gs(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.":Ii[r]?`Install it on this node: ${Ii[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 Ba=".bostrat/handoff/latest.md";function M_(t,e=Ba){return`${t.trim()}
|
|
120
151
|
|
|
121
|
-
(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 Oa=".bostrat/handoff/subtask-result.md",Po=".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 ${Po} \u2014 read that first for context. Your scoped task:
|
|
122
153
|
|
|
123
154
|
${t}
|
|
124
155
|
|
|
125
|
-
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 ${Oa} (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 ${Po} \u2014 read that first for context. Your scoped task:
|
|
126
157
|
|
|
127
158
|
${t}
|
|
128
159
|
|
|
129
|
-
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 ${
|
|
130
|
-
`;return Ol=Ol.then(()=>F.mkdir(Ae,{recursive:!0})).then(()=>F.appendFile(ET,n)).catch(()=>{}),Ol}var ot=(t,e,n={})=>{let r={at:Date.now(),type:e,...n};return t.events.push(r),NT(t.id,r),Ym(t,r),r};rd(async({sessionName:t,from:e,to:n,version:r,status:o,error:s})=>{let i=(await W()).tracks.find(c=>c.leadBox===t||(c.boxes||[]).some(l=>l.name===t));i&&(ot(i,"codex-update",{box:t,from:e,to:r||n,status:o,...s?{error:s}:{}}),await K().catch(()=>{}))});var Cl=(t,e,n)=>e!=null&&n.some(r=>t===r.box&&e>=r.from&&e<=r.to),PT=new Set(["stuck","wrong-approach","too-slow","want-different-model","context-refresh","cost","parallelize","other"]);function bw(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=>PT.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 kw(t,e,n,{kind:r}={}){let o=await J(e);if(!o)throw new Error(`box "${e}" is not running`);let s=Oo(o.agent);await Ce({cwd:o.cwd,sessionId:o.sessionId,sessionName:e,agent:o.agent,agentCmd:s}),await xt(e,n,{...me(o.agent),kind:r||"orchestration"}),await G({flow:"orchestration",phase:"sent",trackId:t,sessionName:e,agent:o.agent,kind:r||"orchestration",promptHead:n.slice(0,160)});let a=await W(),i=tt(a,t);if(!i)return;let c=i.boxes.find(l=>l.name===e);ot(i,"orchestration-prompt",{box:e,agent:c?.agent||o.agent,boxRole:c?.role,prompt:n,kind:r||"orchestration"}),await K()}var In=new Set(["merged","conflict","canceled","failed","handed-off"]),uw=new Map;async function BT(t){let e=U.join(bs(t),`${t.id}.ndjson`),n;try{n=await F.stat(e)}catch{return null}let r=uw.get(t.id);if(r&&r.mtimeMs===n.mtimeMs)return r.at;let o=null;for(let s of await j(t)){if(s.type!=="turn.agent")continue;let a=s.payload?.ts??s.ts;a&&(!o||a>o)&&(o=a)}return uw.set(t.id,{mtimeMs:n.mtimeMs,at:o}),o}async function DT(t,e){let n=e.find(r=>r.name===t.leadBox);return n?.state!=="live"?{working:!1,prompted:!1}:ol(n.name,n.agent)}async function Xe(t,e=null){let n=e??new Map((await on()).map(i=>[i.name,i])),r=t.boxes.map(i=>{let c=n.get(i.name),l=In.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([BT(t),DT(t,r)]);return{...t,boxes:r,lastResponseAt:o,working:s,prompted:a}}var jT=5e3;function LT(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-jT,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 xw(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=Yu(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=LT(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 Uu(d.box,d.sessionId),n=!0}}return n}async function at(){let t=await W();await xw(t)&&await K();let e=new Map((await on()).map(n=>[n.name,n]));return Promise.all(t.tracks.map(n=>Xe(n,e)))}async function Sw(t,e){let n=await W();if(t)for(let r of n.tracks||[]){let o=r.boxes.find(s=>s.sessionId&&s.sessionId===t);if(o)return{track:r,box:o}}if(e)for(let r of n.tracks||[]){let o=r.boxes.find(s=>s.cwd===e);if(o)return{track:r,box:o}}return null}async function vw(t){let e=Nr(t);if(!e)return null;let n=await W();for(let r of n.tracks||[])for(let o of r.boxes||[])if(o.hookTokenHash&&Cu(o.hookTokenHash,e))return{track:r,box:o};return null}async function Y(t){let e=await W(),n=tt(e,t);return n?(await xw(e)&&await K(),Xe(n)):null}var FT={claude:[/^\[Request interrupted by user(?: for tool use)?\]$/i],codex:[/^\[?Request interrupted/i,/^Conversation interrupted/i],cursor:[/^\[?Request interrupted/i]};function dw(t){if(t.role!=="user")return!1;let e=(t.text||"").trim();return(FT[t.agent]||[]).some(n=>n.test(e))}var UT=[/^\/model(?:\s|$)/i,/<command-(?:name|message|args)>[^<]*\bmodel\b/i,/^Model changed to\b/i];function fw(t){let e=(t.text||"").trim();return e?UT.some(n=>n.test(e)):!1}var HT=10*6e4;async function Ll(t,e={}){let n=await Y(t);if(!n)return null;let r=new Set([...n.events.filter(b=>b.type==="orchestration-prompt"&&b.prompt).map(b=>String(b.prompt).trim()),...n.events.filter(b=>b.type==="said"&&b.text).map(b=>String(b.text).trim())]),o=Co(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=Ci(n.id),c=!!(i&&!i.finishedAt),l=c&&i.startedAt&&i.box?[{box:i.box,from:i.startedAt,to:Date.now()+1e12}]:[],u=[...vf(n),...l],d={windows:[...Bd(n),...Dd(n,gs),...a,...u]},h=(await Promise.all(n.boxes.map(b=>Cc(b,e)))).flat(),w=h.map(b=>{let q={kind:"message",...b};if(q.role==="assistant"&&(q.text||"").trim()&&q.tools?.length){let On=po(q.agent).cliTools,Jo=q.tools.filter(Cn=>!On.has(Cn));return{...q,tools:Jo.length?Jo:void 0}}return q}).map(b=>{if(b.role!=="user")return b;let q=ua(b.text);return q?q.kind==="suppress"?null:{...b,note:q,text:Nh(q)}:b}).filter(Boolean).filter(b=>!(b.role==="user"&&r.has((b.text||"").trim()))).filter(b=>!dw(b)).filter(b=>!fw(b)).filter(b=>!lw(b)).filter(b=>!Rl(b)).filter(b=>b.role!=="assistant"||!!(b.text||"").trim()||!!b.images?.length).filter(b=>!jd(b,d)),g=w.filter(b=>b.failed&&b.text===_c);if(g.length){let b=new Map;for(let q of g){if(!b.has(q.box)){let Cn=[...(await mt(q.box,{lines:St}).catch(()=>"")).matchAll(/^\s*■\s+(.+?)\s*$/gm)].map(Va=>Va[1]);b.set(q.box,Cn.length?Cn[Cn.length-1]:null)}let On=b.get(q.box);On&&(q.text=`\u25A0 ${On}`)}}let k=await Promise.all(n.boxes.map(b=>mm(b,e)));await Ch(n,k.flat()),await $h(n,w),await Zm(n),await $f(n,w).catch(()=>{});for(let b of n.boxes)b.agent==="codex"&&b.state==="live"&&await Dh(n,b),b.agent==="cursor"&&b.state==="live"&&await Uh(n,b);let v=await Promise.all(n.boxes.map(b=>pm(b,e)));await Oh(n,v.flat().filter(b=>!Cl(b.box,b.ts,u)));let _=await j(n),M=c&&i.viewId?i.viewId:null,V=ch(_),$=yo(_).filter(b=>!(b.kind==="message"&&(dw(b)||fw(b)||lw(b)||Rl(b)))&&!(b.kind==="view-card"&&M&&b.viewId===M)),L=lh($,V),A=$.filter(b=>!(b.kind==="message"&&b.role==="assistant"&&!L.has(b)&&Cl(b.box,b.ts,V))),O=dl(_,A).map(b=>({kind:"command-batch",id:b.id,ts:b.pending?b.ts:(b.anchorTs??b.ts)-1,anchorTs:b.anchorTs,anchorBox:b.anchorBox,agent:b.agent,boxRole:b.boxRole,commands:b.commands,pending:b.pending})).filter(b=>!Cl(b.anchorBox,b.anchorTs??b.ts,u)),H=[...A,...O].filter(b=>b.ts!=null).sort((b,q)=>b.ts-q.ts),nt=WT(n),y=nt.size?H.filter(b=>b.kind==="event"||!nt.has(b.box??b.anchorBox)):H,p=n.boxes.find(b=>b.name===n.leadBox)||null,S=p?await Fr(p.cwd,p.sessionId,{fallbackNewest:!1}):null;if(!S&&p){let b=[..._].reverse().find(q=>q.type==="context.usage"&&q.boxId===p.name);b?.payload?.window&&(S={pct:b.payload.pct??(b.payload.occupancy!=null?b.payload.occupancy/b.payload.window:null),occupancy:b.payload.occupancy??null,window:b.payload.window,windowKnown:!0,model:b.payload.model||null})}let T=[...h].reverse().find(b=>b.role==="assistant"&&b.model),et=T?.model||null,I=[...n.events||[]].reverse().find(b=>b.type==="model-change"),R=S?.model||et;I&&(!T?.ts||I.at>=T.ts)&&(R=I.to);let E=p?await mt(p.name,{lines:St}).catch(()=>""):"",P=p&&lr(p.agent,E)?await Re(p.name).catch(()=>E):E,Q=p?An(p,P):null;!Q&&p&&!Oe(p.agent,E)&&!pw(n.id).length&&(Q=Wc(y,{leadBoxId:p.name,provider:p.agent,isAnswered:b=>_.some(q=>q.type==="prompt.answered"&&q.payload?.promptId===b)})),Q&&Qm(n,p,Q);let pt=y.filter(b=>!(b.kind==="message"&&b.role==="assistant"&&b.ask&&!String(b.text||"").trim()&&!b.images?.length)),gt=!!p&&!Q&&Oe(p.agent,E);p&&!p.readyAt&&(gt||Q||E&&ds(p.agent,E))&&(p.readyAt=Date.now(),await K());let Et=!!p&&!p.readyAt&&Date.now()-(n.createdAt||0)<HT;if(n.kind==="setup"&&n.setup?.provider&&!n.setup.connectedAt&&p&&ds(p.agent,E)&&!Q){let b=await W(),q=tt(b,n.id);q?.setup&&!q.setup.connectedAt&&(q.setup.connectedAt=Date.now(),n.setup.connectedAt=q.setup.connectedAt,await C(q,{type:"provider.connected",actor:"system",source:"pane",boxId:p.name,provider:p.agent,payload:{provider:p.agent,ts:q.setup.connectedAt}}).catch(()=>{}),await K())}let jt=s?{running:!0,step:o.step,toAgent:o.toAgent||null}:null,Wt=null;if(c){let b=Date.now()-i.startedAt;if(i.viewId&&!gt){let q=Es(n.id);q&&(ot(n,"view-spinup",{box:i.box,startedAt:i.startedAt,endedAt:q.finishedAt}),await K())}else if(Q||b>xf&&!gt&&!i.viewId||b>Oi){let q=Q?"needs-input":b>Oi?"timeout":"no-view";Es(n.id,{error:q})&&await C(n,{type:"view.spinup.failed",actor:"orchestrator",source:"orchestrator",boxId:i.box,provider:p?.agent||null,payload:{ts:i.startedAt,name:i.name,reason:q}}).catch(()=>{})}else Wt={running:!0,step:i.step,name:i.name}}return{trackId:n.id,leadBox:n.leadBox,items:pt,context:S?{pct:S.pct,occupancy:S.occupancy,window:S.window,windowKnown:S.windowKnown,model:S.model}:null,models:{current:R,options:Km(p?.agent)},pendingPrompt:Q,working:gt,leadStarting:Et,handoff:jt,view:Wt,queued:pw(n.id).filter(b=>b.kind!=="ship-notify"&&b.kind!=="prompt-answer")}}function WT(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 Aw(t,e){let n=await Y(t);if(!n)return null;let r=vr(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 j(n),a=yo(s).filter(f=>f.box===o&&!(f.kind==="message"&&f.role==="assistant"&&f.ask&&!String(f.text||"").trim()&&!f.images?.length)),i=dl(s.filter(f=>f.boxId===o),a.filter(f=>f.kind==="message")).map(f=>({kind:"command-batch",id:f.id,ts:f.pending?f.ts:(f.anchorTs??f.ts)-1,anchorTs:f.anchorTs,anchorBox:f.anchorBox,agent:f.agent,boxRole:f.boxRole,commands:f.commands,pending:f.pending})),c=[...a,...i].filter(f=>f.ts!=null).sort((f,d)=>f.ts-d.ts),l=n.boxes.find(f=>f.name===o)||null,u=!1;if(l&&l.state==="live"){let f=await mt(o,{lines:St}).catch(()=>"");u=Oe(l.agent||r.agent,f)}return{items:c,working:u,box:o,agent:r.agent,task:r.task}}async function Fl(t,{promptId:e,answers:n,text:r}={}){let o=tt(await W(),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 J(o.leadBox))throw new Error(`lead box "${o.leadBox}" is not running`);let i=await Lc(s);if(i||(i=await qT(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 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 bt(o.leadBox,["Escape"],{kind:"answer-prompt"}),c={mode:"keys",value:"Escape",count:1};else if(i.answerMode==="say"){let d=Cm(i,n||[],typeof r=="string"?r:"");if(!d)throw new Error("no answer selected \u2014 pick an option or reply in chat");await de(o.id,d,{kind:"prompt-answer"}),c={mode:"say",value:d,count:1}}else if(i.provider==="claude"||i.answerMode==="keys"){let d=Tm(i,n||[]);await bt(o.leadBox,d,{kind:"answer-prompt"}),c={mode:"keys",value:d.join(" "),count:d.length}}else{let d=ud(i,n||[]);await xt(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||""}));nl(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(()=>{}),nl(o,s,i,c),{ok:!0,promptId:i.promptId,sent:c.count}}async function qT(t,e){if(!e)return null;let n=await j(t).catch(()=>[]);return Wc(yo(n),{leadBoxId:e.name,provider:e.agent,isAnswered:r=>n.some(o=>o.type==="prompt.answered"&&o.payload?.promptId===r)})}async function Tw(t,{promptId:e,questionIndex:n}={}){let r=tt(await W(),t);if(!r)throw new Error(`track "${t}" not found`);let o=r.boxes.find(l=>l.name===r.leadBox);if(!o)throw new Error(`track "${t}" has no lead box`);if(!await J(r.leadBox))throw new Error(`lead box "${r.leadBox}" is not running`);let a=await Lc(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=_m(a,n);return c.length?(await bt(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 GT=1500,_o=new Map;function Ul(t){let e=_o.get(t);return e||(e={items:[],timer:null,flushing:!1,idleSince:null},_o.set(t,e)),e}function pw(t){return(_o.get(t)?.items??[]).map(e=>({...e}))}function Ta(t){let e=_o.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 _w(t){let e=Ul(t);e.timer||(e.timer=setInterval(()=>KT(t).catch(()=>{}),GT))}async function KT(t){let e=_o.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 W(),t);if(!n||n.archivedAt){Ta(t);return}let r=Co(t);if(r&&!r.finishedAt&&r.step!=="done"&&!r.error){gr(e,!1);return}let o=await J(n.leadBox);if(!o){gr(e,!1);return}let s=n.boxes.find(c=>c.name===n.leadBox),a=await mt(n.leadBox,{lines:St}).catch(()=>"");if(us(s?.agent,a)){gr(e,!1);return}if(lr(s?.agent,a)){let c=await Re(n.leadBox).catch(()=>a);if(An(s,c)){gr(e,!1);return}}if(!gr(e,!0))return;let i=e.items.shift();e.idleSince=null,await Ew(n,s,o,i.text,i.kind??null,i.attachments??null),await K()}finally{e.flushing=!1}}}async function Ew(t,e,n,r,o=null,s=null){let a=r,i=!1;t.pendingHandoffHydrate&&(a=$T(r,t.pendingHandoffHydrate.artifact||Aa),delete t.pendingHandoffHydrate,i=!0);let c=Array.isArray(s)?s.filter(u=>u&&u.rel):[];if(c.length){let u=cw(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 ${Oa} 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,Vl=null;async function q(){return we||(Vl||=(async()=>{try{we=JSON.parse(await U.readFile(Yw,"utf8"))}catch{we={tracks:[]}}finally{Vl=null}return we})(),Vl)}async function V(){await ae(Yw,we)}var nt=(t,e)=>t.tracks.find(n=>n.id===e)||null,Jl=Promise.resolve();function B_(t,e){let n=JSON.stringify({trackId:t,...e})+`
|
|
161
|
+
`;return Jl=Jl.then(()=>U.mkdir(Oe,{recursive:!0})).then(()=>U.appendFile(R_,n)).catch(()=>{}),Jl}var at=(t,e,n={})=>{let r={at:Date.now(),type:e,...n};return t.events.push(r),B_(t.id,r),Oh(t,r),r};qd(async({sessionName:t,from:e,to:n,version:r,status:o,error:s})=>{let i=(await q()).tracks.find(c=>c.leadBox===t||(c.boxes||[]).some(l=>l.name===t));i&&(at(i,"codex-update",{box:t,from:e,to:r||n,status:o,...s?{error:s}:{}}),await V().catch(()=>{}))});var Xl=(t,e,n)=>e!=null&&n.some(r=>t===r.box&&e>=r.from&&e<=r.to),D_=new Set(["stuck","wrong-approach","too-slow","want-different-model","context-refresh","cost","parallelize","other"]);function Qw(t){if(!t)return null;let e=typeof t=="string"?[t]:Array.isArray(t?.codes)&&t.codes.length?t.codes:t?.code?[t.code]:[],n=[...new Set(e.filter(s=>D_.has(s)))];if(!n.length)return null;let r=typeof t=="object"?t?.note:null,o=r&&String(r).slice(0,500).trim()||null;return{code:n[0],...n.length>1?{codes:n}:{},note:o}}async function tg(t,e,n,{kind:r}={}){let o=await Y(e);if(!o)throw new Error(`box "${e}" is not running`);let s=Uo(o.agent);await Ue({cwd:o.cwd,sessionId:o.sessionId,sessionName:e,agent:o.agent,agentCmd:s}),await vt(e,n,{...Se(o.agent),kind:r||"orchestration"}),await G({flow:"orchestration",phase:"sent",trackId:t,sessionName:e,agent:o.agent,kind:r||"orchestration",promptHead:n.slice(0,160)});let a=await q(),i=nt(a,t);if(!i)return;let c=i.boxes.find(l=>l.name===e);at(i,"orchestration-prompt",{box:e,agent:c?.agent||o.agent,boxRole:c?.role,prompt:n,kind:r||"orchestration"}),await V()}var Bn=new Set(["merged","conflict","canceled","failed","handed-off"]),qw=new Map;async function j_(t){let e=H.join($s(t),`${t.id}.ndjson`),n;try{n=await U.stat(e)}catch{return null}let r=qw.get(t.id);if(r&&r.mtimeMs===n.mtimeMs)return r.at;let o=null;for(let s of await L(t)){if(s.type!=="turn.agent")continue;let a=s.payload?.ts??s.ts;a&&(!o||a>o)&&(o=a)}return qw.set(t.id,{mtimeMs:n.mtimeMs,at:o}),o}async function L_(t,e){let n=e.find(r=>r.name===t.leadBox);return n?.state!=="live"?{working:!1,prompted:!1}:Sl(n.name,n.agent)}async function on(t,e=null){let n=e??new Map((await mn()).map(i=>[i.name,i])),r=t.boxes.map(i=>{let c=n.get(i.name),l=Bn.has(i.state)?i.state:c?i.role==="lead"?"live":i.state:"stopped";return{...i,state:l,cwd:c?.cwd||i.cwd,agent:c?.agent||i.agent,sessionId:c?.sessionId||i.sessionId}}),[o,{working:s,prompted:a}]=await Promise.all([j_(t),L_(t,r)]);return{...t,boxes:r,lastResponseAt:o,working:s,prompted:a}}var F_=5e3;function U_(t,e,n){let r=[...t].sort((l,u)=>l.createdAt-u.createdAt),o=[...e].sort((l,u)=>l.birthMs-u.birthMs),s=new Set,a=[];for(let l of r)l.sessionId&&n(l.sessionId)&&!s.has(l.sessionId)?s.add(l.sessionId):a.push(l);let i=new Map(r.map((l,u)=>[l,u])),c=[];for(let l of a){if(l.sessionId&&n(l.sessionId)){c.push({box:l.name,sessionId:null});continue}if(!l.sessionId||l.pinned)continue;let u=i.get(l),f=l.createdAt-F_,d=r[u+1]?.createdAt??1/0,m=o.find(h=>!s.has(h.id)&&h.birthMs>=f&&h.birthMs<d);m&&(s.add(m.id),c.push({box:l.name,sessionId:m.id}))}return c}async function eg(t){let e=new Map;for(let r of t.tracks)for(let o of r.boxes)o.agent==="claude"&&o.cwd&&(e.has(o.cwd)||e.set(o.cwd,[]),e.get(o.cwd).push({track:r,box:o}));let n=!1;for(let[r,o]of e){let s=o.map(d=>d.box.sessionId),a=jd(r),i=new Set(a.map(d=>d.id)),c=s.every(d=>d&&i.has(d)),l=new Set(s).size===s.length;if(c&&l)continue;let u=o.map(d=>d.box),f=U_(u,a,d=>i.has(d));for(let d of f){let m=u.find(h=>h.name===d.box);m&&(m.sessionId=d.sessionId),await Id(d.box,d.sessionId),n=!0}}return n}async function ct(){let t=await q();await eg(t)&&await V();let e=new Map((await mn()).map(n=>[n.name,n]));return Promise.all(t.tracks.map(n=>on(n,e)))}async function ng(t,e){let n=await q();if(t)for(let r of n.tracks||[]){let o=r.boxes.find(s=>s.sessionId&&s.sessionId===t);if(o)return{track:r,box:o}}if(e)for(let r of n.tracks||[]){let o=r.boxes.find(s=>s.cwd===e);if(o)return{track:r,box:o}}return null}async function rg(t){let e=Hr(t);if(!e)return null;let n=await q();for(let r of n.tracks||[])for(let o of r.boxes||[])if(o.hookTokenHash&&Xu(o.hookTokenHash,e))return{track:r,box:o};return null}async function Q(t){let e=await q(),n=nt(e,t);return n?(await eg(e)&&await V(),on(n)):null}var H_={claude:[/^\[Request interrupted by user(?: for tool use)?\]$/i],codex:[/^\[?Request interrupted/i,/^Conversation interrupted/i],cursor:[/^\[?Request interrupted/i]};function Kw(t){if(t.role!=="user")return!1;let e=(t.text||"").trim();return(H_[t.agent]||[]).some(n=>n.test(e))}var W_=[/^\/model(?:\s|$)/i,/<command-(?:name|message|args)>[^<]*\bmodel\b/i,/^Model changed to\b/i];function Gw(t){let e=(t.text||"").trim();return e?W_.some(n=>n.test(e)):!1}var q_=10*6e4;async function ru(t,e={}){let n=await Q(t);if(!n)return null;let r=new Set([...n.events.filter(k=>k.type==="orchestration-prompt"&&k.prompt).map(k=>String(k.prompt).trim()),...n.events.filter(k=>k.type==="said"&&k.text).map(k=>String(k.text).trim())]),o=Ho(n.id),s=!!(o&&!o.finishedAt&&o.step!=="done"&&!o.error),a=s&&o?.startedAt?[...o.fromBox?[{box:o.fromBox,from:o.startedAt,to:Date.now()+1e12}]:[],...o.toBox?[{box:o.toBox,from:o.startedAt,to:Date.now()+1e12}]:[]]:[],i=nc(n.id),c=!!(i&&!i.finishedAt),l=c&&i.startedAt&&i.box?[{box:i.box,from:i.startedAt,to:Date.now()+1e12}]:[],u=[...up(n),...l],d={windows:[..._f(n),...Ef(n,Cs),...a,...u]},h=(await Promise.all(n.boxes.map(k=>Xc(k,e)))).flat(),w=h.map(k=>{let K={kind:"message",...k};if(K.role==="assistant"&&(K.text||"").trim()&&K.tools?.length){let Ln=vo(K.agent).cliTools,os=K.tools.filter(Fn=>!Ln.has(Fn));return{...K,tools:os.length?os:void 0}}return K}).map(k=>{if(k.role!=="user")return k;let K=Sa(k.text);return K?K.kind==="suppress"?null:{...k,note:K,text:mw(K)}:k}).filter(Boolean).filter(k=>!(k.role==="user"&&r.has((k.text||"").trim()))).filter(k=>!Kw(k)).filter(k=>!Gw(k)).filter(k=>!Ww(k)).filter(k=>!zl(k)).filter(k=>k.role!=="assistant"||!!(k.text||"").trim()||!!k.images?.length).filter(k=>!If(k,d)),g=w.filter(k=>k.failed&&k.text===qc);if(g.length){let k=new Map;for(let K of g){if(!k.has(K.box)){let Fn=[...(await gt(K.box,{lines:Tt}).catch(()=>"")).matchAll(/^\s*■\s+(.+?)\s*$/gm)].map(si=>si[1]);k.set(K.box,Fn.length?Fn[Fn.length-1]:null)}let Ln=k.get(K.box);Ln&&(K.text=`\u25A0 ${Ln}`)}}let b=await Promise.all(n.boxes.map(k=>Vm(k,e)));await fw(n,b.flat()),await uw(n,w),await Mh(n),await gp(n,w).catch(()=>{});for(let k of n.boxes)k.agent==="codex"&&k.state==="live"&&await gw(n,k),k.agent==="cursor"&&k.state==="live"&&await xw(n,k);let v=await Promise.all(n.boxes.map(k=>zm(k,e)));await dw(n,v.flat().filter(k=>!Xl(k.box,k.ts,u)));let A=await L(n),M=c&&i.viewId?i.viewId:null,z=Hh(A),$=Io(A).filter(k=>!(k.kind==="message"&&(Kw(k)||Gw(k)||Ww(k)||zl(k)))&&!(k.kind==="view-card"&&M&&k.viewId===M)),F=Wh($,z),T=$.filter(k=>!(k.kind==="message"&&k.role==="assistant"&&!F.has(k)&&Xl(k.box,k.ts,z))),C=Rl(A,T).map(k=>({kind:"command-batch",id:k.id,ts:k.pending?k.ts:(k.anchorTs??k.ts)-1,anchorTs:k.anchorTs,anchorBox:k.anchorBox,agent:k.agent,boxRole:k.boxRole,commands:k.commands,pending:k.pending})).filter(k=>!Xl(k.anchorBox,k.anchorTs??k.ts,u)),W=[...T,...C].filter(k=>k.ts!=null).sort((k,K)=>k.ts-K.ts),ot=K_(n),y=ot.size?W.filter(k=>k.kind==="event"||!ot.has(k.box??k.anchorBox)):W,p=n.boxes.find(k=>k.name===n.leadBox)||null,S=p?await Qr(p.cwd,p.sessionId,{fallbackNewest:!1}):null;if(!S&&p){let k=[...A].reverse().find(K=>K.type==="context.usage"&&K.boxId===p.name);k?.payload?.window&&(S={pct:k.payload.pct??(k.payload.occupancy!=null?k.payload.occupancy/k.payload.window:null),occupancy:k.payload.occupancy??null,window:k.payload.window,windowKnown:!0,model:k.payload.model||null})}let _=[...h].reverse().find(k=>k.role==="assistant"&&k.model),rt=_?.model||null,I=[...n.events||[]].reverse().find(k=>k.type==="model-change"),R=S?.model||rt;I&&(!_?.ts||I.at>=_.ts)&&(R=I.to);let E=p?await gt(p.name,{lines:Tt}).catch(()=>""):"",P=p&&wr(p.agent,E)?await je(p.name).catch(()=>E):E,et=p?Mn(p,P):null;!et&&p&&!Fe(p.agent,E)&&!zw(n.id).length&&(et=il(y,{leadBoxId:p.name,provider:p.agent,isAnswered:k=>A.some(K=>K.type==="prompt.answered"&&K.payload?.promptId===k)})),et&&Ch(n,p,et);let wt=y.filter(k=>!(k.kind==="message"&&k.role==="assistant"&&k.ask&&!String(k.text||"").trim()&&!k.images?.length)),bt=!!p&&!et&&Fe(p.agent,E);p&&!p.readyAt&&(bt||et||E&&As(p.agent,E))&&(p.readyAt=Date.now(),await V());let Rt=!!p&&!p.readyAt&&Date.now()-(n.createdAt||0)<q_;if(n.kind==="setup"&&n.setup?.provider&&!n.setup.connectedAt&&p&&As(p.agent,E)&&!et){let k=await q(),K=nt(k,n.id);K?.setup&&!K.setup.connectedAt&&(K.setup.connectedAt=Date.now(),n.setup.connectedAt=K.setup.connectedAt,await O(K,{type:"provider.connected",actor:"system",source:"pane",boxId:p.name,provider:p.agent,payload:{provider:p.agent,ts:K.setup.connectedAt}}).catch(()=>{}),await V())}let Ht=s?{running:!0,step:o.step,toAgent:o.toAgent||null}:null,zt=null;if(c){let k=Date.now()-i.startedAt;if(i.viewId&&!bt){let K=Us(n.id);K&&(at(n,"view-spinup",{box:i.box,startedAt:i.startedAt,endedAt:K.finishedAt}),await V())}else if(et||k>cp&&!bt&&!i.viewId||k>ec){let K=et?"needs-input":k>ec?"timeout":"no-view";Us(n.id,{error:K})&&await O(n,{type:"view.spinup.failed",actor:"orchestrator",source:"orchestrator",boxId:i.box,provider:p?.agent||null,payload:{ts:i.startedAt,name:i.name,reason:K}}).catch(()=>{})}else zt={running:!0,step:i.step,name:i.name}}return{trackId:n.id,leadBox:n.leadBox,items:wt,context:S?{pct:S.pct,occupancy:S.occupancy,window:S.window,windowKnown:S.windowKnown,model:S.model}:null,models:{current:R,options:_h(p?.agent)},pendingPrompt:et,working:bt,leadStarting:Rt,handoff:Ht,view:zt,queued:zw(n.id).filter(k=>k.kind!=="ship-notify"&&k.kind!=="prompt-answer")}}function K_(t){let e=new Set;for(let n of t.boxes||[])n.role==="subtask"&&n.name&&e.add(n.name);for(let n of Object.values(t.subtasks||{}))n?.box&&e.add(n.box);return e}async function og(t,e){let n=await Q(t);if(!n)return null;let r=$r(t).find(f=>f.id===e)||null;if(!r)return null;let o=r.box||null;if(!o)return{items:[],working:!1,box:null,agent:r.agent,task:r.task};let s=await L(n),a=Io(s).filter(f=>f.box===o&&!(f.kind==="message"&&f.role==="assistant"&&f.ask&&!String(f.text||"").trim()&&!f.images?.length)),i=Rl(s.filter(f=>f.boxId===o),a.filter(f=>f.kind==="message")).map(f=>({kind:"command-batch",id:f.id,ts:f.pending?f.ts:(f.anchorTs??f.ts)-1,anchorTs:f.anchorTs,anchorBox:f.anchorBox,agent:f.agent,boxRole:f.boxRole,commands:f.commands,pending:f.pending})),c=[...a,...i].filter(f=>f.ts!=null).sort((f,d)=>f.ts-d.ts),l=n.boxes.find(f=>f.name===o)||null,u=!1;if(l&&l.state==="live"){let f=await gt(o,{lines:Tt}).catch(()=>"");u=Fe(l.agent||r.agent,f)}return{items:c,working:u,box:o,agent:r.agent,task:r.task}}async function ou(t,{promptId:e,answers:n,text:r}={}){let o=nt(await q(),t);if(!o)throw new Error(`track "${t}" not found`);let s=o.boxes.find(d=>d.name===o.leadBox);if(!s)throw new Error(`track "${t}" has no lead box`);if(!await Y(o.leadBox))throw new Error(`lead box "${o.leadBox}" is not running`);let i=await rl(s);if(i||(i=await G_(o,s)),!i)throw new Error("no pending prompt to answer");if(e&&e!==i.promptId)throw new Error("prompt changed since it was shown \u2014 reload and answer the current prompt");let c;if(i.answerMode==="text"){let d=typeof r=="string"?r.trim():"";if(!d)throw new Error("this prompt needs a code \u2014 paste it into the card");if(/\s/.test(d)||d.length>2048)throw new Error("that doesn't look like a sign-in code \u2014 paste the code exactly as shown");await vt(o.leadBox,d,{enter:!0,kind:"answer-prompt"}),c={mode:"text-input",value:"\u2039code\u203A",count:1,redacted:!0}}else if(i.answerMode==="esc")await xt(o.leadBox,["Escape"],{kind:"answer-prompt"}),c={mode:"keys",value:"Escape",count:1};else if(i.answerMode==="say"){let d=fh(i,n||[],typeof r=="string"?r:"");if(!d)throw new Error("no answer selected \u2014 pick an option or reply in chat");await Qt(o.id,d,{kind:"prompt-answer"}),c={mode:"say",value:d,count:1}}else if(i.provider==="claude"||i.answerMode==="keys"){let d=sh(i,n||[]);await xt(o.leadBox,d,{kind:"answer-prompt"}),c={mode:"keys",value:d.join(" "),count:d.length}}else{let d=Yd(i,n||[]);await vt(o.leadBox,d,{enter:!0,kind:"answer-prompt"}),c={mode:"text",value:d,count:1}}if(i.answerMode==="say"){let d=i.questions||[];for(let m=0;m<d.length;m++){let h=d[m],w=(Array.isArray(n?.[m])?n[m]:[]).map(g=>h.options?.[g]).filter(Boolean).map(g=>({label:g.label,description:g.description||""}));kl(o,s,i,{...c,groupId:i.groupId||i.promptId,questionIndex:m,questionTotal:i.questionTotal??d.length,header:h.header||"",question:h.question||"",multiSelect:!!h.multiSelect,chosen:w})}return await G({flow:"prompt",phase:"answered",sessionName:o.leadBox,agent:s.agent,provider:i.provider,promptId:i.promptId,...c}).catch(()=>{}),{ok:!0,promptId:i.promptId,sent:c.count}}let l=i.questions?.[0]||{},u=(Array.isArray(n?.[0])?n[0]:[]).map(d=>l.options?.[d]).filter(Boolean).map(d=>({label:d.label,description:d.description||""})),f=i.review?"review":/^claude:model-confirm:/.test(i.promptId||"")?"model-confirm":/^claude:resume-confirm:/.test(i.promptId||"")?"resume-confirm":void 0;return c={...c,groupId:i.groupId||i.promptId,questionIndex:i.questionIndex??0,questionTotal:i.questionTotal??1,header:l.header||"",question:l.question||"",multiSelect:!!l.multiSelect,chosen:u,...f?{control:f}:{}},await G({flow:"prompt",phase:"answered",sessionName:o.leadBox,agent:s.agent,provider:i.provider,promptId:i.promptId,...c}).catch(()=>{}),kl(o,s,i,c),{ok:!0,promptId:i.promptId,sent:c.count}}async function G_(t,e){if(!e)return null;let n=await L(t).catch(()=>[]);return il(Io(n),{leadBoxId:e.name,provider:e.agent,isAnswered:r=>n.some(o=>o.type==="prompt.answered"&&o.payload?.promptId===r)})}async function sg(t,{promptId:e,questionIndex:n}={}){let r=nt(await q(),t);if(!r)throw new Error(`track "${t}" not found`);let o=r.boxes.find(l=>l.name===r.leadBox);if(!o)throw new Error(`track "${t}" has no lead box`);if(!await Y(r.leadBox))throw new Error(`lead box "${r.leadBox}" is not running`);let a=await rl(o);if(!a)throw new Error("no pending prompt to navigate");if(a.provider!=="claude")throw new Error("prompt navigation not supported for this provider");if(a.review)throw new Error("cannot navigate on the review screen \u2014 answer or cancel");let i=a.questionTotal??1;if(i<2)throw new Error("not a multi-question prompt");if(!Number.isInteger(n)||n<0||n>=i)throw new Error("questionIndex out of range");if(e&&e!==a.promptId)throw new Error("prompt changed since it was shown \u2014 reload and try again");let c=ah(a,n);return c.length?(await xt(r.leadBox,c,{kind:"navigate-prompt"}),await G({flow:"prompt",phase:"navigated",sessionName:r.leadBox,agent:o.agent,provider:a.provider,promptId:a.promptId,fromIndex:a.questionIndex??0,toIndex:n,mode:"keys",value:c.join(" "),count:c.length}).catch(()=>{}),{ok:!0,promptId:a.promptId,sent:c.length}):{ok:!0,promptId:a.promptId,sent:0}}var z_=1500,Bo=new Map;function su(t){let e=Bo.get(t);return e||(e={items:[],timer:null,flushing:!1,idleSince:null},Bo.set(t,e)),e}function zw(t){return(Bo.get(t)?.items??[]).map(e=>({...e}))}function Da(t){let e=Bo.get(t);if(!e)return[];let n=e.items.splice(0);return e.idleSince=null,e.timer&&(clearInterval(e.timer),e.timer=null),n}function ag(t){let e=su(t);e.timer||(e.timer=setInterval(()=>V_(t).catch(()=>{}),z_))}async function V_(t){let e=Bo.get(t);if(!(!e||e.flushing)){if(!e.items.length){e.timer&&(clearInterval(e.timer),e.timer=null);return}e.flushing=!0;try{let n=nt(await q(),t);if(!n||n.archivedAt){Da(t);return}let r=Ho(t);if(r&&!r.finishedAt&&r.step!=="done"&&!r.error){Tr(e,!1);return}let o=await Y(n.leadBox);if(!o){Tr(e,!1);return}let s=n.boxes.find(c=>c.name===n.leadBox),a=await gt(n.leadBox,{lines:Tt}).catch(()=>"");if(Ts(s?.agent,a)){Tr(e,!1);return}if(wr(s?.agent,a)){let c=await je(n.leadBox).catch(()=>a);if(Mn(s,c)){Tr(e,!1);return}}if(!Tr(e,!0))return;let i=e.items.shift();e.idleSince=null,await ig(n,s,o,i.text,i.kind??null,i.attachments??null),await V()}finally{e.flushing=!1}}}async function ig(t,e,n,r,o=null,s=null){let a=r,i=!1;t.pendingHandoffHydrate&&(a=M_(r,t.pendingHandoffHydrate.artifact||Ba),delete t.pendingHandoffHydrate,i=!0);let c=Array.isArray(s)?s.filter(u=>u&&u.rel):[];if(c.length){let u=Hw(c.map(f=>f.rel));a=a.trim()?`${a.trim()}
|
|
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:Uo(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}={}){su(t).items.push({id:Fo(),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 lu(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=su(t),u=await gt(s.leadBox,{lines:Tt}).catch(()=>""),f=l.items.length>0||Ts(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:Fo(),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=nc(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}
|
|
131
164
|
|
|
132
|
-
${
|
|
165
|
+
What to preview: ${String(e).trim()}`:Vw;try{await Qt(t,a,{kind:"spin-up-view"})}catch(i){throw Us(n.id,{error:"drive-failed"}),i}return{ok:!0,running:!0}}async function au(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=Da(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(`
|
|
133
166
|
|
|
134
|
-
|
|
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=Wl(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=To(e.agent,o);if(s&&ol(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 Xc(e,{})}catch{return null}if(ch(a,r))return null;let i=await gt(t.leadBox,{lines:Tt}).catch(()=>"");if(To(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],Yl=2;async function sE(t,e,n){if(e?.agent!=="claude")return!0;for(let r=0;r<=Yl;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<Yl&&(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:Yl,textHead:n.slice(0,120)}).catch(()=>{}),!1}async function ug(t,e,n){for(let r=0;r<3;r++){await bs(t,n,{kind:"unsend-wipe"}).catch(()=>{}),await Or(200);let o=await gt(t,{lines:Tt}).catch(()=>""),s=To(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 Qr(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:Uo(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 da(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 Eo(t){let e=[];for(let n of Nt(t?.workspace)){if(!n?.dir)continue;let r=await fl(n.dir,{ignorePlumbing:!0}).catch(()=>!1),o=await pl(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 mi(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/${fu(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 fa(c);await yr(c,f,o,d),s.push({name:u,dir:f,base:c,baseRef:d,repo:l})}return await ja(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 fa(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 ja(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 fl(s.dir,{ignorePlumbing:!0}).catch(()=>!1),i=await pl(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 ja(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(tu,{recursive:!0});let s=H.join(tu,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 mi(n));let i=await pg([n,o,$_]);if(i){let c=`track/${fu(e)}-${t}`;try{let l=await fa(i);await yr(i,s,c,l),await eu(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 eu(s),{dir:s,base:null,branch:null,kind:"repo"}}var Do="# bostrat workspace",yg=`${Do}
|
|
135
168
|
|
|
136
|
-
|
|
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.
|
|
137
178
|
|
|
138
179
|
When asked to run, preview, or demo this app so the operator can review it: do NOT
|
|
139
180
|
start an ad-hoc dev server and do NOT share a localhost URL (the operator is on
|
|
@@ -272,9 +313,9 @@ which steps to run (and which to skip). An offer buried mid-message, phrased as
|
|
|
272
313
|
statement, or omitted entirely renders no controls \u2014 so close EVERY completed
|
|
273
314
|
implementation turn this way, every time. When the operator has already asked you
|
|
274
315
|
to commit / push / open the PR, just do it; do not re-offer.
|
|
275
|
-
`,
|
|
276
|
-
`)),
|
|
277
|
-
`);return`${
|
|
316
|
+
`,cE=yg,No=yg+ph;async function eu(t){try{await U.writeFile(H.join(t,"AGENTS.md"),No,{flag:"wx"})}catch{return}await dE(t,["/AGENTS.md"])}var Zl=t=>typeof t=="string"&&(t===Do||t.startsWith(`${Do}
|
|
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}
|
|
278
319
|
|
|
279
320
|
This directory is NOT a git repo. It is a bostrat Track workspace holding one
|
|
280
321
|
git worktree per tagged repo, ALL on the same branch \`${e}\`:
|
|
@@ -286,25 +327,25 @@ push / open PRs) in each repo separately, always on that shared branch. Work
|
|
|
286
327
|
only needs to land in the repos it actually touches \u2014 an untouched repo's
|
|
287
328
|
branch simply stays at its fork point and never gets pushed. When asked to run
|
|
288
329
|
or preview an app, run it from that app's own repo directory.
|
|
289
|
-
`}async function
|
|
290
|
-
`,`${
|
|
330
|
+
`}async function ja(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 Ql=No.replace(`${Do}
|
|
331
|
+
`,`${Do}
|
|
291
332
|
|
|
292
333
|
(Global copy managed by bostrat \u2014 rewritten on service start; edits will be lost.)
|
|
293
334
|
SCOPE: these instructions apply ONLY when your working directory is under
|
|
294
335
|
~/.bostrat/workspaces/ or ~/.bostrat/worktrees/ \u2014 a bostrat-orchestrated box.
|
|
295
336
|
When working anywhere else, ignore everything below.
|
|
296
|
-
`);async function
|
|
297
|
-
`).includes(s));o.length&&await
|
|
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(`
|
|
338
|
+
`).includes(s));o.length&&await U.appendFile(n,(r.endsWith(`
|
|
298
339
|
`)||!r?"":`
|
|
299
340
|
`)+o.join(`
|
|
300
341
|
`)+`
|
|
301
|
-
`)}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(`
|
|
302
343
|
`).filter(s=>s!==e);o.length!==r.split(`
|
|
303
|
-
`).length&&await
|
|
304
|
-
`))}catch{}}async function
|
|
344
|
+
`).length&&await U.writeFile(n,o.join(`
|
|
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"),No).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,Ql,{flag:"wx"}).catch(()=>{}),t.codexGlobal="created"):Zl(o)&&o!==Ql&&(await U.writeFile(r,Ql).catch(()=>{}),t.codexGlobal="refreshed")}for(let r of[tu,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&&Zl(c)&&c!==No&&(await U.writeFile(i,No).catch(()=>{}),t.refreshed++);let l=H.join(a,"CLAUDE.md"),u=await U.readFile(l,"utf8").catch(()=>null);u!==null&&Zl(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 iu({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 Pa(o,s),i=await q(),c=Fo().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(Ri(f))&&(f=`${t.trim().slice(0,50).trim()} ${c} lead`);let d=await hn(f,l.dir,{agentCmd:a,agent:o});_s({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 Pa(t,!1)).replace(/\s+--(?:dangerously-skip-permissions|dangerously-bypass-approvals-and-sandbox)\b/g,""),s=Fo().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"&&_s({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=jo.get(t);r&&!r.finishedAt&&(r.error=n,r.finishedAt=Date.now()),jo.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 mu(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=$c(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=$c(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 cu(t,e,"merged")}async function cu(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(`
|
|
305
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}
|
|
306
347
|
${s}
|
|
307
|
-
${a}`}async function Gl(t){if(t.archivedAt)return!1;let e=t.boxes.find(r=>r.name===t.leadBox);if(!e||In.has(e.state)||await J(e.name))return!1;let n=yw(e.agent,e.sessionId);if(!await rs(n))return ot(t,"rehydrate-failed",{box:e.name,agent:e.agent,reason:`agent "${e.agent}" not available on this box`}),!1;try{let r=await sn(e.name,e.cwd,{agentCmd:n,agent:e.agent,resumeSessionId:e.sessionId,pinned:e.pinned===!0});return e.hookTokenHash=Nr(r.hookToken),fs({cwd:e.cwd,sessionName:e.name,agent:e.agent,agentCmd:n}),e.state="live",ot(t,"rehydrated",{box:e.name,agent:e.agent}),!0}catch(r){return ot(t,"rehydrate-failed",{box:e.name,agent:e.agent,reason:String(r?.message||r)}),!1}}async function Vw(t){let e=await W(),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,ot(n,"unarchived",{}),await Gl(n),await K(),Xe(n)}async function zw(){let t=await W(),e=new Set((await on()).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 Xt(a.name).catch(()=>{}),ot(s,"orphan-reaped",{box:a.name,agent:a.agent}),r++,o=!0),!In.has(a.state)&&a.state!=="stopped"&&(a.state="stopped",o=!0);return o&&await K(),r}async function Jw(t){let e=await W(),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(In.has(r.state))throw new Error("lead box is terminal \u2014 nothing to restart");if(await J(n.leadBox)){let a=await mt(n.leadBox,{lines:St}).catch(()=>"");if(us(r.agent,a))throw new Error("lead is mid-turn \u2014 retry when it goes idle");await Xt(n.leadBox),r.state="stopped"}let s=await Gl(n);if(await K(),!s)throw new Error("rehydrate failed \u2014 see the track's rehydrate-failed event");return Xe(n)}var Xw=(t,e,n)=>({name:t.name,agent:t.agent,role:e,state:n,cwd:t.cwd,sessionId:t.sessionId,pinned:t.pinned===!0,hookTokenHash:Nr(t.hookToken),createdAt:Date.now()});function Yw(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 Io=new Map,Co=t=>Io.get(t)||(ue?tt(ue,t)?.handoff:null)||null;async function ka(t,e){let n=await W(),r=tt(n,t);r&&(r.handoff={...r.handoff||{},...e},await K())}function g_(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 Zw(){let t=await W(),e=!1;for(let n of t.tracks||[]){let r=n.handoff,o=g_(n);if(!o)continue;e=!0;let s=!!r.toBox&&r.toBox===r.fromBox;if(s&&o.step==="error"){let c=await J(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=Yw(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:Aa,at:u},ot(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 Xt(i).catch(()=>{}),a&&await Xt(a).catch(()=>{})}return e&&await K(),e}async function Kl(t,e,n=!0,r=null){let o=Io.get(t);if(o&&!o.finishedAt)throw new Error("a lead handoff is already running for this Track");let s=tt(await W(),t);if(!s)throw new Error(`track "${t}" not found`);let a=await va(e,n),i=await J(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:bw(r),toBox:s.leadBox,startedAt:Date.now(),finishedAt:null,error:null};return Io.set(t,c),await ka(t,{step:c.step,fromBox:c.fromBox,toAgent:e,toBox:c.toBox,skipPermissions:n,startedAt:c.startedAt,finishedAt:null,error:null}),y_(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 ka(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 y_(t,e,n,r,o){let s=async(_,M={})=>{t.step=_,console.log(`[lead-handoff ${t.trackId}] step \u2192 ${_}`),await ka(t.trackId,{step:_,toBox:t.toBox}),await G({flow:"lead-handoff",phase:_,trackId:t.trackId,fromBox:t.fromBox,toBox:t.toBox,toAgent:o,sessionName:M.sessionName,...M})};console.log(`[lead-handoff ${t.trackId}] ${t.fromBox}: ${n.agent} \u2192 ${o} IN PLACE`);let a=await W(),i=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 Ud(t.fromBox),await s("swap",{cwd:n.cwd,agentCmd:r});let f=await cc(t.fromBox,{agentCmd:r,agent:o});await s("hydrate",{sessionName:t.fromBox}),await Ce({cwd:n.cwd,sessionId:f.sessionId,sessionName:t.fromBox,agent:o,agentCmd:Oo(o)});let d=await mt(t.fromBox,{lines:St}).catch(()=>"");if(!ds(o,d)){await s("rollback",{sessionName:t.fromBox,fromAgent:c});let _=yw(c,l,{skipPermissions:t.skipPermissions});await cc(t.fromBox,{agentCmd:_,agent:c,resumeSessionId:l,pinned:u});let M=await W(),V=tt(M,t.trackId);if(V){let $=V.boxes.find(L=>L.name===t.fromBox);$&&($.state="live"),ot(V,"lead-handoff-failed",{box:t.fromBox,fromAgent:c,toAgent:o,reason:t.reason,error:`incoming ${o} CLI never drew its composer`,rolledBack:!0,handoffStartedAt:t.startedAt}),await K()}throw new Error(`incoming ${o} CLI never drew its composer \u2014 rolled back to ${c} (--resume, context intact)`)}await s("commit");let m=await W(),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 g=Date.now(),k=Yw(w),v=k[k.length-1];v.endedAt=g,t.reason&&(v.endReason=t.reason),k.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:Aa,at:g},ot(h,"lead-handoff",{from:t.fromBox,to:t.fromBox,fromAgent:c,toAgent:o,fromSessionId:l,toSessionId:f.sessionId,epoch:k.length-1,singleBox:!0,forced:f.forced||void 0,skipPermissions:t.skipPermissions,reason:t.reason,handoffStartedAt:t.startedAt}),await K(),await s("done",{sessionName:t.fromBox}),await ka(t.trackId,{finishedAt:Date.now(),error:null}),t.finishedAt=Date.now()}var b_=Number(process.env.BOSTRAT_SUBTASK_TIMEOUT_MS)||18e5,k_=Number(process.env.BOSTRAT_SUBTASK_POLL_MS)||3e3,kr=U.join(Ae,"worktrees"),yr=[To,ba],zt=new Map;var vr=t=>{let e=new Map([...zt.values()].filter(r=>r.trackId===t).map(r=>[r.id,r])),n=ue?tt(ue,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)},Vl=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 x_(t,e,n){let r=await W(),o=tt(r,t);o&&(o.subtasks=o.subtasks||{},o.subtasks[e]={...o.subtasks[e]||{},...n},await K())}var xr=t=>x_(t.trackId,t.id,Vl(t));async function br(t,e){t.step=e,await xr(t)}function S_(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 v_(t){return!!t&&!t.finishedAt&&!t.canceled&&t.step==="working"&&!!t.box&&!!t.dir&&!!t.base&&!!t.branch}async function A_(t,e,n){if(!v_(n)||zt.has(e)||!await J(n.box).catch(()=>null))return!1;try{await F.access(n.dir)}catch{return!1}let o=await J(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),rg(s,o).catch(a=>eg(s,a)),!0}async function Qw(){let t=await W(),e=!1;for(let n of t.tracks||[]){for(let[r,o]of Object.entries(n.subtasks||{})){if(zt.has(r))continue;if(await A_(n,r,o)){e=!0,ot(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=S_(o);if(!a)continue;e=!0,n.subtasks[r]=a;let i=n.boxes.find(c=>c.subtaskId===r);i&&!In.has(i.state)&&(i.state="canceled"),ot(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 ww(n,o.box,o.dir)}for(let r of n.boxes){if(r.role!=="subtask"||In.has(r.state)||zt.has(r.subtaskId)||n.subtasks?.[r.subtaskId])continue;e=!0,r.state="canceled";let o=r.subtaskId?U.join(kr,`${n.id}-${r.subtaskId}`):null;await G({flow:"subtask",phase:"reaped-legacy-orphan",trackId:n.id,subtaskId:r.subtaskId,box:r.name}).catch(()=>{}),await ww(n,r.name,o)}}return e=await T_(t)||e,e&&await K(),e}async function ww(t,e,n){if(e&&await Xt(e).catch(()=>{}),!n)return;if(t.workspace?.kind==="multi"){for(let o of Ot(t.workspace)){let s=U.join(n,o.name);await Ut(o.base,s).then(()=>!0,()=>!1)||await Ut(o.dir,s).catch(()=>{})}await F.rm(n,{recursive:!0,force:!0}).catch(()=>{});return}let r=await J(t.leadBox).catch(()=>null);r?.cwd&&await Ut(r.cwd,n).catch(()=>{})}async function T_(t){let e;try{e=await F.readdir(kr)}catch{return!1}let n=new Set;for(let o of t.tracks||[]){for(let s of Object.keys(o.subtasks||{}))n.add(`${o.id}-${s}`);for(let s of o.boxes||[])s.subtaskId&&n.add(`${o.id}-${s.subtaskId}`)}for(let o of zt.values())o.dir&&n.add(U.basename(o.dir));let r=!1;for(let o of e){if(n.has(o))continue;let s=U.join(kr,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,U.join(s,i.name)).catch(()=>{});await F.rm(s,{recursive:!0,force:!0}).catch(()=>{})}else{let i=a?await J(a.leadBox).catch(()=>null):null;i?.cwd?await Ut(i.cwd,s).catch(()=>{}):await F.rm(s,{recursive:!0,force:!0}).catch(()=>{})}await G({flow:"subtask",phase:"swept-orphan-worktree",dir:s}).catch(()=>{}),r=!0}return r}var zl=t=>t.trim().replace(/\s+/g,"-").replace(/[^A-Za-z0-9._-]/g,"").toLowerCase()||"track",Ro=class extends Error{};async function tg(t,{agent:e,task:n,skipPermissions:r=!0,reason:o=null}={}){if(typeof n!="string"||!ur(n))throw new Error("task (string) required");let s=await va(e,r),a=tt(await W(),t);if(!a)throw new Error(`track "${t}" not found`);let i=await J(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 Qs(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 Qs(i.cwd))throw new Error(`subtask delegation needs the lead's workspace to be a git repo (got ${i.cwd})`);let u=$o().slice(0,8),f={id:u,trackId:t,trackName:a.name,agent:e,task:ur(n),skipPermissions:r,reason:bw(o),step:"snapshot",leadBox:a.leadBox,box:null,branch:`bostrat/${zl(a.name)}-${u}`,base:null,repos:l,dir:U.join(kr,`${t}-${u}`),stat:null,conflicts:[],applied:null,startedAt:Date.now(),finishedAt:null,error:null,canceled:!1,forceMerge:!1};return zt.set(u,f),await xr(f),__(f,i,s).catch(d=>eg(f,d)),f}async function eg(t,e){t.failedStep=t.step,t.error=e instanceof Ro?"canceled":e?.message||String(e),t.step=e instanceof Ro?"canceled":"error",t.finishedAt=Date.now(),await Xl(t).catch(()=>{}),await xr(t).catch(()=>{}),await $_(t).catch(()=>{})}function Jl(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 ng(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 __(t,e,n){if(await br(t,"snapshot"),t.repos){for(let s of t.repos)s.base=await Kc(s.leadDir,`bostrat: snapshot for subtask ${t.id}`);await xr(t)}else t.base=await Kc(e.cwd,`bostrat: snapshot for subtask ${t.id}`);if(await br(t,"spawn"),await F.mkdir(kr,{recursive:!0}),t.repos){await F.mkdir(t.dir,{recursive:!0});for(let a of t.repos)await dr(a.leadDir,U.join(t.dir,a.name),t.branch,a.base);let s=Ot((tt(await W(),t.trackId)||{}).workspace);await _a(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 dr(e.cwd,t.dir,t.branch,t.base),await Dl(t.dir);await E_(t,e);let r=t.trackName.slice(0,38).trim(),o=await sn(`${r} ${t.agent} sub ${t.id}`,t.dir,{agentCmd:n,agent:t.agent});t.box=o.name,await I_(t,o),await kw(t.trackId,o.name,CT(t.task,!!t.repos),{kind:"subtask"}),await rg(t,e)}async function rg(t,e){await br(t,"working");let n=U.join(t.dir,ba);if(await C_(n,t),t.canceled)throw new Ro;if(await br(t,"merge"),t.repos){let o={files:0,insertions:0,deletions:0},s=[],a=[],i=!0;for(let c of t.repos){let l=U.join(t.dir,c.name);await Vc(l,`bostrat: subtask ${t.id} result (auto-committed)`),c.stat=await Yc(c.leadDir,c.base,t.branch,yr),c.files=await Zc(c.leadDir,c.base,t.branch,yr);let u=await Qc(c.leadDir,c.base,t.branch,yr);c.applied=u.applied,c.conflicts=u.conflicts??[],o.files+=c.stat.files,o.insertions+=c.stat.insertions,o.deletions+=c.stat.deletions,s.push(...c.files.map(f=>U.join(c.name,f))),a.push(...c.conflicts.map(f=>f.startsWith("patch did not apply cleanly")?`${c.name}: ${f}`:U.join(c.name,f))),u.applied||(i=!1)}t.stat=o,t.files=s,t.conflicts=a,t.applied=i}else{await Vc(t.dir,`bostrat: subtask ${t.id} result (auto-committed)`),t.stat=await Yc(e.cwd,t.base,t.branch,yr),t.files=await Zc(e.cwd,t.base,t.branch,yr);let o=await Qc(e.cwd,t.base,t.branch,yr);t.applied=o.applied,t.conflicts=o.conflicts}await xr(t);let r=await O_(t,e,n);await br(t,"resume"),await kw(t.trackId,t.leadBox,MT(t.task,t.stat,t.conflicts,r),{kind:"subtask-resume"}),await br(t,"reap"),await Xl(t),t.step="done",t.finishedAt=Date.now(),await R_(t),await xr(t)}async function E_(t,e){let n=U.join(t.dir,U.dirname(To));await F.mkdir(n,{recursive:!0});let r="";try{r=await Qu(e.cwd,e.sessionId)}catch{}if(!r)try{r=await F.readFile(U.join(e.cwd,".bostrat/handoff/latest.md"),"utf8")}catch{r="_(no prior conversation digest available \u2014 rely on the working tree.)_"}let o=`# Lead context for subtask ${t.id}
|
|
348
|
+
${a}`}async function lu(t){if(t.archivedAt)return!1;let e=t.boxes.find(r=>r.name===t.leadBox);if(!e||Bn.has(e.state)||await Y(e.name))return!1;let n=Zw(e.agent,e.sessionId);if(!await gs(n))return at(t,"rehydrate-failed",{box:e.name,agent:e.agent,reason:`agent "${e.agent}" not available on this box`}),!1;try{let r=await hn(e.name,e.cwd,{agentCmd:n,agent:e.agent,resumeSessionId:e.sessionId,pinned:e.pinned===!0});return e.hookTokenHash=Hr(r.hookToken),_s({cwd:e.cwd,sessionName:e.name,agent:e.agent,agentCmd:n}),e.state="live",at(t,"rehydrated",{box:e.name,agent:e.agent}),!0}catch(r){return at(t,"rehydrate-failed",{box:e.name,agent:e.agent,reason:String(r?.message||r)}),!1}}async function Eg(t){let e=await q(),n=nt(e,t);if(!n)throw new Error(`track "${t}" not found`);if(!n.archivedAt)throw new Error(`track "${t}" is not archived`);return delete n.archivedAt,at(n,"unarchived",{}),await lu(n),await V(),on(n)}async function Ig(){let t=await q(),e=new Set((await mn()).map(s=>s.name)),n=new Set((t.tracks||[]).filter(s=>!s.archivedAt).flatMap(s=>(s.boxes||[]).map(a=>a.name))),r=0,o=!1;for(let s of t.tracks||[])if(s.archivedAt)for(let a of s.boxes)e.has(a.name)&&!n.has(a.name)&&(await re(a.name).catch(()=>{}),at(s,"orphan-reaped",{box:a.name,agent:a.agent}),r++,o=!0),!Bn.has(a.state)&&a.state!=="stopped"&&(a.state="stopped",o=!0);return o&&await V(),r}async function Rg(t){let e=await q(),n=nt(e,t);if(!n)throw new Error(`track "${t}" not found`);if(n.archivedAt)throw new Error(`track "${t}" is archived \u2014 restore it before restarting the lead`);let r=n.boxes.find(a=>a.name===n.leadBox);if(!r)throw new Error(`track "${t}" has no lead box`);if(Bn.has(r.state))throw new Error("lead box is terminal \u2014 nothing to restart");if(await Y(n.leadBox)){let a=await gt(n.leadBox,{lines:Tt}).catch(()=>"");if(Ts(r.agent,a))throw new Error("lead is mid-turn \u2014 retry when it goes idle");await re(n.leadBox),r.state="stopped"}let s=await lu(n);if(await V(),!s)throw new Error("rehydrate failed \u2014 see the track's rehydrate-failed event");return on(n)}var $g=(t,e,n)=>({name:t.name,agent:t.agent,role:e,state:n,cwd:t.cwd,sessionId:t.sessionId,pinned:t.pinned===!0,hookTokenHash:Hr(t.hookToken),createdAt:Date.now()});function Og(t){return(!Array.isArray(t.epochs)||t.epochs.length===0)&&(t.epochs=[{agent:t.agent??null,sessionId:t.sessionId??null,pinned:t.pinned===!0,startedAt:t.createdAt??null,endedAt:null}]),t.epochs}var jo=new Map,Ho=t=>jo.get(t)||(we?nt(we,t)?.handoff:null)||null;async function Ma(t,e){let n=await q(),r=nt(n,t);r&&(r.handoff={...r.handoff||{},...e},await V())}function bE(t,e=Date.now()){let n=t?.handoff;if(!n||n.finishedAt)return null;let r=n.step,o=!!n.toBox&&n.toBox===n.fromBox,s=t.boxes?.find?.(i=>i.name===t.leadBox)||null;return(o?t.leadBox===n.fromBox&&!!n.startedAt&&Array.isArray(s?.epochs)&&s.epochs.some(i=>i.startedAt!=null&&i.startedAt>=n.startedAt):!!n.toBox&&t.leadBox===n.toBox)?{...n,step:"done",finishedAt:e,recovered:`committed before restart (was "${r}")`}:{...n,step:"error",finishedAt:e,error:`interrupted by an agent restart during "${r}"; lead "${t.leadBox}" retained \u2014 retry the handoff`,recovered:"rolled-back"}}async function Mg(){let t=await q(),e=!1;for(let n of t.tracks||[]){let r=n.handoff,o=bE(n);if(!o)continue;e=!0;let s=!!r.toBox&&r.toBox===r.fromBox;if(s&&o.step==="error"){let c=await Y(r.fromBox).catch(()=>null),l=n.boxes.find(u=>u.name===r.fromBox);if(c&&l&&c.agent===r.toAgent&&r.toAgent!==l.agent){let u=Date.now(),f=Og(l),d=f[f.length-1],m=d.agent,h=d.sessionId??null;d.endedAt=u,r.reason&&(d.endReason=r.reason);let w=c.agent==="claude"&&!!c.sessionId;f.push({agent:r.toAgent,sessionId:c.sessionId||null,pinned:w,startedAt:u,endedAt:null}),l.agent=r.toAgent,l.sessionId=c.sessionId||null,l.pinned=w,l.state="live",n.pendingHandoffHydrate={artifact:Ba,at:u},at(n,"lead-handoff",{from:r.fromBox,to:r.fromBox,fromAgent:m,toAgent:r.toAgent,fromSessionId:h,toSessionId:c.sessionId||null,epoch:f.length-1,singleBox:!0,skipPermissions:r.skipPermissions,reason:r.reason??null,handoffStartedAt:r.startedAt,recovered:!0}),n.handoff={...r,step:"done",finishedAt:u,recovered:"swap adopted from live tmux tags after restart"},await G({flow:"lead-handoff",phase:"reconciled",trackId:n.id,outcome:"done",recovered:n.handoff.recovered,fromBox:r.fromBox,toBox:r.toBox}).catch(()=>{});continue}}let a=!s&&o.recovered==="rolled-back"&&r.toBox&&r.toBox!==n.leadBox?r.toBox:null,i=!s&&o.step==="done"?r.fromBox:null;n.handoff=o,await G({flow:"lead-handoff",phase:"reconciled",trackId:n.id,outcome:o.step,recovered:o.recovered,fromBox:o.fromBox,toBox:o.toBox}).catch(()=>{}),i&&await re(i).catch(()=>{}),a&&await re(a).catch(()=>{})}return e&&await V(),e}async function uu(t,e,n=!0,r=null){let o=jo.get(t);if(o&&!o.finishedAt)throw new Error("a lead handoff is already running for this Track");let s=nt(await q(),t);if(!s)throw new Error(`track "${t}" not found`);let a=await Pa(e,n),i=await Y(s.leadBox);if(!i)throw new Error(`lead box "${s.leadBox}" is not running`);let c={trackId:t,step:"checkpoint",fromBox:s.leadBox,toAgent:e,skipPermissions:n,reason:Qw(r),toBox:s.leadBox,startedAt:Date.now(),finishedAt:null,error:null};return jo.set(t,c),await Ma(t,{step:c.step,fromBox:c.fromBox,toAgent:e,toBox:c.toBox,skipPermissions:n,startedAt:c.startedAt,finishedAt:null,error:null}),kE(c,s,i,a,e).catch(async l=>{c.error=l?.message||String(l),c.finishedAt=Date.now(),console.error(`[lead-handoff ${t}] FAILED at step "${c.step}": ${c.error}`),await Ma(t,{step:c.step,error:c.error,finishedAt:c.finishedAt}).catch(()=>{}),G({flow:"lead-handoff",phase:"failed",trackId:t,step:c.step,error:c.error,fromBox:c.fromBox,toAgent:c.toAgent,toBox:c.toBox}).catch(()=>{})}),G({flow:"lead-handoff",phase:"started",trackId:t,fromBox:c.fromBox,toAgent:e,cwd:i.cwd}).catch(()=>{}),c}async function kE(t,e,n,r,o){let s=async(A,M={})=>{t.step=A,console.log(`[lead-handoff ${t.trackId}] step \u2192 ${A}`),await Ma(t.trackId,{step:A,toBox:t.toBox}),await G({flow:"lead-handoff",phase:A,trackId:t.trackId,fromBox:t.fromBox,toBox:t.toBox,toAgent:o,sessionName:M.sessionName,...M})};console.log(`[lead-handoff ${t.trackId}] ${t.fromBox}: ${n.agent} \u2192 ${o} IN PLACE`);let a=await q(),i=nt(a,t.trackId)?.boxes.find(A=>A.name===t.fromBox)||null,c=i?.agent||n.agent,l=i?.sessionId??n.sessionId??null,u=i?.pinned===!0;await s("checkpoint",{sessionName:t.fromBox}),await Of(t.fromBox),await s("swap",{cwd:n.cwd,agentCmd:r});let f=await Mc(t.fromBox,{agentCmd:r,agent:o});await s("hydrate",{sessionName:t.fromBox}),await Ue({cwd:n.cwd,sessionId:f.sessionId,sessionName:t.fromBox,agent:o,agentCmd:Uo(o)});let d=await gt(t.fromBox,{lines:Tt}).catch(()=>"");if(!As(o,d)){await s("rollback",{sessionName:t.fromBox,fromAgent:c});let A=Zw(c,l,{skipPermissions:t.skipPermissions});await Mc(t.fromBox,{agentCmd:A,agent:c,resumeSessionId:l,pinned:u});let M=await q(),z=nt(M,t.trackId);if(z){let $=z.boxes.find(F=>F.name===t.fromBox);$&&($.state="live"),at(z,"lead-handoff-failed",{box:t.fromBox,fromAgent:c,toAgent:o,reason:t.reason,error:`incoming ${o} CLI never drew its composer`,rolledBack:!0,handoffStartedAt:t.startedAt}),await V()}throw new Error(`incoming ${o} CLI never drew its composer \u2014 rolled back to ${c} (--resume, context intact)`)}await s("commit");let m=await q(),h=nt(m,t.trackId);if(!h)throw new Error(`track "${t.trackId}" not found during handoff commit \u2014 was it deleted or archived mid-flight?`);let w=h.boxes.find(A=>A.name===t.fromBox);if(!w)throw new Error(`lead box row "${t.fromBox}" missing during handoff commit`);let g=Date.now(),b=Og(w),v=b[b.length-1];v.endedAt=g,t.reason&&(v.endReason=t.reason),b.push({agent:o,sessionId:f.sessionId,pinned:f.pinned,startedAt:g,endedAt:null}),w.agent=o,w.sessionId=f.sessionId,w.pinned=f.pinned,w.state="live",w.role="lead",h.pendingHandoffHydrate={artifact:Ba,at:g},at(h,"lead-handoff",{from:t.fromBox,to:t.fromBox,fromAgent:c,toAgent:o,fromSessionId:l,toSessionId:f.sessionId,epoch:b.length-1,singleBox:!0,forced:f.forced||void 0,skipPermissions:t.skipPermissions,reason:t.reason,handoffStartedAt:t.startedAt}),await V(),await s("done",{sessionName:t.fromBox}),await Ma(t.trackId,{finishedAt:Date.now(),error:null}),t.finishedAt=Date.now()}var xE=Number(process.env.BOSTRAT_SUBTASK_TIMEOUT_MS)||18e5,SE=Number(process.env.BOSTRAT_SUBTASK_POLL_MS)||3e3,Er=H.join(Oe,"worktrees"),Ar=[Po,Oa],Zt=new Map;var $r=t=>{let e=new Map([...Zt.values()].filter(r=>r.trackId===t).map(r=>[r.id,r])),n=we?nt(we,t):null;if(n?.subtasks)for(let[r,o]of Object.entries(n.subtasks))e.has(r)||e.set(r,o);return[...e.values()].sort((r,o)=>r.startedAt-o.startedAt)},du=t=>({id:t.id,trackId:t.trackId,trackName:t.trackName,agent:t.agent,task:t.task,skipPermissions:t.skipPermissions,reason:t.reason||null,step:t.step,failedStep:t.failedStep||null,box:t.box,branch:t.branch,base:t.base,repos:t.repos||null,dir:t.dir,stat:t.stat,files:t.files||null,conflicts:t.conflicts||[],applied:t.applied,error:t.error,canceled:t.canceled,startedAt:t.startedAt,finishedAt:t.finishedAt});async function vE(t,e,n){let r=await q(),o=nt(r,t);o&&(o.subtasks=o.subtasks||{},o.subtasks[e]={...o.subtasks[e]||{},...n},await V())}var Ir=t=>vE(t.trackId,t.id,du(t));async function _r(t,e){t.step=e,await Ir(t)}function TE(t,e=Date.now()){if(!t||t.finishedAt)return null;let n=t.step;return["snapshot","spawn","working"].includes(n)?{...t,step:"error",finishedAt:e,error:`interrupted by an agent restart during "${n}"; no changes were applied to the lead \u2014 retry the delegation`,recovered:"rolled-back"}:{...t,step:"error",finishedAt:e,error:`interrupted by an agent restart during "${n}"; the lead's working tree may hold a PARTIAL merge \u2014 review git status. The subtask's work is preserved on branch ${t.branch}`,recovered:"partial-merge"}}function AE(t){return!!t&&!t.finishedAt&&!t.canceled&&t.step==="working"&&!!t.box&&!!t.dir&&!!t.base&&!!t.branch}async function _E(t,e,n){if(!AE(n)||Zt.has(e)||!await Y(n.box).catch(()=>null))return!1;try{await U.access(n.dir)}catch{return!1}let o=await Y(t.leadBox).catch(()=>null);if(!o?.cwd)return!1;let s={...n,id:e,leadBox:t.leadBox,step:"working",canceled:!1,forceMerge:!1};return Zt.set(e,s),Dg(s,o).catch(a=>Pg(s,a)),!0}async function Cg(){let t=await q(),e=!1;for(let n of t.tracks||[]){for(let[r,o]of Object.entries(n.subtasks||{})){if(Zt.has(r))continue;if(await _E(n,r,o)){e=!0,at(n,"subtask-resumed",{box:o.box,agent:o.agent,task:o.task,subtaskId:r,resumedStep:o.step}),await G({flow:"subtask",phase:"resumed",trackId:n.id,subtaskId:r,box:o.box,branch:o.branch}).catch(()=>{});continue}let s=o.step,a=TE(o);if(!a)continue;e=!0,n.subtasks[r]=a;let i=n.boxes.find(c=>c.subtaskId===r);i&&!Bn.has(i.state)&&(i.state="canceled"),at(n,"subtask-failed",{from:o.box,fromAgent:o.agent,task:o.task,subtaskId:r,failedStep:s,error:a.error,recovered:a.recovered,durationMs:o.startedAt?a.finishedAt-o.startedAt:null}),await G({flow:"subtask",phase:"reconciled",trackId:n.id,subtaskId:r,outcome:a.step,recovered:a.recovered,box:o.box,branch:o.branch}).catch(()=>{}),await Xw(n,o.box,o.dir)}for(let r of n.boxes){if(r.role!=="subtask"||Bn.has(r.state)||Zt.has(r.subtaskId)||n.subtasks?.[r.subtaskId])continue;e=!0,r.state="canceled";let o=r.subtaskId?H.join(Er,`${n.id}-${r.subtaskId}`):null;await G({flow:"subtask",phase:"reaped-legacy-orphan",trackId:n.id,subtaskId:r.subtaskId,box:r.name}).catch(()=>{}),await Xw(n,r.name,o)}}return e=await EE(t)||e,e&&await V(),e}async function Xw(t,e,n){if(e&&await re(e).catch(()=>{}),!n)return;if(t.workspace?.kind==="multi"){for(let o of Nt(t.workspace)){let s=H.join(n,o.name);await Kt(o.base,s).then(()=>!0,()=>!1)||await Kt(o.dir,s).catch(()=>{})}await U.rm(n,{recursive:!0,force:!0}).catch(()=>{});return}let r=await Y(t.leadBox).catch(()=>null);r?.cwd&&await Kt(r.cwd,n).catch(()=>{})}async function EE(t){let e;try{e=await U.readdir(Er)}catch{return!1}let n=new Set;for(let o of t.tracks||[]){for(let s of Object.keys(o.subtasks||{}))n.add(`${o.id}-${s}`);for(let s of o.boxes||[])s.subtaskId&&n.add(`${o.id}-${s.subtaskId}`)}for(let o of Zt.values())o.dir&&n.add(H.basename(o.dir));let r=!1;for(let o of e){if(n.has(o))continue;let s=H.join(Er,o),a=(t.tracks||[]).find(i=>o.startsWith(`${i.id}-`));if(a?.workspace?.kind==="multi"){for(let i of Nt(a.workspace))await Kt(i.base,H.join(s,i.name)).catch(()=>{});await U.rm(s,{recursive:!0,force:!0}).catch(()=>{})}else{let i=a?await Y(a.leadBox).catch(()=>null):null;i?.cwd?await Kt(i.cwd,s).catch(()=>{}):await U.rm(s,{recursive:!0,force:!0}).catch(()=>{})}await G({flow:"subtask",phase:"swept-orphan-worktree",dir:s}).catch(()=>{}),r=!0}return r}var fu=t=>t.trim().replace(/\s+/g,"-").replace(/[^A-Za-z0-9._-]/g,"").toLowerCase()||"track",Lo=class extends Error{};async function Ng(t,{agent:e,task:n,skipPermissions:r=!0,reason:o=null}={}){if(typeof n!="string"||!gr(n))throw new Error("task (string) required");let s=await Pa(e,r),a=nt(await q(),t);if(!a)throw new Error(`track "${t}" not found`);let i=await Y(a.leadBox);if(!i)throw new Error(`lead box "${a.leadBox}" is not running`);if(!i.cwd)throw new Error("can't resolve the lead's working directory");let c=a.workspace?.kind==="multi",l=null;if(c){let d=Nt(a.workspace);if(!d.length)throw new Error("this multi-repo Track has no tagged repos to delegate into");for(let m of d)if(!m?.dir||!await da(m.dir))throw new Error(`tagged checkout "${m?.name??"?"}" is missing from the lead workspace (${m?.dir??"no dir"})`);l=d.map(m=>({name:m.name,leadDir:m.dir,base:null}))}else if(!await da(i.cwd))throw new Error(`subtask delegation needs the lead's workspace to be a git repo (got ${i.cwd})`);let u=Fo().slice(0,8),f={id:u,trackId:t,trackName:a.name,agent:e,task:gr(n),skipPermissions:r,reason:Qw(o),step:"snapshot",leadBox:a.leadBox,box:null,branch:`bostrat/${fu(a.name)}-${u}`,base:null,repos:l,dir:H.join(Er,`${t}-${u}`),stat:null,conflicts:[],applied:null,startedAt:Date.now(),finishedAt:null,error:null,canceled:!1,forceMerge:!1};return Zt.set(u,f),await Ir(f),IE(f,i,s).catch(d=>Pg(f,d)),f}async function Pg(t,e){t.failedStep=t.step,t.error=e instanceof Lo?"canceled":e?.message||String(e),t.step=e instanceof Lo?"canceled":"error",t.finishedAt=Date.now(),await mu(t).catch(()=>{}),await Ir(t).catch(()=>{}),await ME(t).catch(()=>{})}function pu(t){let e=Zt.get(t);if(!e)throw new Error(`subtask "${t}" not found`);if(e.finishedAt)throw new Error("subtask already finished");return e.canceled=!0,e}function Bg(t){let e=Zt.get(t);if(!e)throw new Error(`subtask "${t}" not found`);if(e.finishedAt)throw new Error("subtask already finished");if(e.step!=="working")throw new Error(`subtask is not waiting to merge (step: ${e.step})`);return e.forceMerge=!0,e}async function IE(t,e,n){if(await _r(t,"snapshot"),t.repos){for(let s of t.repos)s.base=await ul(s.leadDir,`bostrat: snapshot for subtask ${t.id}`);await Ir(t)}else t.base=await ul(e.cwd,`bostrat: snapshot for subtask ${t.id}`);if(await _r(t,"spawn"),await U.mkdir(Er,{recursive:!0}),t.repos){await U.mkdir(t.dir,{recursive:!0});for(let a of t.repos)await yr(a.leadDir,H.join(t.dir,a.name),t.branch,a.base);let s=Nt((nt(await q(),t.trackId)||{}).workspace);await ja(t.dir,t.repos.map(a=>{let i=s.find(c=>c?.name===a.name);return{name:a.name,repo:i?.repo??null,baseRef:null}}),t.branch)}else await yr(e.cwd,t.dir,t.branch,t.base),await eu(t.dir);await RE(t,e);let r=t.trackName.slice(0,38).trim(),o=await hn(`${r} ${t.agent} sub ${t.id}`,t.dir,{agentCmd:n,agent:t.agent});t.box=o.name,await $E(t,o),await tg(t.trackId,o.name,N_(t.task,!!t.repos),{kind:"subtask"}),await Dg(t,e)}async function Dg(t,e){await _r(t,"working");let n=H.join(t.dir,Oa);if(await NE(n,t),t.canceled)throw new Lo;if(await _r(t,"merge"),t.repos){let o={files:0,insertions:0,deletions:0},s=[],a=[],i=!0;for(let c of t.repos){let l=H.join(t.dir,c.name);await dl(l,`bostrat: subtask ${t.id} result (auto-committed)`),c.stat=await hl(c.leadDir,c.base,t.branch,Ar),c.files=await wl(c.leadDir,c.base,t.branch,Ar);let u=await gl(c.leadDir,c.base,t.branch,Ar);c.applied=u.applied,c.conflicts=u.conflicts??[],o.files+=c.stat.files,o.insertions+=c.stat.insertions,o.deletions+=c.stat.deletions,s.push(...c.files.map(f=>H.join(c.name,f))),a.push(...c.conflicts.map(f=>f.startsWith("patch did not apply cleanly")?`${c.name}: ${f}`:H.join(c.name,f))),u.applied||(i=!1)}t.stat=o,t.files=s,t.conflicts=a,t.applied=i}else{await dl(t.dir,`bostrat: subtask ${t.id} result (auto-committed)`),t.stat=await hl(e.cwd,t.base,t.branch,Ar),t.files=await wl(e.cwd,t.base,t.branch,Ar);let o=await gl(e.cwd,t.base,t.branch,Ar);t.applied=o.applied,t.conflicts=o.conflicts}await Ir(t);let r=await CE(t,e,n);await _r(t,"resume"),await tg(t.trackId,t.leadBox,P_(t.task,t.stat,t.conflicts,r),{kind:"subtask-resume"}),await _r(t,"reap"),await mu(t),t.step="done",t.finishedAt=Date.now(),await OE(t),await Ir(t)}async function RE(t,e){let n=H.join(t.dir,H.dirname(Po));await U.mkdir(n,{recursive:!0});let r="";try{r=await Fd(e.cwd,e.sessionId)}catch{}if(!r)try{r=await U.readFile(H.join(e.cwd,".bostrat/handoff/latest.md"),"utf8")}catch{r="_(no prior conversation digest available \u2014 rely on the working tree.)_"}let o=`# Lead context for subtask ${t.id}
|
|
308
349
|
|
|
309
350
|
**Delegated task:** ${t.task}
|
|
310
351
|
|
|
@@ -315,47 +356,48 @@ ${a}`}async function Gl(t){if(t.archivedAt)return!1;let e=t.boxes.find(r=>r.name
|
|
|
315
356
|
## Conversation so far (lead \u2192 you)
|
|
316
357
|
|
|
317
358
|
${r}
|
|
318
|
-
`;await
|
|
319
|
-
${n}`}},{type:"section",text:{type:"mrkdwn",text:r}},{type:"context",elements:[{type:"mrkdwn",text:`<${
|
|
359
|
+
`;await U.writeFile(H.join(t.dir,Po),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]||{},...du(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]||{},...du(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 mu(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 Wo from"node:fs/promises";import Ua 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 La(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 hu=(t,e)=>La("chat.postMessage",t,e),Mr=(t,e)=>La("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 La("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 La("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||Ua.join(process.env.HOME||LE.tmpdir(),".bostrat"),ku=()=>Ua.join(Wg(),"slack.json"),Fa=null,wu=null,gu=Promise.resolve();async function Su(){return Fa||(wu??=Wo.readFile(ku(),"utf8").then(t=>JSON.parse(t)?.threads??{}).catch(()=>({})).then(t=>Fa=t).finally(()=>wu=null),wu)}async function Ug(){let t=JSON.stringify({threads:Fa},null,2),e=`${ku()}.${process.pid}.tmp`;await Wo.mkdir(Wg(),{recursive:!0}),await Wo.writeFile(e,t),await Wo.rename(e,ku())}async function Me(){gu=gu.then(Ug,Ug),await gu.catch(()=>{})}async function vu(t){return(await Su())[t]??null}async function FE(t,e){let n=await Su();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 qo(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 qo(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(qo(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:`<${Xs(t.trackId)}|open the Track> \xB7 reachable on the operator's private network only`}]}]}async function xu(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 hu(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 yu(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 bu=(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?Ua.basename(c.base):"fresh workspace"),lead:"claude"},u=await hu(e.botToken,{channel:e.channel,text:`Track started: ${l.name}`,blocks:qg(l),unfurl_links:!1});l.ts=u.ts,(await Su())[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 vu(n.id);s&&(s.archivedAt=Date.now(),await Me(),await xu(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:qo(`${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?Ua.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(`
|
|
320
361
|
`)+(o?`
|
|
321
|
-
_${o} archived_`:"")}async function
|
|
322
|
-
\u2713 *${
|
|
323
|
-
\u2713 _answered_`}}]})}async function
|
|
324
|
-
_runs on the Track's branch; reachable on the operator's private network only_`}}]);return}case"mock.state.changed":return
|
|
325
|
-
`)){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 uE(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 dE(t,e){return t?.transcript_path?t.transcript_path:e?.cwd&&e?.sessionId?pe(e.cwd,e.sessionId):e?.cwd?li(e.cwd):null}async function pg(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 C(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 fE(r,o,t,i);break;default:break}return a}async function fE(t,e,n,r){let o=[],s=dE(n,e);if(s)try{o=lE(await cE.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=uE(await j(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:Yt(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 hE from"node:fs";import au from"node:fs/promises";import wE from"node:http";import _r from"node:path";import Oa from"node:os";import{createRequire as gE}from"node:module";import iu from"ws";import pE from"node:path";import mE from"node:os";var mg=process.env.BOSTRAT_STATE_DIR||pE.join(process.env.HOME||mE.tmpdir(),".bostrat");function hg(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 wg(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 $a=(()=>{try{return gE(import.meta.url)("../package.json").version||null}catch{return null}})(),yE=process.env.BOSTRAT_STATE_DIR||_r.join(process.env.HOME||Oa.tmpdir(),".bostrat");function bE(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 kE({home:t=process.env.HOME||Oa.homedir(),env:e=process.env}={}){let n=r=>{try{return hE.statSync(r),!0}catch{return!1}};return{claude:n(_r.join(t,".claude",".credentials.json")),codex:n(_r.join(e.CODEX_HOME||_r.join(t,".codex"),"auth.json"))}}var _t=1,gg=1e3,xE=3e4,SE=6e4,vE=1500,AE=500,yg=100,TE=2e3,_E=25e3;function bg({url:t,token:e,nodeId:n=Oa.hostname(),name:r=Oa.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=wg(t);let c=a.replace(/^http/,"ws"),l=new Map,u=new Map,f=_r.join(yE,"plane-link","cursor.json"),d={},m=new Map,h=null,w=!1,g=!1,k=gg,v=null,_=null,M=null,V=null,$=!1;async function L(){try{d=JSON.parse(await au.readFile(f,"utf8"))}catch{d={}}}function A(){$=!0,!M&&(M=setTimeout(async()=>{if(M=null,!!$){$=!1;try{await au.mkdir(_r.dirname(f),{recursive:!0}),await au.writeFile(f,JSON.stringify(d))}catch(I){i.error("[plane-link] cursor flush failed:",I?.message||I)}}},TE),M.unref?.())}function O(I){return!h||h.readyState!==iu.OPEN?!1:(h.send(JSON.stringify(I)),!0)}function H(I){for(let{trackId:R,event:E}of I)m.set(E.id,{trackId:R,seq:E.seq});return O({v:_t,ch:"events",type:"append",events:I})}async function nt(){let I;try{I=await o()}catch(R){i.error("[plane-link] backfill listTracks failed:",R?.message||R);return}O({v:_t,ch:"ctl",type:"tracks",ids:I.map(R=>R.id).filter(R=>typeof R=="string")});for(let R of I){if(!w)return;let E;try{E=await j(R)}catch{continue}let P=d[R.id]??0,Q=E.filter(pt=>pt.seq>P&&typeof pt.id=="string");for(let pt=0;pt<Q.length;pt+=yg){let gt=Q.slice(pt,pt+yg).map(Et=>({trackId:R.id,event:Et}));if(!H(gt))return}}}function y(I){!w||!I?.trackId||typeof I.id!="string"||H([{trackId:I.trackId,event:I}])}let p=new Map;async function S(I){let{reqId:R,method:E,path:P,headers:Q={},body:pt}=I,gt=Object.fromEntries(Object.entries(Q).filter(([,jt])=>jt!=null)),Et=new AbortController;p.set(R,Et);try{if(I.host){await T(I,gt,Et);return}let jt=await fetch(a+P,{method:E,headers:gt,body:pt?Buffer.from(pt,"base64"):void 0,signal:Et.signal}),Wt=Object.fromEntries(jt.headers);for(let b of["connection","keep-alive","transfer-encoding","upgrade","content-encoding","content-length"])delete Wt[b];if(Wt["content-type"]||(Wt["content-type"]="application/octet-stream"),O({v:_t,ch:"control",type:"http_head",reqId:R,status:jt.status,headers:Wt}),jt.body){for await(let b of jt.body)if(!O({v:_t,ch:"control",type:"http_data",reqId:R,data:Buffer.from(b).toString("base64")}))break}O({v:_t,ch:"control",type:"http_end",reqId:R,error:null})}finally{p.delete(R)}}function T(I,R,E){let{reqId:P,method:Q,path:pt,body:gt}=I,Et=new URL(a);return new Promise(jt=>{let Wt=wE.request({host:Et.hostname,port:Et.port,method:Q,path:pt,headers:{...R,host:I.host},signal:E.signal},b=>{let{connection:q,"keep-alive":On,"transfer-encoding":Jo,upgrade:Cn,...Va}=b.headers;O({v:_t,ch:"control",type:"http_head",reqId:P,status:b.statusCode||502,headers:Va}),b.on("data",Qy=>{O({v:_t,ch:"control",type:"http_data",reqId:P,data:Buffer.from(Qy).toString("base64")})||b.destroy()}),b.on("end",()=>{O({v:_t,ch:"control",type:"http_end",reqId:P,error:null}),jt()}),b.on("error",()=>{O({v:_t,ch:"control",type:"http_end",reqId:P,error:"view upstream error"}),jt()})});Wt.on("error",b=>{O({v:_t,ch:"control",type:"http_end",reqId:P,error:b?.message||"view request failed"}),jt()}),gt&&Wt.write(Buffer.from(gt,"base64")),Wt.end()})}function et(){if(g)return;h=new iu(t,{headers:{authorization:`Bearer ${e}`}}),h.on("open",async()=>{let R=null;try{R=await s?.()||null}catch{R=null}O({v:_t,ch:"ctl",type:"hello",nodeId:n,name:r,platform:process.platform,version:$a,providers:kE(),...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,k=gg,v=null,i.log(`[plane-link] connected to plane as ${n}`),E.minNodeVersion&&$a&&bE($a,E.minNodeVersion)&&i.error(`[plane-link] this node is bostrat-node ${$a}; the plane asks for \u2265${E.minNodeVersion} \u2014 update with: npm install -g bostrat-node`),V&&clearInterval(V),V=setInterval(()=>O({v:_t,ch:"ctl",type:"ping"}),_E),V.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 Q=m.get(P);Q&&(m.delete(P),(d[Q.trackId]??0)<Q.seq&&(d[Q.trackId]=Q.seq))}A();return}if(E.ch==="control"&&E.type==="http_open"){S(E).catch(P=>O({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 iu(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 pt=u.get(E.streamId)||[];u.delete(E.streamId);for(let{buf:gt,bin:Et}of pt)P.send(gt,{binary:Et})}),P.on("message",(pt,gt)=>{let Et=gt?pt:Buffer.from(String(pt));O({v:_t,ch:"attach",type:"data",streamId:E.streamId,data:Et.toString("base64"),...gt?{bin:!0}:{}})});let Q=()=>{u.delete(E.streamId),l.delete(E.streamId)&&O({v:_t,ch:"attach",type:"close",streamId:E.streamId})};P.on("close",Q),P.on("error",Q);return}if(E.ch==="attach"&&E.type==="data"){let P=l.get(E.streamId);if(!P)return;let Q=Buffer.from(E.data,"base64"),pt=E.bin===!0,gt=u.get(E.streamId);gt?gt.push({buf:Q,bin:pt}):P.send(Q,{binary:pt});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,V&&(clearInterval(V),V=null),m.clear();for(let P of l.values())P.close();l.clear(),u.clear();for(let P of p.values())P.abort();if(p.clear(),g||_)return;v==null&&(v=Date.now());let R=Date.now()-v<SE,E=R?vE+Math.random()*AE:k;_=setTimeout(()=>{_=null,et()},E),_.unref?.(),R||(k=Math.min(k*2,xE))};h.on("close",I),h.on("error",I)}return Lt.on("event",y),L().then(et),{stop:()=>{g=!0,Lt.off("event",y),_&&clearTimeout(_),M&&clearTimeout(M),V&&clearInterval(V),h?.close()},status:()=>({connected:w,nodeId:n,cursor:{...d},inflight:m.size})}}function lu(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 kg=()=>({input:0,output:0,cacheRead:0,cacheCreation:0}),xg=(t,e)=>{for(let n of Object.keys(t))t[n]+=e?.[n]||0},cu=t=>Math.round(t*1e4)/1e4;async function Sg(t){let e=[],n={},r={},o=0,s=0,a=0,i=0,c=new Set,l=kg(),u=0,f=[];for(let d of t||[]){let m=await j(d),h=Zt(m,{boxLabels:lu(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);xg(l,h.totalTokens);for(let[w,g]of Object.entries(h.byModel))n[w]||={usd:0,events:0,tokens:kg()},n[w].usd=cu(n[w].usd+g.usd),n[w].events+=g.events,xg(n[w].tokens,g.tokens);for(let[w,g]of Object.entries(h.byProvider))r[w]||={usd:0,events:0},r[w].usd=cu(r[w].usd+g.usd),r[w].events+=g.events;e.push({id:d.id,name:d.name,usd:h.totalUsd,rollup:h})}return e.sort((d,m)=>m.usd-d.usd),{generatedAt:new Date().toISOString(),pricingBasis:"api-equivalent (subscription usage priced at public API rates)",fleet:{totalUsd:cu(o),turns:s,repricedTurns:a,unpricedTurns:i,unpricedModels:[...c],totalTokens:l,byModel:n,byProvider:r,tracksWithCost:e.length,tracksSilent:u+f.length,tracksSilentArchived:u,tracksSilentLive:f},tracks:e}}var Po=t=>`$${t.toFixed(4)}`,Er=(t,e)=>String(t).padEnd(e);function vg(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 ${Po(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(` ${Er(a,26)} ${Er(Po(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(` ${Er(a.name||a.id,30)} ${Po(a.usd)}`);let i=Object.values(a.rollup.byBox).sort((c,l)=>l.usd-c.usd);for(let c of i)if(r.push(` ${Er(c.label,40)} ${Er(Po(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))}${Er(Po(u.usd),10)} ${u.models.join(",")||"?"}`)}return r.join(`
|
|
326
|
-
`)}import OE from"node:fs/promises";import CE from"node:path";var Ca=t=>t.payload?.ts??t.ts,EE=12e4,Ye=t=>Math.round(t*1e4)/1e4,IE=()=>({usd:0,activeMs:0,turns:0,toolCalls:0,models:{},providers:{},byTrack:new Map});function RE(t,{since:e}){let{track:n,events:r,brief:o}=t,s=v=>Ca(v)>=e,a=Un([Hr(n?.name),Hr(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 _=Un(v.payload.task);_&&i.set(v.boxId,_)}let c=new Map,l=v=>{let _=v||"unattributed";return c.has(_)||c.set(_,{segments:[u(null)],lastTs:null,provider:null}),c.get(_)},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,_)=>Ca(v)-Ca(_)),d=0,m=0,h=0,w={turns:0};for(let v of f){let _=l(v.boxId),M=_.segments[_.segments.length-1],V=Ca(v);switch(_.lastTs!=null&&V>_.lastTs&&s(v)&&(M.activeMs+=Math.min(V-_.lastTs,EE)),_.lastTs=V,v.provider&&(_.provider=v.provider),v.type){case"turn.user":{let $=v.payload?.text;if(v.source==="hook"||!String($||"").trim())break;let L=u(Un($));_.segments.push(L),s(v)&&(L.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||{},L={input:$.tokens?.input||0,output:$.tokens?.output||0,cacheRead:$.tokens?.cacheRead||0,cacheWrite:$.tokens?.cacheWrite??$.tokens?.cacheCreation??0},A=Number($.usd)||0;!A&&$.tokens&&(A=Yt($.model,L)),!A&&$.tokens&&!ks($.model)&&(w.turns+=1),M.usd+=A,M.tokens.input+=L.input,M.tokens.output+=L.output,M.tokens.cacheRead+=L.cacheRead,M.tokens.cacheWrite+=L.cacheWrite;let O=$.model||"unknown";M.models[O]=(M.models[O]||0)+A,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,k=!1;for(let[v,_]of c)for(let M of _.segments){if(!(M.usd>0||M.activeMs>0||M.turns>0||M.toolCalls>0))continue;let $=M.textCat?.category??i.get(v)?.category??hd(M.tools)?.category??a?.category??"uncategorized";g.has($)||g.set($,{usd:0,activeMs:0,turns:0,toolCalls:0,models:{},providers:{}});let L=g.get($);L.usd+=M.usd,L.activeMs+=M.activeMs,L.turns+=M.turns,L.toolCalls+=M.toolCalls;for(let[O,H]of Object.entries(M.models))L.models[O]=(L.models[O]||0)+H;let A=M.provider||_.provider;A&&(L.providers[A]=(L.providers[A]||0)+M.usd),A==="cursor"&&M.usd===0&&(k=!0)}return{perCategory:g,handoffs:d,merged:m,failed:h,sawCursorActivity:k,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:g,events:k}=w;if(!k?.length){a+=1;continue}let{perCategory:v,handoffs:_,merged:M,failed:V,sawCursorActivity:$,unpricedTurns:L}=RE(w,{since:r});$&&(i=!0),c+=L;let A={trackId:g?.id,name:g?.name||g?.id,archived:!!g?.archivedAt,usd:0,activeMs:0,turns:0,categories:{},models:{},providers:{},handoffs:_,subtasksMerged:M,subtasksFailed:V};for(let[O,H]of v){A.usd+=H.usd,A.activeMs+=H.activeMs,A.turns+=H.turns,A.categories[O]={usd:Ye(H.usd),activeMs:H.activeMs};for(let[p,S]of Object.entries(H.models))A.models[p]=Ye((A.models[p]||0)+S);for(let[p,S]of Object.entries(H.providers))A.providers[p]=Ye((A.providers[p]||0)+S);o.has(O)||o.set(O,IE());let nt=o.get(O);nt.usd+=H.usd,nt.activeMs+=H.activeMs,nt.turns+=H.turns,nt.toolCalls+=H.toolCalls;for(let[p,S]of Object.entries(H.models))nt.models[p]=(nt.models[p]||0)+S;for(let[p,S]of Object.entries(H.providers))nt.providers[p]=(nt.providers[p]||0)+S;let y=nt.byTrack.get(g?.id)||{usd:0,activeMs:0};y.usd+=H.usd,y.activeMs+=H.activeMs,nt.byTrack.set(g?.id,y)}(A.usd>0||A.activeMs>0||A.turns>0)&&(A.usd=Ye(A.usd),A.dominantCategory=$E(A.categories),s.push(A))}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:Ye(g.usd),activeMs:g.activeMs,turns:g.turns,toolCalls:g.toolCalls,models:Ag(g.models),providers:Ag(g.providers),topWorkstreams:[...g.byTrack.entries()].sort((k,v)=>v[1].usd-k[1].usd||v[1].activeMs-k[1].activeMs).slice(0,2).map(([k,v])=>({trackId:k,name:m.get(k),usd:Ye(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:Ye(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 $E=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},Ag=t=>Object.fromEntries(Object.entries(t).map(([e,n])=>[e,Ye(n)]));var Ir={"24h":24*36e5,"7d":168*36e5,"30d":720*36e5,all:null};function ME(t){let e=String(t||"7d");return e in Ir?{key:e,ms:Ir[e]}:{key:"7d",ms:Ir["7d"]}}function NE(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=ra(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 PE(t){if(!t?.cwd)return null;try{let n=(await OE.readFile(CE.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 _g(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}:ME(e),f=[];for(let d of t||[]){let m=await j(d),h=NE(d,m);i&&c<o&&(h=h.filter(w=>(w.ts??0)<=c)),f.push({track:d,events:h,brief:await PE(d)})}return{window:l,...Tg(f,{windowMs:u,now:c})}}var Ze=36e5,$g=24*Ze,BE=400,DE=12,Rr=t=>Math.round(t*1e4)/1e4;function jE({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 Ir?String(t||"7d"):"7d",i=Ir[a];return{key:a,since:i==null?null:r-i,until:r}}var LE=(t,e)=>t!=null&&e-t<=48*Ze?Ze:$g;function Eg(t,e){if(e===Ze)return Math.floor(t/Ze)*Ze;let n=new Date(t);return n.setHours(0,0,0,0),n.getTime()}function FE(t,e){if(e===Ze)return t+Ze;let n=new Date(t);return n.setDate(n.getDate()+1),n.setHours(0,0,0,0),n.getTime()}var Ig=()=>({input:0,output:0,cacheRead:0,cacheCreation:0}),Rg=(t,e)=>{for(let n of Object.keys(t))t[n]+=e?.[n]||0},UE=t=>Object.fromEntries(Object.entries(t).map(([e,n])=>[e,Rr(n)]));async function Og(t,e={}){let n=e.now??Date.now(),r=jE({...e,now:n}),o=r.since==null?$g:LE(r.since,r.until),s=A=>(r.since==null||A>=r.since)&&A<r.until,a=r.since!=null?r.until-r.since:null,i=A=>a!=null&&A>=r.since-a&&A<r.since,c={},l={},u=Ig(),f=0,d=0,m=0,h=0,w=0,g=new Set,k=new Map,v=[],_=[],M=[],V=null;for(let A of t||[]){let H=(await j(A)).filter(p=>p.type==="cost.recorded");if(!H.length){A.archivedAt||M.push(A.name||A.id);continue}let nt=[];for(let p of H){let S=p.ts??0;s(S)?nt.push(p):i(S)&&(w+=Ss(p).usd)}if(!nt.length)continue;let y=Zt(nt,{boxLabels:lu(A)});f+=y.totalUsd,d+=y.turns,m+=y.repricedTurns||0,h+=y.unpricedTurns||0;for(let p of y.unpricedModels||[])g.add(p);Rg(u,y.totalTokens);for(let[p,S]of Object.entries(y.byModel))c[p]||={usd:0,events:0,tokens:Ig()},c[p].usd=Rr(c[p].usd+S.usd),c[p].events+=S.events,Rg(c[p].tokens,S.tokens);for(let[p,S]of Object.entries(y.byProvider))l[p]||={usd:0,events:0},l[p].usd=Rr(l[p].usd+S.usd),l[p].events+=S.events;for(let p of nt){let{usd:S}=Ss(p);if(!S)continue;let T=Eg(p.ts??0,o),et=k.get(T)??{t:T,total:0,byProvider:{}};et.total+=S;let I=p.provider||"unknown";et.byProvider[I]=(et.byProvider[I]||0)+S,k.set(T,et),(V==null||T<V)&&(V=T)}v.push({id:A.id,name:A.name||A.id,archived:!!A.archivedAt,usd:y.totalUsd,turns:y.turns,providers:Object.keys(y.byProvider)});for(let[p,S]of Object.entries(y.byBox))_.push({trackId:A.id,trackName:A.name||A.id,boxId:p,...S})}let $=r.since!=null?Eg(r.since,o):V,L=[];for(let A=$,O=0;A!=null&&A<r.until&&O<BE;A=FE(A,o),O++){let H=k.get(A);L.push(H?{t:A,total:Rr(H.total),byProvider:UE(H.byProvider)}:{t:A,total:0,byProvider:{}})}return v.sort((A,O)=>O.usd-A.usd),_.sort((A,O)=>O.usd-A.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:Rr(f),turns:d,repricedTurns:m,unpricedTurns:h,unpricedModels:[...g],totalTokens:u,byModel:c,byProvider:l,tracksWithCost:v.length,tracksSilentLive:M},prior:a!=null?{totalUsd:Rr(w),since:r.since-a,until:r.since}:null,tracks:v.slice(0,50),boxes:_.slice(0,DE)}}import $r from"node:fs/promises";import HE from"node:fs";import wt from"node:path";import WE from"node:os";import{execFile as qE}from"node:child_process";import{promisify as GE}from"node:util";var KE=GE(qE),Cg=process.env.BOSTRAT_STATE_DIR||wt.join(process.env.HOME||WE.tmpdir(),".bostrat"),uu=!1;async function Bo(t,e){let n=await $r.stat(t).catch(()=>null);if(!n?.isFile())return!1;let r=await $r.stat(e).catch(()=>null);return r&&r.size===n.size&&r.mtimeMs>=n.mtimeMs?!1:(await $r.mkdir(wt.dirname(e),{recursive:!0}),await $r.copyFile(t,e),!0)}var VE=new Set([".claude",".codex",".credentials.json","auth.json",".claude.json","integrations.json"]);async function Do(t,e){let n=0,r=await $r.readdir(t,{withFileTypes:!0}).catch(()=>[]);for(let o of r){if(VE.has(o.name))continue;let s=wt.join(t,o.name);o.isDirectory()?n+=await Do(s,wt.join(e,o.name)):o.isFile()&&(n+=await Bo(s,wt.join(e,o.name))?1:0)}return n}async function Mg({stateDir:t=Cg,dest:e=process.env.BOSTRAT_SYNC_DIR||`${Cg}-sync`,remote:n=process.env.BOSTRAT_STATE_REMOTE||null,tracks:r=null}={}){try{await $r.mkdir(e,{recursive:!0});let o=0;o+=await Bo(wt.join(t,"tracks.json"),wt.join(e,"tracks.json"))?1:0,o+=await Bo(wt.join(t,"mocks.json"),wt.join(e,"mocks.json"))?1:0,o+=await Bo(wt.join(t,"runbooks.json"),wt.join(e,"runbooks.json"))?1:0,o+=await Do(wt.join(t,"runbooks"),wt.join(e,"runbooks")),o+=await Do(wt.join(t,"mock-archive"),wt.join(e,"mock-archive")),o+=await Bo(wt.join(t,"events.jsonl"),wt.join(e,"events.jsonl"))?1:0,o+=await Do(wt.join(t,"events"),wt.join(e,"events"));for(let l of r||[])!l?.cwd||!l?.id||(o+=await Do(wt.join(l.cwd,".bostrat"),wt.join(e,"workspaces",l.id,".bostrat")));let s=(...l)=>KE("git",["-C",e,...l]);HE.existsSync(wt.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 pc(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||uu))try{await s("push","-q",n,"HEAD:refs/heads/main"),c=!0,uu=!1}catch{uu=!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 zE}from"node:child_process";import{promisify as JE}from"node:util";import{randomUUID as XE}from"node:crypto";import YE from"node:fs";import ZE from"node:os";import QE from"node:path";var tn=JE(zE),Ma=t=>new Promise(e=>setTimeout(e,t)),en=ei,du=process.env.BOSTRAT_USAGE_PROBE_CWD||QE.join(ZE.homedir(),".bostrat","usage-probe"),tI=Number(process.env.BOSTRAT_USAGE_IDLE_REAP_MS)||5*6e4,eI=Number(process.env.BOSTRAT_USAGE_PANEL_TIMEOUT_MS)||15e3,nI=Number(process.env.BOSTRAT_USAGE_READY_TIMEOUT_MS)||45e3,Ng=Number(process.env.BOSTRAT_USAGE_CACHE_MS)||2e4,Qe=null,jo=null,Lo=null;async function rI(){try{return await tn(N,B("has-session","-t",en)),!0}catch{return!1}}function oI(){Qe&&clearTimeout(Qe),Qe=setTimeout(()=>sI().catch(()=>{}),tI),Qe.unref&&Qe.unref()}async function sI(){Qe&&(clearTimeout(Qe),Qe=null);try{await tn(N,B("kill-session","-t",en))}catch{}}async function aI(){let t=Date.now()+nI;for(;;){let e=await fu().catch(()=>"");if(/for agents|for shortcuts|bypass permissions on/i.test(e)){await Ma(500);return}if(Date.now()>t)throw new Error("usage probe didn't reach the prompt (trust/onboarding may be blocking)");await Ma(500)}}async function iI(){if(await rI())return;if(!await Ie())throw new Error("host tmux server not running \u2014 start globo-tmux.service");YE.mkdirSync(du,{recursive:!0}),await Ee(du);let t=XE();await tn(N,B("new-session","-d","-s",en,"-c",du,"-x","200","-y","60"));let e=`${ti} --session-id ${t}`;await tn(N,B("send-keys","-t",en,e,"Enter")),await aI()}async function fu(){let{stdout:t}=await tn(N,B("capture-pane","-t",en,"-p"));return t}async function cI(){await iI(),await tn(N,B("send-keys","-t",en,"-l","/usage")),await tn(N,B("send-keys","-t",en,"Enter"));let t=Date.now()+eI,e="";for(;await Ma(400),e=await fu(),!(/Current\s+(session|week)/i.test(e)||/\d+%\s*used/i.test(e)||Date.now()>t););return await Ma(900),e=await fu(),await tn(N,B("send-keys","-t",en,"Escape")).catch(()=>{}),oI(),e}function lI(t){let e=String(t||"").split(`
|
|
327
|
-
`).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
|
|
328
|
-
`).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
|
|
329
|
-
`),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:
|
|
330
|
-
`,{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}=yu(await r.listTracks(),a.join(" "));return i?(Dn(qE(t,n,{track:i,responseUrl:e.response_url})),ft(`Archiving *${X(i.name)}*\u2026`)):bu(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}=yu(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`)):bu(f,l)}case"stop":{if(!a.length)return ft("Usage: `/bostrat stop <track>`");let{match:i,live:c}=yu(await r.listTracks(),a.join(" "));return i?(Dn(GE(t,n,{track:i,responseUrl:e.response_url})),ft(`Stopping *${X(i.name)}*\u2026`)):bu(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 vu(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,wa(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:qo(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(qo(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 Wo.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 vu(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 xu(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:`<${Xs(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 xu(r,n);return;case"view.state.changed":{if(o.change!=="created"||o.restarted||!o.slug)return;await jn(r,n,`Live preview ${o.name}: ${Qs(o.slug)}`,[{type:"section",text:{type:"mrkdwn",text:`\u{1F50D} live preview *${X(o.name)}*: ${Qs(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?Ci(e.cwd):null}async function zg(t,e){let n=await e(t?.session_id,t?.cwd);if(!n?.track)return[];let{track:r,box:o}=n,s={boxId:o?.name||null,provider:"claude",source:"hook"},a=[],i=async c=>{a.push(await O(r,{...s,...c}))};switch(t?.hook_event_name){case"SessionStart":await i({type:"session.ready",actor:"system",payload:{sessionId:t.session_id}});break;case"UserPromptSubmit":await i({type:"turn.user",actor:"user",payload:{promptHead:String(t.prompt??t.user_prompt??"").slice(0,160)||null}});break;case"PostToolUse":await i({type:"tool.called",actor:"agent",payload:{tool:t.tool_name??null}});break;case"Stop":await mI(r,o,t,i);break;default:break}return a}async function mI(t,e,n,r){let o=[],s=pI(n,e);if(s)try{o=dI(await uI.readFile(s,"utf8"))}catch{}let a=o.length?o[o.length-1]:null;if(await r({type:"turn.agent",actor:"agent",payload:{model:a?.model??e?.model??null,messageId:a?.messageId??null}}),!o.length)return;let i=fI(await L(t)),c=o.filter(u=>!i.has(u.messageId));if(c.length){let u=new Map;for(let f of c){let d=f.model||"unknown",m=u.get(d)||{tokens:{input:0,output:0,cacheWrite:0,cacheRead:0},messageIds:[]};for(let h of Object.keys(m.tokens))m.tokens[h]+=f.tokens[h];m.messageIds.push(f.messageId),u.set(d,m)}for(let[f,d]of u)await r({type:"cost.recorded",actor:"system",source:"transcript",payload:{model:f,usd:oe(f,d.tokens),tokens:d.tokens,messageId:d.messageIds[d.messageIds.length-1],messageIds:d.messageIds,apiCalls:d.messageIds.length}})}let l=a.tokens.input+a.tokens.cacheWrite+a.tokens.cacheRead;await r({type:"context.usage",actor:"system",source:"transcript",payload:{model:a.model,occupancy:l,tokens:a.tokens}})}import gI from"node:fs";import Tu from"node:fs/promises";import yI from"node:http";import Cr from"node:path";import Wa from"node:os";import{createRequire as bI}from"node:module";import Au from"ws";import hI from"node:path";import wI from"node:os";var Vg=process.env.BOSTRAT_STATE_DIR||hI.join(process.env.HOME||wI.tmpdir(),".bostrat");function Jg(t=process.env){let e=(t.BOSTRAT_HOST||"").trim();if(e!=="all")return e||(t.BOSTRAT_PLANE_URL?"127.0.0.1":void 0)}function Xg(t){let e=String(t||"").trim();if(!e)return"";let n=/^[a-z][a-z0-9+.-]*:\/\//i.test(e)?e:`wss://${e}`,r;try{r=new URL(n)}catch{return e}let o={"https:":"wss:","http:":"ws:","wss:":"wss:","ws:":"ws:"}[r.protocol];return o?(r.protocol=o,(r.pathname===""||r.pathname==="/")&&(r.pathname="/v1/node"),r.toString()):e}var Ha=(()=>{try{return bI(import.meta.url)("../package.json").version||null}catch{return null}})(),kI=process.env.BOSTRAT_STATE_DIR||Cr.join(process.env.HOME||Wa.tmpdir(),".bostrat");function xI(t,e){let n=String(t).split(".").map(Number),r=String(e).split(".").map(Number);if(n.some(Number.isNaN)||r.some(Number.isNaN))return!1;for(let o=0;o<Math.max(n.length,r.length);o++){let s=(n[o]||0)-(r[o]||0);if(s)return s<0}return!1}function SI({home:t=process.env.HOME||Wa.homedir(),env:e=process.env}={}){let n=r=>{try{return gI.statSync(r),!0}catch{return!1}};return{claude:n(Cr.join(t,".claude",".credentials.json")),codex:n(Cr.join(e.CODEX_HOME||Cr.join(t,".codex"),"auth.json"))}}var It=1,Yg=1e3,vI=3e4,TI=6e4,AI=1500,_I=500,Zg=100,EI=2e3,II=25e3;function Qg({url:t,token:e,nodeId:n=Wa.hostname(),name:r=Wa.hostname(),listTracks:o,detectAgents:s=null,selfUrl:a=`http://127.0.0.1:${process.env.PORT||3362}`,log:i=console}){if(!t||!e)throw new Error("plane-link: url and token are required");if(typeof o!="function")throw new Error("plane-link: listTracks is required");t=Xg(t);let c=a.replace(/^http/,"ws"),l=new Map,u=new Map,f=Cr.join(kI,"plane-link","cursor.json"),d={},m=new Map,h=null,w=!1,g=!1,b=Yg,v=null,A=null,M=null,z=null,$=!1;async function F(){try{d=JSON.parse(await Tu.readFile(f,"utf8"))}catch{d={}}}function T(){$=!0,!M&&(M=setTimeout(async()=>{if(M=null,!!$){$=!1;try{await Tu.mkdir(Cr.dirname(f),{recursive:!0}),await Tu.writeFile(f,JSON.stringify(d))}catch(I){i.error("[plane-link] cursor flush failed:",I?.message||I)}}},EI),M.unref?.())}function C(I){return!h||h.readyState!==Au.OPEN?!1:(h.send(JSON.stringify(I)),!0)}function W(I){for(let{trackId:R,event:E}of I)m.set(E.id,{trackId:R,seq:E.seq});return C({v:It,ch:"events",type:"append",events:I})}async function ot(){let I;try{I=await o()}catch(R){i.error("[plane-link] backfill listTracks failed:",R?.message||R);return}C({v:It,ch:"ctl",type:"tracks",ids:I.map(R=>R.id).filter(R=>typeof R=="string")});for(let R of I){if(!w)return;let E;try{E=await L(R)}catch{continue}let P=d[R.id]??0,et=E.filter(wt=>wt.seq>P&&typeof wt.id=="string");for(let wt=0;wt<et.length;wt+=Zg){let bt=et.slice(wt,wt+Zg).map(Rt=>({trackId:R.id,event:Rt}));if(!W(bt))return}}}function y(I){!w||!I?.trackId||typeof I.id!="string"||W([{trackId:I.trackId,event:I}])}let p=new Map;async function S(I){let{reqId:R,method:E,path:P,headers:et={},body:wt}=I,bt=Object.fromEntries(Object.entries(et).filter(([,Ht])=>Ht!=null)),Rt=new AbortController;p.set(R,Rt);try{if(I.host){await _(I,bt,Rt);return}let Ht=await fetch(a+P,{method:E,headers:bt,body:wt?Buffer.from(wt,"base64"):void 0,signal:Rt.signal}),zt=Object.fromEntries(Ht.headers);for(let k of["connection","keep-alive","transfer-encoding","upgrade","content-encoding","content-length"])delete zt[k];if(zt["content-type"]||(zt["content-type"]="application/octet-stream"),C({v:It,ch:"control",type:"http_head",reqId:R,status:Ht.status,headers:zt}),Ht.body){for await(let k of Ht.body)if(!C({v:It,ch:"control",type:"http_data",reqId:R,data:Buffer.from(k).toString("base64")}))break}C({v:It,ch:"control",type:"http_end",reqId:R,error:null})}finally{p.delete(R)}}function _(I,R,E){let{reqId:P,method:et,path:wt,body:bt}=I,Rt=new URL(a);return new Promise(Ht=>{let zt=yI.request({host:Rt.hostname,port:Rt.port,method:et,path:wt,headers:{...R,host:I.host},signal:E.signal},k=>{let{connection:K,"keep-alive":Ln,"transfer-encoding":os,upgrade:Fn,...si}=k.headers;C({v:It,ch:"control",type:"http_head",reqId:P,status:k.statusCode||502,headers:si}),k.on("data",Cb=>{C({v:It,ch:"control",type:"http_data",reqId:P,data:Buffer.from(Cb).toString("base64")})||k.destroy()}),k.on("end",()=>{C({v:It,ch:"control",type:"http_end",reqId:P,error:null}),Ht()}),k.on("error",()=>{C({v:It,ch:"control",type:"http_end",reqId:P,error:"view upstream error"}),Ht()})});zt.on("error",k=>{C({v:It,ch:"control",type:"http_end",reqId:P,error:k?.message||"view request failed"}),Ht()}),bt&&zt.write(Buffer.from(bt,"base64")),zt.end()})}function rt(){if(g)return;h=new Au(t,{headers:{authorization:`Bearer ${e}`}}),h.on("open",async()=>{let R=null;try{R=await s?.()||null}catch{R=null}C({v:It,ch:"ctl",type:"hello",nodeId:n,name:r,platform:process.platform,version:Ha,providers:SI(),...R?{agents:R}:{}})}),h.on("message",R=>{let E;try{E=JSON.parse(R)}catch{return}if(E.ch==="ctl"&&E.type==="hello_ok"){w=!0,b=Yg,v=null,i.log(`[plane-link] connected to plane as ${n}`),E.minNodeVersion&&Ha&&xI(Ha,E.minNodeVersion)&&i.error(`[plane-link] this node is bostrat-node ${Ha}; the plane asks for \u2265${E.minNodeVersion} \u2014 update with: npm install -g bostrat-node`),z&&clearInterval(z),z=setInterval(()=>C({v:It,ch:"ctl",type:"ping"}),II),z.unref?.(),ot().catch(P=>i.error("[plane-link] backfill failed:",P?.message||P));return}if(E.ch==="events"&&E.type==="ack"&&Array.isArray(E.ids)){for(let P of E.ids){let et=m.get(P);et&&(m.delete(P),(d[et.trackId]??0)<et.seq&&(d[et.trackId]=et.seq))}T();return}if(E.ch==="control"&&E.type==="http_open"){S(E).catch(P=>C({v:It,ch:"control",type:"http_end",reqId:E.reqId,error:P?.message||"tunnel error"}));return}if(E.ch==="control"&&E.type==="http_cancel"){p.get(E.reqId)?.abort();return}if(E.ch==="attach"&&E.type==="open"){let P=new Au(c+E.path,Array.isArray(E.protocols)?E.protocols:[],E.host?{headers:{host:E.host}}:void 0);l.set(E.streamId,P),u.set(E.streamId,[]),P.on("open",()=>{let wt=u.get(E.streamId)||[];u.delete(E.streamId);for(let{buf:bt,bin:Rt}of wt)P.send(bt,{binary:Rt})}),P.on("message",(wt,bt)=>{let Rt=bt?wt:Buffer.from(String(wt));C({v:It,ch:"attach",type:"data",streamId:E.streamId,data:Rt.toString("base64"),...bt?{bin:!0}:{}})});let et=()=>{u.delete(E.streamId),l.delete(E.streamId)&&C({v:It,ch:"attach",type:"close",streamId:E.streamId})};P.on("close",et),P.on("error",et);return}if(E.ch==="attach"&&E.type==="data"){let P=l.get(E.streamId);if(!P)return;let et=Buffer.from(E.data,"base64"),wt=E.bin===!0,bt=u.get(E.streamId);bt?bt.push({buf:et,bin:wt}):P.send(et,{binary:wt});return}if(E.ch==="attach"&&E.type==="close"){let P=l.get(E.streamId);l.delete(E.streamId),u.delete(E.streamId),P?.close();return}});let I=()=>{w&&i.log("[plane-link] disconnected from plane"),w=!1,z&&(clearInterval(z),z=null),m.clear();for(let P of l.values())P.close();l.clear(),u.clear();for(let P of p.values())P.abort();if(p.clear(),g||A)return;v==null&&(v=Date.now());let R=Date.now()-v<TI,E=R?AI+Math.random()*_I:b;A=setTimeout(()=>{A=null,rt()},E),A.unref?.(),R||(b=Math.min(b*2,vI))};h.on("close",I),h.on("error",I)}return Wt.on("event",y),F().then(rt),{stop:()=>{g=!0,Wt.off("event",y),A&&clearTimeout(A),M&&clearTimeout(M),z&&clearInterval(z),h?.close()},status:()=>({connected:w,nodeId:n,cursor:{...d},inflight:m.size})}}function Eu(t){let e={};for(let n of t.boxes||[]){let r=n.role&&n.role!=="lead"?` \xB7 ${n.role}`:"";e[n.name]=`${n.name} (${n.agent||"?"}${r})`}return e}var ty=()=>({input:0,output:0,cacheRead:0,cacheCreation:0}),ey=(t,e)=>{for(let n of Object.keys(t))t[n]+=e?.[n]||0},_u=t=>Math.round(t*1e4)/1e4;async function ny(t){let e=[],n={},r={},o=0,s=0,a=0,i=0,c=new Set,l=ty(),u=0,f=[];for(let d of t||[]){let m=await L(d),h=se(m,{boxLabels:Eu(d)});if(h.turns===0){d.archivedAt?u++:f.push(d.name||d.id);continue}o+=h.totalUsd,s+=h.turns,a+=h.repricedTurns||0,i+=h.unpricedTurns||0;for(let w of h.unpricedModels||[])c.add(w);ey(l,h.totalTokens);for(let[w,g]of Object.entries(h.byModel))n[w]||={usd:0,events:0,tokens:ty()},n[w].usd=_u(n[w].usd+g.usd),n[w].events+=g.events,ey(n[w].tokens,g.tokens);for(let[w,g]of Object.entries(h.byProvider))r[w]||={usd:0,events:0},r[w].usd=_u(r[w].usd+g.usd),r[w].events+=g.events;e.push({id:d.id,name:d.name,usd:h.totalUsd,rollup:h})}return e.sort((d,m)=>m.usd-d.usd),{generatedAt:new Date().toISOString(),pricingBasis:"api-equivalent (subscription usage priced at public API rates)",fleet:{totalUsd:_u(o),turns:s,repricedTurns:a,unpricedTurns:i,unpricedModels:[...c],totalTokens:l,byModel:n,byProvider:r,tracksWithCost:e.length,tracksSilent:u+f.length,tracksSilentArchived:u,tracksSilentLive:f},tracks:e}}var Ko=t=>`$${t.toFixed(4)}`,Nr=(t,e)=>String(t).padEnd(e);function ry(t){let{fleet:e,tracks:n}=t,r=[];r.push(`BOSTRAT COST READ-OUT \xB7 ${t.generatedAt}`),t.pricingBasis&&r.push(`basis: ${t.pricingBasis}`);let o=e.tracksSilentLive||[];r.push(`fleet total ${Ko(e.totalUsd)} over ${e.turns} turn(s) \xB7 ${e.tracksWithCost} track(s) with cost, ${e.tracksSilentArchived??e.tracksSilent} silent (archived)`+(o.length?`, ${o.length} LIVE with no cost data: ${o.join(", ")}`:"")),e.repricedTurns&&r.push(`${e.repricedTurns} turn(s) imputed at current rates (recorded before their model was priced)`),e.unpricedTurns&&r.push(`\u26A0 ${e.unpricedTurns} turn(s) UNPRICED \u2014 total under-reports. Add PER_MTOK rate(s) for: ${(e.unpricedModels||[]).join(", ")}`),r.push(""),r.push("BY MODEL");let s=Object.entries(e.byModel).sort((a,i)=>i[1].usd-a[1].usd);s.length||r.push(" (no cost events yet \u2014 hooks emit on each turn of a newly started box)");for(let[a,i]of s){let c=i.tokens;r.push(` ${Nr(a,26)} ${Nr(Ko(i.usd),10)} ${i.events} turn(s) \xB7 in ${c.input} out ${c.output} cacheR ${c.cacheRead} cacheW ${c.cacheCreation}`)}r.push(""),r.push("BY TRACK / TASK");for(let a of n){r.push(` ${Nr(a.name||a.id,30)} ${Ko(a.usd)}`);let i=Object.values(a.rollup.byBox).sort((c,l)=>l.usd-c.usd);for(let c of i)if(r.push(` ${Nr(c.label,40)} ${Nr(Ko(c.usd),10)} ${c.models.join(",")||"?"}`),c.epochs)for(let[l,u]of Object.entries(c.epochs).sort((f,d)=>Number(f[0])-Number(d[0])))r.push(` \u21B3 tenure ${l} (${u.provider||"?"})${" ".repeat(Math.max(1,24-String(u.provider||"?").length))}${Nr(Ko(u.usd),10)} ${u.models.join(",")||"?"}`)}return r.join(`
|
|
367
|
+
`)}import CI from"node:fs/promises";import NI from"node:path";var qa=t=>t.payload?.ts??t.ts,RI=12e4,sn=t=>Math.round(t*1e4)/1e4,$I=()=>({usd:0,activeMs:0,turns:0,toolCalls:0,models:{},providers:{},byTrack:new Map});function OI(t,{since:e}){let{track:n,events:r,brief:o}=t,s=v=>qa(v)>=e,a=Xn([eo(n?.name),eo(n?.workspace?.branch),o].filter(Boolean).join(" ")),i=new Map;for(let v of r)if(v.type==="subtask.delegated"&&v.boxId&&v.payload?.task){let A=Xn(v.payload.task);A&&i.set(v.boxId,A)}let c=new Map,l=v=>{let A=v||"unattributed";return c.has(A)||c.set(A,{segments:[u(null)],lastTs:null,provider:null}),c.get(A)},u=v=>({textCat:v,tools:{},usd:0,tokens:{input:0,output:0,cacheRead:0,cacheWrite:0},models:{},provider:null,activeMs:0,turns:0,toolCalls:0}),f=[...r].sort((v,A)=>qa(v)-qa(A)),d=0,m=0,h=0,w={turns:0};for(let v of f){let A=l(v.boxId),M=A.segments[A.segments.length-1],z=qa(v);switch(A.lastTs!=null&&z>A.lastTs&&s(v)&&(M.activeMs+=Math.min(z-A.lastTs,RI)),A.lastTs=z,v.provider&&(A.provider=v.provider),v.type){case"turn.user":{let $=v.payload?.text;if(v.source==="hook"||!String($||"").trim())break;let F=u(Xn($));A.segments.push(F),s(v)&&(F.turns+=1);break}case"tool.called":{if(v.source==="transcript")break;let $=v.payload?.tool;$&&(M.tools[$]=(M.tools[$]||0)+1),s(v)&&(M.toolCalls+=1);break}case"cost.recorded":{if(!s(v))break;let $=v.payload||{},F={input:$.tokens?.input||0,output:$.tokens?.output||0,cacheRead:$.tokens?.cacheRead||0,cacheWrite:$.tokens?.cacheWrite??$.tokens?.cacheCreation??0},T=Number($.usd)||0;!T&&$.tokens&&(T=oe($.model,F)),!T&&$.tokens&&!Ns($.model)&&(w.turns+=1),M.usd+=T,M.tokens.input+=F.input,M.tokens.output+=F.output,M.tokens.cacheRead+=F.cacheRead,M.tokens.cacheWrite+=F.cacheWrite;let C=$.model||"unknown";M.models[C]=(M.models[C]||0)+T,v.provider&&(M.provider=v.provider);break}case"handoff.done":s(v)&&(d+=1);break;case"subtask.merged":s(v)&&(m+=1);break;case"error":s(v)&&v.payload?.kind==="subtask-failed"&&(h+=1);break;default:break}}let g=new Map,b=!1;for(let[v,A]of c)for(let M of A.segments){if(!(M.usd>0||M.activeMs>0||M.turns>0||M.toolCalls>0))continue;let $=M.textCat?.category??i.get(v)?.category??nf(M.tools)?.category??a?.category??"uncategorized";g.has($)||g.set($,{usd:0,activeMs:0,turns:0,toolCalls:0,models:{},providers:{}});let F=g.get($);F.usd+=M.usd,F.activeMs+=M.activeMs,F.turns+=M.turns,F.toolCalls+=M.toolCalls;for(let[C,W]of Object.entries(M.models))F.models[C]=(F.models[C]||0)+W;let T=M.provider||A.provider;T&&(F.providers[T]=(F.providers[T]||0)+M.usd),T==="cursor"&&M.usd===0&&(b=!0)}return{perCategory:g,handoffs:d,merged:m,failed:h,sawCursorActivity:b,unpricedTurns:w.turns}}function sy(t,{windowMs:e=null,now:n=Date.now()}={}){let r=e==null?0:n-e,o=new Map,s=[],a=0,i=!1,c=0;for(let w of t||[]){let{track:g,events:b}=w;if(!b?.length){a+=1;continue}let{perCategory:v,handoffs:A,merged:M,failed:z,sawCursorActivity:$,unpricedTurns:F}=OI(w,{since:r});$&&(i=!0),c+=F;let T={trackId:g?.id,name:g?.name||g?.id,archived:!!g?.archivedAt,usd:0,activeMs:0,turns:0,categories:{},models:{},providers:{},handoffs:A,subtasksMerged:M,subtasksFailed:z};for(let[C,W]of v){T.usd+=W.usd,T.activeMs+=W.activeMs,T.turns+=W.turns,T.categories[C]={usd:sn(W.usd),activeMs:W.activeMs};for(let[p,S]of Object.entries(W.models))T.models[p]=sn((T.models[p]||0)+S);for(let[p,S]of Object.entries(W.providers))T.providers[p]=sn((T.providers[p]||0)+S);o.has(C)||o.set(C,$I());let ot=o.get(C);ot.usd+=W.usd,ot.activeMs+=W.activeMs,ot.turns+=W.turns,ot.toolCalls+=W.toolCalls;for(let[p,S]of Object.entries(W.models))ot.models[p]=(ot.models[p]||0)+S;for(let[p,S]of Object.entries(W.providers))ot.providers[p]=(ot.providers[p]||0)+S;let y=ot.byTrack.get(g?.id)||{usd:0,activeMs:0};y.usd+=W.usd,y.activeMs+=W.activeMs,ot.byTrack.set(g?.id,y)}(T.usd>0||T.activeMs>0||T.turns>0)&&(T.usd=sn(T.usd),T.dominantCategory=MI(T.categories),s.push(T))}let l=[...o.values()].reduce((w,g)=>w+g.usd,0),u=[...o.values()].reduce((w,g)=>w+g.activeMs,0),f=l>0?"usd":"time",d=f==="usd"?l:u,m=new Map((t||[]).map(w=>[w.track?.id,w.track?.name||w.track?.id])),h=[...o.entries()].map(([w,g])=>({category:w,sharePct:d>0?Math.round((f==="usd"?g.usd:g.activeMs)/d*100):0,shareBasis:f,usd:sn(g.usd),activeMs:g.activeMs,turns:g.turns,toolCalls:g.toolCalls,models:oy(g.models),providers:oy(g.providers),topWorkstreams:[...g.byTrack.entries()].sort((b,v)=>v[1].usd-b[1].usd||v[1].activeMs-b[1].activeMs).slice(0,2).map(([b,v])=>({trackId:b,name:m.get(b),usd:sn(v.usd),activeMs:v.activeMs}))})).sort((w,g)=>g.usd-w.usd||g.activeMs-w.activeMs);return s.sort((w,g)=>g.usd-w.usd||g.activeMs-w.activeMs),{since:r,capturedAt:n,totalUsd:sn(l),activeMs:u,turns:[...o.values()].reduce((w,g)=>w+g.turns,0),bullets:h,workstreams:s,gaps:{tracksWithoutEvents:a,cursorCostUnknown:i,unpricedTurns:c}}}var MI=t=>{let e=null;for(let[n,r]of Object.entries(t))(!e||r.usd>e.v.usd||r.usd===e.v.usd&&r.activeMs>e.v.activeMs)&&(e={cat:n,v:r});return e?.cat??null},oy=t=>Object.fromEntries(Object.entries(t).map(([e,n])=>[e,sn(n)]));var Pr={"24h":24*36e5,"7d":168*36e5,"30d":720*36e5,all:null};function PI(t){let e=String(t||"7d");return e in Pr?{key:e,ms:Pr[e]}:{key:"7d",ms:Pr["7d"]}}function BI(t,e){let n=new Set((e||[]).filter(o=>o.source==="orchestrator").map(o=>`${o.type}|${o.payload?.ts??"?"}`)),r=[...e||[]];for(let o of t?.events||[]){let s=ha(o);!s||n.has(`${s.type}|${s.payload?.ts??"?"}`)||r.push({trackId:t.id,boxId:s.boxId??null,ts:o.at,type:s.type,actor:s.actor,provider:s.provider??null,source:s.source,payload:s.payload})}return r}async function DI(t){if(!t?.cwd)return null;try{let n=(await CI.readFile(NI.join(t.cwd,".bostrat","handoff","latest.md"),"utf8")).match(/^##\s*Objective\s*\n([\s\S]*?)(?:\n##\s|\n*$)/m);return n?n[1].trim().slice(0,1e3):null}catch{return null}}async function ay(t,{window:e="7d",from:n,to:r,now:o=Date.now()}={}){let s=Number(n),a=Number(r),i=Number.isFinite(s)&&s>0,c=i&&Number.isFinite(a)&&a>s?Math.min(a,o):o,{key:l,ms:u}=i?{key:"custom",ms:c-s}:PI(e),f=[];for(let d of t||[]){let m=await L(d),h=BI(d,m);i&&c<o&&(h=h.filter(w=>(w.ts??0)<=c)),f.push({track:d,events:h,brief:await DI(d)})}return{window:l,...sy(f,{windowMs:u,now:c})}}var an=36e5,uy=24*an,jI=400,LI=12,Br=t=>Math.round(t*1e4)/1e4;function FI({window:t,from:e,to:n,now:r=Date.now()}={}){let o=Number(e),s=Number(n);if(Number.isFinite(o)&&o>0){let c=Number.isFinite(s)&&s>o?Math.min(s,r):r;return{key:"custom",since:o,until:c}}let a=String(t||"7d")in Pr?String(t||"7d"):"7d",i=Pr[a];return{key:a,since:i==null?null:r-i,until:r}}var UI=(t,e)=>t!=null&&e-t<=48*an?an:uy;function iy(t,e){if(e===an)return Math.floor(t/an)*an;let n=new Date(t);return n.setHours(0,0,0,0),n.getTime()}function HI(t,e){if(e===an)return t+an;let n=new Date(t);return n.setDate(n.getDate()+1),n.setHours(0,0,0,0),n.getTime()}var cy=()=>({input:0,output:0,cacheRead:0,cacheCreation:0}),ly=(t,e)=>{for(let n of Object.keys(t))t[n]+=e?.[n]||0},WI=t=>Object.fromEntries(Object.entries(t).map(([e,n])=>[e,Br(n)]));async function dy(t,e={}){let n=e.now??Date.now(),r=FI({...e,now:n}),o=r.since==null?uy:UI(r.since,r.until),s=T=>(r.since==null||T>=r.since)&&T<r.until,a=r.since!=null?r.until-r.since:null,i=T=>a!=null&&T>=r.since-a&&T<r.since,c={},l={},u=cy(),f=0,d=0,m=0,h=0,w=0,g=new Set,b=new Map,v=[],A=[],M=[],z=null;for(let T of t||[]){let W=(await L(T)).filter(p=>p.type==="cost.recorded");if(!W.length){T.archivedAt||M.push(T.name||T.id);continue}let ot=[];for(let p of W){let S=p.ts??0;s(S)?ot.push(p):i(S)&&(w+=Bs(p).usd)}if(!ot.length)continue;let y=se(ot,{boxLabels:Eu(T)});f+=y.totalUsd,d+=y.turns,m+=y.repricedTurns||0,h+=y.unpricedTurns||0;for(let p of y.unpricedModels||[])g.add(p);ly(u,y.totalTokens);for(let[p,S]of Object.entries(y.byModel))c[p]||={usd:0,events:0,tokens:cy()},c[p].usd=Br(c[p].usd+S.usd),c[p].events+=S.events,ly(c[p].tokens,S.tokens);for(let[p,S]of Object.entries(y.byProvider))l[p]||={usd:0,events:0},l[p].usd=Br(l[p].usd+S.usd),l[p].events+=S.events;for(let p of ot){let{usd:S}=Bs(p);if(!S)continue;let _=iy(p.ts??0,o),rt=b.get(_)??{t:_,total:0,byProvider:{}};rt.total+=S;let I=p.provider||"unknown";rt.byProvider[I]=(rt.byProvider[I]||0)+S,b.set(_,rt),(z==null||_<z)&&(z=_)}v.push({id:T.id,name:T.name||T.id,archived:!!T.archivedAt,usd:y.totalUsd,turns:y.turns,providers:Object.keys(y.byProvider)});for(let[p,S]of Object.entries(y.byBox))A.push({trackId:T.id,trackName:T.name||T.id,boxId:p,...S})}let $=r.since!=null?iy(r.since,o):z,F=[];for(let T=$,C=0;T!=null&&T<r.until&&C<jI;T=HI(T,o),C++){let W=b.get(T);F.push(W?{t:T,total:Br(W.total),byProvider:WI(W.byProvider)}:{t:T,total:0,byProvider:{}})}return v.sort((T,C)=>C.usd-T.usd),A.sort((T,C)=>C.usd-T.usd),{generatedAt:new Date().toISOString(),pricingBasis:"api-equivalent (subscription usage priced at public API rates)",window:r.key,since:r.since,until:r.until,bucketMs:o,series:F,fleet:{totalUsd:Br(f),turns:d,repricedTurns:m,unpricedTurns:h,unpricedModels:[...g],totalTokens:u,byModel:c,byProvider:l,tracksWithCost:v.length,tracksSilentLive:M},prior:a!=null?{totalUsd:Br(w),since:r.since-a,until:r.since}:null,tracks:v.slice(0,50),boxes:A.slice(0,LI)}}import jr from"node:fs/promises";import qI from"node:fs";import pt from"node:path";import KI from"node:os";import{execFile as GI}from"node:child_process";import{promisify as zI}from"node:util";var VI=zI(GI),fy=process.env.BOSTRAT_STATE_DIR||pt.join(process.env.HOME||KI.tmpdir(),".bostrat"),Iu=!1;async function Go(t,e){let n=await jr.stat(t).catch(()=>null);if(!n?.isFile())return!1;let r=await jr.stat(e).catch(()=>null);return r&&r.size===n.size&&r.mtimeMs>=n.mtimeMs?!1:(await jr.mkdir(pt.dirname(e),{recursive:!0}),await jr.copyFile(t,e),!0)}var JI=new Set([".claude",".codex",".credentials.json","auth.json",".claude.json","integrations.json"]);async function Dr(t,e){let n=0,r=await jr.readdir(t,{withFileTypes:!0}).catch(()=>[]);for(let o of r){if(JI.has(o.name))continue;let s=pt.join(t,o.name);o.isDirectory()?n+=await Dr(s,pt.join(e,o.name)):o.isFile()&&(n+=await Go(s,pt.join(e,o.name))?1:0)}return n}async function py({stateDir:t=fy,dest:e=process.env.BOSTRAT_SYNC_DIR||`${fy}-sync`,remote:n=process.env.BOSTRAT_STATE_REMOTE||null,tracks:r=null}={}){try{await jr.mkdir(e,{recursive:!0});let o=0;o+=await Go(pt.join(t,"tracks.json"),pt.join(e,"tracks.json"))?1:0,o+=await Go(pt.join(t,"mocks.json"),pt.join(e,"mocks.json"))?1:0,o+=await Go(pt.join(t,"runbooks.json"),pt.join(e,"runbooks.json"))?1:0,o+=await Dr(pt.join(t,"runbooks"),pt.join(e,"runbooks")),o+=await Dr(pt.join(t,"mock-archive"),pt.join(e,"mock-archive")),o+=await Go(pt.join(t,"events.jsonl"),pt.join(e,"events.jsonl"))?1:0,o+=await Dr(pt.join(t,"events"),pt.join(e,"events")),o+=await Dr(pt.join(t,"memory"),pt.join(e,"memory"));for(let l of r||[])!l?.cwd||!l?.id||(o+=await Dr(pt.join(l.cwd,".bostrat"),pt.join(e,"workspaces",l.id,".bostrat")));let s=(...l)=>VI("git",["-C",e,...l]);qI.existsSync(pt.join(e,".git"))||(await s("init","-q"),await s("config","user.email","agent@bostrat.local"),await s("config","user.name","bostrat state sync")),n&&/^https:\/\/github\.com\//i.test(n)&&(await s("config","--get","credential.useHttpPath").then(()=>!0).catch(()=>!1)||await pi(e).catch(()=>{}));let a=!1,{stdout:i}=await s("status","--porcelain");i.trim()&&(await s("add","-A"),await s("commit","-q","-m",`state sync ${new Date().toISOString()}`),a=!0);let c=!1;if(n&&(a||Iu))try{await s("push","-q",n,"HEAD:refs/heads/main"),c=!0,Iu=!1}catch{Iu=!0}return{staged:o,committed:a,pushed:c}}catch(o){return{staged:0,committed:!1,pushed:!1,error:o?.message||String(o)}}}import{execFile as XI}from"node:child_process";import{promisify as YI}from"node:util";import{randomUUID as ZI}from"node:crypto";import QI from"node:fs";import tR from"node:os";import eR from"node:path";var ln=YI(XI),Ka=t=>new Promise(e=>setTimeout(e,t)),un=Ai,Ru=process.env.BOSTRAT_USAGE_PROBE_CWD||eR.join(tR.homedir(),".bostrat","usage-probe"),nR=Number(process.env.BOSTRAT_USAGE_IDLE_REAP_MS)||5*6e4,rR=Number(process.env.BOSTRAT_USAGE_PANEL_TIMEOUT_MS)||15e3,oR=Number(process.env.BOSTRAT_USAGE_READY_TIMEOUT_MS)||45e3,my=Number(process.env.BOSTRAT_USAGE_CACHE_MS)||2e4,cn=null,zo=null,Vo=null;async function sR(){try{return await ln(N,B("has-session","-t",un)),!0}catch{return!1}}function aR(){cn&&clearTimeout(cn),cn=setTimeout(()=>iR().catch(()=>{}),nR),cn.unref&&cn.unref()}async function iR(){cn&&(clearTimeout(cn),cn=null);try{await ln(N,B("kill-session","-t",un))}catch{}}async function cR(){let t=Date.now()+oR;for(;;){let e=await $u().catch(()=>"");if(/for agents|for shortcuts|bypass permissions on/i.test(e)){await Ka(500);return}if(Date.now()>t)throw new Error("usage probe didn't reach the prompt (trust/onboarding may be blocking)");await Ka(500)}}async function lR(){if(await sR())return;if(!await De())throw new Error("host tmux server not running \u2014 start globo-tmux.service");QI.mkdirSync(Ru,{recursive:!0}),await Ne(Ru);let t=ZI();await ln(N,B("new-session","-d","-s",un,"-c",Ru,"-x","200","-y","60"));let e=`${Ti} --session-id ${t}`;await ln(N,B("send-keys","-t",un,e,"Enter")),await cR()}async function $u(){let{stdout:t}=await ln(N,B("capture-pane","-t",un,"-p"));return t}async function uR(){await lR(),await ln(N,B("send-keys","-t",un,"-l","/usage")),await ln(N,B("send-keys","-t",un,"Enter"));let t=Date.now()+rR,e="";for(;await Ka(400),e=await $u(),!(/Current\s+(session|week)/i.test(e)||/\d+%\s*used/i.test(e)||Date.now()>t););return await Ka(900),e=await $u(),await ln(N,B("send-keys","-t",un,"Escape")).catch(()=>{}),aR(),e}function dR(t){let e=String(t||"").split(`
|
|
368
|
+
`).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&&zo&&Date.now()-zo.at<my?zo.data:Vo||(Vo=(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 zo={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 zo={at:Date.now()-(my-3e3),data:n},n}finally{Vo=null}})(),Vo)}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 Ou(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 Ga({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 Ou(i);if(c.subscription){a=c;break}(!a||a.error&&!c.error)&&(a=c)}r.push(a||await Ou(null))}return n.has("cursor")&&r.push(await Ga({agent:"cursor"})),{providers:r,capturedAt:Date.now()}}async function Ga(t,{fresh:e=!1}={}){let n=t?.agent||"claude";return n==="codex"?Ou(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||""}`}),za=(t,e)=>Object.assign(new Error(t),{status:e});async function yy(t,e){if(!wR(e))throw za("node is not plane-linked \u2014 Gmail unavailable",503);let n;try{n=await fetch(`${be(e)}${t}`,{headers:gR(e)})}catch(o){throw za(`plane unreachable \u2014 Gmail unavailable (${o?.message||o})`,503)}let r=await n.json().catch(()=>({}));if(!n.ok)throw za(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 za("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 Va 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=Va.readFileSync(o,"utf8").trim();if(a)return a}catch{}let s=vy.randomBytes(32).toString("base64url");try{Va.mkdirSync(e,{recursive:!0}),Va.writeFileSync(o,s+`
|
|
371
|
+
`,{mode:384}),Va.chmodSync(o,384)}catch(a){n.error?.(`[bostrat-agent] could not persist the node API token: ${a?.message||a}`)}return s}function Jo(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||Jo(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:Jo(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:sc(e)}:null}var _R=t=>`<!doctype html><meta charset="utf-8">
|
|
331
372
|
<meta http-equiv="refresh" content="3">
|
|
332
373
|
<title>${t.name} \u2014 starting</title>
|
|
333
374
|
<body style="font-family:system-ui;background:#0b0e14;color:#dce3f0;display:grid;place-items:center;height:100vh;margin:0">
|
|
334
375
|
<div style="text-align:center"><p style="font-size:2rem;margin:0">\u23F3</p>
|
|
335
|
-
<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
|
|
336
377
|
\r
|
|
337
378
|
bostrat: no live view "${s}"\r
|
|
338
|
-
`),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
|
|
339
380
|
`)+`\r
|
|
340
381
|
\r
|
|
341
|
-
`),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
|
|
342
|
-
`)){let s=o.trim();if(!s)continue;if(s.startsWith("#")){n==null&&(n=s.replace(/^#+\s*/,"").trim().slice(0,_I)||null);continue}let a=/^[-*]\s+(.*)$/.exec(s);if(!a)continue;let i=a[1].trim(),c=null,l=new RegExp(`^\\[([^\\]]{1,${EI}})\\]\\s+(.*)$`).exec(i);if(l&&(c=l[1].trim(),i=l[2].trim()),!!i){if(i.length>ey)throw new Error(`bullet ${r.length+1} is ${i.length} chars \u2014 over the ${ey}-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>ty)throw new Error(`${r.length} bullets \u2014 over the ${ty}-bullet cap; group related changes into one line`);return{title:n||null,bullets:r}}async function ry(t,{repos:e=mu()}={}){let n=new Set(e);return(await ae()).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 oy(t,{md:e,day:n=null}){let r=$I(e),o=n==null||n===""?RI():String(n);if(!II.test(o)||Number.isNaN(Date.parse(o)))throw new Error(`invalid day "${n}" \u2014 expected YYYY-MM-DD`);let s=await C({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 OI}from"node:child_process";import{promisify as CI}from"node:util";var wu=CI(OI),sy=3e4,MI=30*6e4,NI=sy,PI=10*6e4,BI=10080*6e4,DI=60*6e4,jI=8,LI=1440*6e4,FI=3,ay="number,url,title,headRefName,headRefOid,state,mergedAt,closedAt",hu=60*6e4,UI=15*6e4,Da=new Map,ja=new Map,HI=/could not resolve|not found|no pull requests?/i,WI=t=>HI.test(String(t?.message||t||""));function qI(t,e){return[t.ciFix?.delegatedAt,t.reconcile?.delegatedAt,t.ciFixAwait?.since,t.reconcileAwait?.since].some(n=>n&&e-n<hu)}var iy={fast:NI,slow:PI,decay:DI};function cy(t,e){return t.armedAt&&e-t.armedAt<MI||qI(t,e)?"fast":e-(t.firstSeen||0)>BI?"decay":"slow"}function GI(t,e){return iy[cy(t,e)]}function qo(t,e,n,r){let o=t.lanes?.get(lt(e.repo,e.number))||{};return ma({change:n,repo:e.repo,number:Number(e.number),githubAt:r||null,lane:o.lane||null,intervalMs:o.intervalMs||null,source:"watch"})}async function KI(t,e){Se("watch");try{let{stdout:n}=await wu("gh",["pr","view",String(e),"--repo",t,"--json",ay],{timeout:3e4,maxBuffer:4194304,env:{...process.env,...await ie({slug:t})}});return JSON.parse(n||"null")}catch(n){throw We(n)&&qe(n),n}}async function VI(t){Se("watch");try{let{stdout:e}=await wu("gh",["pr","list","--repo",t,"--state","all","--limit","100","--json",ay],{timeout:3e4,maxBuffer:8388608,env:{...process.env,...await ie({slug:t})}}),n=new Map;for(let r of JSON.parse(e||"[]"))n.set(Number(r.number),r);return n}catch(e){throw We(e)&&qe(e),e}}async function zI(){let t=new Map;for(let e of await at().catch(()=>[]))t.set(e.id,e);return t}async function JI(t,e){await C({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 XI(t,e){let n=await _n(t);return n?hr(n,e):null}async function YI(t,e,n){let r=await _n(t);if(!r)throw new Error(`no local checkout for ${t}`);return wr(r,e,{method:n})}async function ZI(t,e){let n=await _n(t);if(!n)throw new Error(`no local checkout for ${t}`);return So(n,e)}async function QI(t,e){await C({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 Or(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 tR=10*6e4;async function eR(t,e,n){let r=t.autoMerge?.armedAt||0;if(r&&(n.now??Date.now())-r>LI){await Or(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 Or(t,o,e,n,`check${o.checks.failed===1?"":"s"} failed: ${a}`);return}if(o.mergeable==="CONFLICTING"){await Or(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<tR)return;await Or(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 Or(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 Or(t,o,e,n,String(a?.message||a).trim()||"merge failed");return}await ly(t,{...o,state:"MERGED"},e,n,{change:"auto-merged",githubAt:o.checks.settledAt||null})}}async function Uo(t,e,n){await C({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 nR(t,e){try{return await wu("git",["-C",t.cwd,"cat-file","-e",`${e}^{commit}`],{timeout:1e4}),!0}catch{return!1}}async function Ho(t,e,n,r=()=>!0){return(await j(t).catch(()=>[])).some(s=>s.type==="pr.state.changed"&&s.payload?.change===e&&s.payload?.number===Number(n)&&r(s))}var Wo=(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 rR(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 Hs(t.repo,t.number,{headSha:s}).catch(()=>{}),t.reconcile?.headSha&&s!==t.reconcile.headSha&&await qs(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<UI;await Ap(t.repo,t.number,s).catch(()=>{}),t.baseFoldAt&&await vp(t.repo,t.number).catch(()=>{}),!i&&!c&&!l&&a&&!await r.localHasCommit(a,s).catch(()=>!1)&&!await Ho(a,"pushed",t.number,f=>f.payload?.sha===s)&&await Uo(a,"pushed",{...Wo(t,e),sha:s}).catch(()=>{})}if(t.ciFixAwait)if(o-(t.ciFixAwait.since||0)>hu)await Ws(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 Ws(t.repo,t.number).catch(()=>{});else if(i.checks.total>0&&i.checks.pending===0){let c=qo(r,t,"ci-fixed",i.checks.settledAt||null),l=t.ciFixAwait.since||0;a&&!await Ho(a,"ci-fixed",t.number,u=>(u.payload?.ts||0)>=l)&&await Uo(a,"ci-fixed",{...Wo(t,e),passed:i.checks.passed,githubAt:c.githubAt,latencyMs:c.latencyMs,lane:c.lane}).catch(()=>{}),await Ws(t.repo,t.number).catch(()=>{})}}}if(t.reconcileAwait)if(o-(t.reconcileAwait.since||0)>hu)await Gs(t.repo,t.number).catch(()=>{});else{let i=await r.detail(t.repo,t.number).catch(()=>null);if(i){if(i.mergeable==="CONFLICTING")await Gs(t.repo,t.number).catch(()=>{});else if(i.mergeable==="MERGEABLE"){qo(r,t,"reconciled",null);let c=t.reconcileAwait.since||0;a&&!await Ho(a,"reconciled",t.number,l=>(l.payload?.ts||0)>=c)&&await Uo(a,"reconciled",{...Wo(t,e),base:i.baseRefName||null}).catch(()=>{}),await Gs(t.repo,t.number).catch(()=>{})}}}}async function oR(t,e,n,r){let o=qo(r,t,"closed",Date.parse(e?.closedAt||"")||null),s=n.get(t.trackId);s&&!await Ho(s,"closed",t.number)&&await Uo(s,"closed",{...Wo(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 sR(t,e,n){qo(n,t,"lost",null);let r=e.get(t.trackId);r&&!await Ho(r,"lost",t.number)&&await Uo(r,"lost",Wo(t,null)).catch(()=>{}),await n.notifyIfResolved(t.repo,t.number,"lost").catch(()=>{}),await n.mark(t.repo,t.number,"lost").catch(()=>{})}async function ly(t,e,n,r,o={}){let s=qo(r,t,o.change||"merged",o.githubAt??(Date.parse(e.mergedAt||"")||null)),a=n.get(t.trackId);a&&((await j(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 aR(t={}){let{now:e=Date.now(),auth:n=bl,view:r=KI,list:o=VI,links:s=_p,trackMap:a=zI,emitMerged:i=JI,mark:c=je,archiveIfArmed:l=Ke,notifyIfArmed:u=Ve,notifyIfResolved:f=ql,batchThreshold:d=FI,detail:m=XI,merge:h=YI,updateBranch:w=ZI,lookup:g=Tt,disarm:k=oo,emitDisarmed:v=QI,localHasCommit:_=nR}=t;if(!(await n().catch(()=>null))?.authenticated||ia()>0)return 0;let $=(await s().catch(()=>[])).filter(p=>e-(Da.get(lt(p.repo,p.number))||0)>=GI(p,e));if(!$.length)return 0;let L=Date.now(),A=new Map,O=new Map;for(let p of $){let S=cy(p,e);A.set(lt(p.repo,p.number),{lane:S,intervalMs:iy[S]}),Da.set(lt(p.repo,p.number),e),O.has(p.repo)||O.set(p.repo,[]),O.get(p.repo).push(p)}let H=await a().catch(()=>new Map),nt={now:e,lanes:A,emitMerged:i,mark:c,archiveIfArmed:l,notifyIfArmed:u,notifyIfResolved:f,detail:m,merge:h,updateBranch:w,lookup:g,disarm:k,emitDisarmed:v,localHasCommit:_};for(let[p,S]of O){let T,et=new Set;if(S.length>d)T=await o(p).catch(()=>null);else{T=new Map;for(let I of S)try{let R=await r(p,I.number);R?.number!=null&&T.set(Number(R.number),R)}catch(R){WI(R)&&et.add(lt(I.repo,I.number))}}if(T)for(let I of S){let R=lt(I.repo,I.number),E=T.get(Number(I.number));if(!E){if(et.has(R)){let P=(ja.get(R)||0)+1;ja.set(R,P),P>=jI&&(await sR(I,H,nt),ja.delete(R),Da.delete(R))}continue}ja.delete(R),E.state==="MERGED"||E.state==="CLOSED"?(E.state==="MERGED"?await ly(I,E,H,nt):await oR(I,E,H,nt),Da.delete(R)):(await rR(I,E,H,nt).catch(()=>{}),I.autoMerge&&await eR(I,H,nt).catch(()=>{}))}}let y={};for(let{lane:p}of A.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()-L}ms`),$.length}function uy(){let t=!1;setInterval(async()=>{if(!t){t=!0;try{await aR()}catch{}finally{t=!1}}},sy)}import _e from"node:fs/promises";import Cr from"node:path";import gy from"node:os";import dy from"node:fs/promises";import La from"node:path";import{execFile as iR}from"node:child_process";import{promisify as cR}from"node:util";import{fileURLToPath as lR}from"node:url";var uR=cR(iR),py=La.dirname(lR(import.meta.url)),dR=La.resolve(py,"..","..","client","dist"),fR="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",pR=new Map([...fR].map((t,e)=>[t,e]));function mR(t){let e=[],n=0,r=0;for(let o of t){let s=pR.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 hR(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=mR(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 wR(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 fy=new Map;async function gR(t){let e;try{e=await dy.stat(t)}catch{return null}let n=fy.get(t);if(n&&n.mtimeMs===e.mtimeMs)return n;try{let r=JSON.parse(await dy.readFile(t,"utf8")),o={mtimeMs:e.mtimeMs,sources:r.sources||[],lines:hR(r.mappings||"")};return fy.set(t,o),o}catch{return null}}var gu=null;function yR(){return gu||(gu=process.env.BOSTRAT_RELEASE?Promise.resolve(process.env.BOSTRAT_RELEASE):uR("git",["rev-parse","--short","HEAD"],{cwd:La.resolve(py,"..","..")}).then(t=>t.stdout.trim()).catch(()=>null)),gu}var bR=/(?:https?:\/\/[^\s()]*)?\/assets\/([^\s()/]+\.js):(\d+):(\d+)/g;async function kR(t,{distDir:e=dR}={}){let n=[],r=0,o=!1;for(let s of t.matchAll(bR)){let[a,i,c,l]=s,u=await gR(La.join(e,"assets",`${i}.map`)),f=u?wR(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 my(t,e={}){try{if(!t?.error?.stack||!t.release||t.release==="unknown")return t;let n=e.release??await yR();if(!n||t.release!==n)return t;let{stack:r,symbolicated:o}=await kR(t.error.stack,e);o&&(t.error.stack=r,t.error.symbolicated=!0)}catch{}return t}var xR=()=>process.env.BOSTRAT_STATE_DIR||Cr.join(process.env.HOME||gy.tmpdir(),".bostrat"),nn=()=>Cr.join(xR(),"telemetry"),yy=()=>Cr.join(nn(),".relay-offsets.json"),SR=50,vR=14,AR=3e4,hy=500,Dt=(t,e)=>typeof t=="string"?t.slice(0,e):"";function TR(t,e){if(!t||typeof t!="object")return null;let n=Dt(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:Dt(t.anonId,64),sessionId:Dt(t.sessionId,64),userId:Dt(t.userId,64),release:Dt(t.release,64),appVersion:Dt(t.appVersion,32),route:Dt(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:Dt(t.error.kind,256),message:Dt(t.error.message,8192),stack:Dt(t.error.stack,32768),fingerprint:Dt(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:Dt(s?.n,64),r:Dt(s?.r,128)}))),o}var by=t=>new Date(t).toISOString().slice(0,10),wy=0;async function ky(t,e=Date.now()){let r=(Array.isArray(t?.events)?t.events.slice(0,SR):[]).map(s=>TR(s,e)).filter(Boolean);if(!r.length)return 0;for(let s of r)s.error?.stack&&await my(s);await _e.mkdir(nn(),{recursive:!0});let o=Cr.join(nn(),`${by(e)}.ndjson`);return await _e.appendFile(o,r.map(s=>JSON.stringify(s)+`
|
|
343
|
-
`).join(""),"utf8"),e-
|
|
344
|
-
`).filter(Boolean),f=[];for(let d of u)try{f.push(JSON.parse(d))}catch{}for(let d=0;d<f.length;d+=hy){let m=f.slice(d,d+hy).map(w=>ER(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 vy(r)}return{sent:o,disabled:!1}}var yu=null,bu=!1;function Ay(){yu||(yu=setInterval(async()=>{if(!bu){bu=!0;try{await IR()}catch(t){console.error("[telemetry] relay failed:",t?.message||t)}finally{bu=!1}}},AR),yu.unref?.())}import Ha from"node:fs/promises";import Ry from"node:path";import RR from"node:os";var $y=()=>process.env.BOSTRAT_STATE_DIR||Ry.join(process.env.HOME||RR.tmpdir(),".bostrat"),Oy=()=>Ry.join($y(),"dictation-glossary.json"),$R=100,OR=500,CR=["bostrat","globopi","codex","tmux","worktree","Deepgram","Linear","Firecracker","Tigris","WireGuard","misous","postyx","cedar","gerbil","Anthropic","Opus","Haiku","Sonnet","keyterm","monorepo","dogfood"],Wa=null,ku=null,xu=Promise.resolve();function Ty(){let t={},e=Date.now();for(let n of CR)t[n.toLowerCase()]={term:n,count:1,lastUsed:e,source:"seed"};return t}async function Cy(){return Wa||(ku??=Ha.readFile(Oy(),"utf8").then(t=>JSON.parse(t)?.terms??Ty()).catch(()=>Ty()).then(t=>Wa=t).finally(()=>ku=null),ku)}async function _y(){let t=JSON.stringify({terms:Wa},null,2),e=Oy(),n=`${e}.${process.pid}.tmp`;await Ha.mkdir($y(),{recursive:!0}),await Ha.writeFile(n,t),await Ha.rename(n,e)}function MR(){return xu=xu.then(_y,_y),xu.catch(()=>{})}async function Su(){return Object.values(await Cy()).sort((e,n)=>n.count-e.count||n.lastUsed-e.lastUsed)}var NR=t=>Math.max(1,Math.ceil(t.length/4));async function PR(){let t=[],e=0;for(let{term:n}of await Su()){let r=NR(n);if(!(e+r>OR)&&(t.push(n),e+=r,t.length>=$R))break}return t}async function BR(t,e){let n=await Cy(),r=t.toLowerCase(),o=n[r];n[r]={term:t,count:(o?.count??0)+1,lastUsed:Date.now(),source:o?.source??e}}var Ey=t=>(t||"").split(/\s+/).map(e=>e.replace(/^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu,"")).filter(Boolean),Ua=t=>t.toLowerCase();function Iy(t){return!new RegExp("\\p{L}","u").test(t)||t.length<3?!1:/^[a-z]+$/.test(t)?t.length>=4:!0}async function My(t,e){let n=Ey(t),r=Ey(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]=Ua(n[u])===Ua(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;)Ua(n[c])===Ua(r[l])?(c++,l++):a[c+1][l]>=a[c][l+1]?c++:(Iy(r[l])&&i.add(r[l]),l++);for(;l<s;l++)Iy(r[l])&&i.add(r[l]);if(!i.size)return[];for(let u of i)await BR(u,"learned");return await MR(),[...i]}var Go=null;function DR(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 Ny(){if(!Go)return null;let t=await Zr();if(!t||t.apiKey.slice(-6)!==Go.keyTail)return null;let{keyTail:e,...n}=Go;return n}async function Py(t,e){let n=await Zr();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 PR())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=DR(o.status,i);c.persistent&&(Go={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 Go=null,{transcript:(await o.json())?.results?.channels?.[0]?.alternatives?.[0]?.transcript??""}}var vu={"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 jR(t){if(!t)return null;let e=String(t).trim();if(vu[e])return e;let n=null;for(let r of Object.keys(vu))e.startsWith(r)&&(!n||r.length>n.length)&&(n=r);return n}function LR(t,e={}){let n=vu[jR(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 FR(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 Ko=null;async function UR(){if(!Ko)return null;let t=await Ns();if(!t||t.apiKey.slice(-6)!==Ko.keyTail)return null;let{keyTail:e,...n}=Ko;return n}async function HR(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=FR(e.status,o);s.persistent&&(Ko={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}Ko=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 WR={openai:HR};async function By(){let t=await Ns();return t?{configured:!0,provider:t.provider,model:t.model,voice:t.voice,problem:await UR()}:{configured:!1}}async function Dy(){let t=await Ns();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 WR[t.provider](t);return{provider:t.provider,model:t.model,voice:t.voice,...e}}async function jy(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=LR(e,s),i=await C(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 qy=ct.dirname(GR(import.meta.url)),Ly=Number(process.env.PORT)||3362,qa=hg(process.env),Gy=Number(process.env.BOSTRAT_AUTO_TRIGGER_PCT)||80,Mr=Hg(process.env.BOSTRAT_ALLOW_IPS),Ky=qg(),Vy=process.env.BOSTRAT_ALLOW_LAN_TOKENLESS!=="1",x=fe();x.disable("x-powered-by");x.use((t,e,n)=>{if(Fo(t)||Mr.allowed(Mr.clientIp(t)))return n();e.status(403).type("text/plain").send("bostrat: access restricted to the allowlist")});x.use(Yg);x.use(Vg({token:Ky,enforce:Vy}));x.use("/api/mocks",fe.json({limit:"1mb"}));x.use("/api/mocks",fe.raw({type:"image/png",limit:"24mb"}));x.use("/api/runbooks",fe.json({limit:"1mb"}));x.use("/api/telemetry",fe.json({limit:"256kb"}));x.use("/api/hook",fe.json({limit:"2mb"}));x.use("/api/dictation",fe.raw({type:["audio/*","video/mp4","application/octet-stream"],limit:"32mb"}));x.use("/api/tracks/:id/upload",fe.raw({type:()=>!0,limit:"25mb"}));x.use(fe.json({limit:"64kb"}));x.get("/api/health",async(t,e)=>{e.json({ok:!0,tmuxSocket:Nt,hostTmuxAlive:await Ie(),autoTriggerPct:Gy})});x.get("/api/sessions",async(t,e)=>{e.json({sessions:await on()})});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 sn(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(!ht(n))return e.status(400).json({error:"invalid session name"});if(!await J(n))return e.status(404).json({error:"session not found"});try{await Xt(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(!ht(n))return e.status(400).json({error:"invalid session name"});let r=await J(n);if(!r)return e.status(404).json({error:"session not found"});let o=await Fr(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(!ht(n))return e.status(400).json({error:"invalid session name"});try{let r=await Ld(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=gs(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 Fy=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>Fy){let s=new Set([...r].sort((a,i)=>(i.archivedAt||0)-(a.archivedAt||0)).slice(0,Fy).map(a=>a.id));o=n.filter(a=>!a.archivedAt||s.has(a.id))}e.json({tracks:o,archivedTotal:r.length,agents:Sa,agentAvailability:await jl(),tickets:await js()})});x.get("/api/cost",async(t,e)=>{try{let n=await at();t.query.track&&(n=n.filter(o=>o.id===t.query.track));let r=await Sg(n);if(r.cursorUsage=hl(),t.query.format==="text")return e.type("text/plain").send(vg(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 _g(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 Og(await at(),{window:t.query.window,from:t.query.from,to:t.query.to});n.cursorUsage=hl(),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 Dg(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 Op(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 Cp(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 Gw()})}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 Mu(t.get(Yo),{byToken:vw,legacy:()=>Sw(t.body?.session_id,t.body?.cwd)});if(n==="refused")return Zo("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&&Zo("mismatch",{track:r.track.id,box:r.box.name}),Zo(n,{track:r?.track?.id,hook:t.body?.hook_event_name});let o=await pg(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(!xy(Mr.clientIp(t)))return e.json({ok:!0,dropped:!0});let n=await ky(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 ke(),r=new Set((await Promise.all(n.map(a=>Z(a.path)))).filter(Boolean).map(a=>a.toLowerCase())),s=((await io()).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 Xs(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:Js(),calls:bh(),latency:Gh()})});x.post("/api/github/refresh",(t,e)=>{Wp(),e.json({ok:!0})});x.get("/api/gmail/search",async(t,e)=>{try{let n=await Lg(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 Fg(String(t.params.id))})}catch(n){e.status(n?.status||502).json({error:n?.message||String(n)})}});function Ru(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 Ga({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 Y(s.trackId);if(a)return a}return n?(await at()).find(s=>s.workspace?.branch===n&&Ru(s,t,r))??null:null}async function zy({repo:t=null,number:e=null,branch:n=null}){let r=await Ga({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&&Ru(a,t,o))??null}async function Jt(t,{number:e,url:n=null,title:r=null,branch:o=null,repo:s=null,track:a=null,extra:i={}}){let c=a||await Ga({repo:s,number:e,branch:o}).catch(()=>null);if(!c)return;let l=i.repo??(s?await Z(s).catch(()=>null):null);await C({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 JR(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&&Ru(a,n.path,r));s&&await Pt({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 Zh();await JR(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(!xo.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 k=await Z((await ve(n)).path);if(!k)throw new Error("repo has no GitHub remote");let v=await Ga({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 So(n,g),await Pt({repo:k,number:g,trackId:v.id,url:f,headRef:l,title:u,source:"matched"}).catch(()=>{}),!await ro(k,g,{merge:{method:w}}))throw new Error("couldn't record the auto-merge arm");return await tr(k,g,i===!0),await Ks(k,g,c===!0&&i!==!0),le(),await Jt("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 wr(n,r,{method:o||"squash"});await Jt("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 tr(m,d.number,!0).catch(()=>{}),c===!0&&i!==!0&&await Ks(m,d.number,!0).catch(()=>{}),await Ve(m,d.number).catch(()=>{}),h=await Ke(m,d.number).catch(()=>!1),await je(m,d.number,"merged").catch(()=>{})),i===!0&&!h){let w=await Ga({repo:n,number:d.number,branch:l}).catch(()=>null);w&&!w.archivedAt&&await Sr(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 ve(n)).path).catch(()=>null),l=c?await oo(c,i).catch(()=>null):null;try{await Sl(n,i)}catch(u){if(!l)throw u}le(),await Jt("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 nw(n,{method:r||"merge",onMerged:a=>{Jt("merged",{number:a.number,url:a.url,title:a.title,branch:a.headRefName,repo:n}),o&&Ve(o,a.number).catch(()=>{}).then(()=>Ke(o,a.number)).catch(()=>{}).then(()=>je(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:ew()})});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(!xo.has(i))throw new Error(`invalid merge method: ${i}`);if(!n)return e.status(400).json({error:"trackId required"});let c=await Y(n);if(!c)return e.status(404).json({error:`Track ${n} isn't on this node`});let l=(await ae()).filter(d=>d.trackId===c.id&&d.state!=="merged"&&d.state!=="closed");if(!l.length)return e.status(409).json({error:"no open PRs on this Track"});for(let d of l)await tr(d.repo,d.number,o===!0).catch(()=>{}),await Ks(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 _n(d.repo);if(!h)throw new Error(`no local checkout for ${d.repo}`);let w=await hr(h,d.number);f.push({r:m,l:d,repoPath:h,pr:w,plan:Qh(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 tw(f))try{if(g.action==="skip")d.action="skipped",d.reason=g.reason;else if(g.action==="merge")await wr(h,m.number,{method:i}),await Jt("merged",{number:m.number,url:w.url,title:w.title,branch:w.headRefName,repo:h,track:c}),await Ve(m.repo,m.number).catch(()=>{}),await Ke(m.repo,m.number).catch(()=>{}),await je(m.repo,m.number,"merged").catch(()=>{}),d.action="merged";else{if(g.action==="update-arm"&&(await So(h,m.number).catch(()=>{}),d.action="updated-armed"),!await ro(m.repo,m.number,{merge:{method:i}}))throw new Error("couldn't record the auto-merge arm");await Jt("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(k){d.ok=!1,d.action="failed",d.reason=k?.message||String(k)}if(le(),!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 Y(n);if(!r)return e.status(404).json({error:`Track ${n} isn't on this node`});let o=(await ae()).filter(a=>a.trackId===r.id&&a.state!=="merged"&&a.state!=="closed"),s=[];for(let a of o){let i=await oo(a.repo,a.number).catch(()=>null),c=await _n(a.repo).catch(()=>null);c&&await Sl(c,a.number).catch(()=>{}),i&&(await Jt("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}))}le(),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 Y(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 g=u.find(k=>k.name===w||k.repo===w||k.base===w||k.dir===w);if(!g)throw new Error(`invalid repo "${w}" \u2014 not tagged on this Track`);return g});else if(f=await go(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 Al(w.dir,{title:r,body:o,base:s}),k=w.repo||await Z(w.dir).catch(()=>null);k&&g.number&&await Pt({repo:k,number:g.number,trackId:i.id,url:g.url,headRef:i.workspace?.branch??null,title:r,source:"api"}),await Jt("opened",{number:g.number,url:g.url,title:r,track:i,branch:i.workspace?.branch,extra:{repo:k??w.name}}),m.push({repo:w.name,slug:k??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 Al(i.cwd,{title:r,body:o,base:s}),l=await Z(i.cwd).catch(()=>null);l&&c.number&&await Pt({repo:l,number:c.number,trackId:i.id,url:c.url,headRef:i.workspace?.branch??null,title:r,source:"api"}),await Jt("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 Y(t.params.id);if(!n)return e.status(404).json({error:"track not found"});let r=await go(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 Bw(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 Dw(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 hr(n,r),s=await Z(n).catch(()=>null),a=await zy({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(k=>k.base===n||s&&k.repo===s)??null,c=a.workspace?.kind==="multi",l=await rw(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(k=>`\u2550\u2550\u2550\u2550 ${k.name}${k.link?` \xB7 ${k.link}`:""} \u2550\u2550\u2550\u2550
|
|
345
|
-
${
|
|
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)&&sc(n[0])?n.slice(1).join("."):e}var Ny=10,Py=200,IR=120,RR=24,$R=/^\d{4}-\d{2}-\d{2}$/;function Mu(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=uo(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=Mu()}={}){let n=new Set(e);return(await pe()).filter(r=>r.state==="merged"&&(r.resolvedAt??0)>=t&&n.has(r.repo)).sort((r,o)=>(r.resolvedAt??0)-(o.resolvedAt??0)).map(r=>({repo:r.repo,number:r.number,title:r.title||null,url:r.url||null,mergedAt:r.resolvedAt??null}))}async function jy(t,{md:e,day:n=null}){let r=MR(e),o=n==null||n===""?OR():String(n);if(!$R.test(o)||Number.isNaN(Date.parse(o)))throw new Error(`invalid day "${n}" \u2014 expected YYYY-MM-DD`);let s=await O({id:t.id,cwd:t.cwd},{type:"changelog.published",actor:"agent",source:"orchestrator",boxId:t.leadBox||null,payload:{ts:Date.now(),day:o,title:r.title,bullets:r.bullets}});return{day:o,title:r.title,bullets:r.bullets,eventId:s.id}}import{execFile as CR}from"node:child_process";import{promisify as NR}from"node:util";var Nu=NR(CR),Ly=3e4,PR=30*6e4,BR=Ly,DR=10*6e4,jR=10080*6e4,LR=60*6e4,FR=8,UR=1440*6e4,HR=3,Fy="number,url,title,headRefName,headRefOid,state,mergedAt,closedAt",Cu=60*6e4,WR=15*6e4,Ja=new Map,Xa=new Map,qR=/could not resolve|not found|no pull requests?/i,KR=t=>qR.test(String(t?.message||t||""));function GR(t,e){return[t.ciFix?.delegatedAt,t.reconcile?.delegatedAt,t.ciFixAwait?.since,t.reconcileAwait?.since].some(n=>n&&e-n<Cu)}var Uy={fast:BR,slow:DR,decay:LR};function Hy(t,e){return t.armedAt&&e-t.armedAt<PR||GR(t,e)?"fast":e-(t.firstSeen||0)>jR?"decay":"slow"}function zR(t,e){return Uy[Hy(t,e)]}function Qo(t,e,n,r){let o=t.lanes?.get(dt(e.repo,e.number))||{};return _a({change:n,repo:e.repo,number:Number(e.number),githubAt:r||null,lane:o.lane||null,intervalMs:o.intervalMs||null,source:"watch"})}async function VR(t,e){Re("watch");try{let{stdout:n}=await Nu("gh",["pr","view",String(e),"--repo",t,"--json",Fy],{timeout:3e4,maxBuffer:4194304,env:{...process.env,...await ee({slug:t})}});return JSON.parse(n||"null")}catch(n){throw Ye(n)&&Ze(n),n}}async function JR(t){Re("watch");try{let{stdout:e}=await Nu("gh",["pr","list","--repo",t,"--state","all","--limit","100","--json",Fy],{timeout:3e4,maxBuffer:8388608,env:{...process.env,...await ee({slug:t})}}),n=new Map;for(let r of JSON.parse(e||"[]"))n.set(Number(r.number),r);return n}catch(e){throw Ye(e)&&Ze(e),e}}async function XR(){let t=new Map;for(let e of await ct().catch(()=>[]))t.set(e.id,e);return t}async function YR(t,e){await O({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"user",source:"orchestrator",boxId:null,payload:{ts:Date.now(),change:"merged",...e}})}async function ZR(t,e){let n=await Nn(t);return n?Sr(n,e):null}async function QR(t,e,n){let r=await Nn(t);if(!r)throw new Error(`no local checkout for ${t}`);return vr(r,e,{method:n})}async function t$(t,e){let n=await Nn(t);if(!n)throw new Error(`no local checkout for ${t}`);return Mo(n,e)}async function e$(t,e){await O({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"user",source:"orchestrator",boxId:null,payload:{ts:Date.now(),change:"auto-canceled",...e}})}async function Lr(t,e,n,r,o){if(!await r.disarm(t.repo,t.number).catch(()=>null))return;let a=n.get(t.trackId);a&&await r.emitDisarmed(a,{number:Number(t.number),url:e?.url||t.url,title:e?.title||t.title,branch:e?.headRefName||t.headRef,repo:t.repo,reason:o}).catch(()=>{})}var n$=10*6e4;async function r$(t,e,n){let r=t.autoMerge?.armedAt||0;if(r&&(n.now??Date.now())-r>UR){await Lr(t,null,e,n,"auto-merge expired \u2014 checks never settled within 24h");return}let o=await n.detail(t.repo,t.number).catch(()=>null);if(!o)return;if(o.checks.failed>0){let a=o.checks.failing.map(i=>i.name).join(", ");await Lr(t,o,e,n,`check${o.checks.failed===1?"":"s"} failed: ${a}`);return}if(o.mergeable==="CONFLICTING"){await Lr(t,o,e,n,`conflicts with ${o.baseRefName}`);return}if(!o.checks.total){let a=Math.max(t.autoMerge?.armedAt||0,Date.parse(o.updatedAt||"")||0);if((n.now??Date.now())-a<n$)return;await Lr(t,o,e,n,o.checks.unavailable?"this token can't read the checks":"no CI checks appeared");return}if(o.checks.pending>0||o.mergeable==="UNKNOWN"||o.mergeStateStatus==="UNKNOWN"||o.isDraft)return;let s=await n.lookup(t.repo,t.number).catch(()=>null);if(s?.autoMerge){if(o.mergeStateStatus==="BEHIND"){try{await n.updateBranch(t.repo,t.number)}catch(a){let i=String(a?.message||a).trim();/no new commits|already up.to.date/i.test(i)||await Lr(t,o,e,n,i||`couldn't update with ${o.baseRefName}`)}return}try{await n.merge(t.repo,t.number,s.autoMerge.method||"merge")}catch(a){if(/already merged|not open|is closed/i.test(String(a?.message||a)))return;await Lr(t,o,e,n,String(a?.message||a).trim()||"merge failed");return}await Wy(t,{...o,state:"MERGED"},e,n,{change:"auto-merged",githubAt:o.checks.settledAt||null})}}async function Xo(t,e,n){await O({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"user",source:"orchestrator",boxId:null,payload:{ts:Date.now(),change:e,...n}})}async function o$(t,e){try{return await Nu("git",["-C",t.cwd,"cat-file","-e",`${e}^{commit}`],{timeout:1e4}),!0}catch{return!1}}async function Yo(t,e,n,r=()=>!0){return(await L(t).catch(()=>[])).some(s=>s.type==="pr.state.changed"&&s.payload?.change===e&&s.payload?.number===Number(n)&&r(s))}var Zo=(t,e)=>({number:Number(t.number),url:e?.url||t.url,title:e?.title||t.title,branch:e?.headRefName||t.headRef,repo:t.repo});async function s$(t,e,n,r){let o=r.now??Date.now(),s=e.headRefOid||null,a=n.get(t.trackId)||null;if(s&&(t.ciFix?.headSha&&s!==t.ciFix.headSha&&await na(t.repo,t.number,{headSha:s}).catch(()=>{}),t.reconcile?.headSha&&s!==t.reconcile.headSha&&await oa(t.repo,t.number,{headSha:s}).catch(()=>{})),s&&s!==t.lastHeadSha){let i=!t.lastHeadSha,c=!!(t.ciFix||t.reconcile||t.ciFixAwait||t.reconcileAwait),l=!!t.baseFoldAt&&o-t.baseFoldAt<WR;await dm(t.repo,t.number,s).catch(()=>{}),t.baseFoldAt&&await um(t.repo,t.number).catch(()=>{}),!i&&!c&&!l&&a&&!await r.localHasCommit(a,s).catch(()=>!1)&&!await Yo(a,"pushed",t.number,f=>f.payload?.sha===s)&&await Xo(a,"pushed",{...Zo(t,e),sha:s}).catch(()=>{})}if(t.ciFixAwait)if(o-(t.ciFixAwait.since||0)>Cu)await ra(t.repo,t.number).catch(()=>{});else{let i=await r.detail(t.repo,t.number).catch(()=>null);if(i?.checks){if(i.checks.failed>0)await ra(t.repo,t.number).catch(()=>{});else if(i.checks.total>0&&i.checks.pending===0){let c=Qo(r,t,"ci-fixed",i.checks.settledAt||null),l=t.ciFixAwait.since||0;a&&!await Yo(a,"ci-fixed",t.number,u=>(u.payload?.ts||0)>=l)&&await Xo(a,"ci-fixed",{...Zo(t,e),passed:i.checks.passed,githubAt:c.githubAt,latencyMs:c.latencyMs,lane:c.lane}).catch(()=>{}),await ra(t.repo,t.number).catch(()=>{})}}}if(t.reconcileAwait)if(o-(t.reconcileAwait.since||0)>Cu)await sa(t.repo,t.number).catch(()=>{});else{let i=await r.detail(t.repo,t.number).catch(()=>null);if(i){if(i.mergeable==="CONFLICTING")await sa(t.repo,t.number).catch(()=>{});else if(i.mergeable==="MERGEABLE"){Qo(r,t,"reconciled",null);let c=t.reconcileAwait.since||0;a&&!await Yo(a,"reconciled",t.number,l=>(l.payload?.ts||0)>=c)&&await Xo(a,"reconciled",{...Zo(t,e),base:i.baseRefName||null}).catch(()=>{}),await sa(t.repo,t.number).catch(()=>{})}}}}async function a$(t,e,n,r){let o=Qo(r,t,"closed",Date.parse(e?.closedAt||"")||null),s=n.get(t.trackId);s&&!await Yo(s,"closed",t.number)&&await Xo(s,"closed",{...Zo(t,e),githubAt:o.githubAt,latencyMs:o.latencyMs,lane:o.lane}).catch(()=>{}),await r.notifyIfResolved(t.repo,t.number,"closed").catch(()=>{}),await r.mark(t.repo,t.number,"closed").catch(()=>{})}async function i$(t,e,n){Qo(n,t,"lost",null);let r=e.get(t.trackId);r&&!await Yo(r,"lost",t.number)&&await Xo(r,"lost",Zo(t,null)).catch(()=>{}),await n.notifyIfResolved(t.repo,t.number,"lost").catch(()=>{}),await n.mark(t.repo,t.number,"lost").catch(()=>{})}async function Wy(t,e,n,r,o={}){let s=Qo(r,t,o.change||"merged",o.githubAt??(Date.parse(e.mergedAt||"")||null)),a=n.get(t.trackId);a&&((await L(a).catch(()=>[])).some(l=>l.type==="pr.state.changed"&&l.payload?.change==="merged"&&l.payload?.number===Number(t.number))||await r.emitMerged(a,{number:Number(t.number),url:e.url||t.url,title:e.title||t.title,branch:e.headRefName||t.headRef,repo:t.repo,githubAt:s.githubAt,latencyMs:s.latencyMs,lane:s.lane}).catch(()=>{})),await r.notifyIfArmed(t.repo,t.number).catch(()=>{}),await r.archiveIfArmed(t.repo,t.number).catch(()=>{}),await r.mark(t.repo,t.number,"merged").catch(()=>{})}async function c$(t={}){let{now:e=Date.now(),auth:n=Dl,view:r=VR,list:o=JR,links:s=pm,trackMap:a=XR,emitMerged:i=YR,mark:c=ze,archiveIfArmed:l=tn,notifyIfArmed:u=en,notifyIfResolved:f=cu,batchThreshold:d=HR,detail:m=ZR,merge:h=QR,updateBranch:w=t$,lookup:g=Et,disarm:b=yo,emitDisarmed:v=e$,localHasCommit:A=o$}=t;if(!(await n().catch(()=>null))?.authenticated||ba()>0)return 0;let $=(await s().catch(()=>[])).filter(p=>e-(Ja.get(dt(p.repo,p.number))||0)>=zR(p,e));if(!$.length)return 0;let F=Date.now(),T=new Map,C=new Map;for(let p of $){let S=Hy(p,e);T.set(dt(p.repo,p.number),{lane:S,intervalMs:Uy[S]}),Ja.set(dt(p.repo,p.number),e),C.has(p.repo)||C.set(p.repo,[]),C.get(p.repo).push(p)}let W=await a().catch(()=>new Map),ot={now:e,lanes:T,emitMerged:i,mark:c,archiveIfArmed:l,notifyIfArmed:u,notifyIfResolved:f,detail:m,merge:h,updateBranch:w,lookup:g,disarm:b,emitDisarmed:v,localHasCommit:A};for(let[p,S]of C){let _,rt=new Set;if(S.length>d)_=await o(p).catch(()=>null);else{_=new Map;for(let I of S)try{let R=await r(p,I.number);R?.number!=null&&_.set(Number(R.number),R)}catch(R){KR(R)&&rt.add(dt(I.repo,I.number))}}if(_)for(let I of S){let R=dt(I.repo,I.number),E=_.get(Number(I.number));if(!E){if(rt.has(R)){let P=(Xa.get(R)||0)+1;Xa.set(R,P),P>=FR&&(await i$(I,W,ot),Xa.delete(R),Ja.delete(R))}continue}Xa.delete(R),E.state==="MERGED"||E.state==="CLOSED"?(E.state==="MERGED"?await Wy(I,E,W,ot):await a$(I,E,W,ot),Ja.delete(R)):(await s$(I,E,W,ot).catch(()=>{}),I.autoMerge&&await r$(I,W,ot).catch(()=>{}))}}let y={};for(let{lane:p}of T.values())y[p]=(y[p]||0)+1;return console.log(`[pr-watch] probed ${$.length} link(s) (${Object.entries(y).map(([p,S])=>`${p} ${S}`).join(", ")}) in ${Date.now()-F}ms`),$.length}function qy(){let t=!1;setInterval(async()=>{if(!t){t=!0;try{await c$()}catch{}finally{t=!1}}},Ly)}import Ce from"node:fs/promises";import Fr from"node:path";import Yy from"node:os";import Ky from"node:fs/promises";import Ya from"node:path";import{execFile as l$}from"node:child_process";import{promisify as u$}from"node:util";import{fileURLToPath as d$}from"node:url";var f$=u$(l$),zy=Ya.dirname(d$(import.meta.url)),p$=Ya.resolve(zy,"..","..","client","dist"),m$="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",h$=new Map([...m$].map((t,e)=>[t,e]));function w$(t){let e=[],n=0,r=0;for(let o of t){let s=h$.get(o);if(s===void 0)return e;n+=(s&31)<<r,s&32?r+=5:(e.push(n&1?-(n>>>1):n>>>1),n=0,r=0)}return e}function g$(t){let e=[],n=0,r=0,o=0;for(let s of t.split(";")){let a=[],i=0;if(s)for(let c of s.split(",")){let l=w$(c);l.length!==0&&(i+=l[0],l.length>=4&&(n+=l[1],r+=l[2],o+=l[3],a.push([i,n,r,o])))}e.push(a)}return e}function y$(t,e){if(!t||t.length===0||t[0][0]>e)return null;let n=0,r=t.length-1;for(;n<r;){let o=n+r+1>>1;t[o][0]<=e?n=o:r=o-1}return t[n]}var Gy=new Map;async function b$(t){let e;try{e=await Ky.stat(t)}catch{return null}let n=Gy.get(t);if(n&&n.mtimeMs===e.mtimeMs)return n;try{let r=JSON.parse(await Ky.readFile(t,"utf8")),o={mtimeMs:e.mtimeMs,sources:r.sources||[],lines:g$(r.mappings||"")};return Gy.set(t,o),o}catch{return null}}var Pu=null;function k$(){return Pu||(Pu=process.env.BOSTRAT_RELEASE?Promise.resolve(process.env.BOSTRAT_RELEASE):f$("git",["rev-parse","--short","HEAD"],{cwd:Ya.resolve(zy,"..","..")}).then(t=>t.stdout.trim()).catch(()=>null)),Pu}var x$=/(?:https?:\/\/[^\s()]*)?\/assets\/([^\s()/]+\.js):(\d+):(\d+)/g;async function S$(t,{distDir:e=p$}={}){let n=[],r=0,o=!1;for(let s of t.matchAll(x$)){let[a,i,c,l]=s,u=await b$(Ya.join(e,"assets",`${i}.map`)),f=u?y$(u.lines[Number(c)-1],Number(l)-1):null;if(n.push(t.slice(r,s.index)),f&&u.sources[f[1]]){let d=u.sources[f[1]].replace(/^(\.\.\/)+/,"");n.push(`${d}:${f[2]+1}:${f[3]+1}`),o=!0}else n.push(a);r=s.index+a.length}return n.push(t.slice(r)),{stack:n.join(""),symbolicated:o}}async function Vy(t,e={}){try{if(!t?.error?.stack||!t.release||t.release==="unknown")return t;let n=e.release??await k$();if(!n||t.release!==n)return t;let{stack:r,symbolicated:o}=await S$(t.error.stack,e);o&&(t.error.stack=r,t.error.symbolicated=!0)}catch{}return t}var v$=()=>process.env.BOSTRAT_STATE_DIR||Fr.join(process.env.HOME||Yy.tmpdir(),".bostrat"),dn=()=>Fr.join(v$(),"telemetry"),Zy=()=>Fr.join(dn(),".relay-offsets.json"),T$=50,A$=14,_$=3e4,Jy=500,Ut=(t,e)=>typeof t=="string"?t.slice(0,e):"";function E$(t,e){if(!t||typeof t!="object")return null;let n=Ut(t.name,64);if(!n)return null;let r=Number.isFinite(t.ts)?t.ts:e,o={name:n,ts:Math.abs(r-e)<168*3600*1e3?r:e,receivedAt:e,anonId:Ut(t.anonId,64),sessionId:Ut(t.sessionId,64),userId:Ut(t.userId,64),release:Ut(t.release,64),appVersion:Ut(t.appVersion,32),route:Ut(t.route,128),deviceClass:t.deviceClass==="phone"||t.deviceClass==="desktop"?t.deviceClass:""};return t.props&&typeof t.props=="object"&&!Array.isArray(t.props)&&(JSON.stringify(t.props).length<=8192?o.props=t.props:o.props={clamped:!0}),t.error&&typeof t.error=="object"&&(o.error={kind:Ut(t.error.kind,256),message:Ut(t.error.message,8192),stack:Ut(t.error.stack,32768),fingerprint:Ut(t.error.fingerprint,64)}),Array.isArray(t.breadcrumbs)&&(o.breadcrumbs=t.breadcrumbs.slice(-50).map(s=>({t:Number.isFinite(s?.t)?s.t:0,n:Ut(s?.n,64),r:Ut(s?.r,128)}))),o}var Qy=t=>new Date(t).toISOString().slice(0,10),Xy=0;async function tb(t,e=Date.now()){let r=(Array.isArray(t?.events)?t.events.slice(0,T$):[]).map(s=>E$(s,e)).filter(Boolean);if(!r.length)return 0;for(let s of r)s.error?.stack&&await Vy(s);await Ce.mkdir(dn(),{recursive:!0});let o=Fr.join(dn(),`${Qy(e)}.ndjson`);return await Ce.appendFile(o,r.map(s=>JSON.stringify(s)+`
|
|
384
|
+
`).join(""),"utf8"),e-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 Za=new Map;function eb(t,e=Date.now()){let n=t||"unknown",r=Za.get(n);return r||(Za.size>512&&Za.clear(),r={tokens:40,at:e},Za.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 Bu=null,Du=!1;function ob(){Bu||(Bu=setInterval(async()=>{if(!Du){Du=!0;try{await $$()}catch(t){console.error("[telemetry] relay failed:",t?.message||t)}finally{Du=!1}}},_$),Bu.unref?.())}import ti from"node:fs/promises";import lb from"node:path";import O$ from"node:os";var ub=()=>process.env.BOSTRAT_STATE_DIR||lb.join(process.env.HOME||O$.tmpdir(),".bostrat"),db=()=>lb.join(ub(),"dictation-glossary.json"),M$=100,C$=500,N$=["bostrat","globopi","codex","tmux","worktree","Deepgram","Linear","Firecracker","Tigris","WireGuard","misous","postyx","cedar","gerbil","Anthropic","Opus","Haiku","Sonnet","keyterm","monorepo","dogfood"],ei=null,ju=null,Lu=Promise.resolve();function sb(){let t={},e=Date.now();for(let n of N$)t[n.toLowerCase()]={term:n,count:1,lastUsed:e,source:"seed"};return t}async function fb(){return ei||(ju??=ti.readFile(db(),"utf8").then(t=>JSON.parse(t)?.terms??sb()).catch(()=>sb()).then(t=>ei=t).finally(()=>ju=null),ju)}async function ab(){let t=JSON.stringify({terms:ei},null,2),e=db(),n=`${e}.${process.pid}.tmp`;await ti.mkdir(ub(),{recursive:!0}),await ti.writeFile(n,t),await ti.rename(n,e)}function P$(){return Lu=Lu.then(ab,ab),Lu.catch(()=>{})}async function Fu(){return Object.values(await fb()).sort((e,n)=>n.count-e.count||n.lastUsed-e.lastUsed)}var B$=t=>Math.max(1,Math.ceil(t.length/4));async function D$(){let t=[],e=0;for(let{term:n}of await Fu()){let r=B$(n);if(!(e+r>C$)&&(t.push(n),e+=r,t.length>=M$))break}return t}async function j$(t,e){let n=await fb(),r=t.toLowerCase(),o=n[r];n[r]={term:t,count:(o?.count??0)+1,lastUsed:Date.now(),source:o?.source??e}}var ib=t=>(t||"").split(/\s+/).map(e=>e.replace(/^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu,"")).filter(Boolean),Qa=t=>t.toLowerCase();function cb(t){return!new RegExp("\\p{L}","u").test(t)||t.length<3?!1:/^[a-z]+$/.test(t)?t.length>=4:!0}async function pb(t,e){let n=ib(t),r=ib(e);if(!n.length||!r.length)return[];let o=n.length,s=r.length,a=Array.from({length:o+1},()=>new Array(s+1).fill(0));for(let u=o-1;u>=0;u--)for(let f=s-1;f>=0;f--)a[u][f]=Qa(n[u])===Qa(r[f])?a[u+1][f+1]+1:Math.max(a[u+1][f],a[u][f+1]);let i=new Set,c=0,l=0;for(;c<o&&l<s;)Qa(n[c])===Qa(r[l])?(c++,l++):a[c+1][l]>=a[c][l+1]?c++:(cb(r[l])&&i.add(r[l]),l++);for(;l<s;l++)cb(r[l])&&i.add(r[l]);if(!i.size)return[];for(let u of i)await j$(u,"learned");return await P$(),[...i]}var ts=null;function L$(t,e){let n=null;try{n=JSON.parse(e)}catch{}let r=n?.err_code||n?.error_code||n?.category||"";return t===402||/PAYMENT_REQUIRED/i.test(r)?{status:402,code:"credits_exhausted",persistent:!0,message:"Deepgram credits are exhausted \u2014 dictation is down until you top up at console.deepgram.com or drop a new key into ~/.bostrat/integrations.json."}:t===401||t===403?{status:502,code:"key_invalid",persistent:!0,message:"Deepgram rejected the API key (revoked, or missing ASR scope) \u2014 replace it in ~/.bostrat/integrations.json."}:{status:502,code:"transcription_failed",persistent:!1,message:`Deepgram error ${t}: ${e.slice(0,300)}`}}async function mb(){if(!ts)return null;let t=await fo();if(!t||t.apiKey.slice(-6)!==ts.keyTail)return null;let{keyTail:e,...n}=ts;return n}async function hb(t,e){let n=await fo();if(!n){let i=new Error("Deepgram is not configured \u2014 add deepgram.apiKey to ~/.bostrat/integrations.json");throw i.status=503,i.code="not_configured",i}let r=new URL("https://api.deepgram.com/v1/listen");r.searchParams.set("model","nova-3"),r.searchParams.set("smart_format","true");for(let i of await D$())r.searchParams.append("keyterm",i);let o=await fetch(r,{method:"POST",headers:{Authorization:`Token ${n.apiKey}`,"Content-Type":e||"application/octet-stream"},body:t});if(!o.ok){let i=await o.text().catch(()=>""),c=L$(o.status,i);c.persistent&&(ts={code:c.code,message:c.message,at:Date.now(),keyTail:n.apiKey.slice(-6)});let l=new Error(c.message);throw l.status=c.status,l.code=c.code,l}return ts=null,{transcript:(await o.json())?.results?.channels?.[0]?.alternatives?.[0]?.transcript??""}}var Uu={"gpt-realtime-2.1":{textIn:4,textInCached:.4,textOut:24,audioIn:32,audioInCached:.4,audioOut:64},"gpt-realtime-2":{textIn:4,textInCached:.4,textOut:24,audioIn:32,audioInCached:.4,audioOut:64},"gpt-realtime":{textIn:4,textInCached:.4,textOut:16,audioIn:32,audioInCached:.4,audioOut:64},"gpt-realtime-2.1-mini":{textIn:.6,textInCached:.06,textOut:2.4,audioIn:10,audioInCached:.3,audioOut:20},"gpt-realtime-mini":{textIn:.6,textInCached:.06,textOut:2.4,audioIn:10,audioInCached:.3,audioOut:20}};function F$(t){if(!t)return null;let e=String(t).trim();if(Uu[e])return e;let n=null;for(let r of Object.keys(Uu))e.startsWith(r)&&(!n||r.length>n.length)&&(n=r);return n}function U$(t,e={}){let n=Uu[F$(t)];if(!n)return 0;let r=0;for(let o of Object.keys(n))r+=(Number(e[o])||0)*n[o];return r/1e6}function H$(t,e){let n=null;try{n=JSON.parse(e)}catch{}let r=n?.error?.code||n?.error?.type||"";return t===429&&/insufficient_quota/i.test(r)?{status:402,code:"credits_exhausted",persistent:!0,message:"The OpenAI account is out of credit \u2014 voice chat is down until you top up at platform.openai.com or drop a new key into ~/.bostrat/integrations.json."}:t===401||t===403?{status:502,code:"key_invalid",persistent:!0,message:"OpenAI rejected the API key (revoked, or missing Realtime access) \u2014 replace it in ~/.bostrat/integrations.json."}:{status:502,code:"mint_failed",persistent:!1,message:`OpenAI realtime session mint failed (${t}): ${e.slice(0,300)}`}}var es=null;async function W$(){if(!es)return null;let t=await Vs();if(!t||t.apiKey.slice(-6)!==es.keyTail)return null;let{keyTail:e,...n}=es;return n}async function q$(t){let e=await fetch("https://api.openai.com/v1/realtime/client_secrets",{method:"POST",headers:{Authorization:`Bearer ${t.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify({session:{type:"realtime",model:t.model,audio:{output:{voice:t.voice}}}})});if(!e.ok){let o=await e.text().catch(()=>""),s=H$(e.status,o);s.persistent&&(es={code:s.code,message:s.message,at:Date.now(),keyTail:t.apiKey.slice(-6)});let a=new Error(s.message);throw a.status=s.status,a.code=s.code,a}es=null;let n=await e.json(),r=n?.value??n?.client_secret?.value;if(!r){let o=new Error("OpenAI returned no client secret");throw o.status=502,o.code="mint_failed",o}return{clientSecret:{value:r,expiresAt:n?.expires_at??n?.client_secret?.expires_at??null}}}var K$={openai:q$};async function wb(){let t=await Vs();return t?{configured:!0,provider:t.provider,model:t.model,voice:t.voice,problem:await W$()}:{configured:!1}}async function gb(){let t=await Vs();if(!t){let n=new Error("Voice chat is not configured \u2014 add openai.apiKey to ~/.bostrat/integrations.json");throw n.status=503,n.code="not_configured",n}let e=await K$[t.provider](t);return{provider:t.provider,model:t.model,voice:t.voice,...e}}async function yb(t,{model:e,usage:n={},seconds:r}={}){let o=c=>Math.max(0,Math.round(Number(c)||0)),s={textIn:o(n.textIn),textInCached:o(n.textInCached),textOut:o(n.textOut),audioIn:o(n.audioIn),audioInCached:o(n.audioInCached),audioOut:o(n.audioOut)},a=U$(e,s),i=await O(t,{type:"cost.recorded",actor:"user",provider:"openai-realtime",payload:{model:e||"unknown",usd:a,tokens:{input:s.textIn+s.audioIn,output:s.textOut+s.audioOut,cacheRead:s.textInCached+s.audioInCached,cacheWrite:0},voice:s,seconds:Math.max(0,Math.round(Number(r)||0)),source:"voice-chat"}});return{usd:a,seq:i.seq}}var Tb=ut.dirname(z$(import.meta.url)),bb=Number(process.env.PORT)||3362,ni=Jg(process.env),Ab=Number(process.env.BOSTRAT_AUTO_TRIGGER_PCT)||80,Ur=Sy(process.env.BOSTRAT_ALLOW_IPS),_b=Ty(),Eb=process.env.BOSTRAT_ALLOW_LAN_TOKENLESS!=="1",x=ge();x.disable("x-powered-by");x.use((t,e,n)=>{if(Jo(t)||Ur.allowed(Ur.clientIp(t)))return n();e.status(403).type("text/plain").send("bostrat: access restricted to the allowlist")});x.use(Oy);x.use(Ey({token:_b,enforce:Eb}));x.use("/api/mocks",ge.json({limit:"1mb"}));x.use("/api/mocks",ge.raw({type:"image/png",limit:"24mb"}));x.use("/api/runbooks",ge.json({limit:"1mb"}));x.use("/api/telemetry",ge.json({limit:"256kb"}));x.use("/api/hook",ge.json({limit:"2mb"}));x.use("/api/dictation",ge.raw({type:["audio/*","video/mp4","application/octet-stream"],limit:"32mb"}));x.use("/api/tracks/:id/upload",ge.raw({type:()=>!0,limit:"25mb"}));x.use(ge.json({limit:"64kb"}));x.get("/api/health",async(t,e)=>{e.json({ok:!0,tmuxSocket:jt,hostTmuxAlive:await De(),autoTriggerPct:Ab})});x.get("/api/sessions",async(t,e)=>{e.json({sessions:await mn()})});x.post("/api/sessions",async(t,e)=>{let{displayName:n,cwd:r}=t.body||{};if(typeof n!="string")return e.status(400).json({error:"displayName (string) required"});try{let o=await hn(n,r||void 0);e.json({ok:!0,...o})}catch(o){let s=o?.message||String(o),a=/already exists/i.test(s)?409:/must be|invalid/i.test(s)?400:500;e.status(a).json({error:s})}});x.delete("/api/sessions/:name",async(t,e)=>{let n=decodeURIComponent(t.params.name);if(!yt(n))return e.status(400).json({error:"invalid session name"});if(!await Y(n))return e.status(404).json({error:"session not found"});try{await re(n),e.json({ok:!0})}catch(r){e.status(500).json({error:`failed to kill: ${r?.message||r}`})}});x.get("/api/context/:name",async(t,e)=>{let n=decodeURIComponent(t.params.name);if(!yt(n))return e.status(400).json({error:"invalid session name"});let r=await Y(n);if(!r)return e.status(404).json({error:"session not found"});let o=await Qr(r.cwd,r.sessionId);if(!o)return e.json({available:!1,cwd:r.cwd,sessionId:r.sessionId||null});e.json({available:!0,...o})});x.post("/api/handoff/:name",async(t,e)=>{let n=decodeURIComponent(t.params.name);if(!yt(n))return e.status(400).json({error:"invalid session name"});try{let r=await Rf(n);e.json({ok:!0,step:r.step,file:r.file,startedAt:r.startedAt})}catch(r){e.status(409).json({error:r?.message||String(r)})}});x.get("/api/handoff/:name",(t,e)=>{let n=decodeURIComponent(t.params.name),r=Cs(n);if(!r)return e.json({running:!1});e.json({running:!r.finishedAt,step:r.step,error:r.error,file:r.file,startedAt:r.startedAt,finishedAt:r.finishedAt})});var kb=10;x.get("/api/tracks",async(t,e)=>{let n=await ct(),r=n.filter(s=>s.archivedAt),o=n;if(t.query.archived!=="all"&&r.length>kb){let s=new Set([...r].sort((a,i)=>(i.archivedAt||0)-(a.archivedAt||0)).slice(0,kb).map(a=>a.id));o=n.filter(a=>!a.archivedAt||s.has(a.id))}e.json({tracks:o,archivedTotal:r.length,agents:Na,agentAvailability:await nu(),tickets:await Zs()})});x.get("/api/cost",async(t,e)=>{try{let n=await ct();t.query.track&&(n=n.filter(o=>o.id===t.query.track));let r=await ny(n);if(r.cursorUsage=Cl(),t.query.format==="text")return e.type("text/plain").send(ry(r));e.json(r)}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/usage/roundup",async(t,e)=>{try{e.json(await ay(await ct(),{window:t.query.window,from:t.query.from,to:t.query.to}))}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/usage/spend",async(t,e)=>{try{let n=await dy(await ct(),{window:t.query.window,from:t.query.from,to:t.query.to});n.cursorUsage=Cl(),n.subscriptionMonthlyUsd=Number(process.env.BOSTRAT_SUBSCRIPTION_MONTHLY_USD)||null,e.json(n)}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/usage/limits",async(t,e)=>{try{let n=t.query.fresh==="1"||t.query.fresh==="true";e.json(await gy(await ct(),{fresh:n}))}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/arcs",async(t,e)=>{try{e.json(await ym(await ct()))}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/arcs/:key",async(t,e)=>{try{let n=await bm(await ct(),t.params.key);if(!n)return e.status(404).json({error:"unknown arc"});e.json(n)}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.post("/api/arcs/backfill-beats",async(t,e)=>{try{e.json({ok:!0,...await Ag()})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.post("/api/hook",async(t,e)=>{try{let{mode:n,target:r}=await Yu(t.get(as),{byToken:rg,legacy:()=>ng(t.body?.session_id,t.body?.cwd)});if(n==="refused")return is("refused",{reason:"unknown-token",hook:t.body?.hook_event_name}),e.json({ok:!1,error:"unknown hook token"});n==="token"&&t.body?.session_id&&r.box?.sessionId&&t.body.session_id!==r.box.sessionId&&is("mismatch",{track:r.track.id,box:r.box.name}),is(n,{track:r?.track?.id,hook:t.body?.hook_event_name});let o=await zg(t.body||{},()=>r);e.json({ok:!0,events:o.length})}catch(n){e.json({ok:!1,error:n?.message||String(n)})}});x.post("/api/telemetry",async(t,e)=>{try{if(!eb(Ur.clientIp(t)))return e.json({ok:!0,dropped:!0});let n=await tb(t.body||{});e.json({ok:!0,events:n})}catch(n){e.json({ok:!1,error:n?.message||String(n)})}});x.get("/api/repos",async(t,e)=>{try{let n=await ye(),r=new Set((await Promise.all(n.map(a=>tt(a.path)))).filter(Boolean).map(a=>a.toLowerCase())),s=((await Gr()).repos||[]).filter(a=>!r.has(String(a.slug).toLowerCase())).map(a=>({slug:a.slug,name:a.slug.split("/")[1],branch:a.defaultBranch||null,private:!!a.private,remote:!0}));e.json({repos:[...n,...s]})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/github/token",async(t,e)=>{try{e.json(await ds(String(t.query.repo||"")))}catch(n){e.status(n?.status||502).json({error:n?.message||String(n)})}});x.get("/api/github/status",(t,e)=>{e.json({configured:us(),calls:Qh(),latency:Aw()})});x.post("/api/github/refresh",(t,e)=>{id(),e.json({ok:!0})});x.get("/api/gmail/search",async(t,e)=>{try{let n=await by(String(t.query.q||""),{max:Number(t.query.max)||10});e.json({messages:n})}catch(n){e.status(n?.status||502).json({error:n?.message||String(n)})}});x.get("/api/gmail/messages/:id",async(t,e)=>{try{e.json({message:await ky(String(t.params.id))})}catch(n){e.status(n?.status||502).json({error:n?.message||String(n)})}});function zu(t,e,n){let r=t.workspace;return r?.kind==="multi"?(r.repos??[]).some(o=>e&&o?.base===e||n&&o?.repo===n):!r?.base||!e||r.base===e||n&&r.repo===n}async function ri({repo:t=null,number:e=null,branch:n=null}){let r=t?await tt(t):null;if(r&&e!=null){let s=await Et(r,e),a=s&&await Q(s.trackId);if(a)return a}return n?(await ct()).find(s=>s.workspace?.branch===n&&zu(s,t,r))??null:null}async function Ib({repo:t=null,number:e=null,branch:n=null}){let r=await ri({repo:t,number:e,branch:n}).catch(()=>null);if(r&&!r.archivedAt)return r;if(!n)return null;let o=t?await tt(t).catch(()=>null):null;return(await ct()).find(a=>!a.archivedAt&&a.workspace?.branch===n&&zu(a,t,o))??null}async function te(t,{number:e,url:n=null,title:r=null,branch:o=null,repo:s=null,track:a=null,extra:i={}}){let c=a||await ri({repo:s,number:e,branch:o}).catch(()=>null);if(!c)return;let l=i.repo??(s?await tt(s).catch(()=>null):null);await O({id:c.id,cwd:c.cwd},{type:"pr.state.changed",actor:"user",source:"orchestrator",payload:{ts:Date.now(),change:t,number:e,url:n,title:r,branch:o??c.workspace?.branch??null,...l?{repo:l}:{},...i}}).catch(()=>{})}async function Y$(t){let e=(await ct()).filter(n=>!n.archivedAt);for(let n of t??[]){if(!n.prs?.length)continue;let r=await tt(n.path);if(r)for(let o of n.prs){if(await Et(r,o.number))continue;let s=e.find(a=>a.workspace?.branch===o.headRefName&&zu(a,n.path,r));s&&await Lt({repo:r,number:o.number,trackId:s.id,url:o.url,headRef:o.headRefName,title:o.title,source:"matched"})}}}x.get("/api/git/prs",async(t,e)=>{try{let n=await Mw();await Y$(n.repos).catch(()=>{}),e.json(n)}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.post("/api/git/prs/merge",async(t,e)=>{let{repo:n,number:r,method:o,auto:s,update:a,archive:i,notify:c,headRefName:l,title:u,url:f}=t.body||{};try{if(s){let w=o||"merge";if(!Oo.has(w))throw new Error(`invalid merge method: ${w}`);let g=Number(r);if(!Number.isInteger(g)||g<=0)throw new Error("invalid PR number");let b=await tt((await $e(n)).path);if(!b)throw new Error("repo has no GitHub remote");let v=await ri({repo:n,number:g,branch:l}).catch(()=>null);if(!v)throw new Error("auto-merge needs a Track to watch this PR \u2014 none owns it");if(a&&await Mo(n,g),await Lt({repo:b,number:g,trackId:v.id,url:f,headRef:l,title:u,source:"matched"}).catch(()=>{}),!await go(b,g,{merge:{method:w}}))throw new Error("couldn't record the auto-merge arm");return await lr(b,g,i===!0),await aa(b,g,c===!0&&i!==!0),he(),await te("auto-armed",{number:g,url:f,title:u,branch:l,repo:n,track:v,...a?{extra:{updated:!0}}:{}}),e.json({ok:!0,number:g,auto:!0})}let d=await vr(n,r,{method:o||"squash"});await te("merged",{number:d.number,url:f,title:u,branch:l,repo:n});let m=await tt(n).catch(()=>null),h=!1;if(m&&(i===!0&&await lr(m,d.number,!0).catch(()=>{}),c===!0&&i!==!0&&await aa(m,d.number,!0).catch(()=>{}),await en(m,d.number).catch(()=>{}),h=await tn(m,d.number).catch(()=>!1),await ze(m,d.number,"merged").catch(()=>{})),i===!0&&!h){let w=await ri({repo:n,number:d.number,branch:l}).catch(()=>null);w&&!w.archivedAt&&await Rr(w.id).catch(()=>{})}e.json(d)}catch(d){let m=d?.message||String(d),h=/invalid|unknown repo|required|needs a Track|no GitHub remote/i.test(m)?400:502;e.status(h).json({error:m})}});x.post("/api/git/prs/disarm-auto",async(t,e)=>{let{repo:n,number:r,headRefName:o,title:s,url:a}=t.body||{};try{let i=Number(r);if(!Number.isInteger(i)||i<=0)throw new Error("invalid PR number");let c=await tt((await $e(n)).path).catch(()=>null),l=c?await yo(c,i).catch(()=>null):null;try{await Fl(n,i)}catch(u){if(!l)throw u}he(),await te("auto-canceled",{number:i,url:a,title:s,branch:o,repo:n}),e.json({ok:!0,number:i})}catch(i){let c=i?.message||String(i),l=/invalid|unknown repo|required/i.test(c)?400:502;e.status(l).json({error:c})}});x.post("/api/git/prs/merge-all",async(t,e)=>{let{repo:n,method:r}=t.body||{};try{let o=await tt(n).catch(()=>null),s=await Bw(n,{method:r||"merge",onMerged:a=>{te("merged",{number:a.number,url:a.url,title:a.title,branch:a.headRefName,repo:n}),o&&en(o,a.number).catch(()=>{}).then(()=>tn(o,a.number)).catch(()=>{}).then(()=>ze(o,a.number,"merged")).catch(()=>{})}});e.json(s)}catch(o){let s=o?.message||String(o),a=/already running/i.test(s)?409:/invalid|unknown repo|no PRs ready/i.test(s)?400:502;e.status(a).json({error:s})}});x.get("/api/git/prs/merge-all",(t,e)=>{e.json({jobs:Pw()})});x.post("/api/git/prs/merge-track",async(t,e)=>{let{trackId:n,update:r,archive:o,notify:s,method:a}=t.body||{};try{let i=a||"merge";if(!Oo.has(i))throw new Error(`invalid merge method: ${i}`);if(!n)return e.status(400).json({error:"trackId required"});let c=await Q(n);if(!c)return e.status(404).json({error:`Track ${n} isn't on this node`});let l=(await pe()).filter(d=>d.trackId===c.id&&d.state!=="merged"&&d.state!=="closed");if(!l.length)return e.status(409).json({error:"no open PRs on this Track"});for(let d of l)await lr(d.repo,d.number,o===!0).catch(()=>{}),await aa(d.repo,d.number,s===!0&&o!==!0).catch(()=>{});let u=[],f=[];for(let d of l){let m={repo:d.repo,number:d.number,ok:!0,action:"armed"};u.push(m);try{let h=await Nn(d.repo);if(!h)throw new Error(`no local checkout for ${d.repo}`);let w=await Sr(h,d.number);f.push({r:m,l:d,repoPath:h,pr:w,plan:Cw(w,{update:r!==!1})})}catch(h){m.ok=!1,m.action="failed",m.reason=h?.message||String(h)}}for(let{r:d,l:m,repoPath:h,pr:w,plan:g}of Nw(f))try{if(g.action==="skip")d.action="skipped",d.reason=g.reason;else if(g.action==="merge")await vr(h,m.number,{method:i}),await te("merged",{number:m.number,url:w.url,title:w.title,branch:w.headRefName,repo:h,track:c}),await en(m.repo,m.number).catch(()=>{}),await tn(m.repo,m.number).catch(()=>{}),await ze(m.repo,m.number,"merged").catch(()=>{}),d.action="merged";else{if(g.action==="update-arm"&&(await Mo(h,m.number).catch(()=>{}),d.action="updated-armed"),!await go(m.repo,m.number,{merge:{method:i}}))throw new Error("couldn't record the auto-merge arm");await te("auto-armed",{number:m.number,url:w.url,title:w.title,branch:w.headRefName,repo:h,track:c,...d.action==="updated-armed"?{extra:{updated:!0}}:{}})}}catch(b){d.ok=!1,d.action="failed",d.reason=b?.message||String(b)}if(he(),!u.some(d=>d.ok))return e.status(502).json({error:u.map(d=>`${d.repo}#${d.number}: ${d.reason}`).join("; "),results:u});e.json({ok:!0,trackId:c.id,results:u})}catch(i){let c=i?.message||String(i),l=/invalid|required/i.test(c)?400:502;e.status(l).json({error:c})}});x.post("/api/git/prs/disarm-track",async(t,e)=>{let{trackId:n}=t.body||{};try{if(!n)return e.status(400).json({error:"trackId required"});let r=await Q(n);if(!r)return e.status(404).json({error:`Track ${n} isn't on this node`});let o=(await pe()).filter(a=>a.trackId===r.id&&a.state!=="merged"&&a.state!=="closed"),s=[];for(let a of o){let i=await yo(a.repo,a.number).catch(()=>null),c=await Nn(a.repo).catch(()=>null);c&&await Fl(c,a.number).catch(()=>{}),i&&(await te("auto-canceled",{number:a.number,url:a.url,title:a.title,branch:a.headRef,repo:c,track:r}),s.push({repo:a.repo,number:a.number}))}he(),e.json({ok:!0,trackId:r.id,canceled:s})}catch(r){let o=r?.message||String(r),s=/invalid|required/i.test(o)?400:502;e.status(s).json({error:o})}});x.post("/api/git/prs/open",async(t,e)=>{let{trackId:n,title:r,body:o,base:s,repos:a}=t.body||{};try{if(!n)return e.status(400).json({error:"trackId required"});let i=await Q(n);if(!i)return e.status(404).json({error:`Track ${n} isn't on this node`});if(i.workspace?.kind==="multi"){let u=Nt(i.workspace),f,d=Array.isArray(a)?a.filter(Boolean):[];if(d.length)f=d.map(w=>{let g=u.find(b=>b.name===w||b.repo===w||b.base===w||b.dir===w);if(!g)throw new Error(`invalid repo "${w}" \u2014 not tagged on this Track`);return g});else if(f=await Eo(i),!f.length)return e.status(409).json({error:"no tagged repo has work to ship"});let m=[];for(let w of f)try{let g=await Hl(w.dir,{title:r,body:o,base:s}),b=w.repo||await tt(w.dir).catch(()=>null);b&&g.number&&await Lt({repo:b,number:g.number,trackId:i.id,url:g.url,headRef:i.workspace?.branch??null,title:r,source:"api"}),await te("opened",{number:g.number,url:g.url,title:r,track:i,branch:i.workspace?.branch,extra:{repo:b??w.name}}),m.push({repo:w.name,slug:b??null,ok:!0,url:g.url,number:g.number})}catch(g){m.push({repo:w.name,slug:w.repo??null,ok:!1,error:g?.message||String(g)})}let h=m.filter(w=>w.ok);return h.length?e.json({ok:!0,multi:!0,results:m,url:h[0].url,number:h[0].number}):e.status(502).json({error:m.map(w=>`${w.repo}: ${w.error}`).join("; "),results:m})}let c=await Hl(i.cwd,{title:r,body:o,base:s}),l=await tt(i.cwd).catch(()=>null);l&&c.number&&await Lt({repo:l,number:c.number,trackId:i.id,url:c.url,headRef:i.workspace?.branch??null,title:r,source:"api"}),await te("opened",{number:c.number,url:c.url,title:r,track:i,branch:i.workspace?.branch,extra:{...l?{repo:l}:{}}}),e.json(c)}catch(i){let c=i?.message||String(i),l=/required|invalid/i.test(c)?400:502;e.status(l).json({error:c})}});x.get("/api/tracks/:id/repos",async(t,e)=>{try{let n=await Q(t.params.id);if(!n)return e.status(404).json({error:"track not found"});let r=await Eo(n),o=new Map(r.map(a=>[a.base,a])),s=Nt(n.workspace).map(a=>{let i=o.get(a.base);return{name:a.name,base:a.base,baseRef:a.baseRef??null,repo:a.repo??null,dirty:i?.dirty??!1,ahead:i?.ahead??0,changed:!!i}});e.json({repos:s,branch:n.workspace?.branch??null,kind:n.workspace?.kind??null})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.post("/api/tracks/:id/repos",async(t,e)=>{let{repo:n}=t.body||{};try{if(!n||typeof n!="string")return e.status(400).json({error:"repo (path or owner/name) required"});let r=await wg(t.params.id,n);e.json({ok:!0,added:{name:r.name,base:r.base,baseRef:r.baseRef??null,repo:r.repo??null}})}catch(r){let o=r?.message||String(r),s=/not found/i.test(o)?404:/already tagged|needs a multi|not a usable|archived|required/i.test(o)?400:502;e.status(s).json({error:o})}});x.delete("/api/tracks/:id/repos/:name",async(t,e)=>{try{let n=await gg(t.params.id,t.params.name);e.json({ok:!0,...n})}catch(n){let r=n?.message||String(n),o=/track .* not found/i.test(r)?404:/has work|still running|last repo/i.test(r)?409:/needs a multi|not tagged/i.test(r)?400:502;e.status(o).json({error:r})}});x.post("/api/git/prs/fix",async(t,e)=>{let{repo:n,number:r}=t.body||{};try{let o=await Sr(n,r),s=await tt(n).catch(()=>null),a=await Ib({repo:n,number:r,branch:o.headRefName});if(!a)return e.status(404).json({error:`no active Track owns PR #${o.number} (branch "${o.headRefName}")`});let i=Nt(a.workspace).find(b=>b.base===n||s&&b.repo===s)??null,c=a.workspace?.kind==="multi",l=await Dw(n,o.number);if(!l.length)return e.status(409).json({error:"no failing checks on this PR"});let u=c&&i?ut.join("ci",i.name):"ci",f=ut.join(a.cwd,".bostrat",u),d=ut.join(".bostrat",u,`pr-${o.number}-fail.log`);await Bt.promises.mkdir(f,{recursive:!0}),await Bt.promises.writeFile(ut.join(a.cwd,d),l.map(b=>`\u2550\u2550\u2550\u2550 ${b.name}${b.link?` \xB7 ${b.link}`:""} \u2550\u2550\u2550\u2550
|
|
386
|
+
${b.log??"(no fetchable log \u2014 see link)"}
|
|
346
387
|
`).join(`
|
|
347
|
-
`));let m=l.map(k=>k.name).join(", "),h=c&&i?` in the ${i.name}/ checkout`:"",w=await de(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 Pt({repo:g,number:o.number,trackId:a.id,url:o.url,headRef:o.headRefName,title:o.title,source:"matched"}),await kp(g,o.number,{headSha:o.headSha,trackId:a.id}).catch(()=>{})),await Jt("fix-delegated",{number:o.number,url:o.url,title:o.title,branch:o.headRefName,repo:n,track:a,extra:{checks:l.map(k=>k.name)}}),e.json({ok:!0,leadBox:w.leadBox,queued:!!w.queued,checks:l.map(k=>k.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 hr(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 zy({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 de(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 Pt({repo:u,number:o.number,trackId:a.id,url:o.url,headRef:o.headRefName,title:o.title,source:"matched"}),await xp(u,o.number,{headSha:o.headSha,trackId:a.id}).catch(()=>{})),await Jt("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=ah.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 Y(t.params.id);if(!a)return e.status(404).json({error:"track not found"});let i=await j(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 de(a.id,oa(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 C({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&&Iw(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 Vo=t=>e=>({...e,url:Qg(e,t.headers)});x.get("/api/views",async(t,e)=>{try{let n=await Rs({trackId:t.query.track||void 0});e.json({views:n.map(Vo(t)),maxLive:Ne})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/views/:id",async(t,e)=>{let n=await Tf(t.params.id);if(!n)return e.status(404).json({error:"view not found"});e.json({view:Vo(t)(n),session:Kn(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 Y(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 Bi({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:Vo(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:Vo(t)(await _f(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:Vo(t)(await Ef(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 If(t.params.id))}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});var XR=[["linear",fn,qi],["jira",Ms,Hi]],Au=new Map;x.get("/api/integrations/ticketing",async(t,e)=>{try{let n=await Ui(),r={};for(let[o,s,a]of XR){let i=await s();if(!i){Au.delete(o),r[o]={configured:!1};continue}let c=Au.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},Au.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 Ui()})}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 Gf(o),i=n?await Y(n):null;!i&&r&&(i=(await at()).find(l=>l.cwd&&(r===l.cwd||r.startsWith(l.cwd+ct.sep)))||null),i&&await C({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 Y(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 Qn(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 Y(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 ip(),ticket:await se(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 se(t.params.id)})});x.post("/api/tracks/:id/ticket",async(t,e)=>{try{let n=await Y(t.params.id);if(!n)return e.status(404).json({error:"track not found"});let r=await Qn(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 ec(t.params.id)})});var Ka=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 dp(t.params.id))}catch(n){let r=n?.message||String(n);e.status(Ka(r)).json({error:r})}});x.post("/api/tracks/:id/ticket/state",async(t,e)=>{try{e.json({ok:!0,...await nc(t.params.id,(t.body||{}).stateId)})}catch(n){let r=n?.message||String(n);e.status(Ka(r)).json({error:r})}});x.post("/api/tracks/:id/ticket/comment",async(t,e)=>{try{e.json({ok:!0,...await fp(t.params.id,(t.body||{}).body)})}catch(n){let r=n?.message||String(n);e.status(Ka(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 Y(t.params.id);if(!o)return e.status(404).json({error:"track not found"});e.json({ok:!0,...await pp(o,n,r===!0)})}catch(o){let s=o?.message||String(o);e.status(Ka(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 Y(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 hp(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 mp()})}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 Y(t.params.id);if(!s)return e.status(404).json({error:"track not found"});e.json({ok:!0,...await wp(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 As({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 Me(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 Me(t.params.id);if(!n)return e.status(404).json({error:"mock not found"});let r=ln(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 Me(t.params.id);if(!n)return e.status(404).json({error:"mock not found"});let r=ln(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 Y(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 sf({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 af(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 lf(t.params.id),o=r.length?` over ${r.map(s=>`"${s.name}"`).join(", ")}`:"";await de(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 uf(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 $s({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 Li(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 Li(t.params.id);if(!n)return e.status(404).json({error:"runbook not found"});let r=Mf(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(Vn(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 Y(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 Bf({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 Df(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 jf(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 Lf(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=ny(t.query.since||null);e.json({since:n,repos:mu(),prs:await ry(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 Y(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 oy(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 YR=120,Tu=400,Iu=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>Tu)return e.status(400).json({error:`message is ${i.length} chars \u2014 over the ${Tu} cap; a notification is a nudge, put the detail in chat`});let c=Yr(`${n||""}
|
|
348
|
-
${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
|
|
388
|
+
`));let m=l.map(b=>b.name).join(", "),h=c&&i?` in the ${i.name}/ checkout`:"",w=await Qt(a.id,`CI is red on PR #${o.number} "${o.title}" (${o.url}) \u2014 branch ${o.headRefName}${h}. Failing check${l.length===1?"":"s"}: ${m}. The failure log is at ${d} in this ${c?"workspace":"worktree"} \u2014 read it, fix the root cause${c&&i?` in ${i.name}/`:""}, run the relevant tests locally, then commit and push to update the PR.`,{kind:"ci-fix"}),g=s;g&&(await Lt({repo:g,number:o.number,trackId:a.id,url:o.url,headRef:o.headRefName,title:o.title,source:"matched"}),await im(g,o.number,{headSha:o.headSha,trackId:a.id}).catch(()=>{})),await te("fix-delegated",{number:o.number,url:o.url,title:o.title,branch:o.headRefName,repo:n,track:a,extra:{checks:l.map(b=>b.name)}}),e.json({ok:!0,leadBox:w.leadBox,queued:!!w.queued,checks:l.map(b=>b.name),log:d})}catch(o){let s=o?.message||String(o),a=/unknown repo|invalid|required/i.test(s)?400:/not found|no active Track/i.test(s)?404:502;e.status(a).json({error:s})}});x.post("/api/git/prs/reconcile",async(t,e)=>{let{repo:n,number:r}=t.body||{};try{let o=await Sr(n,r);if(o.mergeable!=="CONFLICTING")return e.status(409).json({error:"this PR does not conflict with its base branch"});let s=await tt(n).catch(()=>null),a=await Ib({repo:n,number:r,branch:o.headRefName});if(!a)return e.status(404).json({error:`no active Track owns PR #${o.number} (branch "${o.headRefName}")`});let i=Nt(a.workspace).find(f=>f.base===n||s&&f.repo===s)??null,c=a.workspace?.kind==="multi"&&i?` In the ${i.name}/ checkout:`:"",l=await Qt(a.id,`PR #${o.number} "${o.title}" (${o.url}) \u2014 branch ${o.headRefName} \u2014 has merge conflicts with ${o.baseRefName}.${c} Bring ${o.baseRefName} into this branch (fetch origin, then merge origin/${o.baseRefName} \u2014 don't rebase this pushed PR branch), and resolve every conflict so that BOTH sides' intent is preserved. Run the relevant tests, then commit and push to update the PR. If any conflict is a genuine ambiguity \u2014 where you can't confidently tell which side's behavior should win, or reconciling would drop work from either side \u2014 STOP and ask me here rather than guessing, laying out the specific choice and your recommendation.`,{kind:"reconcile"}),u=s;u&&(await Lt({repo:u,number:o.number,trackId:a.id,url:o.url,headRef:o.headRefName,title:o.title,source:"matched"}),await cm(u,o.number,{headSha:o.headSha,trackId:a.id}).catch(()=>{})),await te("reconcile-delegated",{number:o.number,url:o.url,title:o.title,branch:o.headRefName,repo:n,track:a,extra:{base:o.baseRefName}}),e.json({ok:!0,leadBox:l.leadBox,queued:!!l.queued,base:o.baseRefName})}catch(o){let s=o?.message||String(o),a=/unknown repo|invalid|required/i.test(s)?400:/not found|no active Track/i.test(s)?404:502;e.status(a).json({error:s})}});x.post("/api/tracks/:id/pr-offer",async(t,e)=>{let{offerId:n,action:r}=t.body||{};try{let o=null;if(Array.isArray(t.body?.actions)){if(o=Fh.filter(f=>t.body.actions.includes(f)),!o.length)return e.status(400).json({error:"actions must include commit, rebase, push and/or pr"})}else if(r==="pr")o=["commit","push","pr"];else if(r==="push")o=["commit","push"];else if(r!=="dismiss")return e.status(400).json({error:'offerId and actions ["commit"|"rebase"|"push"|"pr"] (or action "dismiss") required'});if(!n)return e.status(400).json({error:"offerId required"});let s=o!=null&&t.body?.notify===!0,a=await Q(t.params.id);if(!a)return e.status(404).json({error:"track not found"});let i=await L(a),c=i.find(f=>f.type==="pr.offered"&&f.payload?.offerId===n);if(!c)return e.status(404).json({error:"unknown offer"});if(i.some(f=>f.type==="pr.offer.answered"&&f.payload?.offerId===n))return e.status(409).json({error:"offer already answered"});let l=null;o&&(l=await Qt(a.id,wa(o,{branch:c.payload.branch,base:c.payload.base,toPr:c.payload.toPr||null,repos:c.payload.repos||null,offeredRebase:Array.isArray(c.payload.offered)&&c.payload.offered.includes("rebase")}),{kind:o.includes("pr")?"pr-go":"push-go"}));let u=o?o.includes("pr")?"pr":o.includes("push")?"push":o.includes("commit")?"commit":"rebase":"dismiss";await O({id:a.id,cwd:a.cwd},{type:"pr.offer.answered",actor:"user",source:"orchestrator",boxId:c.boxId,payload:{ts:Date.now(),offerId:n,action:u,actions:o,...s?{notify:!0}:{}}}),s&&cg(a.id,{actions:o}),e.json({ok:!0,offerId:n,action:u,actions:o,notify:s,queued:!!l?.queued})}catch(o){let s=o?.message||String(o),a=/required|invalid/i.test(s)?400:/not found|not running|archived/i.test(s)?409:502;e.status(a).json({error:s})}});var ns=t=>e=>({...e,url:Cy(e,t.headers)});x.get("/api/views",async(t,e)=>{try{let n=await Ws({trackId:t.query.track||void 0});e.json({views:n.map(ns(t)),maxLive:We})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/views/:id",async(t,e)=>{let n=await fp(t.params.id);if(!n)return e.status(404).json({error:"view not found"});e.json({view:ns(t)(n),session:er(n.slug)})});x.post("/api/views",async(t,e)=>{let{trackId:n,name:r,cmd:o,cwd:s,port:a}=t.body||{};try{let i=n?await Q(n):null;if(!i&&s&&(i=(await ct()).find(u=>u.cwd&&(s===u.cwd||s.startsWith(u.cwd+ut.sep)))||null),!i)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});let c=await ac({trackId:i.id,name:r,cmd:o,cwd:i.cwd,serveCwd:s,port:a,box:i.leadBox,branch:i.workspace?.branch??null});e.json({ok:!0,view:ns(t)(c)})}catch(i){let c=i?.message||String(i),l=i?.status??(/cap reached/i.test(c)?409:/required|invalid|already owned|no free/i.test(c)?400:500);e.status(l).json({error:c})}});x.post("/api/views/:id/stop",async(t,e)=>{try{e.json({ok:!0,view:ns(t)(await pp(t.params.id))})}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});x.post("/api/views/:id/restart",async(t,e)=>{try{e.json({ok:!0,view:ns(t)(await mp(t.params.id))})}catch(n){let r=n?.message||String(n),o=/not found/i.test(r)?404:/cap reached|no known command/i.test(r)?409:500;e.status(o).json({error:r})}});x.delete("/api/views/:id",async(t,e)=>{try{e.json(await hp(t.params.id))}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});var Z$=[["linear",xn,mc],["jira",zs,fc]],Hu=new Map;x.get("/api/integrations/ticketing",async(t,e)=>{try{let n=await dc(),r={};for(let[o,s,a]of Z$){let i=await s();if(!i){Hu.delete(o),r[o]={configured:!1};continue}let c=Hu.get(o);if(!c||Date.now()-c.at>300*1e3){let l;try{l={configured:!0,ok:!0,...await a(i)}}catch(u){l={configured:!0,ok:!1,error:u?.message||String(u)}}c={at:Date.now(),body:l},Hu.set(o,c)}r[o]={...c.body,flow:n},o==="jira"&&i.tokenExpiresAt&&(r[o].tokenExpiresAt=i.tokenExpiresAt)}e.json(r)}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/tickets/flow",async(t,e)=>{try{e.json({flow:await dc()})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.post("/api/tickets/flow",async(t,e)=>{let{trackId:n,cwd:r,set:o,actor:s}=t.body||{};try{let a=await Mp(o),i=n?await Q(n):null;!i&&r&&(i=(await ct()).find(l=>l.cwd&&(r===l.cwd||r.startsWith(l.cwd+ut.sep)))||null),i&&await O({id:i.id,cwd:i.cwd},{type:"ticket.flow.changed",actor:s==="user"?"user":"agent",source:"orchestrator",payload:{ts:Date.now(),set:{...o},flow:a}}).catch(()=>{}),e.json({ok:!0,flow:a})}catch(a){let i=a?.message||String(a);e.status(/unknown|nothing to set/i.test(i)?400:500).json({error:i})}});x.post("/api/tickets/link",async(t,e)=>{let{trackId:n,cwd:r,ref:o}=t.body||{};try{let s=n?await Q(n):null;if(!s&&r&&(s=(await ct()).find(c=>c.cwd&&(r===c.cwd||r.startsWith(c.cwd+ut.sep)))||null),!s)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});if(!o)return e.status(400).json({error:"ref (issue URL or key) required"});let a=await cr(s,o,{source:n?"api":"cli"});e.json({ok:!0,trackId:s.id,ticket:a})}catch(s){let a=s?.message||String(s);e.status(/not a Linear|not found|no ticketing|required/i.test(a)?400:502).json({error:a})}});x.get("/api/tickets/status",async(t,e)=>{let{cwd:n,track:r}=t.query;try{let o=r?await Q(r):null;if(!o&&n&&(o=(await ct()).find(a=>a.cwd&&(n===a.cwd||String(n).startsWith(a.cwd+ut.sep)))||null),!o)return e.status(400).json({error:"track (or a cwd inside a Track workspace) required"});e.json({trackId:o.id,configured:await Vp(),ticket:await fe(o.id)})}catch(o){e.status(500).json({error:o?.message||String(o)})}});x.get("/api/tracks/:id/ticket",async(t,e)=>{e.json({ticket:await fe(t.params.id)})});x.post("/api/tracks/:id/ticket",async(t,e)=>{try{let n=await Q(t.params.id);if(!n)return e.status(404).json({error:"track not found"});let r=await cr(n,(t.body||{}).ref,{source:"api"});e.json({ok:!0,ticket:r})}catch(n){let r=n?.message||String(n);e.status(/not a Linear|not found|no ticketing|required/i.test(r)?400:502).json({error:r})}});x.delete("/api/tracks/:id/ticket",async(t,e)=>{e.json({ok:!0,removed:await Ac(t.params.id)})});var oi=t=>/no ticket linked|unknown offer|unknown state/i.test(t)?404:/already answered/i.test(t)?409:/required|no ticketing/i.test(t)?400:502;x.get("/api/tracks/:id/ticket/states",async(t,e)=>{try{e.json(await Zp(t.params.id))}catch(n){let r=n?.message||String(n);e.status(oi(r)).json({error:r})}});x.post("/api/tracks/:id/ticket/state",async(t,e)=>{try{e.json({ok:!0,...await _c(t.params.id,(t.body||{}).stateId)})}catch(n){let r=n?.message||String(n);e.status(oi(r)).json({error:r})}});x.post("/api/tracks/:id/ticket/comment",async(t,e)=>{try{e.json({ok:!0,...await Qp(t.params.id,(t.body||{}).body)})}catch(n){let r=n?.message||String(n);e.status(oi(r)).json({error:r})}});x.post("/api/tracks/:id/ticket-offer",async(t,e)=>{let{offerId:n,accept:r}=t.body||{};try{let o=await Q(t.params.id);if(!o)return e.status(404).json({error:"track not found"});e.json({ok:!0,...await tm(o,n,r===!0)})}catch(o){let s=o?.message||String(o);e.status(oi(s)).json({error:s})}});x.post("/api/tickets/propose",async(t,e)=>{let{trackId:n,cwd:r,plan:o}=t.body||{};try{let s=n?await Q(n):null;if(!s&&r&&(s=(await ct()).find(i=>i.cwd&&(r===i.cwd||r.startsWith(i.cwd+ut.sep)))||null),!s)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});e.json({ok:!0,trackId:s.id,proposal:await nm(s,o)})}catch(s){let a=s?.message||String(s);e.status(/no ticketing|are off|needs an epic|too many|required/i.test(a)?400:502).json({error:a})}});x.get("/api/tickets/teams",async(t,e)=>{try{e.json({teams:await em()})}catch(n){let r=n?.message||String(n);e.status(/no ticketing/i.test(r)?400:502).json({error:r})}});x.post("/api/tracks/:id/ticket-create-offer",async(t,e)=>{let{proposalId:n,accept:r,teamId:o}=t.body||{};try{let s=await Q(t.params.id);if(!s)return e.status(404).json({error:"track not found"});e.json({ok:!0,...await rm(s,n,r===!0,{teamId:o||null})})}catch(s){let a=s?.message||String(s);e.status(/unknown proposal|unknown team/i.test(a)?404:/already answered/i.test(a)?409:/required|no ticketing|pick one/i.test(a)?400:502).json({error:a})}});x.get("/api/mocks",async(t,e)=>{try{e.json({mocks:await js({trackId:t.query.track||void 0,group:t.query.group||void 0})})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/mocks/:id",async(t,e)=>{let n=await He(t.params.id);if(!n)return e.status(404).json({error:"mock not found"});e.json({mock:n})});x.get("/api/mocks/:id/png",async(t,e)=>{let n=await He(t.params.id);if(!n)return e.status(404).json({error:"mock not found"});let r=yn(n,t.query.v??null);if(!r)return e.status(409).json({error:`mock is ${n.state} \u2014 no captured version yet`});e.set("cache-control","private, max-age=31536000, immutable"),e.sendFile(qt(n,r.v).png,o=>{o&&!e.headersSent&&e.status(404).json({error:"rendered artifact missing"})})});x.get("/api/mocks/:id/html",async(t,e)=>{let n=await He(t.params.id);if(!n)return e.status(404).json({error:"mock not found"});let r=yn(n,t.query.v??null);if(!r?.htmlRel)return e.status(404).json({error:"this version has no HTML source (image-sourced)"});e.set("content-security-policy","sandbox"),e.sendFile(qt(n,r.v).html,o=>{o&&!e.headersSent&&e.status(404).json({error:"source artifact missing"})})});x.post("/api/mocks",async(t,e)=>{let n=Buffer.isBuffer(t.body)?t.body:null,r=n?t.query:t.body||{},{trackId:o,name:s,cwd:a,width:i,group:c}=r;try{let l=o?await Q(o):null;if(!l&&a&&(l=(await ct()).find(m=>m.cwd&&(a===m.cwd||a.startsWith(m.cwd+ut.sep)))||null),!l)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});let u=(l.boxes||[]).find(d=>d.name===l.leadBox),f=await zf({trackId:l.id,name:s,html:n?null:r.html,image:n,cwd:l.cwd,width:i,group:c,box:l.leadBox,agent:u?.agent||null});e.json({ok:!0,mock:f})}catch(l){let u=l?.message||String(l);e.status(/required|invalid|must be/i.test(u)?400:500).json({error:u})}});x.post("/api/mocks/:id/update",async(t,e)=>{let n=Buffer.isBuffer(t.body)?t.body:null;try{let r=await Vf(t.params.id,{html:n?null:(t.body||{}).html,image:n,width:n?t.query.width:(t.body||{}).width});e.json({ok:!0,mock:r})}catch(r){let o=r?.message||String(r),s=/not found/i.test(o)?404:/required|invalid|must be/i.test(o)?400:500;e.status(s).json({error:o})}});x.post("/api/mocks/:id/pick",async(t,e)=>{try{let{mock:n,siblings:r}=await Xf(t.params.id),o=r.length?` over ${r.map(s=>`"${s.name}"`).join(", ")}`:"";await Qt(n.trackId,`Design decision: go with "${n.name}" (mock:${n.id})${o} \u2014 proceed with this design.`,{kind:"mock-picked"}).catch(()=>{}),e.json({ok:!0,mock:n,siblings:r})}catch(n){let r=n?.message||String(n),o=/not found/i.test(r)?404:/nothing to pick/i.test(r)?409:500;e.status(o).json({error:r})}});x.delete("/api/mocks/:id",async(t,e)=>{try{e.json(await Yf(t.params.id))}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});x.get("/api/runbooks",async(t,e)=>{try{let n=t.query.track||void 0;if(!n&&t.query.cwd){let r=await ct(),o=String(t.query.cwd);if(n=r.find(s=>s.cwd&&(o===s.cwd||o.startsWith(s.cwd+ut.sep)))?.id,!n)return e.json({runbooks:[]})}e.json({runbooks:await qs({trackId:n,status:t.query.status||void 0})})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/runbooks/:id",async(t,e)=>{let n=await lc(t.params.id);if(!n)return e.status(404).json({error:"runbook not found"});e.json({runbook:n})});x.get("/api/runbooks/:id/md",async(t,e)=>{let n=await lc(t.params.id);if(!n)return e.status(404).json({error:"runbook not found"});let r=kp(n,t.query.v??null);if(!r)return e.status(404).json({error:"no such version"});e.set("content-security-policy","sandbox"),e.set("cache-control","private, max-age=31536000, immutable"),e.type("text/markdown; charset=utf-8"),e.sendFile(nr(n,r.v),o=>{o&&!e.headersSent&&e.status(404).json({error:"runbook artifact missing"})})});x.post("/api/runbooks",async(t,e)=>{let{trackId:n,name:r,md:o,cwd:s}=t.body||{};try{let a=n?await Q(n):null;if(!a&&s&&(a=(await ct()).find(u=>u.cwd&&(s===u.cwd||s.startsWith(u.cwd+ut.sep)))||null),!a)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});let i=(a.boxes||[]).find(l=>l.name===a.leadBox),c=await vp({trackId:a.id,trackName:a.name,name:r,md:o,cwd:a.cwd,box:a.leadBox,agent:i?.agent||null});e.json({ok:!0,runbook:c})}catch(a){let i=a?.message||String(a);e.status(/required|invalid|over the|secret VALUE/i.test(i)?400:500).json({error:i})}});x.post("/api/runbooks/:id/update",async(t,e)=>{try{let{md:n,note:r}=t.body||{};e.json({ok:!0,runbook:await Tp(t.params.id,{md:n,note:r})})}catch(n){let r=n?.message||String(n),o=/not found/i.test(r)?404:/orphaned/i.test(r)?410:/required|invalid|over the|secret VALUE/i.test(r)?400:500;e.status(o).json({error:r})}});x.post("/api/runbooks/:id/status",async(t,e)=>{try{e.json({ok:!0,runbook:await Ap(t.params.id,(t.body||{}).status)})}catch(n){let r=n?.message||String(n),o=/not found/i.test(r)?404:/orphaned/i.test(r)?410:/must be/i.test(r)?400:500;e.status(o).json({error:r})}});x.delete("/api/runbooks/:id",async(t,e)=>{try{e.json(await _p(t.params.id))}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});x.get("/api/changelog/draft",async(t,e)=>{try{let n=By(t.query.since||null);e.json({since:n,repos:Mu(),prs:await Dy(n)})}catch(n){let r=n?.message||String(n);e.status(/unparseable/i.test(r)?400:500).json({error:r})}});x.post("/api/changelog/publish",async(t,e)=>{let{md:n,day:r,trackId:o,cwd:s}=t.body||{};try{let a=o?await Q(o):null;if(!a&&s&&(a=(await ct()).find(c=>c.cwd&&(s===c.cwd||String(s).startsWith(c.cwd+ut.sep)))||null),!a)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});e.json({ok:!0,entry:await jy(a,{md:n,day:r})})}catch(a){let i=a?.message||String(a);e.status(/required|invalid|no `- `|over the|secret VALUE/i.test(i)?400:500).json({error:i})}});var Q$=120,Wu=400,Gu=null;x.post("/api/notify",async(t,e)=>{let{title:n,body:r,urgency:o,cwd:s,trackId:a}=t.body||{};try{let i=typeof r=="string"?r.trim():"";if(!i)return e.status(400).json({error:"body (the message) required"});if(i.length>Wu)return e.status(400).json({error:`message is ${i.length} chars \u2014 over the ${Wu} cap; a notification is a nudge, put the detail in chat`});let c=uo(`${n||""}
|
|
389
|
+
${i}`);if(c.length)return e.status(400).json({error:`notification appears to contain a secret VALUE (${c.join(", ")}) \u2014 notifications ship off-box; never put a credential in one`});let l=a?await Q(a):null;if(!l&&s&&(l=(await 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,Wu),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:Gu?Gu.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 fo(),terms:await Fu(),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 iu({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(Ho(t.params.id)),subtasks:$r(t.params.id).map(rs)})});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 Ac(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 ru(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}
|
|
349
390
|
data: ${JSON.stringify(i)}
|
|
350
391
|
|
|
351
|
-
`),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
|
|
352
393
|
|
|
353
|
-
`),25e3);t.on("close",()=>{clearInterval(a),s()})});x.get("/api/tracks/:id/search",async(t,e)=>{let n=await
|
|
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=no(await L(n)),a=Es(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||!Gl(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(Kl(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?Ni(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 au(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:Da(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 ou(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 Ga(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:rs(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(rs)})});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:rs(pu(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:rs(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 rs(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 uu(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(Ho(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:hs(),paths:Ed,trace:await _d({limit:n})})});var qu=ut.resolve(Tb,"..","..","client","dist");Bt.existsSync(qu)&&(x.use(ge.static(qu)),x.get(/^(?!\/api\/).*/,(t,e)=>e.sendFile(ut.join(qu,"index.html"))));var $b=V$.createServer(x),nO=new X$({noServer:!0}),Ob=(t,e,n)=>{if(!(Jo(t)||Ur.allowed(Ur.clientIp(t)))){e.write(`HTTP/1.1 403 Forbidden\r
|
|
354
396
|
\r
|
|
355
|
-
`),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
|
|
356
398
|
\r
|
|
357
|
-
`),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
|
|
358
400
|
\r
|
|
359
|
-
`),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
|
|
360
402
|
\r
|
|
361
|
-
`),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||Ku.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,ni,()=>{console.log(`[bostrat-agent] https listening on ${ni||"0.0.0.0"}:${xb}`)})}$b.listen(bb,ni,()=>{console.log(`[bostrat-agent] listening on ${ni||"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:iu,archiveTrack:Rr,listRepos:ye,resolveTrack:Q,sayToLead:Qt,answerPrompt:ou,startLeadHandoff:uu,interruptLead:au,listSubtaskJobs:$r,cancelSubtask:pu,readEvents:L,appendEvent:O,knownAgents:Na}),rO(),Yr().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&&(Gu=Qg({url:process.env.BOSTRAT_PLANE_URL,token:process.env.BOSTRAT_PLANE_TOKEN,nodeId:process.env.BOSTRAT_NODE_ID||Ku.hostname(),name:process.env.BOSTRAT_NODE_NAME||Ku.hostname(),listTracks:ct,detectAgents:nu}),console.log(`[bostrat-agent] plane link \u2192 ${process.env.BOSTRAT_PLANE_URL}`))});function rO(){oO(),Hs().catch(()=>{}),setInterval(()=>Hs().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 ru(n.id).catch(()=>{}),await jw(n).catch(()=>{}))}catch{}finally{t=!1}}},4e3)}
|