bostrat-node 0.2.5 → 0.2.7
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/bin/bostrat-changelog +2 -0
- package/bin/bostrat-git-credential +5 -5
- package/bin/bostrat-gmail +2 -0
- package/bin/gh +13 -2
- package/dist/cli.js +23 -23
- package/dist/index.js +150 -116
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,109 +1,139 @@
|
|
|
1
|
-
var
|
|
1
|
+
var tb=Object.defineProperty;var $u=(t,e)=>{for(var n in e)tb(t,n,{get:e[n],enumerable:!0})};import ct from"node:path";import Mt from"node:fs";import qR from"node:readline";import{fileURLToPath as GR}from"node:url";import fe from"express";import KR from"node:http";import VR from"node:https";import Eu from"node:os";import{WebSocketServer as zR}from"ws";import{execFile as hb}from"node:child_process";import{promisify as wb}from"node:util";import Qa from"node:fs";import{randomUUID as gb}from"node:crypto";import Mn from"node:fs/promises";import Nu from"node:os";import rn from"node:path";import{createHash as eb,randomBytes as nb,timingSafeEqual as rb}from"node:crypto";var Xo="BOSTRAT_HOOK_TOKEN",Yo="x-bostrat-hook";function Ou(){return nb(32).toString("hex")}function Nr(t){let e=typeof t=="string"?t.trim():"";return e?eb("sha256").update(e).digest("hex"):null}function Cu(t,e){return typeof t!="string"||typeof e!="string"||t.length!==e.length||!t?!1:rb(Buffer.from(t),Buffer.from(e))}var za=new Map,Ja=new Map,ob=6e4;function Zo(t,e={},{now:n=Date.now(),log:r=console.warn}={}){if(za.set(t,(za.get(t)||0)+1),Ja.has(t)&&n-Ja.get(t)<ob)return!1;Ja.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=${za.get(t)}${o?` ${o}`:""}`),!0}async function Mu(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 Qo(){return/^(1|true|yes|on)$/i.test(process.env.BOSTRAT_CLAUDE_USE_API_KEY||"")&&process.env.ANTHROPIC_API_KEY?.trim()||null}function sb(){return process.env.CLAUDE_CONFIG_DIR?rn.join(process.env.CLAUDE_CONFIG_DIR,".claude.json"):rn.join(Nu.homedir(),".claude.json")}function ab(){return process.env.CLAUDE_CONFIG_DIR?rn.join(process.env.CLAUDE_CONFIG_DIR,"settings.json"):rn.join(Nu.homedir(),".claude","settings.json")}async function Ee(t){if(!t||typeof t!="string")return;let e=rn.resolve(t),n=sb(),r=ab(),o={};try{o=JSON.parse(await Mn.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 Mn.mkdir(rn.dirname(n),{recursive:!0}),await Mn.writeFile(n,JSON.stringify(o,null,2));let i={};try{i=JSON.parse(await Mn.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 "${Yo}: $${Xo}" --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 Mn.mkdir(rn.dirname(r),{recursive:!0}),await Mn.writeFile(r,JSON.stringify(i,null,2))}import Xa from"node:fs/promises";import ib from"node:os";import ts from"node:path";function cb(){let t=process.env.CODEX_HOME||ts.join(ib.homedir(),".codex");return ts.join(t,"config.toml")}function Pu(t){return`[projects."${t}"]`}function lb(t,e){let n=Pu(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 Nn(t){if(!t||typeof t!="string")return;let e=ts.resolve(t),n=cb(),r="";try{r=await Xa.readFile(n,"utf8")}catch{}if(lb(r,e))return;let o=Pu(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
2
|
trust_level = "trusted"
|
|
3
|
-
`;r=r.slice(0,a)+
|
|
3
|
+
`;r=r.slice(0,a)+f+r.slice(l)}else{let s=`
|
|
4
4
|
${o}
|
|
5
5
|
trust_level = "trusted"
|
|
6
6
|
`;r=r.trimEnd()+s+(r.length&&!r.endsWith(`
|
|
7
7
|
`)?`
|
|
8
|
-
`:"")}try{await
|
|
9
|
-
`,"utf8")}import
|
|
10
|
-
`,"utf8");let n=(await
|
|
11
|
-
`).filter(Boolean);n.length>
|
|
8
|
+
`:"")}try{await Xa.mkdir(ts.dirname(n),{recursive:!0}),await Xa.writeFile(n,r)}catch(s){console.warn(`[codex-config] could not seed folder trust for ${e}: ${s?.message||s}`)}}import Bu from"node:fs/promises";import ub from"node:os";import Pr from"node:path";function db(t){let n=Pr.resolve(t).slice(1).replace(/\//g,"-").replace(/\./g,""),r=process.env.CURSOR_CONFIG_DIR||Pr.join(ub.homedir(),".cursor");return Pr.join(r,"projects",n)}async function Pn(t){if(!t||typeof t!="string")return;let e=Pr.resolve(t),n=db(e),r=Pr.join(n,".workspace-trusted");await Bu.mkdir(n,{recursive:!0}),await Bu.writeFile(r,`${JSON.stringify({trustedAt:new Date().toISOString(),workspacePath:e},null,2)}
|
|
9
|
+
`,"utf8")}import Br from"node:fs/promises";import fb from"node:os";import ju from"node:path";var pb=[/[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 Ya(t){let e=String(t??"");for(let n of pb)e=e.replace(n,"\u2039redacted\u203A");return e}var Za=process.env.BOSTRAT_DEBUG_DIR||ju.join(fb.homedir(),".bostrat","debug"),Bn=ju.join(Za,"handoff.jsonl"),Du=Number(process.env.BOSTRAT_HANDOFF_DEBUG_MAX)||500;function es(){let t=process.env.BOSTRAT_HANDOFF_DEBUG;return t==="1"||t==="true"}async function mb(t){try{await Br.mkdir(Za,{recursive:!0}),await Br.appendFile(Bn,`${JSON.stringify(t)}
|
|
10
|
+
`,"utf8");let n=(await Br.readFile(Bn,"utf8")).split(`
|
|
11
|
+
`).filter(Boolean);n.length>Du&&await Br.writeFile(Bn,`${n.slice(-Du).join(`
|
|
12
12
|
`)}
|
|
13
|
-
`,"utf8")}catch(e){console.warn(`[handoff-debug] failed to write ${
|
|
14
|
-
`).filter(Boolean).slice(-t).map(r=>{try{
|
|
15
|
-
`).filter(Boolean).map(r=>{let[o,s,a,i,c,l,u,d
|
|
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
|
|
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
|
-
`))jy.test(n)&&(e=Math.max(e,n.trimEnd().length));return e}function Ha(t,e){let n=xt[t];if(!n?.busy)return"idle";if(Wa(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=Ly(e);return t==="claude"&&r>0&&r<Dy?"unknown":"idle"}var Fy=/\bWaiting for \d+\b[^\n]*\bto finish\b|\/workflows to (?:stop|monitor)\b/;function Iu(t,e){return t==="claude"&&Fy.test(String(e||""))}function jn(t,e){return Ha(t,e)==="busy"}function Ho(t,e){return Ha(t,e)!=="idle"}function Wo(t,e){let n=xt[t];return n?.up?n.up.test(e):!0}function Uy(t,e){let n=xt[t];return n?Wa(t,e)?!1:n.up.test(e)&&Ha(t,e)==="idle":!0}function Wa(t,e){let n=xt[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 Eu(t,e,n){let r=Date.now()+n,o=0;for(await tn(t).catch(()=>{});;){let s=await wt(t);if(Uy(e,s)){await Ae(Mr);return}if(e==="cursor"&&My.test(s)&&!xt.cursor.up.test(s)&&o<3){if(!_u(t,"cursor-trust")){await Ae(Ua);continue}o+=1,await q({flow:"agent-ready",phase:"gate-accept",sessionName:t,agent:e,attempt:o}).catch(()=>{}),await kt(t,"a",{enter:!1,kind:"cursor-trust-accept"}),await Ae(Mr);continue}if(e==="codex"&&o<3){let a=Ny(s);if(a){if(!_u(t,`codex-update:${a.from}->${a.to}`)){await Ae(Ua);continue}o+=1,await q({flow:"agent-ready",phase:"gate-accept",sessionName:t,agent:e,attempt:o,update:`${a.from} -> ${a.to}`}).catch(()=>{}),await kt(t,"3",{enter:!1,kind:"codex-update-skip"}),bu({sessionName:t,from:a.from,to:a.to}),await Ae(Mr);continue}}if(Date.now()>r){if(Wa(e,s)){let a=`${e} is blocked on an interactive prompt`;throw e==="codex"?a=xt.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"&&xt.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"&&xt.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"&&xt.claude.block.test(s)&&(a='claude is waiting on its "Press Enter to continue" screen \u2014 answer the Continue card in chat (or press Enter in the Terminal tab), then send again'),q({flow:"agent-ready",phase:"blocked",sessionName:t,agent:e,timeoutMs:n,gateAccepts:o,error:a}).catch(()=>{}),new Error(a)}console.warn(`[agent-ready] ${e} readiness unconfirmed in ${t} after ${n}ms \u2014 driving the box anyway.`),q({flow:"agent-ready",phase:"timeout",sessionName:t,agent:e,timeoutMs:n,gateAccepts:o,paneBlocked:!!xt[e]?.block?.test(s),paneUp:!!xt[e]?.up?.test(s),paneBusy:!!xt[e]?.busy?.test(s)}).catch(()=>{}),await Ae(Mr);return}await Ae(Ua)}}function Ru({agent:t,agentCmd:e}={}){let n=e?Pn(e):Pn(t||"");return{label:n,realClaude:e?Mn(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 qo({cwd:t,sessionName:e,agent:n,agentCmd:r}={}){let{realCodex:o,realCursor:s}=Ru({agent:n,agentCmd:r});return!e||!o&&!s?null:Te({cwd:t,sessionName:e,agent:n,agentCmd:r}).catch(()=>{})}async function Te({cwd:t,sessionId:e,sessionName:n,agent:r,agentCmd:o,timeoutMs:s=Cy()}={}){let{label:a,realClaude:i,realCodex:c,realCursor:l}=Ru({agent:r,agentCmd:o});if(i&&t&&await xe(t),c&&t&&await $n(t),l&&t&&await On(t),(i||c||l)&&n){await Eu(n,a,s);return}let u=xt[r]?r:xt[a]?a:null;if(process.env.BOSTRAT_FAKE_POLL_READY==="1"&&n&&u){await Eu(n,u,s);return}await Ae(Mr)}var Hy=(()=>{let t=Number(process.env.BOSTRAT_CODEX_SUBMIT_SETTLE_MS??120);return Number.isFinite(t)&&t>=0?t:120})();function le(t){return t==="codex"?{enter:!0,submitDelayMs:Hy}:{enter:!0}}var Cu=".bostrat/handoff",Mu="latest.md",Pu="Checkpoint: write a work-in-progress handoff",Nu="HANDOFF WRITTEN",Bu="Resume from .bostrat/handoff/latest.md",qy="/clear",Gy=t=>`${Pu} 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 ${Nu} and take no further action.`,Ky=`${Bu}: read it, restate the Objective and the single next step in one line, then continue the work from 'Next steps'.`,Du="Beat: write a",ju="BEAT WRITTEN",Vy=".bostrat/arc",Jy="beat.txt",$u=200,Lu=4,Fu=5,zy=36,Xy=t=>`${Du} 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 ${Lu} further lines, each starting with "- " and naming ONE piece in at most ${Fu} 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 ${ju} and take no further action.`,Ou=/^(?:[-–—*•·]|\d+[.)])\s+/,Yy=(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 Zy(t){let e=String(t||"").split(/\r?\n/).map(s=>s.trim()).filter(Boolean),n=[],r=[];for(let s of e)if(Ou.test(s)){if(r.length>=Lu)continue;let a=Yy(s.replace(Ou,"").replace(/[.;,:]+$/,""),Fu,zy);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>$u&&(o=`${o.slice(0,$u-1).trimEnd()}\u2026`),{text:o,items:r.length>1?r:[]}}async function Uu(t,e,{timeoutMs:n=9e4}={}){let r=Ln.join(e,Vy),o=Ln.join(r,Jy);await Pr.mkdir(r,{recursive:!0});let s=await Go(o),a=await z(t);if(!a)throw new Error(`session "${t}" not found`);return await Ko(t,a,Xy(o)),await Ju(o,s,n),Zy(await Pr.readFile(o,"utf8"))}function Qy(t,e){let n=(e||"").trim();return n?t==="assistant"?n===Nu||n===ju:n.startsWith(Pu)||n===qy||n.startsWith(Bu)||n.startsWith(Du):!1}var tb=6e5;function Hu(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-tb;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 Wu(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 qu(t,{windows:e=[]}={}){if(Qy(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 eb=3500,nb=3e5,qa=new Map;function Vo(t){return qa.get(t)||null}async function Gu(t){let e=qa.get(t);if(e&&!e.finishedAt)throw new Error("a handoff is already running for this session");let n=await z(t);if(!n)throw new Error(`session "${t}" not found`);if(!n.cwd)throw new Error(`can't resolve cwd for "${t}"`);let r={name:t,step:"summarize",startedAt:Date.now(),finishedAt:null,error:null,file:Ln.join(n.cwd,Cu,Mu)};return qa.set(t,r),rb(r,n.cwd).catch(o=>{r.error=o?.message||String(o),r.finishedAt=Date.now()}),r}async function rb(t,e){t.step="summarize",await Ku(t.name,e),t.step="clear";let n=await z(t.name);await Ko(t.name,n,"/clear"),await zu(eb),t.step="resume",await Ko(t.name,n,Ky),t.step="done",t.finishedAt=Date.now()}async function Ku(t,e){let n=Ln.join(e,Cu),r=Ln.join(n,Mu);await Pr.mkdir(n,{recursive:!0});let o=await Go(r),s=await z(t);return await q({flow:"checkpoint",phase:"summarize-start",sessionName:t,cwd:e,file:r,beforeMtime:o}),await Ko(t,s,Gy(r)),await Ju(r,o,nb),await q({flow:"checkpoint",phase:"summarize-done",sessionName:t,file:r,afterMtime:await Go(r)}),await ob(n,r),r}async function Vu(t){let e=await z(t);if(!e)throw new Error(`session "${t}" not found`);if(!e.cwd)throw new Error(`can't resolve cwd for "${t}"`);return Ku(t,e.cwd)}async function Go(t){try{return(await Pr.stat(t)).mtimeMs}catch{return 0}}async function Ju(t,e,n){let r=Date.now()+n;for(;;){if(await Go(t)>e)return;if(Date.now()>r)throw new Error(`timed out waiting for the handoff file to be written (${t})`);await zu(2e3)}}async function ob(t,e){try{if(!Wy.existsSync(e))return;let n=new Date().toISOString().replace(/[:.]/g,"-");await Pr.copyFile(e,Ln.join(t,`${n}.md`))}catch{}}function zu(t){return new Promise(e=>setTimeout(e,t))}async function Ko(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 Te({cwd:e.cwd,sessionId:e.sessionId,sessionName:t,agent:e.agent,agentCmd:o})}let r=await z(t);if(!r)throw new Error(`session "${t}" disappeared before the orchestration prompt could be sent`);await kt(t,n,{kind:"orchestration-drive",...le(r.agent)})}import D from"node:fs/promises";import j from"node:path";import{randomUUID as mo}from"node:crypto";import{execFile as $v}from"node:child_process";import{promisify as Ov}from"node:util";import Cf from"node:path";import Ga from"node:fs/promises";import Fn from"node:path";import sb from"node:os";import{EventEmitter as ab}from"node:events";import{randomUUID as ib}from"node:crypto";var cb=new Set(["session.ready","turn.user","turn.agent","turn.retracted","tool.called","tool.result","prompt.raised","prompt.answered","context.usage","cost.recorded","handoff.started","handoff.step","handoff.done","handoff.failed","subtask.delegated","subtask.merged","job.started","pr.state.changed","pr.offered","pr.offer.answered","box.state.changed","view.state.changed","view.spinup.failed","mock.state.changed","runbook.state.changed","ticket.linked","ticket.sync.failed","ticket.move.offered","ticket.move.answered","ticket.state.changed","ticket.commented","ticket.flow.changed","ticket.create.offered","ticket.create.answered","ticket.created","notify.raised","provider.connected","track.summary.recorded","stall.detected","error"]),lb=new Set(["user","agent","orchestrator","system"]),ub=new Set(["hook","otel","statusline","transcript","pane","orchestrator","vendor"]),jt=new ab;jt.setMaxListeners(0);var Ka=process.env.BOSTRAT_STATE_DIR||Fn.join(process.env.HOME||sb.tmpdir(),".bostrat");function zo(t){return t?.cwd?Fn.join(t.cwd,".bostrat","events"):Fn.join(Ka,"events")}var Xu=(t,e)=>Fn.join(t,`${e}.ndjson`),Jo=new Map;async function db(t,e){if(!Jo.has(e)){let o=(await F(t,e)).reduce((s,a)=>a.seq>s?a.seq:s,0);Jo.set(e,o)}let n=Jo.get(e)+1;return Jo.set(e,n),n}function fb(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||!cb.has(e.type))throw new Error(`appendEvent: unknown event type "${e?.type}"`);if(e.actor&&!lb.has(e.actor))throw new Error(`appendEvent: bad actor "${e.actor}"`);if(e.source&&!ub.has(e.source))throw new Error(`appendEvent: bad source "${e.source}"`);let r=typeof t=="string"?Fn.join(Ka,"events"):zo(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=fb(c,Number(e.payload?.ts??e.ts)||Date.now())}let s=await db(r,n),a=e.userId??process.env.BOSTRAT_OWNER??null,i={id:e.id||ib(),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 Ga.mkdir(r,{recursive:!0}),await Ga.appendFile(Xu(r,n),JSON.stringify(i)+`
|
|
31
|
-
`)
|
|
32
|
-
|
|
33
|
-
|
|
13
|
+
`,"utf8")}catch(e){console.warn(`[handoff-debug] failed to write ${Bn}: ${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&&es())try{e.paneTail=Ya((await mt(e.sessionName,{lines:35})).slice(-4e3))}catch{e.paneTail=null}(es()||e.error||/^(checkpoint|spawn|hydrate|commit|done|summarize|clear|resume|ready|send|failed)/.test(String(e.phase||e.step||"")))&&await mb(e)}async function Lu({limit:t=80}={}){try{return(await Br.readFile(Bn,"utf8")).split(`
|
|
14
|
+
`).filter(Boolean).slice(-t).map(r=>{try{let o=JSON.parse(r);return typeof o?.paneTail=="string"&&(o.paneTail=Ya(o.paneTail)),o}catch{return{raw:Ya(r)}}})}catch{return[]}}var Fu={DEBUG_DIR:Za,LOG_FILE:Bn};var it=wb(hb);function Dr(t){return new Promise(e=>setTimeout(e,t))}function ns(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",Nt=process.env.BOSTRAT_TMUX_SOCKET||"/tmp/tmux-1000/default",yb=process.env.BOSTRAT_DEFAULT_CWD||process.env.HOME||"/",ti=process.env.BOSTRAT_AGENT_CMD||"claude --dangerously-skip-permissions",ei="bostrat-usage-probe",bb=new Set([ei]);function ni(t,e){let n=Dn(t),r=/(^|\s)(--session-id|--resume|--continue|-r|-c)(\s|=|$)/.test(t);return!n||r?t:`${t} --session-id ${e}`}function Dn(t){return/(^|\/|\s)claude(\s|$)/.test(String(t||""))}function jn(t){return/(^|\/|\s)claude(\s|$)/.test(t)?"claude":/cursor/.test(t)?"cursor":/codex/.test(t)?"codex":/gemini/.test(t)?"gemini":"agent"}async function ri(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 it(r,[o,'command -v "$1" >/dev/null 2>&1',"_",t],{timeout:5e3}),!0}catch(s){return s?.code===1?!1:n}}async function rs(t){return ri(String(t||"").trim().split(/\s+/)[0])}var oi={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",Nt,...t]}async function Ie(){if(!Qa.existsSync(Nt))return!1;try{return await it(N,B("list-sessions")),!0}catch(t){let e=t&&t.stderr?String(t.stderr):"";return!/no server running/i.test(e)}}async function jr(){if(await Ie())return!0;try{Qa.mkdirSync(Nt.replace(/\/[^/]+$/,""),{recursive:!0}),await it(N,B("new-session","-d","-s","keeper","sleep 2147483647"))}catch{}return await Ie()}var kb=/^[A-Za-z0-9_.-]{1,64}$/;function ht(t){return typeof t=="string"&&kb.test(t)}async function on(){if(!Qa.existsSync(Nt))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 it(N,B("list-sessions","-F",t)),n=Date.now();return e.trim().split(`
|
|
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=>!bb.has(r.name)&&!r.name.startsWith("bostrat-view-"))}catch{return[]}}async function J(t){return(await on()).find(n=>n.name===t)||null}var xb=/^[A-Za-z0-9 ._↻-]+$/,Sb=64;function vb(t){if(typeof t!="string")return!1;let e=t.trim();return e.length>=1&&e.length<=Sb&&xb.test(e)}function si(t){return t.trim().replace(/\s+/g,"-").toLowerCase().replace(/[^A-Za-z0-9_.-]/g,"").replace(/^-+|-+$/g,"")}async function Ab(t){try{let{stdout:e}=await it(N,B("list-sessions","-F","#{session_name}"));return e.split(`
|
|
16
|
+
`).map(n=>n.trim()).includes(t)}catch{return!1}}async function sn(t,e=yb,n={}){if(!vb(t))throw new Error("displayName must be 1\u201364 chars (letters, digits, space . _ -)");let r=t.trim(),o=si(r);if(!ht(o))throw new Error("resulting tmux session name is invalid");if(!await jr())throw new Error(`host tmux server not running on ${Nt} and auto-start failed \u2014 start one: ${N} -S ${Nt} new-session -d -s keeper "sleep 2147483647"`);if(await Ab(o))throw new Error(`tmux session "${o}" already exists`);let s=n.agentCmd||ti,a=n.agent||jn(s),i=!!n.resumeSessionId,c=n.resumeSessionId||gb(),l=i?s:ni(s,c),u=i?n.pinned===!0:/(^|\s)--session-id(\s|=)/.test(l),f=a==="claude"&&Dn(s),d=n.firstRun===!0;d||(f&&await Ee(e),a==="codex"&&await Nn(e),a==="cursor"&&await Pn(e));let m=f&&!d?Qo():null,h=Ou(),w=["-e",`${Xo}=${h}`];m&&w.push("-e",`ANTHROPIC_API_KEY=${m}`);try{await it(N,B("new-session","-d","-s",o,"-c",e,...w))}catch(g){throw ns(m?ns(g,m):g,h)}return await an(o),await it(N,B("set-option","-t",o,"@bostrat-session","1")),await it(N,B("set-option","-t",o,"@bostrat-title",r)),await it(N,B("set-option","-t",o,"@bostrat-sid",c)),await it(N,B("set-option","-t",o,"@bostrat-agent",a)),await it(N,B("send-keys","-t",o,l,"Enter")),{name:o,title:r,cwd:e,sessionId:c,agent:a,pinned:u,hookToken:h}}async function Xt(t){if(!ht(t))throw new Error("invalid session name");await it(N,B("kill-session","-t",t))}async function Uu(t,e){if(ht(t))try{let n=e?B("set-option","-t",t,"@bostrat-sid",e):B("set-option","-u","-t",t,"@bostrat-sid");await it(N,n)}catch{}}async function mt(t,{lines:e=40}={}){if(!ht(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 it(N,n);return String(r||"")}catch{return""}}var Tb=Number(process.env.BOSTRAT_PROMPT_CAPTURE_COLS)||100,_b=Number(process.env.BOSTRAT_PROMPT_CAPTURE_ROWS)||40,St=Number(process.env.BOSTRAT_PROMPT_CAPTURE_LINES)||80,Eb=Number(process.env.BOSTRAT_BOX_COLS)||120,Ib=Number(process.env.BOSTRAT_BOX_ROWS)||40;async function Hu(t){if(!ht(t))return 0;try{let{stdout:e}=await it(N,B("list-clients","-t",t,"-F","1"));return String(e||"").trim().split(`
|
|
17
|
+
`).filter(Boolean).length}catch{return 0}}async function an(t,{cols:e=Eb,rows:n=Ib}={}){if(ht(t)&&!await Hu(t))try{let r=await qu(t);if(r.cols===e&&r.rows===n)return;await it(N,B("resize-window","-t",t,"-x",String(e),"-y",String(n))),await it(N,B("set-window-option","-t",t,"window-size","latest")),await Dr(80)}catch{}}async function Wu(t){if(!ht(t))return 0;try{let{stdout:e}=await it(N,B("display-message","-t",t,"-p","#{pane_pid}"));return Number(String(e||"").trim())||0}catch{return 0}}async function qu(t){if(!ht(t))return{cols:0,rows:0};try{let{stdout:e}=await it(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 os(t,{cols:e=Tb,rows:n=_b}={}){if(ht(t)&&!await Hu(t))try{let r=await qu(t),o=Math.max(r.cols,e),s=Math.max(r.rows,n);(o>r.cols||s>r.rows)&&(await it(N,B("resize-window","-t",t,"-x",String(o),"-y",String(s))),await it(N,B("set-window-option","-t",t,"window-size","latest")),await Dr(80))}catch{}}async function Re(t){return await os(t),mt(t,{lines:St})}var Rb=new Set(["Up","Down","Left","Right","Space","Enter","Tab","BTab","Escape","BSpace"]);async function bt(t,e,{settleMs:n=40,kind:r}={}){if(!ht(t))throw new Error("invalid session name");let o=(Array.isArray(e)?e:[e]).filter(s=>Rb.has(s));for(let s=0;s<o.length;s++)await it(N,B("send-keys","-t",t,o[s])),n>0&&s<o.length-1&&await Dr(n);return G({flow:"tmux",phase:"send-keys",sessionName:t,kind:r||"keys",keys:o.join(" ")}).catch(()=>{}),o.length}async function ss(t,e,{kind:n}={}){if(!ht(t))throw new Error("invalid session name");let r=Math.max(0,Math.floor(Number(e)||0));return r>0&&await it(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 as(t,e,{perCharDelayMs:n=120,kind:r}={}){if(!ht(t))throw new Error("invalid session name");let o=[...String(e)];for(let s of o)await it(N,B("send-keys","-t",t,"-l",s)),n>0&&await Dr(n);G({flow:"tmux",phase:"type",sessionName:t,kind:r||"type",textLen:o.length,textHead:String(e).slice(0,120)}).catch(()=>{})}async function xt(t,e,{enter:n=!0,submitDelayMs:r=0,kind:o}={}){if(!ht(t))throw new Error("invalid session name");if(typeof e=="string"&&e.length>0&&await it(N,B("send-keys","-t",t,"-l",e)),n){let s=Number(r);Number.isFinite(s)&&s>0&&await Dr(s),await it(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 $b from"node-pty";async function Ku(t,e,n={}){if(!ht(e)){is(t,`invalid session name: ${e}`),t.close(1008,"invalid session name");return}if(!await Ie()){is(t,`host tmux server not running on ${Nt}`),t.close(1011,"tmux server unavailable");return}if(!await J(e)){is(t,`session "${e}" not found`),t.close(1008,"session not found");return}let r=Gu(n.cols,120,Vu),o=Gu(n.rows,32,zu),s;try{s=$b.spawn(N,["-S",Nt,"attach-session","-d","-t",e],{name:"xterm-256color",cols:r,rows:o,cwd:process.env.HOME||"/",env:{...process.env,TERM:"xterm-256color"}})}catch(a){is(t,`failed to spawn tmux attach: ${a.message}`),t.close(1011,"spawn failed");return}s.onData(a=>{t.readyState===t.OPEN&&ai(t,{type:"data",data:a})}),s.onExit(({exitCode:a,signal:i})=>{t.readyState===t.OPEN&&(ai(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"&&Ob(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{}an(e).catch(()=>{})})}var Vu=20,zu=6;function Ob(t,e){return Number.isFinite(Number(t))&&Number.isFinite(Number(e))&&Number(t)>=Vu&&Number(e)>=zu}function Gu(t,e,n){let r=Number(t);return!Number.isFinite(r)||r<n?e:Math.min(500,r|0)}function ai(t,e){try{t.send(JSON.stringify(e))}catch{}}function is(t,e){ai(t,{type:"error",message:e})}function Ju(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 Lr from"node:fs";import Cb from"node:fs/promises";import Ln from"node:path";import Mb from"node:os";var Xu={"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},Nb=2e5,ii=process.env.BOSTRAT_CLAUDE_PROJECTS||Ln.join(Mb.homedir(),".claude","projects");function ci(t){return t.replace(/[^A-Za-z0-9]/g,"-")}function li(t){let e=Ln.join(ii,ci(t)),n;try{n=Lr.readdirSync(e).filter(o=>o.endsWith(".jsonl"))}catch{return null}let r=null;for(let o of n){let s=Ln.join(e,o);try{let{mtimeMs:a}=Lr.statSync(s);(!r||a>r.mtimeMs)&&(r={full:s,mtimeMs:a})}catch{}}return r?r.full:null}function pe(t,e){if(!e)return null;let n=Ln.join(ii,ci(t),`${e}.jsonl`);return Lr.existsSync(n)?n:null}function Yu(t){if(!t)return[];let e=Ln.join(ii,ci(t)),n;try{n=Lr.readdirSync(e).filter(o=>o.endsWith(".jsonl"))}catch{return[]}let r=[];for(let o of n)try{let s=Lr.statSync(Ln.join(e,o));r.push({id:o.slice(0,-6),birthMs:s.birthtimeMs||s.ctimeMs||s.mtimeMs})}catch{}return r}async function It(t,e=131072){let n=await Cb.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 Pb(t){let e=t.split(`
|
|
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 Fr(t,e=null,{fallbackNewest:n=!0}={}){if(!t)return null;let r,o;if(e){if(r=pe(t,e),o=!0,!r)return null}else if(n){if(r=li(t),o=!1,!r)return null}else return null;let s;try{s=Pb(await It(r))}catch{return null}if(!s)return null;let a=Object.prototype.hasOwnProperty.call(Xu,s.model),i=Xu[s.model]||Nb;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 Bb(t,e){return!t||!e?null:pe(t,e)}function Db(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(`
|
|
19
|
+
`).trim():""}function jb(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 Lb(t){return Array.isArray(t)&&t.length>0&&t.every(e=>e&&e.type==="tool_result")}function ui(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 Zu(t,e){if(!t.uuid)return null;let n=ui(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 Fb=/\.(png|jpe?g|gif|webp|bmp|svg|tiff?|avif|heic|heif|ico)$/i;async function di(t,{maxBytes:e=524288,limit:n=80}={}){let r;try{r=await It(t,e)}catch{return[]}let o=[],s=new Map;for(let a of r.split(`
|
|
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"&&Fb.test(w)&&s.set(h.id,w.split("/").pop())}if(Lb(l.content)){let h=Zu(c,s);h&&o.push({role:"assistant",text:"",images:h,ts:c.timestamp?Date.parse(c.timestamp):null,model:null});continue}let f=Db(l.content),d=u==="assistant"?jb(l.content):null,m=u==="user"?Zu(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 Ub=(t,e)=>t.length>e?`${t.slice(0,e).trimEnd()}\u2026`:t;async function Qu(t,e,{turns:n=16}={}){let r=Bb(t,e);if(!r)return"";let o=await di(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}:** ${Ub(s.text||c,700)} ${i}`.trim()}).join(`
|
|
21
|
+
|
|
22
|
+
`):""}import Ik from"node:fs";import qr from"node:fs/promises";import Hn from"node:path";import Hb from"node:fs/promises";import{execFile as Wb}from"node:child_process";import{promisify as qb}from"node:util";var fi=qb(Wb),Gb=Number(process.env.BOSTRAT_CODEX_UPDATE_TIMEOUT_MS)||5*6e4,td=new Set,ed=Promise.resolve(),pi=null;function rd(t){pi=typeof t=="function"?t:null}function Kb(t){let e=String(t||""),n=e.indexOf("/lib/node_modules/");return n>0?e.slice(0,n):null}function od({sessionName:t,from:e,to:n}={}){let r=String(n||"").trim();!r||td.has(r)||(td.add(r),ed=ed.then(()=>Vb({sessionName:t,from:e,to:r})).catch(()=>{}))}async function Vb({sessionName:t,from:e,to:n}){await G({flow:"codex-update",phase:"start",sessionName:t,from:e,to:n}).catch(()=>{});try{let r=(await fi("/bin/sh",["-c","command -v codex"])).stdout.trim();if(!r)throw new Error("codex not on PATH");let o=await Hb.realpath(r),s=Kb(o),a=s?{...process.env,PATH:`${s}/bin:${process.env.PATH||""}`}:process.env;await fi("npm",["install","-g","@openai/codex"],{env:a,timeout:Gb});let i=(await fi(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 nd({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 nd({sessionName:t,from:e,to:n,status:"failed",error:o})}}async function nd(t){if(pi)try{await pi(t)}catch{}}var zb=/^\s*[›>*\-❯]?\s*(\d+)[.)]\s+(.+?)\s*$/,sd=/\b(allow|approve|apply (this )?patch|run (this )?command|do you want to run|grant)\b/i;function Jb(t,{provider:e,box:n}){let r=String(t).replace(/\r/g,"").split(`
|
|
23
|
+
`);if(!sd.test(t))return null;let o=[],s=!1;for(let l=r.length-1;l>=0;l--){let u=r[l].match(zb);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"&&sd.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 cs=/(^|\n)\s*›/,cd=/^\s*Welcome to Codex\b/;function Fn(t){let e=String(t??"").replace(/\r/g,""),n=e.split(`
|
|
24
|
+
`);for(let r=n.length-1;r>=0;r--)if(cd.test(n[r]))return n.slice(r).join(`
|
|
25
|
+
`);return e}function Xb(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 Yb(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 ad=/^\s*(>)?\s*(\d+)\.\s+(.+?)\s*$/;function Zb(t,e=""){let n=Fn(t);if(cs.test(n)||!/Sign in with ChatGPT/.test(n)||!/Press enter to continue/.test(n))return null;let r=n.split(`
|
|
26
|
+
`),o=[],s=-1;for(let l=0;l<r.length;l++){let u=r[l].match(ad);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(ad.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&&!cd.test(r[l])&&Xb(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=Yb(l.label);return{label:l.label,description:[l.desc,u].filter(Boolean).join(" \xB7 ")}})}]}}var id=/Enter this one-time code/,Qb=/^\s*([A-Z0-9]{2,10}-[A-Z0-9]{2,10})\s*$/;function tk(t,e=""){let n=Fn(t);if(cs.test(n)||!id.test(n))return null;let r=n.split(`
|
|
27
|
+
`),o=r.findIndex(i=>id.test(i)),s="";for(let i=o+1;i<r.length;i++){let c=r[i].match(Qb);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 ek(t,e=""){let n=Fn(t);return cs.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 nk(t,e=""){let n=Fn(t);return cs.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 mi=/Run Everything|Add a follow-up|Plan, search, build anything/;function rk(t,e=""){let n=String(t);if(!/Press any key to log in/.test(n)||mi.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
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 ok=/click this link to log in|Signing in with the browser/;function sk(t,e=""){let n=String(t);if(!ok.test(n)||mi.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
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 ak(t,e=""){let n=String(t);return!/Scan this QR code to log in/.test(n)||mi.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 ld(t,e,n=""){if(t!=="cursor"&&t!=="codex")return null;if(t==="codex"){let r=tk(e,n)||ek(e,n)||nk(e,n)||Zb(e,n);if(r)return r}if(t==="cursor"){let r=sk(e,n)||ak(e,n)||rk(e,n);if(r)return r}return Jb(e,{provider:t,box:n})}function ud(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 ik=()=>Number(process.env.BOSTRAT_AGENT_READY_TIMEOUT_MS)||9e4,hi=Number(process.env.BOSTRAT_AGENT_READY_POLL_MS)||500,Ur=Number(process.env.BOSTRAT_AGENT_READY_SETTLE_MS)||800,vt={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/}},ck=/Trust this workspace|\[a\]\s*Trust/,lk=/Update available!\s*([\d][\w.-]*)\s*->\s*([\d][\w.-]*)/;function uk(t){let e=String(t||"");if(!vt.codex.block.test(e))return null;let n=Fn(e);if(!vt.codex.block.test(n))return null;let r=n.match(lk);return{from:r?.[1]||null,to:r?.[2]||null}}function $e(t){return new Promise(e=>setTimeout(e,t))}var dk=()=>Number(process.env.BOSTRAT_GATE_RESEND_MS)||3e3,ls=new Map;function dd(t,e){let n=Date.now(),r=dk();for(let[s,a]of ls)n-a>=r&&ls.delete(s);let o=`${t}::${e}`;return ls.has(o)?!1:(ls.set(o,n),!0)}var fk=68,pk=/^\s*[─━]{10,}\s*$/;function mk(t){let e=0;for(let n of String(t).split(`
|
|
30
|
+
`))pk.test(n)&&(e=Math.max(e,n.trimEnd().length));return e}function wi(t,e){let n=vt[t];if(!n?.busy)return"idle";if(gi(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=mk(e);return t==="claude"&&r>0&&r<fk?"unknown":"idle"}var hk=/\bWaiting for \d+\b[^\n]*\bto finish\b|\/workflows to (?:stop|monitor)\b/;function pd(t,e){return t==="claude"&&hk.test(String(e||""))}function Oe(t,e){return wi(t,e)==="busy"}function us(t,e){return wi(t,e)!=="idle"}function ds(t,e){let n=vt[t];return n?.up?n.up.test(e):!0}function wk(t,e){let n=vt[t];return n?gi(t,e)?!1:n.up.test(e)&&wi(t,e)==="idle":!0}function gi(t,e){let n=vt[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 fd(t,e,n){let r=Date.now()+n,o=0;for(await an(t).catch(()=>{});;){let s=await mt(t);if(wk(e,s)){await $e(Ur);return}if(e==="cursor"&&ck.test(s)&&!vt.cursor.up.test(s)&&o<3){if(!dd(t,"cursor-trust")){await $e(hi);continue}o+=1,await G({flow:"agent-ready",phase:"gate-accept",sessionName:t,agent:e,attempt:o}).catch(()=>{}),await xt(t,"a",{enter:!1,kind:"cursor-trust-accept"}),await $e(Ur);continue}if(e==="codex"&&o<3){let a=uk(s);if(a){if(!dd(t,`codex-update:${a.from}->${a.to}`)){await $e(hi);continue}o+=1,await G({flow:"agent-ready",phase:"gate-accept",sessionName:t,agent:e,attempt:o,update:`${a.from} -> ${a.to}`}).catch(()=>{}),await xt(t,"3",{enter:!1,kind:"codex-update-skip"}),od({sessionName:t,from:a.from,to:a.to}),await $e(Ur);continue}}if(Date.now()>r){if(gi(e,s)){let a=`${e} is blocked on an interactive prompt`;throw e==="codex"?a=vt.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"&&vt.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"&&vt.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"&&vt.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:!!vt[e]?.block?.test(s),paneUp:!!vt[e]?.up?.test(s),paneBusy:!!vt[e]?.busy?.test(s)}).catch(()=>{}),await $e(Ur);return}await $e(hi)}}function md({agent:t,agentCmd:e}={}){let n=e?jn(e):jn(t||"");return{label:n,realClaude:e?Dn(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 fs({cwd:t,sessionName:e,agent:n,agentCmd:r}={}){let{realCodex:o,realCursor:s}=md({agent:n,agentCmd:r});return!e||!o&&!s?null:Ce({cwd:t,sessionName:e,agent:n,agentCmd:r}).catch(()=>{})}async function Ce({cwd:t,sessionId:e,sessionName:n,agent:r,agentCmd:o,timeoutMs:s=ik()}={}){let{label:a,realClaude:i,realCodex:c,realCursor:l}=md({agent:r,agentCmd:o});if(i&&t&&await Ee(t),c&&t&&await Nn(t),l&&t&&await Pn(t),(i||c||l)&&n){await fd(n,a,s);return}let u=vt[r]?r:vt[a]?a:null;if(process.env.BOSTRAT_FAKE_POLL_READY==="1"&&n&&u){await fd(n,u,s);return}await $e(Ur)}var gk=(()=>{let t=Number(process.env.BOSTRAT_CODEX_SUBMIT_SETTLE_MS??120);return Number.isFinite(t)&&t>=0?t:120})(),yk=(()=>{let t=Number(process.env.BOSTRAT_CLAUDE_SUBMIT_SETTLE_MS??120);return Number.isFinite(t)&&t>=0?t:120})();function me(t){return t==="codex"?{enter:!0,submitDelayMs:gk}:t==="claude"?{enter:!0,submitDelayMs:yk}:{enter:!0}}import ms from"node:fs/promises";import kd from"node:path";var bk=["bug-fix","orchestration","design","ops-infra","docs-writing","research","feature-build"],kk={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 Un(t){let e=String(t||"").toLowerCase();if(!e.trim())return null;let n=null;for(let r of bk){let o=0;for(let s of kk[r])s.test(e)&&o++;o>0&&(!n||o>n.score)&&(n={category:r,score:o})}return n}var xk=new Set(["Read","Grep","Glob","LS","WebSearch","WebFetch","NotebookRead"]),Sk=new Set(["Edit","Write","MultiEdit","NotebookEdit"]);function hd(t){let e=0,n=0;for(let[r,o]of Object.entries(t||{}))xk.has(r)&&(e+=o||0),Sk.has(r)&&(n+=o||0);return e>=5&&n===0?{category:"research",score:1}:n>=3?{category:"feature-build",score:1}:null}function Hr(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 wd=1.5,gd=.75,vk=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 bd(t){let e=Hr(String(t||"")).toLowerCase(),n=[];for(let r of e.split(/[^a-z0-9]+/))r.length<2||vk.has(r)||n.push(r);return n}function Ak(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(yd(n,"user",o))}else if(n.type==="turn.agent"){if(!o)continue;e.push(yd(n,"assistant",o))}}return e}function yd(t,e,n){let r=Un(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 Wr(t){let e=Ak(t),n=new Map,r=new Array(e.length).fill(0),o=0;e.forEach((a,i)=>{let c=bd(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 ps(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(bd(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],k=w+wd*(1-gd+gd*(g/(t.avgdl||1))),v=m*(w*(wd+1)/k);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 xd="Relevant prior discussion (verbatim, retrieved";async function Tk(t){let e=kd.join(t,".bostrat","events"),n;try{n=await ms.readdir(e)}catch{return Wr([])}let r=[];for(let o of n)if(o.endsWith(".ndjson"))try{let s=await ms.readFile(kd.join(e,o),"utf8");for(let a of s.split(`
|
|
31
|
+
`))if(a.trim())try{r.push(JSON.parse(a))}catch{}}catch{}return Wr(r)}function _k(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}
|
|
32
|
+
${o}`.trim()||e.slice(0,2e3)}function Ek(t,e,{k:n=6,maxChars:r=4e3,exclude:o=null}={}){let s=ps(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?["",`## ${xd}, 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(`
|
|
33
|
+
`):""}async function Sd(t,e,n={}){let{k:r=6,maxChars:o=4e3,exclude:s=null}=n;try{let a=await ms.readFile(e,"utf8");if(a.includes(xd))return{appended:!1,reason:"already-enriched"};let i=await Tk(t);if(!i.N)return{appended:!1,reason:"empty-index"};let c=Ek(i,_k(a),{k:r,maxChars:o,exclude:s});return c?(await ms.appendFile(e,`
|
|
34
|
+
`+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)}}}var Td=".bostrat/handoff",_d="latest.md",Ed="Checkpoint: write a work-in-progress handoff",Id="HANDOFF WRITTEN",Rd="Resume from .bostrat/handoff/latest.md",Rk="/clear",$k=t=>`${Ed} 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 ${Id} and take no further action.`,Ok=`${Rd}: read it, restate the Objective and the single next step in one line, then continue the work from 'Next steps'.`,$d="Beat: write a",Od="BEAT WRITTEN",Ck=".bostrat/arc",Mk="beat.txt",vd=200,Cd=4,Md=5,Nk=36,Pk=t=>`${$d} 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 ${Cd} further lines, each starting with "- " and naming ONE piece in at most ${Md} words \u2014 a label, not a sentence (e.g. "- stale working flag on poll"). Write nothing else in the file. When the file is written, reply only with ${Od} and take no further action.`,Ad=/^(?:[-–—*•·]|\d+[.)])\s+/,Bk=(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 Dk(t){let e=String(t||"").split(/\r?\n/).map(s=>s.trim()).filter(Boolean),n=[],r=[];for(let s of e)if(Ad.test(s)){if(r.length>=Cd)continue;let a=Bk(s.replace(Ad,"").replace(/[.;,:]+$/,""),Md,Nk);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>vd&&(o=`${o.slice(0,vd-1).trimEnd()}\u2026`),{text:o,items:r.length>1?r:[]}}async function Nd(t,e,{timeoutMs:n=9e4}={}){let r=Hn.join(e,Ck),o=Hn.join(r,Mk);await qr.mkdir(r,{recursive:!0});let s=await hs(o),a=await J(t);if(!a)throw new Error(`session "${t}" not found`);return await ws(t,a,Pk(o)),await Hd(o,s,n),Dk(await qr.readFile(o,"utf8"))}function Pd(t,e){let n=(e||"").trim();return n?t==="assistant"?n===Id||n===Od:n.startsWith(Ed)||n===Rk||n.startsWith(Rd)||n.startsWith($d):!1}var jk=6e5;function Bd(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-jk;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 Dd(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 jd(t,{windows:e=[]}={}){if(Pd(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 Lk=3500,Fk=3e5,yi=new Map;function gs(t){return yi.get(t)||null}async function Ld(t){let e=yi.get(t);if(e&&!e.finishedAt)throw new Error("a handoff is already running for this session");let n=await J(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:Hn.join(n.cwd,Td,_d)};return yi.set(t,r),Uk(r,n.cwd).catch(o=>{r.error=o?.message||String(o),r.finishedAt=Date.now()}),r}async function Uk(t,e){t.step="summarize",await Fd(t.name,e),t.step="clear";let n=await J(t.name);await ws(t.name,n,"/clear"),await Wd(Lk),t.step="resume",await ws(t.name,n,Ok),t.step="done",t.finishedAt=Date.now()}async function Fd(t,e){let n=Hn.join(e,Td),r=Hn.join(n,_d);await qr.mkdir(n,{recursive:!0});let o=await hs(r),s=await J(t);await G({flow:"checkpoint",phase:"summarize-start",sessionName:t,cwd:e,file:r,beforeMtime:o}),await ws(t,s,$k(r)),await Hd(r,o,Fk),await G({flow:"checkpoint",phase:"summarize-done",sessionName:t,file:r,afterMtime:await hs(r)});let a=await Sd(e,r,{exclude:i=>Pd(i.role,i.text)});return await G({flow:"checkpoint",phase:"seed-enriched",sessionName:t,file:r,...a}),await Hk(n,r),r}async function Ud(t){let e=await J(t);if(!e)throw new Error(`session "${t}" not found`);if(!e.cwd)throw new Error(`can't resolve cwd for "${t}"`);return Fd(t,e.cwd)}async function hs(t){try{return(await qr.stat(t)).mtimeMs}catch{return 0}}async function Hd(t,e,n){let r=Date.now()+n;for(;;){if(await hs(t)>e)return;if(Date.now()>r)throw new Error(`timed out waiting for the handoff file to be written (${t})`);await Wd(2e3)}}async function Hk(t,e){try{if(!Ik.existsSync(e))return;let n=new Date().toISOString().replace(/[:.]/g,"-");await qr.copyFile(e,Hn.join(t,`${n}.md`))}catch{}}function Wd(t){return new Promise(e=>setTimeout(e,t))}async function ws(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 Ce({cwd:e.cwd,sessionId:e.sessionId,sessionName:t,agent:e.agent,agentCmd:o})}let r=await J(t);if(!r)throw new Error(`session "${t}" disappeared before the orchestration prompt could be sent`);await xt(t,n,{kind:"orchestration-drive",...me(r.agent)})}import F from"node:fs/promises";import U from"node:path";import{randomUUID as $o}from"node:crypto";import{execFile as AT}from"node:child_process";import{promisify as TT}from"node:util";import Ep from"node:path";import bi from"node:fs/promises";import Wn from"node:path";import Wk from"node:os";import{EventEmitter as qk}from"node:events";import{randomUUID as Gk}from"node:crypto";var Kk=new Set(["session.ready","turn.user","turn.agent","turn.retracted","tool.called","tool.result","prompt.raised","prompt.answered","context.usage","cost.recorded","handoff.started","handoff.step","handoff.done","handoff.failed","subtask.delegated","subtask.merged","job.started","pr.state.changed","pr.offered","pr.offer.answered","box.state.changed","view.state.changed","view.spinup.failed","mock.state.changed","runbook.state.changed","ticket.linked","ticket.sync.failed","ticket.move.offered","ticket.move.answered","ticket.state.changed","ticket.commented","ticket.flow.changed","ticket.create.offered","ticket.create.answered","ticket.created","notify.raised","provider.connected","track.summary.recorded","changelog.published","stall.detected","error"]),Vk=new Set(["user","agent","orchestrator","system"]),zk=new Set(["hook","otel","statusline","transcript","pane","orchestrator","vendor"]),Lt=new qk;Lt.setMaxListeners(0);var ki=process.env.BOSTRAT_STATE_DIR||Wn.join(process.env.HOME||Wk.tmpdir(),".bostrat");function bs(t){return t?.cwd?Wn.join(t.cwd,".bostrat","events"):Wn.join(ki,"events")}var qd=(t,e)=>Wn.join(t,`${e}.ndjson`),ys=new Map;async function Jk(t,e){if(!ys.has(e)){let o=(await j(t,e)).reduce((s,a)=>a.seq>s?a.seq:s,0);ys.set(e,o)}let n=ys.get(e)+1;return ys.set(e,n),n}function Xk(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 C(t,e){let n=typeof t=="string"?t:t?.id;if(!n)throw new Error("appendEvent: a track (or trackId) is required");if(!e||!Kk.has(e.type))throw new Error(`appendEvent: unknown event type "${e?.type}"`);if(e.actor&&!Vk.has(e.actor))throw new Error(`appendEvent: bad actor "${e.actor}"`);if(e.source&&!zk.has(e.source))throw new Error(`appendEvent: bad source "${e.source}"`);let r=typeof t=="string"?Wn.join(ki,"events"):bs(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=Xk(c,Number(e.payload?.ts??e.ts)||Date.now())}let s=await Jk(r,n),a=e.userId??process.env.BOSTRAT_OWNER??null,i={id:e.id||Gk(),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 bi.mkdir(r,{recursive:!0}),await bi.appendFile(qd(r,n),JSON.stringify(i)+`
|
|
36
|
+
`),Lt.emit("event",i),Lt.emit(`track:${n}`,i),i}async function j(t,e){let n,r;typeof t=="string"&&e?(n=t,r=e):(r=typeof t=="string"?t:t?.id,n=typeof t=="string"?Wn.join(ki,"events"):bs(t));let o;try{o=await bi.readFile(qd(n,r),"utf8")}catch{return[]}let s=[];for(let a of o.split(`
|
|
37
|
+
`))if(a.trim())try{s.push(JSON.parse(a))}catch{}return s}function Gd(t,e){let n=`track:${t}`;return Lt.on(n,e),()=>Lt.off(n,e)}var xi={"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}},Kd={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 Vd(t){if(!t)return null;let e=String(t).trim();return xi[e]?e:Kd[e]?Kd[e]:Object.keys(xi).find(r=>e.startsWith(r))||null}var ks=t=>Vd(t)!==null;function Yt(t,e={}){let n=xi[Vd(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 zd=()=>({input:0,output:0,cacheRead:0,cacheCreation:0});function Yk(t){return t?{input:t.input||0,output:t.output||0,cacheRead:t.cacheRead||0,cacheWrite:t.cacheWrite??t.cacheCreation??0}:null}function Jd(t,e){e&&(t.input+=e.input,t.output+=e.output,t.cacheRead+=e.cacheRead,t.cacheCreation+=e.cacheWrite)}function Ss(t){let e=t.payload?.model||"unknown",n=Yk(t.payload?.tokens),r=Number(t.payload?.usd)||0,o=!1;!r&&n&&(r=Yt(e,n),r>0&&(o=!0));let s=n?n.input+n.output+n.cacheRead+n.cacheWrite:0,a=!r&&s>0&&!ks(e);return{usd:r,model:e,tokens:n,repriced:o,unpriced:a}}function Zt(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=zd();for(let f of n){let{usd:d,model:m,tokens:h,repriced:w,unpriced:g}=Ss(f),k=f.provider||"unknown",v=f.boxId||"unattributed";if(w&&i++,g&&(c++,l.add(m)),a+=d,Jd(u,h),r[m]||={usd:0,tokens:zd(),events:0},r[m].usd+=d,r[m].events+=1,Jd(r[m].tokens,h),o[k]||={usd:0,events:0},o[k].usd+=d,o[k].events+=1,s[v]||={usd:0,events:0,provider:k,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 _=(s[v].epochs||={})[f.epoch]||={usd:0,events:0,provider:k,models:new Set};_.usd+=d,_.events+=1,_.provider=k,m!=="unknown"&&_.models.add(m)}}return{totalUsd:Gr(a),totalTokens:u,turns:n.length,repricedTurns:i,unpricedTurns:c,unpricedModels:[...l],byModel:xs(r,f=>({...f,usd:Gr(f.usd)})),byProvider:xs(o,f=>({...f,usd:Gr(f.usd)})),byBox:xs(s,f=>({usd:Gr(f.usd),events:f.events,provider:f.provider,label:f.label,models:[...f.models],...f.epochs?{epochs:xs(f.epochs,d=>({usd:Gr(d.usd),events:d.events,provider:d.provider,models:[...d.models]}))}:{}}))}}var Gr=t=>Math.round(t*1e4)/1e4,xs=(t,e)=>Object.fromEntries(Object.entries(t).map(([n,r])=>[n,e(r)]));import kt from"node:fs/promises";import Rt from"node:path";import Kr from"node:os";import{randomUUID as tx}from"node:crypto";import{execFile as ex}from"node:child_process";import{promisify as nx}from"node:util";import{chromium as rx}from"playwright-core";import Si from"node:fs/promises";import Zk from"node:path";var Qk=0;async function Qt(t,e,{mode:n}={}){await Si.mkdir(Zk.dirname(t),{recursive:!0});let r=`${t}.${process.pid}.${++Qk}.tmp`;await Si.writeFile(r,JSON.stringify(e,null,2)+`
|
|
38
|
+
`,n!=null?{mode:n}:void 0),await Si.rename(r,t)}var Yd=nx(ex),Zd=process.env.BOSTRAT_STATE_DIR||Rt.join(process.env.HOME||Kr.tmpdir(),".bostrat"),Qd=Rt.join(Zd,"mocks.json"),ox=process.env.BOSTRAT_MOCK_ENGINE||"chromium",sx=process.env.BOSTRAT_CHROMIUM_BIN||"/usr/bin/chromium",ax=process.env.BOSTRAT_FIREFOX_BIN||"firefox",ix=1300,qn=2,cx=32,tf=1500,Gn=Number(process.env.BOSTRAT_MOCK_TIMEOUT_MS)||75e3,lx=700*1024*1024;async function ux(){if(process.platform!=="darwin")return Kr.freemem();try{let{stdout:t}=await Yd("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:Kr.freemem()}catch{return Kr.freemem()}}var qt=class extends Error{constructor(e,n=""){super(n?`${e}
|
|
34
39
|
${n.trim().split(`
|
|
35
40
|
`).slice(-6).join(`
|
|
36
|
-
`)}`:e),this.name="MockRenderError"}},en=null,za=null;async function ue(){return en||(za||=(async()=>{try{en=JSON.parse(await yt.readFile(sd,"utf8"))}catch{en={mocks:[]}}finally{za=null}return en.mocks||=[],en})(),za)}async function Yt(){await Xt(sd,en)}function qt(t,e,n={}){return O({id:t.trackId,cwd:t.cwd},{type:"mock.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box||null,provider:t.agent||null,payload:{ts:Date.now(),change:e,mockId:t.id,name:t.name,...t.group?{group:t.group}:{},...n}}).catch(()=>{})}var Zo=t=>It.join(t,".bostrat","mocks");function Lt(t,e){let n=Zo(t.cwd);return{html:It.join(n,`${t.id}.v${e}.html`),png:It.join(n,`${t.id}.v${e}.png`)}}function nn(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 Ya(t){return t.length<24||t.readUInt32BE(12)!==1229472850?null:{width:t.readUInt32BE(16),height:t.readUInt32BE(20)}}var Eb=Buffer.from([137,80,78,71,13,10,26,10]);function id(t){return Buffer.isBuffer(t)&&t.length>24&&t.subarray(0,8).equals(Eb)}async function Ib(t){let e=Buffer.alloc(24),n=await yt.open(t,"r");try{await n.read(e,0,24,0)}finally{await n.close()}return e}function Rb({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 $b(t,e){return t.evaluate(async n=>{let r=new Image;await new Promise((m,h)=>{r.onload=m,r.onerror=()=>h(new Error("decode failed")),r.src=`data:image/png;base64,${n}`});let o=r.naturalWidth,s=r.naturalHeight;if(!o||!s||o*s>24e6)return null;let a=document.createElement("canvas");a.width=o,a.height=s;let i=a.getContext("2d",{willReadFrequently:!0});i.drawImage(r,0,0);let c=i.getImageData(0,0,o,s).data,l=[c[0],c[1],c[2]],u=o,d=s,f=-1,p=-1;for(let m=0;m<s;m++)for(let h=m*o*4,b=0;b<o;b++,h+=4)Math.abs(c[h]-l[0])+Math.abs(c[h+1]-l[1])+Math.abs(c[h+2]-l[2])>24&&(b<u&&(u=b),b>f&&(f=b),m<d&&(d=m),p=m);return f<0?null:{imgW:o,imgH:s,bg:l,bbox:{left:u,top:d,right:f,bottom:p}}},e.toString("base64"))}async function Ob(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((d,f)=>{c.onload=d,c.onerror=()=>f(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 Cb({htmlPath:t,outPath:e,width:n}){let r=Date.now(),o;try{o=await bb.launch({executablePath:xb,args:["--disable-gpu"],timeout:3e4})}catch(s){throw new Wt("chromium failed to launch",s?.message||"")}try{let a=await(await o.newContext({viewport:{width:n,height:800},deviceScaleFactor:Un})).newPage();a.setDefaultTimeout(Hn),await a.goto(`file://${t}`,{waitUntil:"load",timeout:Hn}),await a.waitForTimeout(250);let i;for(let l=1;;l++)try{i=await a.screenshot({fullPage:!0,timeout:Hn});break}catch(u){if(l>=3||!/captureScreenshot|Unable to capture/i.test(u?.message||""))throw u;await a.waitForTimeout(500)}try{let l=await $b(a,i),u=l&&Rb({...l,pad:Ab*Un,align:Un});u&&(i=await Ob(a,i,{bg:l.bg,crop:u}))}catch{}if(i.length<ad)throw new Wt(`capture produced a ${i.length}-byte image`);let c=Ya(i);if(!c)throw new Wt("output is not a PNG");return await yt.writeFile(e,i),{width:Math.round(c.width/Un),height:Math.round(c.height/Un),dpr:Un,renderMs:Date.now()-r}}catch(s){throw s instanceof Wt?s:new Wt(/timeout/i.test(s?.message||"")?`render timed out after ${Hn}ms`:"chromium render failed",s?.message||"")}finally{await o.close().catch(()=>{})}}async function nd({htmlPath:t,outPath:e,width:n}){let r=await yt.mkdtemp(It.join(Br.tmpdir(),"bostrat-mock-profile-")),o=Date.now(),s="";try{try{s=(await rd(Sb,["--headless","--no-remote","--profile",r,`--window-size=${n}`,"--screenshot",e,`file://${t}`],{env:{...process.env,MOZ_HEADLESS:"1"},timeout:Hn,killSignal:"SIGKILL",maxBuffer:4194304})).stderr||""}catch(c){throw s=c?.stderr||"",new Wt(c?.killed?`render timed out after ${Hn}ms`:"firefox exited non-zero",s)}let a=await yt.stat(e).catch(()=>null);if(!a||a.size<ad)throw new Wt(`render produced ${a?`a ${a.size}-byte file`:"no file"} (silent no-op \u2014 another firefox holding the profile?)`,s);let i=Ya(await Ib(e));if(!i)throw new Wt("output is not a PNG",s);return{width:i.width,height:i.height,dpr:1,renderMs:Date.now()-o}}finally{await yt.rm(r,{recursive:!0,force:!0}).catch(()=>{})}}async function Mb(t){if(kb==="firefox")return nd(t);try{return await Cb(t)}catch(e){if(/failed to launch/i.test(e?.message||""))return nd(t);throw e}}var Pb=Mb;var Xa=Promise.resolve();function cd(t){return Xa=Xa.then(t,t),Xa}async function ld(t,e){let n=await ue(),r=n.mocks.find(o=>o.id===t);if(r){r.state="rendering",await Yt(),await qt(r,"rendering",{version:e});try{let o=await _b();if(o<Tb)throw new Wt(`box is low on memory (${Math.round(o/1e6)} MB available) \u2014 not spawning a browser`);let s=Lt(r,e),a=await Pb({htmlPath:s.html,outPath:s.png,width:r.width});if(!n.mocks.some(i=>i.id===t)){await yt.rm(s.png,{force:!0}).catch(()=>{});return}ud(r,{v:e,source:"html",htmlRel:It.relative(r.cwd,s.html),pngRel:It.relative(r.cwd,s.png),width:a.width,height:a.height,dpr:a.dpr,renderMs:a.renderMs,createdAt:Date.now()}),await Yt(),await qt(r,"ready",{version:e,width:a.width,height:a.height,dpr:a.dpr})}catch(o){if(!n.mocks.some(s=>s.id===t))return;r.state="error",r.error=o?.message||String(o),await Yt(),await qt(r,"error",{version:e,error:r.error})}}}function ud(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 Nb({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?vb:Number(r);if(!Number.isInteger(o)||o<320||o>4e3)throw new Error("invalid width (320-4000)");return o}async function Qo({trackId:t,group:e}={}){let n=await ue(),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 Ee(t){let n=(await ue()).mocks.find(r=>r.id===t);return n?{...n}:null}async function dd({trackId:t,name:e,html:n,image:r,cwd:o,width:s,group:a,box:i,agent:c,prompt:l}){let u=Nb({trackId:t,name:e,cwd:o,width:s}),d=typeof n=="string"&&n.trim();if(!d&&!r)throw new Error("html (string) or image (png) required");if(r&&!id(r))throw new Error("image must be a PNG");let f=await ue(),p={id:wb().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:d?"html":"image",version:1,readyVersion:null,versions:[],error:null,renderMs:null,createdAt:Date.now(),updatedAt:Date.now()};return await yt.mkdir(Zo(o),{recursive:!0}),f.mocks.push(p),d?(await yt.writeFile(Lt(p,1).html,n),await Yt(),await qt(p,"created"),cd(()=>ld(p.id,1))):(await pd(p,1,r),await Yt(),await qt(p,"created"),await qt(p,"ready",{version:1,width:p.width,height:p.height,dpr:p.dpr})),{...p}}async function fd(t,{html:e,image:n,width:r}={}){let s=(await ue()).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&&!id(n))throw new Error("image must be a PNG");if(r!=null){let c=Number(r);if(!Number.isInteger(c)||c<320||c>4e3)throw new Error("invalid width (320-4000)");s.width=c}let i=s.version+1;return s.version=i,s.updatedAt=Date.now(),s.source=a?"html":"image",await yt.mkdir(Zo(s.cwd),{recursive:!0}),a?(s.state="queued",await yt.writeFile(Lt(s,i).html,e),await Yt(),await qt(s,"updated",{version:i}),cd(()=>ld(s.id,i))):(await pd(s,i,n),await Yt(),await qt(s,"updated",{version:i}),await qt(s,"ready",{version:i,width:s.width,height:s.height,dpr:s.dpr})),{...s}}async function pd(t,e,n){let r=Ya(n);if(!r)throw new Error("image must be a PNG");let o=Lt(t,e);await yt.writeFile(o.png,n),ud(t,{v:e,source:"image",htmlRel:null,pngRel:It.relative(t.cwd,o.png),width:r.width,height:r.height,dpr:1,renderMs:null,createdAt:Date.now()}),t.width=r.width}async function md(t){let e=await ue(),n=e.mocks.find(o=>o.id===t);if(!n)throw new Error("mock not found");if(n.readyVersion==null)throw new Error(`mock is ${n.state} \u2014 nothing to pick yet`);let r=n.group?e.mocks.filter(o=>o.trackId===n.trackId&&o.group===n.group&&o.id!==t&&o.readyVersion!=null).map(o=>({mockId:o.id,name:o.name})):[];return await qt(n,"picked",{version:n.readyVersion,siblings:r}),{mock:{...n},siblings:r}}async function hd(t){let e=await ue(),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=Lt(r,o);await yt.rm(s.html,{force:!0}).catch(()=>{}),await yt.rm(s.png,{force:!0}).catch(()=>{})}return await Yt(),await qt(r,"deleted"),{ok:!0,id:t,name:r.name}}async function wd(t){let n=(await ue()).mocks.filter(i=>i.trackId===t.id);if(!n.length)return 0;let r=It.join(od,"mock-archive",t.id),o=Zo(t.cwd),s=It.join(r,".bostrat","mocks");await yt.mkdir(s,{recursive:!0});let a=await yt.readdir(o).catch(()=>[]);for(let i of a)await yt.copyFile(It.join(o,i),It.join(s,i));for(let i of n){for(let c of i.versions||[])for(let l of[c.htmlRel,c.pngRel]){if(!l)continue;if(!(await yt.stat(It.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 Yt(),n.length}async function gd(t){let e=await ue(),n=e.mocks.filter(r=>r.trackId===t);return n.length&&(e.mocks=e.mocks.filter(r=>r.trackId!==t),await Yt()),n.length}import jr from"node:fs/promises";import Rt from"node:path";import Bb from"node:os";import Db from"node:net";import{randomUUID as jb}from"node:crypto";import{execFile as Lb}from"node:child_process";import{promisify as Fb}from"node:util";var Qt=Fb(Lb),Ub=process.env.BOSTRAT_STATE_DIR||Rt.join(process.env.HOME||Bb.tmpdir(),".bostrat"),Sd=Rt.join(Ub,"views.json"),[yd,bd]=(process.env.BOSTRAT_VIEW_PORTS||"4300-4349").split("-").map(t=>Number(t)||0),Ie=Number(process.env.BOSTRAT_VIEW_MAX)||3,Hb="bostrat-view-",Wn=t=>`${Hb}${t}`,kd=new Set(["bostrat","www","api","views","app"]),ts=t=>Object.assign(new Error(t),{status:400}),Wb=new Set(["npm","pnpm","yarn","bun"]),ti="package.json",qb=/[|;&`]|\$\(/,Gb=new Set(["sh","bash","zsh","dash","env","exec","nohup","time"]),Kb=/(^|\s)(-C|--cwd|--manifest-path|--dir)(=|\s)/,vd=t=>String(t).trim().split(/\s+/).filter(Boolean),Vb=/^[A-Za-z_][A-Za-z0-9_]*=/;function Jb(t){for(let e of vd(t))if(!Vb.test(e))return e;return null}function Ad(t){let e=vd(t),n=e.indexOf("--");return n===-1?e:e.slice(0,n)}function zb(t,e){let n=Ad(e);for(let r=0;r<n.length;r++){let o=/^--prefix=(.+)$/.exec(n[r]);if(o)return Rt.resolve(t,o[1]);if(n[r]==="--prefix"&&n[r+1])return Rt.resolve(t,n[r+1])}return t}var Xb=t=>jr.stat(t).then(e=>e.isDirectory(),()=>!1),Td=t=>jr.stat(t).then(e=>e.isFile(),()=>!1),es=t=>jr.realpath(t).catch(()=>Rt.resolve(t));async function Yb(t,e){if(Rt.resolve(t)===Rt.resolve(e))return!0;let[n,r]=await Promise.all([es(t),es(e)]);return n===r}async function Zb(t){let n=(await jr.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 Td(Rt.join(t,o.name,ti))?`./${o.name}`:null))).filter(Boolean)}async function Qb(t,e){let[n,r]=await Promise.all([es(t),es(e)]),o=Rt.relative(n,r);if(o&&(o.startsWith("..")||Rt.isAbsolute(o)))throw ts(`serve directory escapes the Track workspace: ${e}`)}async function tk(t,e){if(!await Xb(t))throw ts(`serve directory does not exist: ${t}`);let n=Jb(e);if(!n)return;if(!n.includes("/")&&!await Ra(n,{loginShell:!0,failOpen:!0}))throw ts(`\`${n}\` is not on the box's PATH \u2014 install it in the box first`);if(qb.test(e)||Gb.has(n)||!Wb.has(n)||!Ad(e).includes("run")||Kb.test(e))return;let r=zb(t,e);if(await Td(Rt.join(r,ti)))return;let o=await Zb(r),s=o.length?` \u2014 found one in ${o.join(", ")}; run bostrat-view from there`:"";throw ts(`no ${ti} in ${r}${s}`)}var Zt=null,Za=null;async function pe(){return Zt||(Za||=(async()=>{try{Zt=JSON.parse(await jr.readFile(Sd,"utf8"))}catch{Zt={views:[],adoptWatermarks:{}}}finally{Za=null}return Zt.views||=[],Zt.adoptWatermarks||={},Zt})(),Za)}async function te(){await Xt(Sd,Zt)}function fe(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 Dr=new Map,ri=Number(process.env.BOSTRAT_VIEW_JOB_TIMEOUT)||3e5,_d=12e3;function oi(t){return Dr.get(t)||null}function Ed(t,{name:e,box:n}={}){let r=Dr.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 Dr.set(t,o),o}function ei(t,e){let n=Dr.get(t);n&&!n.finishedAt&&!n.viewId&&(n.viewId=e,n.step="booting")}function ns(t,{error:e=null}={}){let n=Dr.get(t);return!n||n.finishedAt?null:(n.finishedAt=Date.now(),e&&(n.error=e),n)}function Id(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 ni(t){return String(t||"").trim().toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,40).replace(/-+$/g,"")||"view"}function ek(t,e){let n=ni(e),r=new Set(t.views.map(o=>o.slug));if(!r.has(n)&&!kd.has(n))return n;for(let o=2;;o++){let s=`${n}-${o}`;if(!r.has(s)&&!kd.has(s))return s}}function Lr(t,{timeoutMs:e=1200}={}){return new Promise(n=>{let r=Db.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 si(t){if(process.platform!=="darwin"){await Qt("fuser",["-k",`${t}/tcp`]).catch(()=>{});return}let e="";try{({stdout:e}=await Qt("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{}}}async function nk(t,e){let n=new Set(t.views.map(r=>r.port));if(e){let r=Number(e);if(!Number.isInteger(r)||r<1024||r>65535)throw new Error("invalid port");if(n.has(r))throw new Error(`port ${r} is already owned by a view`);return r}for(let r=yd;r<=bd;r++)if(!n.has(r)&&!await Lr(r,{timeoutMs:250}))return r;throw new Error(`no free view port in ${yd}-${bd}`)}async function rk(t){try{let{stdout:e}=await Qt(C,P("list-sessions","-F","#{session_name}"));return e.split(`
|
|
37
|
-
`).some(n=>n.trim()===
|
|
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()===Kn(t))}catch{return!1}}async function Xr(t){try{await ne(N,B("kill-session","-t",Kn(t)))}catch{}}async function Ni(t){if(!await jr())throw new Error("host tmux server not running and auto-start failed \u2014 cannot spawn a view server");let e=Kn(t.slug),n=t.serveCwd??t.cwd,r=$t.dirname(process.execPath);await ne(N,B("new-session","-d","-s",e,"-c",n)),await ne(N,B("send-keys","-t",e,"-l",`export PORT=${t.port} PATH="${r}:$PATH"`)),await ne(N,B("send-keys","-t",e,"Enter")),await ne(N,B("send-keys","-t",e,"-l",t.cmd)),await ne(N,B("send-keys","-t",e,"Enter"))}async function wf(t,e=12){try{let{stdout:n}=await ne(N,B("capture-pane","-t",Kn(t),"-p"));return String(n||"").trimEnd().split(`
|
|
38
43
|
`).filter(Boolean).slice(-e).join(`
|
|
39
|
-
`)}catch{return""}}var de=new Map,ok=12e4;async function sk(t){if(t.state==="stopped"||t.state==="error")return!1;let e=t.origin!=="detected",n=e?await rk(t.slug):!0;if(e&&!n){let s=t.state==="starting";return t.state=s?"error":"stopped",s&&(t.error=await xd(t.slug)||"server exited before listening"),de.delete(t.id),await fe(t,t.state,t.error?{error:t.error}:{}),!0}if(await Lr(t.port))return de.delete(t.id),t.lastHealthyAt=Date.now(),t.state!=="live"?(t.state="live",t.error=null,await fe(t,"live"),!0):!1;if(t.state==="starting")return Date.now()-t.createdAt>ok?(t.state="error",t.error=await xd(t.slug)||"never started listening",await fe(t,"error",{error:t.error}),!0):!1;let o=(de.get(t.id)||0)+1;return de.set(t.id,o),o>=3&&t.state==="live"?(t.state="unhealthy",!0):!1}var Qa=!1;async function rs(){if(!Qa){Qa=!0;try{let t=await pe(),e=!1;for(let n of t.views)await sk(n)&&(e=!0);e&&await te()}finally{Qa=!1}}}async function os({trackId:t}={}){await rs();let e=await pe();return(t?e.views.filter(r=>r.trackId===t):e.views).map(r=>({...r}))}async function Rd(t){let n=(await pe()).views.find(r=>r.id===t);return n?{...n}:null}function ii(t){return Zt&&Zt.views.find(e=>e.slug===t)||null}async function ci({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=Rt.resolve(r,o??"."),d=i==="detected"?null:async()=>{await Qb(r,u),await tk(u,n)},f=await pe();if(i!=="detected"){let h=ni(e),b=String(n).trim(),x=f.views.find(v=>v.trackId===t&&(v.slug===h||ni(v.name)===h||v.cmd===b&&Rt.resolve(v.serveCwd??v.cwd)===u));if(x)return ak(f,x,{cmd:b,box:a,branch:c,serveCwd:l?u:void 0,validate:d})}if(d&&await d(),f.views.filter(h=>h.state==="live"||h.state==="starting").length>=Ie)throw new Error(`view cap reached (${Ie} live) \u2014 stop one first`);let m={id:jb().slice(0,8),trackId:t,box:a||null,name:e.trim(),slug:ek(f,e),cmd:n?String(n).trim():null,cwd:r,serveCwd:u,branch:c,port:await nk(f,s),origin:i,state:"starting",error:null,createdAt:Date.now(),lastHealthyAt:null};return i!=="detected"&&await ai(m),f.views.push(m),await te(),await fe(m,"created",{origin:i}),ei(t,m.id),i==="detected"&&await Lr(m.port)&&(m.state="live",m.lastHealthyAt=Date.now(),await te(),await fe(m,"live")),{...m}}async function ak(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 Yb(e.serveCwd??e.cwd,s);if((e.state==="live"||e.state==="starting")&&!i&&!c&&await Lr(e.port))return await te(),ei(e.trackId,e.id),{...e,reused:!0};if(a&&await a(),t.views.filter(d=>d.id!==e.id&&(d.state==="live"||d.state==="starting")).length>=Ie)throw new Error(`view cap reached (${Ie} live) \u2014 stop one first`);return e.origin==="detected"?(await si(e.port),e.origin="agent"):await Fr(e.slug),e.cmd=n,s!==void 0&&(e.serveCwd=s),e.state="starting",e.error=null,e.createdAt=Date.now(),de.delete(e.id),await ai(e),await te(),await fe(e,"created",{restarted:!0,reused:!0}),ei(e.trackId,e.id),{...e,reused:!0}}async function $d(t){let n=(await pe()).views.find(r=>r.id===t);if(!n)throw new Error("view not found");return n.origin==="detected"?await si(n.port):await Fr(n.slug),n.state!=="stopped"&&(n.state="stopped",de.delete(n.id),await te(),await fe(n,"stopped")),{...n}}async function Od(t){let e=await pe(),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>=Ie)throw new Error(`view cap reached (${Ie} live) \u2014 stop one first`);return await Fr(n.slug),n.state="starting",n.error=null,n.createdAt=Date.now(),de.delete(n.id),await ai(n),await te(),await fe(n,"created",{restarted:!0}),{...n}}async function Cd(t){let e=await pe(),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 si(r.port):await Fr(r.slug),e.views.splice(n,1),de.delete(r.id),await te(),await fe(r,"deleted"),{ok:!0,id:t,slug:r.slug}}async function Md(t){let e=await pe(),n=e.views.filter(r=>r.trackId===t);for(let r of n)r.origin!=="detected"&&await Fr(r.slug),de.delete(r.id);return n.length&&(e.views=e.views.filter(r=>r.trackId!==t),await te()),n.length}var ik=/https?:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0)(?::(\d{2,5}))?(?:\/|\b)/g;async function Pd(t,e){if(!t?.id||!Array.isArray(e)||!e.length)return[];let n=await pe(),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 te()),!o.length)return[];let a=new Set;for(let c of o)for(let l of String(c.text||"").matchAll(ik)){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(c!==Number(process.env.PORT||3362)&&!n.views.some(l=>l.port===c)&&await Lr(c))try{i.push(await ci({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 on from"node:fs/promises";import Re from"node:path";import ck from"node:os";import{randomUUID as lk}from"node:crypto";var Gn=process.env.BOSTRAT_STATE_DIR||Re.join(process.env.HOME||ck.tmpdir(),".bostrat"),Bd=Re.join(Gn,"runbooks.json"),ui=Re.join(Gn,"runbooks"),Nd=256*1024,uk=[{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 di(t){return uk.filter(({re:e})=>e.test(t)).map(({label:e})=>e)}var rn=null,li=null;async function $e(){return rn||(li||=(async()=>{try{rn=JSON.parse(await on.readFile(Bd,"utf8"))}catch{rn={runbooks:[]}}finally{li=null}return rn.runbooks||=[],rn})(),li)}async function Kn(){await Xt(Bd,rn)}function Vn(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 qn(t,e){return Re.join(ui,`${t.id}.v${e}.md`)}function Dd(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 jd(t){if(typeof t!="string"||!t.trim())throw new Error("md (markdown string) required");let e=Buffer.byteLength(t,"utf8");if(e>Nd)throw new Error(`runbook is ${Math.round(e/1024)} KB \u2014 over the ${Nd/1024} KB cap; a doc that size belongs in the repo, not a runbook`);let n=di(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 Ld(t,e){if(t.trackDeletedAt)throw new Error(`runbook is orphaned (its Track was deleted) \u2014 cannot ${e} it`)}async function ss({trackId:t,status:e}={}){let n=await $e(),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 fi(t){let n=(await $e()).runbooks.find(r=>r.id===t);return n?{...n}:null}async function Fd({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=jd(r),c=await $e(),l={id:lk().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 on.mkdir(ui,{recursive:!0}),await on.writeFile(qn(l,1),r),l.versions.push({v:1,mdRel:Re.relative(Gn,qn(l,1)),bytes:i,note:null,createdAt:Date.now()}),c.runbooks.push(l),await Kn(),await Vn(l,"created",{version:1}),{...l}}async function Ud(t,{md:e,note:n}={}){let o=(await $e()).runbooks.find(i=>i.id===t);if(!o)throw new Error("runbook not found");Ld(o,"update");let s=jd(e),a=o.version+1;return o.version=a,o.updatedAt=Date.now(),await on.mkdir(ui,{recursive:!0}),await on.writeFile(qn(o,a),e),o.versions.push({v:a,mdRel:Re.relative(Gn,qn(o,a)),bytes:s,note:n?String(n).slice(0,200):null,createdAt:Date.now()}),await Kn(),await Vn(o,"updated",{version:a,...n?{note:String(n).slice(0,200)}:{}}),{...o}}async function Hd(t,e){if(e!=="open"&&e!=="done")throw new Error('status must be "open" or "done"');let r=(await $e()).runbooks.find(o=>o.id===t);if(!r)throw new Error("runbook not found");return Ld(r,e==="done"?"complete":"reopen"),r.status===e?{...r}:(r.status=e,r.updatedAt=Date.now(),await Kn(),await Vn(r,e==="done"?"done":"reopened"),{...r})}async function Wd(t){let e=await $e(),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 on.rm(Re.join(Gn,o.mdRel),{force:!0}).catch(()=>{});return await Kn(),r.trackDeletedAt||await Vn(r,"deleted"),{ok:!0,id:t,name:r.name}}async function qd(t){let n=(await $e()).runbooks.filter(r=>r.trackId===t&&!r.trackDeletedAt);for(let r of n)r.trackDeletedAt=Date.now(),await Vn(r,"orphaned"),r.cwd=null;return n.length&&await Kn(),n.length}async function Gd(t){let e=await $e(),n=e.runbooks.filter(r=>r.trackId===t);for(let r of n){await Vn(r,"deleted");for(let o of r.versions||[])await on.rm(Re.join(Gn,o.mdRel),{force:!0}).catch(()=>{})}return n.length&&(e.runbooks=e.runbooks.filter(r=>r.trackId!==t),await Kn()),n.length}import Wr from"node:fs/promises";import af from"node:path";import jk from"node:os";import{randomUUID as cf}from"node:crypto";import dk from"node:fs/promises";import Kd from"node:path";import fk from"node:os";var Vd=()=>Kd.join(process.env.BOSTRAT_STATE_DIR||Kd.join(process.env.HOME||fk.tmpdir(),".bostrat"),"integrations.json");async function sn(){try{return JSON.parse(await dk.readFile(Vd(),"utf8"))||{}}catch{return{}}}var as=["linked","prOpened","prMerged","create"],pi=["prompt","auto","off"],pk={create:["prompt","off"]},Jd=t=>pk[t]??pi;function is(t){let e=t.linear||{},n=e.transitions===!0?"auto":"prompt",r={};for(let o of as){let s=Jd(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 an(){let t=await sn(),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:is(t),defaultTeam:n}:null}async function cs(){let t=await sn(),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:is(t)}}async function mi(){return is(await sn())}async function zd(t){let e=Object.entries(t||{});if(!e.length)throw new Error(`nothing to set \u2014 phases: ${as.join(", ")}`);for(let[r,o]of e){if(!as.includes(r))throw new Error(`unknown phase "${r}" \u2014 configurable phases: ${as.join(", ")}`);let s=Jd(r);if(!s.includes(o))throw new Error(s.length<pi.length?`mode "${o}" not allowed for "${r}" \u2014 ${r} is always confirmed (modes: ${s.join(", ")})`:`unknown mode "${o}" \u2014 modes: ${pi.join(", ")}`)}let n=await sn();return n.ticketFlow={...is(n),...Object.fromEntries(e)},n.linear&&(delete n.linear.ticketFlow,delete n.linear.transitions),await Xt(Vd(),n,{mode:384}),n.ticketFlow}async function Ur(){let t=(await sn()).deepgram||{},e=process.env.DEEPGRAM_API_KEY||t.apiKey;return e?{apiKey:e}:null}async function Xd(){let t=(await sn()).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 cn(){let t=(await sn()).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={};Jl(wi,{createComment:()=>Sk,findIssueUrl:()=>gk,issueStates:()=>Tk,mdToAdf:()=>Qd,moveIssue:()=>_k,parseIssueRef:()=>wk,resolveIssue:()=>yk,uploadImage:()=>vk,upsertCard:()=>bk,whoami:()=>hi});var Jn=t=>String(t.baseUrl||"").replace(/\/$/,""),Zd=t=>`Basic ${Buffer.from(`${t.email}:${t.apiToken}`).toString("base64")}`;async function ln(t,e,n,r){let o=await fetch(`${Jn(t)}${n}`,{method:e,headers:{authorization:Zd(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 mk=/\/browse\/([A-Za-z][A-Za-z0-9]{0,9}-\d+)/,hk=/^([A-Z][A-Z0-9]{0,9}-\d+)$/;function wk(t){let e=String(t||"").trim();if(/^https?:\/\//i.test(e)){let r=e.match(mk);return r?r[1].toUpperCase():null}let n=e.match(hk);return n?n[1]:null}function gk(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 yk(t,e){let n;try{n=await ln(t,"GET",`/rest/api/3/issue/${encodeURIComponent(e)}?fields=summary,status`)}catch(r){throw r.status===404?new Error(`jira: issue "${e}" not found`):r}return{issueId:n.id,key:n.key,title:n.fields?.summary??null,url:`${Jn(t)}/browse/${n.key}`,state:n.fields?.status?.name??null}}async function bk(t,e,{url:n,title:r,subtitle:o}){await ln(t,"POST",`/rest/api/3/issue/${encodeURIComponent(e.key)}/remotelink`,{globalId:n,application:{type:"com.bostrat.tracks",name:"Bostrat"},object:{url:n,title:r,summary:o,icon:{title:"Bostrat"}}})}var kk=/(!\[[^\]]*\]\([^)\s]+\)|\*\*[^*\n]+\*\*|`[^`\n]+`|_[^_\n]+_|https?:\/\/[^\s)]+)/g;function xk(t){let e=[],n=0;for(let r of t.matchAll(kk)){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 Qd(t){let e=String(t??"").split(/\n{2,}/).map(n=>n.trim()).filter(Boolean).map(n=>{let r=[];return n.split(`
|
|
40
|
-
`).forEach((o,s)=>{s>0&&r.push({type:"hardBreak"}),r.push(...
|
|
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(...i0(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 c0(t,e,n){let r=await mn(t,"POST",`/rest/api/3/issue/${encodeURIComponent(e.key)}/comment`,{body:Jf(n)});return r?.id?`${Yn(t)}/browse/${e.key}?focusedCommentId=${r.id}`:null}async function Hi(t){let e=await mn(t,"GET","/rest/api/3/myself"),n=null;try{n=new URL(Yn(t)).host}catch{n=t.baseUrl??null}return{user:e?.displayName??e?.emailAddress??null,org:n}}async function l0(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(`${Yn(t)}/rest/api/3/issue/${encodeURIComponent(e)}/attachments`,{method:"POST",headers:{authorization:zf(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??`${Yn(t)}/rest/api/3/attachment/content/${c.id}`}var u0={new:"unstarted",indeterminate:"started",done:"completed"},Vf=t=>u0[t?.key]??"started";async function d0(t,e){let n=encodeURIComponent(e.key),[r,o]=await Promise.all([mn(t,"GET",`/rest/api/3/issue/${n}?fields=status`),mn(t,"GET",`/rest/api/3/issue/${n}/transitions`)]),s=r?.fields?.status??null,a=s?{id:`status:${s.id}`,name:s.name,type:Vf(s.statusCategory)}:null,i=(o?.transitions??[]).map((c,l)=>({id:c.id,name:c.to?.name??c.name,type:Vf(c.to?.statusCategory),position:l}));return{current:a,states:i}}async function f0(t,e,n){await mn(t,"POST",`/rest/api/3/issue/${encodeURIComponent(e.key)}/transitions`,{transition:{id:String(n)}})}var Vi={};$u(Vi,{createComment:()=>k0,createIssue:()=>Ki,createProject:()=>Gi,findIssueUrl:()=>g0,issueStates:()=>S0,moveIssue:()=>v0,parseIssueRef:()=>w0,resolveIssue:()=>y0,teams:()=>Zn,uploadImage:()=>x0,upsertCard:()=>b0,whoami:()=>qi});var p0=()=>process.env.BOSTRAT_LINEAR_API_URL||"https://api.linear.app/graphql";async function oe(t,e,n={}){let r=await fetch(p0(),{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 m0=/linear\.app\/[^/\s]+\/issue\/([A-Za-z][A-Za-z0-9]{0,9}-\d+)/,h0=/^([A-Z][A-Z0-9]{0,9}-\d+)$/;function w0(t){let e=String(t||"").trim(),n=e.match(m0);if(n)return n[1].toUpperCase();let r=e.match(h0);return r?r[1]:null}function g0(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 y0(t,e){let r=(await oe(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 b0(t,e,{url:n,title:r,subtitle:o}){let s=await oe(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 k0(t,e,n){let r=await oe(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 qi(t){let e=await oe(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 x0(t,{filename:e,contentType:n,buffer:r}){let o=await oe(t,`mutation($contentType: String!, $filename: String!, $size: Int!) {
|
|
41
46
|
fileUpload(contentType: $contentType, filename: $filename, size: $size) {
|
|
42
47
|
success
|
|
43
48
|
uploadFile { uploadUrl assetUrl headers { key value } }
|
|
44
49
|
}
|
|
45
|
-
}`,{contentType:n,filename:e,size:r.length}),s=o.fileUpload?.uploadFile;if(!o.fileUpload?.success||!s?.uploadUrl)throw new Error("linear: fileUpload did not succeed");let a={"content-type":n};for(let c of s.headers||[])a[c.key]=c.value;let i=await fetch(s.uploadUrl,{method:"PUT",headers:a,body:r});if(!i.ok)throw new Error(`linear: asset PUT failed (HTTP ${i.status})`);return s.assetUrl}async function Bk(t,e){let n=await ee(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 Dk(t,e,n){if(!(await ee(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 ee(t,"query { teams(first: 100) { nodes { id key name } } }")).teams?.nodes??[]).map(n=>({id:n.id,key:n.key,name:n.name}))}var tf=255;async function yi(t,{teamId:e,name:n,description:r}){let o=r||null,s=!!o&&o.length>tf,a=s?`${o.slice(0,tf-1).trimEnd()}\u2026`:o,i=await ee(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 bi(t,{teamId:e,title:n,description:r,projectId:o,priority:s}){let a=await ee(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 lf={linear:{api:ki,config:an},jira:{api:wi,config:cs}};async function _i(){let t=[];for(let[e,{api:n,config:r}]of Object.entries(lf)){let o=await r();o&&t.push({name:e,api:n,cfg:o})}return t}async function un(t){let e=lf[t?.provider];if(!e)return null;let n=await e.config();return n?{name:t.provider,api:e.api,cfg:n}:null}function Ei(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 uf=()=>process.env.BOSTRAT_STATE_DIR||af.join(process.env.HOME||jk.tmpdir(),".bostrat"),Ai=()=>af.join(uf(),"tickets.json"),df=()=>(process.env.BOSTRAT_PUBLIC_URL||"http://bostrat.globopi").replace(/\/$/,""),us=t=>`${df()}/track/${t}`,ls=null,xi=null,Si=Promise.resolve();async function ds(){return ls||(xi??=Wr.readFile(Ai(),"utf8").then(t=>JSON.parse(t)?.tickets??{}).catch(()=>({})).then(t=>ls=t).finally(()=>xi=null),xi)}async function ef(){let t=JSON.stringify({tickets:ls},null,2),e=`${Ai()}.${process.pid}.tmp`;await Wr.mkdir(uf(),{recursive:!0}),await Wr.writeFile(e,t),await Wr.rename(e,Ai())}async function Oe(){Si=Si.then(ef,ef),await Si.catch(()=>{})}async function ne(t){return(await ds())[t]??null}async function fs(){return{...await ds()}}async function ff(){return(await _i()).length>0}async function Xn(t,e,{source:n="api"}={}){let r=await _i();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 d=u.api.parseIssueRef(e);if(d)try{s=await u.api.resolveIssue(u.cfg,d),o=u;break}catch(f){a=f}}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 Oe(),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 qr(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 Hr(c,`**Bostrat** \u2014 Track **${c.track.name}** linked${l.length?` (${l.join(", ")})`:""}.`),await hf(t.id,c,"linked"),c}async function Ii(t){let e=await ds();return e[t]?(delete e[t],await Oe(),!0):!1}var Ti=t=>`$${t>=100?Math.round(t):t.toFixed(2)}`;async function pf(t,e){if(!t.track?.cwd)return null;let n=_e(await F({id:e,cwd:t.track.cwd}));return!n.totalUsd||n.totalUsd<.01?null:n}function Lk(t){let e=Object.entries(t.byProvider).sort((r,o)=>o[1].usd-r[1].usd).map(([r,o])=>`${r} ${Ti(o.usd)}`).join(" \xB7 "),n=Object.keys(t.byBox).length;return`${Ti(t.totalUsd)}${e?` (${e})`:""}${n>1?` across ${n} boxes`:""}`}function Fk(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(Ti(e.totalUsd)),n.join(" \xB7 ")||"linked"}async function qr(t,e){let n=await un(e);if(!n)return;let r=await pf(e,t).catch(()=>null);await n.api.upsertCard(n.cfg,e,{url:us(t),title:`Bostrat Track: ${e.track.name}`,subtitle:Fk(e,r)})}function ps(t){let e=new URL(df());return`${e.protocol}//${t}.${e.host}/`}var Uk={linked:"linked",opened:"prOpened",merged:"prMerged"};function Hk(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 mf(t,e,{from:n,to:r,mode:o,phase:s=null,actor:a="user"}){e.state=r,await Oe(),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 hf(t,e,n){let r=await un(e);if(!r)return;let o=r.cfg.ticketFlow?.[Uk[n]]??"prompt";if(o==="off")return;let{current:s,states:a}=await r.api.issueStates(r.cfg,e),i=Ei(s,a),c=Hk(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 mf(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:cf(),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 wf(t){let e=await ne(t);if(!e)throw new Error("no ticket linked to this Track");let n=await un(e);if(!n)throw new Error("no ticketing integration configured");let{current:r,states:o}=await n.api.issueStates(n.cfg,e),s=Ei(r,o);return s?.name&&e.state!==s.name&&(e.state=s.name,await Oe()),{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 Ri(t,e,{mode:n="manual"}={}){if(!e)throw new Error("stateId required");let r=await ne(t);if(!r)throw new Error("no ticket linked to this Track");let o=await un(r);if(!o)throw new Error("no ticketing integration configured");let{current:s,states:a}=await o.api.issueStates(o.cfg,r),i=Ei(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 Oe()),{key:r.key,from:l,to:c.name,moved:!1}):(await o.api.moveIssue(o.cfg,r,c.id),await mf(t,r,{from:l,to:c.name,mode:n}),{key:r.key,from:l,to:c.name,moved:!0})}async function gf(t,e){let n=await ne(t);if(!n)throw new Error("no ticket linked to this Track");let r=await un(n);if(!r)throw new Error("no ticketing integration configured");let o=String(e||"").trim();if(!o)throw new Error("comment body required");let s=await r.api.createComment(r.cfg,n,o);return await 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 yf(t,e,n){if(!e)throw new Error("offerId required");let r=await F({id:t.id,cwd:t.cwd}),o=r.find(a=>a.type==="ticket.move.offered"&&a.payload?.offerId===e);if(!o)throw new Error("unknown offer");if(r.some(a=>a.type==="ticket.move.answered"&&a.payload?.offerId===e))throw new Error("offer already answered");let s=null;return n&&(s=await Ri(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 nf=50;async function bf(){let t=await an();if(!t)throw new Error("no ticketing integration configured");return zn(t)}function Wk(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>nf)throw new Error(`too many issues (${n.length}); cap is ${nf} per proposal`);return{epic:r,issues:n,team:e.team?String(e.team).trim():null,linkCurrentTrack:e.linkCurrentTrack===!0}}async function qk(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 ne(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 kf(t,e){let n=await an();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=Wk(e),o=await zn(n),s=await qk(n,r,t,o),a=s?{id:s.id,key:s.key,name:s.name}:null,i=cf();return await O({id:t.id,cwd:t.cwd},{type:"ticket.create.offered",actor:"system",source:"orchestrator",payload:{ts:Date.now(),proposalId:i,provider:"linear",epic:r.epic,issues:r.issues,team:a,needsTeam:!s,linkCurrentTrack:r.linkCurrentTrack}}),{proposalId:i,epic:r.epic,issues:r.issues,team:a,needsTeam:!s}}async function xf(t,e,n,{teamId:r=null}={}){if(!e)throw new Error("proposalId required");let o=await F({id:t.id,cwd:t.cwd}),s=o.find(m=>m.type==="ticket.create.offered"&&m.payload?.proposalId===e);if(!s)throw new Error("unknown proposal");if(o.some(m=>m.type==="ticket.create.answered"&&m.payload?.proposalId===e))throw new Error("proposal already answered");if(!n)return await 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 an();if(!a)throw new Error("no ticketing integration configured");let i=s.payload,c=i.team??null;if(!c&&r){let m=(await zn(a)).find(h=>h.id===r);if(!m)throw new Error("unknown team");c={id:m.id,key:m.key,name:m.name}}if(!c)throw new Error("a team is required to create \u2014 pick one");let l=[],u=[],d=null;if(i.epic){let m=await yi(a,{teamId:c.id,name:i.epic.name,description:i.epic.description});d=m.id,l.push({kind:"epic",title:m.name,url:m.url})}for(let m of i.issues||[])try{let h=await bi(a,{teamId:c.id,title:m.title,description:m.description,projectId:d,priority:m.priority});l.push({kind:"issue",key:h.key,title:h.title,url:h.url})}catch(h){u.push({title:m.title,message:h?.message||String(h)})}await 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 f=l.filter(m=>m.kind==="issue"),p=null;return i.linkCurrentTrack&&f.length===1&&!await ne(t.id)&&(p=await Xn(t,f[0].key,{source:"created"}).catch(()=>null)),{proposalId:e,action:"created",created:l,failed:u,linked:p?p.key:null}}async function Hr(t,e){let n=await un(t);n&&await n.api.createComment(n.cfg,t,e)}async function Gk(t,e,n){let r=await un(t);if(!r)return null;let o=await Ee(e);if(!o)return null;let s=nn(o,n);if(!s)return null;let a=await Wr.readFile(Lt(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 rf=()=>Number(process.env.BOSTRAT_TICKET_COST_SYNC_MS??12e4),vi=new Map;function Kk(t,e){if(rf()<=0)return qr(t,e);if(vi.has(t))return;let n=setTimeout(()=>{vi.delete(t),ne(t).then(r=>r?qr(t,r):null).catch(()=>{})},rf());n.unref?.(),vi.set(t,n)}var of=new Map;async function sf(t,e,n){let r=of.get(t)??0;Date.now()-r<600*1e3||(of.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 Vk(t,{resolveTrack:e=null}={}){let n=await ne(t.trackId),r=t.payload||{};if(!n){if(t.type!=="turn.user"||t.actor!=="user")return;let o=null;for(let a of await _i())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 Xn(s,o,{source:"chat"})}catch(a){await sf(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 Oe())}try{switch(t.type){case"handoff.done":n.track.leadAgent=r.toAgent??n.track.leadAgent,await Oe(),await qr(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 Oe(),await qr(t.trackId,n),r.change==="opened"||r.change==="merged"){let o=r.change==="merged"?await pf(n,t.trackId).catch(()=>null):null;await Hr(n,`**Bostrat** \u2014 PR ${r.change} for **${n.track.name}**${r.url?`: ${r.url}`:r.number?` (#${r.number})`:""}`+(o?`
|
|
46
|
-
|
|
47
|
-
Agent cost for this Track: ${
|
|
48
|
-
|
|
49
|
-
`)}else if(r.change==="picked"){let o=(r.siblings??[]).map(s=>`**${s.name}**`).join(", ");await
|
|
50
|
-
_(runs on the Track's branch; reachable on the operator's private network only)_`);break}case"cost.recorded":await Kk(t.trackId,n);break;default:break}}catch(o){let s=e?await e(t.trackId).catch(()=>null):null;await sf(t.trackId,s?.cwd??null,o)}}function Sf({resolveTrack:t}){jt.on("event",e=>{Vk(e,{resolveTrack:t}).catch(()=>{})})}import ms from"node:fs/promises";import vf from"node:path";import Jk from"node:os";var Af=process.env.BOSTRAT_STATE_DIR||vf.join(process.env.HOME||Jk.tmpdir(),".bostrat"),Oi=vf.join(Af,"pr-links.json"),it=(t,e)=>`${t}#${Number(e)}`,hs=null,$i=null,N=Promise.resolve();async function gt(){return hs||($i??=ms.readFile(Oi,"utf8").then(t=>JSON.parse(t)?.links??{}).catch(()=>({})).then(t=>hs=t).finally(()=>$i=null),$i)}async function X(){let t=JSON.stringify({links:hs},null,2),e=`${Oi}.${process.pid}.tmp`;await ms.mkdir(Af,{recursive:!0}),await ms.writeFile(e,t),await ms.rename(e,Oi)}var zk=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 gt(),c=it(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:zk.has(a),firstSeen:Date.now()},N=N.then(X,X),await N.catch(()=>{}),{link:i[c],created:!0})}async function Gr(t,e){if(!t||e==null)return null;let n=(await gt())[it(t,e)];return!n||n.announced?null:(n.announced=!0,N=N.then(X,X),await N.catch(()=>{}),n)}async function vt(t,e){return!t||e==null?null:(await gt())[it(t,e)]??null}async function Ce(t,e,n){if(!t||e==null)return null;let r=(await gt())[it(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,N=N.then(X,X),await N.catch(()=>{}),r):null}async function Tf(t,e,{headSha:n=null,trackId:r=null}={}){if(!t||e==null)return null;let o=(await gt())[it(t,e)];return o?(o.ciFix={delegatedAt:Date.now(),headSha:n,trackId:r},N=N.then(X,X),await N.catch(()=>{}),o):null}async function _f(t,e,{headSha:n=null,trackId:r=null}={}){if(!t||e==null)return null;let o=(await gt())[it(t,e)];return o?(o.reconcile={delegatedAt:Date.now(),headSha:n,trackId:r},N=N.then(X,X),await N.catch(()=>{}),o):null}async function ws(t,e,{headSha:n=null}={}){if(!t||e==null)return null;let r=(await gt())[it(t,e)];return!r||!r.ciFix?null:(r.ciFixAwait={since:Date.now(),trackId:r.ciFix.trackId??null,headSha:n},delete r.ciFix,N=N.then(X,X),await N.catch(()=>{}),r)}async function gs(t,e){if(!t||e==null)return null;let n=(await gt())[it(t,e)];return!n||!n.ciFixAwait?null:(delete n.ciFixAwait,N=N.then(X,X),await N.catch(()=>{}),n)}async function ys(t,e,{headSha:n=null}={}){if(!t||e==null)return null;let r=(await gt())[it(t,e)];return!r||!r.reconcile?null:(r.reconcileAwait={since:Date.now(),trackId:r.reconcile.trackId??null,headSha:n},delete r.reconcile,N=N.then(X,X),await N.catch(()=>{}),r)}async function bs(t,e){if(!t||e==null)return null;let n=(await gt())[it(t,e)];return!n||!n.reconcileAwait?null:(delete n.reconcileAwait,N=N.then(X,X),await N.catch(()=>{}),n)}async function Ef(t,e,{at:n=Date.now()}={}){if(!t||e==null)return null;let r=(await gt())[it(t,e)];return r?(r.baseFoldAt=n,N=N.then(X,X),await N.catch(()=>{}),r):null}async function If(t,e){if(!t||e==null)return null;let n=(await gt())[it(t,e)];return!n||!n.baseFoldAt?null:(delete n.baseFoldAt,N=N.then(X,X),await N.catch(()=>{}),n)}async function Rf(t,e,n){if(!t||e==null||!n)return null;let r=(await gt())[it(t,e)];return r?(r.lastHeadSha=n,N=N.then(X,X),await N.catch(()=>{}),r):null}async function Kr(t,e,{merge:n=null}={}){if(!t||e==null)return null;let r=(await gt())[it(t,e)];return r?(r.armedAt=Date.now(),n&&(r.autoMerge={method:n.method||"merge",armedAt:r.armedAt}),N=N.then(X,X),await N.catch(()=>{}),r):null}async function Yn(t,e,n=!0){if(!t||e==null)return null;let r=(await gt())[it(t,e)];return r?(n?r.archiveOnMerge=!0:delete r.archiveOnMerge,N=N.then(X,X),await N.catch(()=>{}),r):null}async function ks(t,e,n=!0){if(!t||e==null)return null;let r=(await gt())[it(t,e)];return r?(n?r.notifyOnMerge=!0:delete r.notifyOnMerge,N=N.then(X,X),await N.catch(()=>{}),r):null}async function $f(t,e,n){if(!t||e==null)return null;let r=(await gt())[it(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()],N=N.then(X,X),await N.catch(()=>{}),r}async function Vr(t,e){if(!t||e==null)return null;let n=(await gt())[it(t,e)];return!n||!n.armedAt&&!n.autoMerge?null:(delete n.armedAt,delete n.autoMerge,delete n.archiveOnMerge,delete n.notifyOnMerge,delete n.notifyPending,N=N.then(X,X),await N.catch(()=>{}),n)}var Xk=new Set(["merged","closed","lost"]);async function Of(){return Object.values(await gt()).filter(t=>!Xk.has(t.state))}async function Me(){return Object.values(await gt()).sort((t,e)=>e.firstSeen-t.firstSeen)}var Mf=t=>Math.round(t*100)/100;function Yk(t){return!t||t.kind==="setup"?[]:$t(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():Cf.basename(e.base)}))}function Pf(t){let e=new Map;for(let n of t||[])for(let r of Yk(n)){let o=e.get(r.key);o||e.set(r.key,o={...r,tracks:[]}),o.tracks.push(n)}return e}function Nf(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 Ci=(t,e)=>`${t} ${e}${t===1?"":"s"}`;function Zk(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 Mi(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 d=u.payload||{};!d.repo||d.number==null||(d.change==="merged"?o.set(`${d.repo}#${d.number}`,d):d.change==="opened"&&s++)}if(o.size){let u=[...o.keys()].map(d=>{let[f,p]=d.split("#");return`${f.split("/").pop()}#${p}`});n.push(`Merged ${u.slice(0,4).join(", ")}${u.length>4?` +${u.length-4}`:""}`)}else s&&n.push(Ci(s,"PR")+" opened");let a=Object.keys(t?.subtasks||{}).length||r.filter(u=>u.type==="subtask-delegated").length;a&&n.push(Ci(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(d=>`${d.fromAgent}\u2192${d.toAgent}`).join(", ");n.push(`${Ci(i.length,"handoff")} ${u}${i.length>3?"\u2026":""}`)}let c=_e(e);c.totalUsd>=.01&&n.push(`$${c.totalUsd.toFixed(2)}`);let l=Number.isFinite(t?.archivedAt)&&Number.isFinite(t?.createdAt)?Zk(t.archivedAt-t.createdAt):null;return n.length&&l&&n.push(l),n.length?n.join(" \xB7 "):null}function Qk(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 Bf(t,e,n){let r=new Set(t.tracks.map(f=>f.id)),o=0,s=0,a=0,i=null,c=null,l=0;for(let f of t.tracks){f.archivedAt?s++:o++,f.beat?.text&&a++;let p=Nf(f);p&&(!i||p>i)&&(i=p),f.createdAt&&(!c||f.createdAt<c)&&(c=f.createdAt),l+=e.get(f.id)||0}let u=0,d=0;for(let f of n)r.has(f.trackId)&&(t.slug&&f.repo!==t.slug||t.slug&&(f.state==="merged"?d++:f.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:d},usd:Mf(l),firstActivityAt:c,lastActivityAt:i}}async function Df(t){let e=Pf(t),n=await Me().catch(()=>[]),r=new Map,o=new Set;for(let a of e.values())for(let i of a.tracks){if(o.has(i.id))continue;o.add(i.id);let c=await F(i).catch(()=>[]);r.set(i.id,_e(c).totalUsd)}let s=[...e.values()].map(a=>Bf(a,r,n)).sort((a,i)=>(i.lastActivityAt??0)-(a.lastActivityAt??0));return{generatedAt:Date.now(),arcs:s}}async function jf(t,e){let r=Pf(t).get(e);if(!r)return null;let[o,s,a,i,c]=await Promise.all([Me().catch(()=>[]),Qo().catch(()=>[]),os().catch(()=>[]),ss().catch(()=>[]),fs().catch(()=>({}))]),l=new Map,u=[];for(let d of[...r.tracks].sort((f,p)=>(p.createdAt||0)-(f.createdAt||0))){let f=await F(d).catch(()=>[]),p=_e(f);l.set(d.id,p.totalUsd);let m=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:Qk(d),handoffs:(d.events||[]).filter(h=>h.type==="lead-handoff").length,subtasks:Object.keys(d.subtasks||{}).length,repos:$t(d.workspace).map(h=>h.repo||Cf.basename(h.base||"")),prs:o.filter(h=>h.trackId===d.id&&(!r.slug||h.repo===r.slug)).map(h=>({repo:h.repo,number:h.number,state:h.state??null,title:h.title??null,url:h.url??null})),mocks:s.filter(h=>h.trackId===d.id).length,views:a.filter(h=>h.trackId===d.id).length,runbooks:i.filter(h=>h.trackId===d.id).map(h=>({name:h.name,status:h.status})),ticket:m?{provider:m.provider,key:m.key,url:m.url??null}:null,usd:Mf(p.totalUsd),lastActivityAt:Nf(d)})}return{...Bf(r,l,o),generatedAt:Date.now(),segments:u}}import{execFile as tx}from"node:child_process";import{promisify as ex}from"node:util";import{randomUUID as nx}from"node:crypto";var dn=ex(tx),xs=t=>new Promise(e=>setTimeout(e,t)),rx=Number(process.env.BOSTRAT_SWAP_EXIT_TIMEOUT_MS)||15e3,Lf=Number(process.env.BOSTRAT_SWAP_SHELL_TIMEOUT_MS)||8e3,ox=250,sx=400,Ff={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 Hf(t){let e=await ou(t);if(!e)return!1;try{return await dn("pgrep",["-P",String(e)]),!1}catch(n){return n?.code===1}}async function Pi(t,e){let n=Date.now()+e;for(;;){if(await Hf(t))return!0;if(Date.now()>n)return!1;await xs(ox)}}async function ax(t,e){let n=Ff[e]||Ff.default;n.escape&&(await bt(t,["Escape"],{kind:"swap-exit"}),await xs(150)),n.wipe&&await Do(t,400,{kind:"swap-exit"}),await jo(t,n.text,{perCharDelayMs:90,kind:"swap-exit"});let r=le(e).submitDelayMs||0;await xs(Math.max(r,250)),await bt(t,["Enter"],{kind:"swap-exit"})}async function ix(t,e){try{let{stdout:n}=await dn(C,P("show-environment","-t",t,e));return!String(n||"").trimStart().startsWith("-")}catch{return!1}}async function Uf(t){await dn(C,P("respawn-pane","-k","-t",t))}async function Ni(t,e={}){if(!pt(t))throw new Error("invalid session name");let n=String(e.agentCmd||"").trim();if(!n)throw new Error("agentCmd is required");let r=await z(t);if(!r)throw new Error(`session "${t}" not found`);if(!r.cwd)throw new Error(`can't resolve cwd for "${t}"`);let o=r.agent||"agent",s=e.agent||Pn(n),a=Number(e.exitTimeoutMs)||rx;await q({flow:"swap",phase:"start",sessionName:t,from:o,to:s,cwd:r.cwd});let i=!1;if(!await Hf(t)&&(await ax(t,o),!await Pi(t,a)&&(i=!0,await q({flow:"swap",phase:"respawn-forced",sessionName:t,from:o}),await Uf(t),!await Pi(t,Lf))))throw new Error(`pane shell did not come back after respawn-pane in "${t}"`);let c=s==="claude"&&Mn(n),l=c?Co():null,u=await ix(t,"ANTHROPIC_API_KEY"),d=!1;try{l?(await dn(C,P("set-environment","-t",t,"ANTHROPIC_API_KEY",l)),d=!0):u&&(await dn(C,P("set-environment","-u","-t",t,"ANTHROPIC_API_KEY")),d=!0)}catch(b){throw l?Ta(b,l):b}if(d&&!i&&(await Uf(t),!await Pi(t,Lf)))throw new Error(`pane shell did not come back after env respawn in "${t}"`);let f=!!e.resumeSessionId,p=e.resumeSessionId||nx(),m=f?n:Ia(n,p),h=f?e.pinned===!0:/(^|\s)--session-id(\s|=)/.test(m);return c&&await xe(r.cwd),s==="codex"&&await $n(r.cwd),s==="cursor"&&await On(r.cwd),await tn(t),await dn(C,P("set-option","-t",t,"@bostrat-sid",p)),await dn(C,P("set-option","-t",t,"@bostrat-agent",s)),await xs(sx),await kt(t,m,{kind:"swap-launch"}),await q({flow:"swap",phase:"done",sessionName:t,from:o,to:s,sid:p,forced:i,envRespawned:d}),{name:t,cwd:r.cwd,sessionId:p,agent:s,pinned:h,forced:i,envRespawned:d}}import yx from"node:fs";import qf from"node:fs/promises";import Jr from"node:path";import bx from"node:os";import{fileURLToPath as kx}from"node:url";import{execFile as xx}from"node:child_process";import{promisify as Sx}from"node:util";import Di from"node:fs/promises";import Pe from"node:path";import{execFile as cx}from"node:child_process";import{promisify as lx}from"node:util";var ux=lx(cx),Wf=process.env.HOME||"/home/globo",dx=Pe.join(process.env.BOSTRAT_STATE_DIR||Pe.join(Wf,".bostrat"),"repos"),fx=[...(process.env.BOSTRAT_REPOS_ROOT||Pe.join(Wf,"Documents")).split(","),dx].map(t=>t.trim()).filter(Boolean),px=Number(process.env.BOSTRAT_REPOS_DEPTH)||3,mx=new Set(["node_modules",".git",".cache","dist","build",".next","vendor","__pycache__","venv",".venv","target"]);async function hx(t){try{return(await Di.stat(Pe.join(t,".git"))).isDirectory()?"repo":"worktree"}catch{return null}}async function wx(t){let e=i=>Di.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 me(){let t=new Map;async function e(n,r){if(t.has(n))return;let o=await hx(n);if(o==="worktree")return;if(o==="repo"){t.set(n,{path:n,name:Pe.basename(n),branch:await wx(n)});return}if(r>=px)return;let s;try{s=await Di.readdir(n,{withFileTypes:!0})}catch{return}for(let a of s)!a.isDirectory()||a.name.startsWith(".")||mx.has(a.name)||await e(Pe.join(n,a.name),r+1)}for(let n of fx)await e(Pe.resolve(n),0);return[...t.values()].sort((n,r)=>n.name.localeCompare(r.name))}function gx(t){let e=String(t||"").trim().match(/github\.com[:/]+([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/i);return e?`${e[1]}/${e[2]}`:null}var Bi=new Map;async function Y(t){if(!t)return null;if(Bi.has(t))return Bi.get(t);let e=null;try{let{stdout:n}=await ux("git",["-C",t,"config","--get","remote.origin.url"],{timeout:5e3});e=gx(n)}catch{e=null}return Bi.set(t,e),e}var Gf=Sx(xx),vx=process.env.BOSTRAT_STATE_DIR||Jr.join(process.env.HOME||bx.tmpdir(),".bostrat"),Ax=Jr.join(vx,"repos"),Kf=/^[^/\s]+\/[^/\s]+$/,Tx=5*6e4,_x=6e4,Ex=10*6e4,Vf=fetch,ji=new Map,fn=null;function Li(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 Ss(t=process.env){return!!(Li(t)&&t.BOSTRAT_PLANE_TOKEN)}var Jf=t=>({authorization:`Bearer ${t.BOSTRAT_PLANE_TOKEN||""}`}),Zn=(t,e)=>Object.assign(new Error(t),{status:e});async function vs(t,{env:e=process.env}={}){let n=String(t||"").toLowerCase();if(!Kf.test(n))throw Zn("repo must be owner/name",400);let r=ji.get(n);if(r&&r.expiresAt-Date.now()>Tx)return r;if(!Ss(e))throw Zn("node is not plane-linked \u2014 GitHub auth unavailable",503);let o;try{o=await Vf(`${Li(e)}/v1/github/token?repo=${encodeURIComponent(t)}`,{headers:Jf(e)})}catch(i){if(r&&r.expiresAt>Date.now())return r;throw Zn(`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 Zn(s?.error||`plane replied ${o.status}`,o.status)}let a={token:s.token,expiresAt:Number(s.expiresAt)||Date.now()+55*6e4,username:s.username??null,installationId:s.installationId??null};return ji.set(n,a),a}async function zr({env:t=process.env,force:e=!1}={}){if(!Ss(t))return{configured:!1,repos:[],username:null};if(!e&&fn&&Date.now()-fn.at<_x)return fn.data;try{let n=await Vf(`${Li(t)}/v1/github/repos`,{headers:Jf(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 zf(){ji.clear(),fn=null}async function re({cwd:t=null,slug:e=null,env:n=process.env}={}){try{let r=e||(t?await Y(t):null);if(!r)return{};let{token:o}=await vs(r,{env:n});return o?{GH_TOKEN:o}:{}}catch{return{}}}async function Fi({env:t=process.env}={}){return(await zr({env:t})).username??null}var Ix=()=>kx(new URL("../bin/bostrat-git-credential",import.meta.url)),Rx=(t=process.env)=>`http://127.0.0.1:${Number(t.PORT)||3362}`;function $x(t){let[e,n]=String(t).split("/");return Jr.join(Ax,e,n)}async function Ui(t,{env:e=process.env}={}){let n=(...r)=>Gf("git",["-C",t,...r],{timeout:1e4});await n("config","--replace-all","credential.helper",""),await n("config","--add","credential.helper",`!"${Ix()}" ${Rx(e)}`),await n("config","credential.useHttpPath","true")}async function Hi(t,{env:e=process.env,baseUrl:n="https://github.com"}={}){if(!Kf.test(String(t||"")))throw Zn(`bad repo slug "${t}"`,400);let r=$x(t);if(yx.existsSync(Jr.join(r,".git")))return r;let{token:o}=await vs(t,{env:e});await qf.mkdir(Jr.dirname(r),{recursive:!0});let s="!f() { echo username=x-access-token; echo password=$BOSTRAT_GIT_TOKEN; }; f";try{await Gf("git",["-c","credential.helper=","-c",`credential.helper=${s}`,"clone","--no-tags","-q",`${n}/${t}.git`,r],{timeout:Ex,env:{...e,BOSTRAT_GIT_TOKEN:o,GIT_TERMINAL_PROMPT:"0"}})}catch(a){throw await qf.rm(r,{recursive:!0,force:!0}).catch(()=>{}),Zn(`clone of ${t} failed: ${a?.stderr?.trim?.()||a?.message||a}`,502)}return await Ui(r,{env:e}),r}import l0 from"node:fs/promises";function Ox(t){let e=[],n="",r=null;for(let o=0;o<t.length;o++){let s=t[o];if(r){n+=s,s===r&&t[o-1]!=="\\"&&(r=null);continue}if(s==="'"||s==='"'||s==="`"){r=s,n+=s;continue}if(s==="&"&&t[o+1]==="&"){e.push(n),n="",o++;continue}if(s==="|"&&t[o+1]==="|"){e.push(n),n="",o++;continue}if(s==="|"||s===";"||s===`
|
|
51
|
-
`){e.push(n),n="";continue}n+=s}return e.push(n),e.map(o=>o.trim()).filter(Boolean)}function
|
|
52
|
-
`,n);i!==-1&&(e=e.slice(0,i))}let r=
|
|
53
|
-
`).map(n=>n.trim()).find(Boolean)||t;return e.length>88?`${e.slice(0,88).trimEnd()}\u2026`:e}function
|
|
54
|
-
`)){let a=s.trim();if(!a||a[0]!=="{")continue;let i;try{i=JSON.parse(a)}catch{continue}let c=i.message;if(!c)continue;let l=[];if(
|
|
55
|
-
`)){let c=i.trim();if(!c||c[0]!=="{")continue;let l;try{l=JSON.parse(c)}catch{continue}if(l.isSidechain)continue;let u=l.message?.content;if(!Array.isArray(u))continue;let
|
|
56
|
-
`).trim():"",
|
|
57
|
-
`);if(i!==-1)return o.slice(0,i);if(s+=a,o.length>=e)break}return o}finally{await n.close()}}async function
|
|
58
|
-
`).trim():"";async function
|
|
59
|
-
`)){let
|
|
60
|
-
`;else if(s==="t")n+=" ";else if(s==="r")n+="\r";else if(s==="u")if(t[r+1]==="{"){let a=t.indexOf("}",r+2);if(a===-1)return null;n+=String.fromCodePoint(parseInt(t.slice(r+2,a),16)),r=a}else n+=String.fromCharCode(parseInt(t.slice(r+1,r+5),16)),r+=4;else n+=s}else{if(o===e)return n;if(e==="`"&&o==="$"&&t[r+1]==="{")return null;n+=o}}return null}function
|
|
61
|
-
`)){let c=i.trim();if(!c||c[0]!=="{")continue;let l;try{l=JSON.parse(c)}catch{continue}if(l.type!=="response_item")continue;let u=l.payload;if(!u)continue;let
|
|
62
|
-
`),o=-1;for(let u=r.length-1;u>=0;u--)if(/^\s*Switch model\?\s*$/.test(r[u])){o=u;break}if(o<0)return null;let s=[],a=[];for(let u=o+1;u<r.length;u++){let
|
|
63
|
-
`),r=n.length-1;for(;r>=0&&n[r].trim()==="";)r--;if(r<0||!
|
|
64
|
-
`),o=[];for(let c=0;c<r.length;c++){let l=r[c].match(
|
|
65
|
-
`),o=-1;for(let u=r.length-1;u>=0;u--)if(
|
|
66
|
-
`),o=
|
|
67
|
-
`),o=
|
|
68
|
-
`),o="";for(let s=0;s<r.length;s++)if(/^\s*https:\/\//.test(r[s])){o=
|
|
69
|
-
`)].reverse().find(a=>
|
|
70
|
-
`),o=
|
|
71
|
-
`),o=-1;for(let l=r.length-1;l>=0;l--)if(r[l].trim()!==""){o=l;break}if(o<0)return null;let s=r[o].match(
|
|
72
|
-
`),o
|
|
73
|
-
`).pop()||"")?!0:
|
|
74
|
-
`).pop()||""):t==="codex"&&/Sign in with ChatGPT|one-time code|Paste or type your API key|link to authenticate/i.test(n)?!0:t==="cursor"||t==="codex"?/\b(allow|approve|apply (this )?patch|run (this )?command|do you want to run|grant)\b/i.test(n):!1:!1}function
|
|
75
|
-
`).map(c=>c.replace(/\s+$/,"")),r=-1;for(let c=n.length-1;c>=0;c--)if(
|
|
76
|
-
|
|
77
|
-
`)
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
`),n=e.findIndex(s=>/Available models|Models matching/i.test(s));if(n<0)return null;let r=/Models matching/i.test(e[n]),o=[];for(let s=n+1;s<e.length;s++){let a=e[s];if(/^\s*Filter:/i.test(a)||!a.trim())continue;if(/to filter|Enter to select|^\s*\d+-\d+ of \d+/i.test(a))break;let i=a.match(/^\s*(→\s+)?(.+?)\s*$/);if(!i)continue;let c=i[2].replace(/\s*\(Tab to modify\)\s*$/i,""),l=null,u=c.split(/\s{2,}/);u.length>1&&(c=u[0],l=u.slice(1).join(" ").trim()||null),c&&o.push({label:c.trim(),params:l,active:!!i[1]})}return o.length?{filtered:r,rows:o}:null}function zp(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&&!Kp.has(o))throw new Error(`unknown codex effort "${o}" (low | medium | high | xhigh)`);return{model:r,effort:o||null}}if(t==="cursor"){let r=Vp.find(o=>o.alias===n);return{model:n,filter:r?r.label:n}}if(!H0.has(n))throw new Error(`unknown model "${n}"`);return{model:n}}async function eo(t,e,{timeoutMs:n=F0}={}){let r=Date.now()+n;for(;;){let o=await wt(t,{lines:80}).catch(()=>""),s=e(o);if(s)return s;if(Date.now()>r)return null;await Is(U0)}}async function G0(t,e){return await kt(t,`/model ${e.model}`,le("claude")),{verified:!1,note:"confirmed when the next turn's transcript model flips"}}async function K0(t,e){await Bo(t),await kt(t,"/model",{enter:!1}),await Is(250),await bt(t,["Enter"],{kind:"model-switch"});let n=await eo(t,Hp);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=q0(i);return!c||c.model!==e.model||e.effort&&c.effort&&c.effort.toLowerCase()!==e.effort.toLowerCase()?null:c};await kt(t,String(r.n),{enter:!1,kind:"model-switch"});let s=await eo(t,i=>Wp(i)||!Hp(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?Kp.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 kt(t,String(c.n),{enter:!1,kind:"model-switch"})}let a=s.done||await eo(t,i=>!Wp(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 V0(){let t=Up.join(process.env.CURSOR_CONFIG_DIR||Up.join(L0.homedir(),".cursor"),"cli-config.json");try{return JSON.parse(await j0.readFile(t,"utf8"))?.model||null}catch{return null}}async function J0(t,e){await Bo(t),await jo(t,`/model ${e.filter}`,{kind:"model-switch"});let n=await eo(t,qp),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 eo(t,qp,{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 Is(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 Is(800);let a=await V0();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 Xp(t,e,n){return t==="codex"?K0(e,n):t==="cursor"?J0(e,n):G0(e,n)}var Nt=t=>t||null;function $s(t){let e=t||{},n=z0(e);return n&&(n.payload={ts:e.at,...n.payload}),n}function z0(t){switch(t.type){case"said":return{type:"turn.user",actor:"user",source:"orchestrator",boxId:t.box,provider:Nt(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:Nt(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:Nt(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:Nt(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:Nt(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:Nt(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:Nt(t.agent),payload:{...cr(t)}};case"subtask-failed":return{type:"error",actor:"system",source:"orchestrator",boxId:t.box||t.to,provider:Nt(t.agent||t.toAgent),payload:{kind:"subtask-failed",...cr(t)}};case"subtask-resumed":return{type:"box.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Nt(t.agent),payload:{change:"subtask-resumed",...cr(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",...cr(t)}};case"archived":case"unarchived":case"track-deleted":return{type:"box.state.changed",actor:"user",source:"orchestrator",payload:{change:t.type,...cr(t)}};case"rehydrated":return{type:"box.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Nt(t.agent),payload:{change:"rehydrated"}};case"retagged":return{type:"box.state.changed",actor:"user",source:"orchestrator",payload:{change:"retagged",...cr(t)}};case"rehydrate-failed":return{type:"error",actor:"system",source:"orchestrator",boxId:t.box,provider:Nt(t.agent),payload:{kind:"rehydrate-failed",reason:t.reason}};default:return null}}var Rs=Promise.resolve(),Tc=t=>(Rs=Rs.then(t).catch(()=>{}),Rs),Zp=()=>Rs;function Qp(t,e){let n=$s(e);if(!n)return;let r=e?.type==="track-deleted"?{id:t.id}:t;Tc(()=>O(r,n))}var Yp=new Set;async function tm(t){if(!(!t?.id||Yp.has(t.id))){Yp.add(t.id);try{let e=await F(t),n=new Set(e.filter(r=>r.source==="orchestrator").map(r=>`${r.type}|${r.payload?.ts??"?"}`));for(let r of t.events||[]){let o=$s(r);!o||n.has(`${o.type}|${o.payload?.ts??"?"}`)||await O(t,o)}}catch{}}}function cr(t){let{type:e,at:n,...r}=t;return r}var Ac=new Map;function em(t,e,n){!n?.promptId||Ac.get(t.id)===n.promptId||(Ac.set(t.id,n.promptId),Tc(()=>O(t,{type:"prompt.raised",actor:"agent",source:"pane",boxId:e?.name,provider:Nt(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 nm(t,e,n,r={}){Ac.delete(t.id),Tc(()=>O(t,{type:"prompt.answered",actor:"user",source:"orchestrator",boxId:e?.name,provider:Nt(n?.provider||e?.agent),payload:{promptId:n?.promptId,...r}}))}import{randomUUID as ES}from"node:crypto";var om=/(?<!\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,sm=/(?<!\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,am=/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,im=/(?<!\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,X0=new RegExp(`\\b(?:${om}|${sm}|${am}|${im})\\b`,"i"),Y0=/\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,Z0=/^(?:commit|push|open|create|raise|rebase|ready|ok(?:ay)?\s+to|good\s+to)\b/i,Q0=90;function tS(t){return String(t).split(/(?<=[.?!])\s+|\n+/).map(e=>e.trim()).filter(Boolean)}function cm(t){let e=String(t||"").trim();if(!e)return null;let n=e.slice(-600);for(let r of tS(n).reverse()){if(!X0.test(r))continue;let o=r.endsWith("?")&&r.length<=Q0&&Z0.test(r);if(Y0.test(r)||o)return r.length>200?r.slice(0,197)+"\u2026":r}return null}var lm=["commit","rebase","push","pr"],eS=new RegExp(`\\b(?:${om})\\b`,"i"),nS=new RegExp(`\\b(?:${im})\\b`,"i"),rS=new RegExp(`\\b(?:${sm})\\b`,"i"),oS=new RegExp(`\\b(?:${am})\\b`,"i"),sS=/\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 um(t){let e=String(t||""),n=[];eS.test(e)&&n.push("commit"),nS.test(e)&&n.push("rebase"),rS.test(e)&&n.push("push");let r=sS.test(e);!r&&oS.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 Os(t,{branch:e=null,base:n=null,toPr:r=null,repos:o=null,offeredRebase:s=!1}={}){Array.isArray(t)||(t=t==="push"?["commit","push"]:["commit","push","pr"]);let a=new Set(t),i=Array.isArray(o)&&o.length>0,c=i?o.map(p=>`${p.name}/`).join(", "):null,l=e?` on this branch (${e})`:"",u=[];if(a.has("commit")&&u.push(i?`commit the work in each changed checkout (${c})${l} with a clear commit message per repo`:`commit the work${l} with a clear commit message`),a.has("rebase")){let p=a.has("commit")?"the branch":`this branch${e?` (${e})`:""}`;u.push(i?`rebase ${p} in each changed checkout (${c}) onto its own base branch (fetch origin first), resolving any conflicts so both sides' intent is preserved`:`rebase ${p} onto ${n?`the latest ${n}`:"its base branch"} (fetch origin first), resolving any conflicts so both sides' intent is preserved`)}if(a.has("push")||a.has("pr")){let p=r&&!a.has("pr")?` (the open PR${r.number?` #${r.number}`:""} picks it up)`:"",m=a.has("rebase")?" with `--force-with-lease` (the rebase rewrote history)":"";i?u.push(a.has("commit")||a.has("rebase")?`push each one to its remote${m}${p}`:`push this branch${e?` (${e})`:""} from each changed checkout (${c}) to its remote${p}`):u.push(a.has("commit")||a.has("rebase")?`push it to the remote${m}${p}`:`push this branch${l} to the remote${p}`)}a.has("pr")&&u.push(i?"open a pull request in each changed repo with a concise title and a description of what changed and why (run `gh pr create` inside each checkout)":`open a pull request${n?` against ${n}`:""} with a concise title and a description of what changed and why (use \`gh pr create\`)`);let d=[];a.has("commit")||d.push("don't create any new commits \u2014 ship what's already committed"),s&&!a.has("rebase")&&d.push("don't rebase \u2014 leave the branch's history as it is"),!a.has("push")&&!a.has("pr")&&d.push("don't push"),a.has("pr")||d.push("don't open a pull request");let f=`Yes \u2014 go ahead: ${u.join(", then ")}.`;return d.length&&(f+=` ${d.join(", and ").replace(/^./,p=>p.toUpperCase())}.`),a.has("pr")&&(f+=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."),f}var aS=3e4;function dm(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<=aS&&n.push({box:r.box,from:r.from,to:e+1e12}),n}function fm(t,e){let n=new Set;for(let r of e||[]){if(!r.settled)continue;let o=null;for(let s of t||[])s.kind!=="message"||s.role!=="assistant"||s.box!==r.box||s.ts==null||s.ts<r.from||s.ts>r.to||(!o||s.ts>=o.ts)&&(o=s);o&&n.add(o)}return n}var rm={commit:"commit",rebase:"rebase",push:"push",pr:"open a PR"};function pm(t){let e=(Array.isArray(t)?t:[]).filter(r=>rm[r]).map(r=>rm[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 dS}from"node:child_process";import{promisify as fS}from"node:util";import iS from"node:fs";import _c from"node:fs/promises";import gm from"node:path";import cS from"node:os";var ym=process.env.BOSTRAT_STATE_DIR||gm.join(process.env.HOME||cS.tmpdir(),".bostrat"),Cs=gm.join(ym,"github-metrics.json"),bm=60*6e4,mm=5*6e4,lS=6e4,lr=Date.now(),he=new Map,De=null,xn=0,hm=0,Ms=t=>[...t.values()].reduce((e,n)=>e+n,0);try{let t=JSON.parse(iS.readFileSync(Cs,"utf8"));t?.windowStart&&(Date.now()-t.windowStart<bm?(lr=t.windowStart,he=new Map(Object.entries(t.byCaller||{})),De=t.lastHour||null):Ms(new Map(Object.entries(t.byCaller||{})))&&(De={start:t.windowStart,total:Ms(new Map(Object.entries(t.byCaller||{}))),byCaller:t.byCaller||{}}))}catch{}var wm=Promise.resolve();function km(t,e=!1){if(!e&&t-hm<lS)return;hm=t;let n=JSON.stringify({windowStart:lr,byCaller:Object.fromEntries(he),lastHour:De});wm=wm.then(()=>_c.mkdir(ym,{recursive:!0})).then(()=>_c.writeFile(`${Cs}.tmp`,n)).then(()=>_c.rename(`${Cs}.tmp`,Cs)).catch(()=>{})}function xm(t){t-lr<bm||(De={start:lr,total:Ms(he),byCaller:Object.fromEntries(he)},De.total&&console.log(`[github] ${De.total} calls last hour`,De.byCaller),lr=t,he=new Map,km(t,!0))}function we(t="other",e=Date.now()){xm(e),he.set(t,(he.get(t)||0)+1),km(e)}function Sm(t=Date.now()){return xm(t),{windowStart:lr,total:Ms(he),byCaller:Object.fromEntries(he),lastHour:De,backoffUntil:xn>t?xn:null}}var uS=/rate limit|too quickly|abuse detection|HTTP 429/i;function je(t){return uS.test(String(t?.message||t||""))}function Le(t,e=Date.now()){return xn<=e&&console.warn(`[github] rate limited \u2014 backing off ${mm/6e4} min:`,String(t?.message||t||"").slice(0,200)),xn=Math.max(xn,e+mm),xn}function Ps(t=Date.now()){return Math.max(0,xn-t)}async function Ec(t,e){let n={working:!1,prompted:!1};if(!t)return n;let r=await wt(t,{lines:_t}).catch(()=>"");if(!jn(e,r))return Iu(e,r)?{working:!0,prompted:!1}:n;if(sr(e,r)){let o=await ve(t).catch(()=>r);if(bn({name:t,agent:e},o))return{working:!1,prompted:!0}}return{working:!0,prompted:!1}}async function Ns(t,e){return(await Ec(t,e)).working}var vm=fS(dS),Am=[4e3,15e3,45e3],pS=2500,mS=5*6e4;async function Ic(t,e,{midTurn:n=Ns,pollMs:r=pS,capMs:o=mS}={}){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 hS=6e4,wS=9e4,gS=2e3,yS=/(?:^|[;&|]\s*|\)\s*)gh\s+(?:[^\s;&|"'`$]+\s+)*?pr\s+create\b/,bS=/(?:^|[;&|]\s*|\)\s*)gh\s+(?:[^\s;&|"'`$]+\s+)*?pr\s+merge\b/,kS=/\s--web\b/,Tm=/(?:--repo|-R)[\s=]+([\w.-]+\/[\w.-]+)/;function Rc(t,e){return t.slice(e).replace(/^[\s;&|)]+/,"").replace(/"[^"]*"|'[^']*'/g,'""').split(/[;&|]/)[0]}function _m(t){let e=String(t||""),n=e.match(yS);if(!n)return null;let r=Rc(e,n.index);return kS.test(r)?null:{repo:r.match(Tm)?.[1]??null}}var xS=new Set(["--subject","-t","--body","-b","--body-file","-F","--match-head-commit","--author-email","-A","--repo","-R"]);function Em(t){let e=String(t||""),n=e.match(bS);if(!n)return null;let r=Rc(e,n.index),o=Rc(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("-")){xS.has(c)&&i++;continue}s=c.replace(/^["']+|["']+$/g,"")||null;break}return{repo:r.match(Tm)?.[1]??null,selector:s,auto:/\s--auto\b/.test(r)}}var SS="number,url,title,headRefName,createdAt";async function vS(t,e){let n=async s=>{we("observer");let{stdout:a}=await vm("gh",["pr","list","--repo",t,"--author","@me","--state","all","--limit","10","--json",SS],{timeout:3e4,maxBuffer:4*1024*1024,env:{...process.env,...s}}).catch(i=>{throw je(i)&&Le(i),i});return JSON.parse(a||"[]")},r=await re({slug:t}),o=await n(r);return!o.length&&r.GH_TOKEN&&(o=await n({}).catch(()=>[])),o.filter(s=>Date.parse(s.createdAt)>=e-hS).sort((s,a)=>Date.parse(a.createdAt)-Date.parse(s.createdAt))}async function AS(t,e){if(e)return[e];let n=[];for(let r of Im(t)){let o=await Y(r).catch(()=>null);o&&!n.includes(o)&&n.push(o)}for(let r of await me().catch(()=>[])){let o=await Y(r.path).catch(()=>null);o&&!n.includes(o)&&n.push(o)}return n}function Im(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 TS(t,e,n){let r=t.workspace?.branch,o=[];for(let s of await AS(t,e)){let a;try{a=await vS(s,n)}catch{continue}for(let i of a){let c=await vt(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 Fe=new Set;function Rm(t,e,{delays:n=Am,turnEnd:r}={}){let o=_m(e.command);if(!o)return null;let s=!!Em(e.command),a=`${t.id}|${e.id??e.ts??e.command}`;if(Fe.has(a))return null;Fe.add(a);let i=e.ts??Date.now(),c=(async()=>{try{for(let l of n){await new Promise(h=>setTimeout(h,l));let u=await TS(t,o.repo,i).catch(()=>null);if(!u)continue;let{slug:d,pr:f}=u,{link:p,created:m}=await Pt({repo:d,number:f.number,trackId:t.id,url:f.url,headRef:f.headRefName,title:f.title,source:"observed"});return!m&&!(p?.trackId===t.id&&await Gr(d,f.number))?null:(await Ic(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:f.number,url:f.url,title:f.title,branch:f.headRefName,repo:d,...s?{selfMerge:!0}:{}}}),f)}return null}finally{Fe.delete(a)}})();return c.catch(()=>Fe.delete(a)),c}var _S="number,url,title,headRefName,state";function $m(t,e,{delays:n=Am,turnEnd:r,openedWait:o}={}){let s=Em(e.command);if(!s)return null;let a=`merge|${t.id}|${e.id??e.ts??e.command}`;if(Fe.has(a))return null;Fe.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 d=[];if(c)d=[c];else{let m=Im(t);if(m.length>1&&/^\d+$/.test(String(s.selector??"")))return null;for(let h of m){let b=await Y(h).catch(()=>null);b&&!d.includes(b)&&d.push(b)}}if(l||=t.workspace?.branch,!d.length||!l)return null;c=d[0];let f=async(m,h)=>{let b=await vt(c,m.number);return b&&b.trackId!==t.id?null:(b||await Pt({repo:c,number:m.number,trackId:t.id,url:m.url,headRef:m.headRefName,title:m.title,source:"merge-observed"}),await Kr(c,m.number),h&&((await F(t).catch(()=>[])).some(E=>E.type==="pr.state.changed"&&(E.payload?.change==="auto-armed"||E.payload?.change==="merged")&&E.payload?.number===m.number)||(await Ic(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:m.number,url:m.url,title:m.title,branch:m.headRefName,repo:c}}))),m)},p=null;for(let m of n){await new Promise(I=>setTimeout(I,m));let h=[];for(let I of d)try{we("observer");let{stdout:L}=await vm("gh",["pr","view",String(l),"--repo",I,"--json",_S],{timeout:3e4,maxBuffer:4*1024*1024,env:{...process.env,...await re({slug:I})}}),T=JSON.parse(L||"null");T?.number&&h.push({slug:I,pr:T})}catch(L){je(L)&&Le(L)}let b=h.find(I=>I.pr.state==="MERGED")??(h.length===1?h[0]:null);if(!b)continue;c=b.slug;let x=b.pr;if(!x?.number)continue;if(x.state==="CLOSED")return null;if(x.state!=="MERGED"){if(p=x,s.auto)return f(x,!0);continue}let v=await vt(c,x.number);if(v&&v.trackId!==t.id)return null;v||await Pt({repo:c,number:x.number,trackId:t.id,url:x.url,headRef:x.headRefName,title:x.title,source:"merge-observed"}),await Ic(e.box,e.agent,r);let E=await F(t).catch(()=>[]);if(E.some(I=>I.type==="pr.state.changed"&&I.payload?.change==="merged"&&I.payload?.number===x.number))return null;let R=I=>I.some(L=>L.type==="pr.state.changed"&&L.payload?.change==="opened"&&L.payload?.number===x.number),st=0;if(!R(E)){if(_m(e.command)&&await Gr(c,x.number)){let I=Date.now();st=I+1,await O({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:I,change:"opened",number:x.number,url:x.url,title:x.title,branch:x.headRefName,repo:c,selfMerge:!0}})}else if((await vt(c,x.number))?.announced){let I=o?.capMs??wS,L=o?.pollMs??gS,T=Date.now()+I;for(;Date.now()<T&&(await new Promise(U=>setTimeout(U,L)),!R(await F(t).catch(()=>[]))););}}return await O({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:Math.max(Date.now(),st),change:"merged",number:x.number,url:x.url,title:x.title,branch:x.headRefName,repo:c}}),await He(c,x.number).catch(()=>{}),await Ue(c,x.number).catch(()=>{}),await Ce(c,x.number,"merged"),x}return p?f(p,!1):null}finally{Fe.delete(a)}})();return i.catch(()=>Fe.delete(a)),i}function Oc(t){return t.sourceId?`${t.role}|id:${t.sourceId}`:`${t.role}|${t.ts??"?"}|${String(t.text||"").slice(0,40)}`}function Om(t){return t.id?`cmd|${t.box}|id:${t.id}`:`cmd|${t.box}|${t.ts??"?"}|${String(t.command||"").slice(0,40)}`}function Cm(t){return`job|${t.box}|id:${t.id}`}var $c=new Map,Bs=new Map;async function Cc(t){return $c.has(t.id)?$c.get(t.id):(Bs.has(t.id)||Bs.set(t.id,F(t).then(e=>{let n={keys:new Set,cmdN:0};for(let r of e)if((r.type==="turn.user"||r.type==="turn.agent")&&r.payload?.text!=null){let o=r.type==="turn.user"?"user":"assistant";r.payload.sourceId&&n.keys.add(Oc({role:o,sourceId:r.payload.sourceId})),n.keys.add(Oc({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(Om({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(Cm({id:r.payload.id,box:r.boxId}));return $c.set(t.id,n),n}).finally(()=>Bs.delete(t.id))),Bs.get(t.id))}async function Mm(t,e){try{let n=(await Cc(t)).keys;for(let r of e||[]){if(!r||r.ts==null||!String(r.text||"").trim()&&!r.images?.length)continue;let o=Oc(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=cm(r.text);if(s){let{offered:a,toPr:i}=um(s),c;if(t.workspace?.kind==="multi"){let l=await no(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:ES(),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 Pm(t,e){try{let n=await Cc(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=Om(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}}),Rm(t,o),$m(t,o))}}catch{}}async function Nm(t,e){try{let n=await Cc(t),r=[...e||[]].filter(o=>o&&o.id).sort((o,s)=>(o.ts||0)-(s.ts||0));for(let o of r){let s=Cm(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 IS=/^Base directory for this skill:\s*(\S+)/,RS=/^Run the "([^"]+)" workflow\./,$S=/<command-name>\s*\/?([a-z0-9:_-]+)\s*<\/command-name>/i,OS=/^<local-command-(?:stdout|caveat)>/i,CS=/^\[Image: original \d+x\d+, displayed at \d+x\d+\.\s*Multiply coordinates by [\d.]+ to map to original image\.\]$/,MS=/^<task-notification>/i,PS=/<summary>([\s\S]*?)<\/summary>/i,NS=/<task-id>([\s\S]*?)<\/task-id>/i,BS=/<tool-use-id>([\s\S]*?)<\/tool-use-id>/i,DS=/<status>([\s\S]*?)<\/status>/i,jS=/<result>([\s\S]*?)(?:<\/result>|$)/i;var Mc=new Set(["model","clear","compact","login"]);function Bm(t){return Mc.has(String(t||"").replace(/^\//,"").toLowerCase())}var LS=/^\/([a-z0-9:_-]+)$/i;function Ds(t){let e=String(t||"").trim();if(!e)return null;let n=e.match(IS);if(n)return{kind:"skill",name:n[1].split("/").filter(Boolean).pop()||"skill"};let r=e.match(RS);if(r)return{kind:"skill",name:r[1]};let o=e.match($S);if(o){let a=o[1].toLowerCase();return Mc.has(a)?{kind:"suppress"}:{kind:"command",command:`/${a}`}}let s=e.match(LS);if(s&&Mc.has(s[1].toLowerCase()))return{kind:"suppress"};if(OS.test(e))return{kind:"suppress"};if(CS.test(e))return{kind:"suppress"};if(MS.test(e)){let a=c=>FS(e.match(c)?.[1].trim()||"")||null,i=a(jS);return{kind:"task",summary:a(PS)||"",taskId:a(NS),toolUseId:a(BS),status:a(DS)?.toLowerCase()||null,resultHead:i?i.length>600?`${i.slice(0,600).trimEnd()}\u2026`:i:null}}return null}function FS(t){return t.replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/'/g,"'").replace(/&/g,"&")}function Dm(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 jm(t){return t?.kind==="skill"?"skill-run":t?.kind==="task"?"task-done":"command-run"}var nt=t=>t.payload?.ts??t.ts,US=new Set(["opened","ci-fixed","reconciled","pushed"]),Lm=t=>`${t.repo??""}#${t.number}`;function js(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$/,d=new Map;for(let g of t||[]){if(g.type!=="tool.called"||g.source!=="transcript")continue;let w=String(g.payload?.command||"");if(/\bbostrat-mock\s+(?:add|update)\b/.test(w))for(let y of w.matchAll(/[\w./~-]+\.(?:png|jpe?g|gif|webp|bmp|svg)\b/gi)){let A=y[0].split("/").pop().toLowerCase();d.has(A)||d.set(A,[]),d.get(A).push(g.payload?.ts??g.ts)}}let f=3*6e4,p=20*6e4,m=(g,w)=>!!(g?.name&&d.get(String(g.name).toLowerCase()))?.some(A=>A>=w-f&&A<=w+p);for(let g of t||[])if(g.type==="turn.retracted"){r.add(`${g.boxId}|${g.payload?.saidTs}`);let w=kn(g.payload?.text);w&&(o.has(g.boxId)||o.set(g.boxId,[]),o.get(g.boxId).push(w))}else if(g.type==="turn.user"&&g.source==="orchestrator"){let w=kn(g.payload?.text);w&&(s.has(g.boxId)||s.set(g.boxId,new Set),s.get(g.boxId).add(w))}else if(g.type==="job.started"&&g.payload?.id)a.add(g.payload.id),(g.payload.kind||"agent")==="agent"&&g.payload.name&&c.add(g.payload.name);else if(g.type==="turn.user"&&g.payload?.note?.kind==="task"){let w=g.payload.note,y={status:w.status||null,summary:w.summary||null,resultHead:w.resultHead??null,ts:nt(g)};w.toolUseId&&i.set(w.toolUseId,y);let A=String(w.summary||"").match(u);A&&l.set(A[1],y)}let h=new Map,b=new Map,x=null,v=new Map,E=new Map,R=new Map,st=new Map,I=new Map,L=new Map,T=new Map,U=new Map,J=(g,w)=>{let y=o.get(g);if(!y?.length)return!1;let A=kn(w),K=s.get(g);return y.some(B=>{if(!A.startsWith(B))return!1;let $=kn(A.slice(B.length));return!!$&&!!K?.has($)})};for(let g of t||[]){let w=g.payload||{};switch(g.type){case"turn.user":{if(g.source==="hook"||!String(w.text||"").trim()&&!w.images?.length)break;{let y=w.note||Ds(w.text);if(y){if(y.kind==="task"){if(y.toolUseId&&a.has(y.toolUseId))break;let A=String(y.summary||"").match(u);if(!y.toolUseId&&A&&c.has(A[1]))break}if(y.kind==="command"&&Bm(y.command))break;y.kind!=="suppress"&&e.push({id:g.id,kind:"event",type:jm(y),name:y.name??null,command:y.command??null,summary:y.summary??null,ts:nt(g),box:g.boxId,agent:g.provider});break}}if(r.has(`${g.boxId}|${nt(g)}`)||g.source==="transcript"&&J(g.boxId,w.text))break;if(w.kind==="merge-notify"||w.kind==="ship-notify"){e.push({id:g.id,kind:"event",type:w.kind==="ship-notify"?"ship-notified":"merge-notified",text:w.text,ts:nt(g),box:g.boxId,agent:g.provider});break}if(w.kind==="mock-picked"||w.kind==="ci-fix"||w.kind==="reconcile"||w.kind==="pr-go"||w.kind==="push-go"||w.kind==="spin-up-view")break;for(let y of v.values())y.status="stale";v.clear(),e.push({id:g.id,kind:"message",role:"user",text:w.text,ts:nt(g),box:g.boxId,agent:g.provider,boxRole:w.boxRole,...g.actor==="orchestrator"?{orchestration:w.kind||!0}:{},...w.sourceId?{sourceId:w.sourceId}:{},...w.attachments?.length?{attachments:w.attachments}:{},...w.images?.length?{images:w.images}:{}});break}case"turn.agent":{T.clear();let y=(w.images||[]).filter(K=>!m(K,nt(g)));if(!String(w.text||"").trim()&&!y.length)break;if(g.provider==="cursor"){let K=`${g.boxId}|${w.text}`;if(n.has(K))break;n.add(K)}let A=g.provider==="codex"?(w.tools||[]).filter(K=>!Qi.has(K)&&!Qr.has(K)):w.tools||[];e.push({id:g.id,kind:"message",role:"assistant",text:w.text,ts:nt(g),box:g.boxId,agent:g.provider,boxRole:w.boxRole,...A.length?{tools:A}:{},...w.model?{model:w.model}:{},...w.sourceId?{sourceId:w.sourceId}:{},...y.length?{images:y}:{}});break}case"job.started":{if(!w.id)break;let y=nt(g),A=i.get(w.id);if(!A&&(w.kind||"agent")==="agent"&&w.name){let _=l.get(w.name);_&&_.ts>=y&&(A=_)}let K={jobId:w.id,jobKind:w.kind||"agent",name:w.name||null,detail:w.detail||null,status:A?A.status&&A.status!=="completed"?"failed":"done":"running",startedTs:y,finishedTs:A?.ts??null,summary:A?.summary??null,resultHead:A?.resultHead??null},B=U.get(g.boxId);if(B&&B.jobs.some(_=>(_.finishedTs??1/0)>=y))B.jobs.push(K);else{let _={id:g.id,kind:"job-dock",dockId:w.id,ts:y,box:g.boxId,agent:g.provider,jobs:[K]};U.set(g.boxId,_),e.push(_)}break}case"handoff.done":e.push(We(g,"lead-handoff"));break;case"subtask.delegated":e.push({...We(g,"subtask-delegated"),to:g.boxId,toAgent:g.provider});break;case"subtask.merged":e.push(We(g,"subtask-merged"));break;case"pr.state.changed":{if(!w.change)break;if(w.change==="opened"){for(let A of v.values())A.status="stale";v.clear()}let y=We(g,`pr-${w.change}`);if(US.has(w.change)&&w.number!=null){let A=Lm(w);if(h.set(A,{number:w.number,url:w.url??null,title:w.title??null,repo:w.repo??null,branch:w.branch??null}),h.size>=2){for(let B of b.values())for(let $ of B)$.grouped=!0;y.groupPrs=[...h.values()];let K=[y];for(let B of h.keys())b.set(B,K)}else{let K=b.get(A)??[];K.push(y),b.set(A,K)}}else if(w.change==="merged"||w.change==="closed"||w.change==="lost"){let A=Lm(w),K=x&&!x.complete||h.size>=2;if(h.has(A))h.delete(A),b.delete(A);else if(w.number!=null)for(let B of[...h.keys()])B.endsWith(`#${w.number}`)&&(h.delete(B),b.delete(B));if(K&&w.number!=null){(!x||x.complete)&&(x={id:g.id,kind:"event",type:"pr-group-settled",prs:[],remaining:[],complete:!1,at:nt(g),ts:nt(g),box:g.boxId,agent:g.provider},e.push(x)),x.prs.push({number:w.number,url:w.url??null,title:w.title??null,repo:w.repo??null,branch:w.branch??null,change:w.change,ts:nt(g)}),x.remaining=[...h.values()],x.complete=h.size===0;break}}e.push(y);break}case"pr.offered":{if(!w.offerId)break;for(let A of v.values())A.status="stale";v.clear();let y={id:g.id,kind:"pr-offer",offerId:w.offerId,excerpt:w.excerpt||null,branch:w.branch||null,base:w.base||null,offered:Array.isArray(w.offered)&&w.offered.length?w.offered:null,toPr:w.toPr&&typeof w.toPr=="object"?{number:w.toPr.number??null}:null,repos:Array.isArray(w.repos)&&w.repos.length?w.repos:null,status:"open",ts:nt(g),box:g.boxId,agent:g.provider};v.set(w.offerId,y),E.set(w.offerId,y),e.push(y);break}case"pr.offer.answered":{let y=E.get(w.offerId);y&&(y.status=w.action||"dismiss",y.actions=Array.isArray(w.actions)?w.actions:null,y.notify=w.notify===!0,v.delete(w.offerId));break}case"box.state.changed":{let y=w.change==="model"?"model-change":w.change;if(!y)break;let A=We(g,y);y==="model-change"&&(A.to=w.model),e.push(A);break}case"view.state.changed":{if(w.change!=="created"||w.restarted)break;e.push({id:g.id,kind:"view-card",viewId:w.viewId,name:w.name,slug:w.slug,port:w.port,origin:w.origin,ts:nt(g),box:g.boxId,agent:g.provider});break}case"view.spinup.failed":{e.push({id:g.id,kind:"view-spinup-failed",name:w.name||null,reason:w.reason||"no-view",ts:nt(g),box:g.boxId,agent:g.provider});break}case"mock.state.changed":{if(w.change==="picked"){e.push({id:g.id,kind:"mock-picked",mockId:w.mockId,name:w.name,group:w.group??null,version:w.version??null,siblings:w.siblings??[],ts:nt(g),box:g.boxId,agent:g.provider});break}if(w.change!=="created")break;e.push({id:g.id,kind:"mock-card",mockId:w.mockId,name:w.name,group:w.group??null,ts:nt(g),box:g.boxId,agent:g.provider});break}case"runbook.state.changed":{if(w.change!=="created")break;e.push({id:g.id,kind:"runbook-card",runbookId:w.runbookId,name:w.name,ts:nt(g),box:g.boxId,agent:g.provider});break}case"ticket.linked":{e.push({id:g.id,kind:"ticket-card",provider:w.provider||"linear",key:w.key,url:w.url,title:w.title??null,linkSource:w.linkSource??null,ts:nt(g),box:g.boxId,agent:g.provider});break}case"ticket.move.offered":{if(!w.offerId)break;for(let A of R.values())A.key===w.key&&(A.status="stale",R.delete(A.offerId));let y={id:g.id,kind:"ticket-offer",offerId:w.offerId,provider:w.provider||"linear",key:w.key,url:w.url??null,phase:w.phase??null,from:w.from??null,to:w.to?.name??null,prNumber:w.prNumber??null,status:"open",ts:nt(g),box:g.boxId,agent:g.provider};R.set(w.offerId,y),st.set(w.offerId,y),e.push(y);break}case"ticket.move.answered":{let y=st.get(w.offerId);y&&(y.status=w.action==="moved"?"moved":"dismiss",R.delete(w.offerId));break}case"ticket.state.changed":{for(let y of R.values())y.key===w.key&&y.to===w.to&&(y.status="stale",R.delete(y.offerId));if(w.mode==="offer")break;e.push(We(g,"ticket-state"));break}case"ticket.commented":{e.push({id:g.id,kind:"ticket-comment",key:w.key,url:w.url??null,excerpt:w.excerpt??null,ts:nt(g),box:g.boxId,agent:g.provider});break}case"ticket.flow.changed":e.push(We(g,"ticket-flow"));break;case"ticket.create.offered":{if(!w.proposalId)break;for(let A of I.values())A.status="stale";I.clear();let y={id:g.id,kind:"ticket-create-offer",proposalId:w.proposalId,provider:w.provider||"linear",epic:w.epic??null,issues:Array.isArray(w.issues)?w.issues:[],team:w.team??null,needsTeam:!!w.needsTeam,linkCurrentTrack:!!w.linkCurrentTrack,status:"open",ts:nt(g),box:g.boxId,agent:g.provider};I.set(w.proposalId,y),L.set(w.proposalId,y),e.push(y);break}case"ticket.create.answered":{let y=L.get(w.proposalId);y&&(y.status=w.action==="created"?"created":"dismiss",y.createdCount=w.createdCount??null,y.failedCount=w.failedCount??null,I.delete(w.proposalId));break}case"ticket.created":{let y={items:Array.isArray(w.items)?w.items:[],failed:Array.isArray(w.failed)?w.failed:[],team:w.team??null},A=w.proposalId?L.get(w.proposalId):null;if(A){A.created=y;break}e.push({id:g.id,kind:"ticket-created",...y,ts:nt(g),box:g.boxId,agent:g.provider});break}case"error":{if(!w.kind)break;e.push(We(g,w.kind));break}case"provider.connected":{e.push({id:g.id,kind:"event",type:"provider-connected",provider:w.provider||g.provider,ts:nt(g),at:nt(g),box:g.boxId,agent:g.provider});break}case"prompt.answered":{if(w.control||!w.question)break;let y=w.groupId||w.promptId;if(!y)break;let A=T.get(y);A||(A={id:g.id,kind:"prompt",groupId:y,provider:g.provider,box:g.boxId,agent:g.provider,questionTotal:w.questionTotal??1,questions:[],ts:nt(g)},T.set(y,A));let K=w.questionIndex??0,B={index:K,header:w.header||"",question:w.question,multiSelect:!!w.multiSelect,chosen:Array.isArray(w.chosen)?w.chosen:[]},$=A.questions.findIndex(M=>M.index===K);$>=0?A.questions[$]=B:A.questions.push(B),A.questions.sort((M,at)=>M.index-at.index),A.questionTotal=Math.max(A.questionTotal,w.questionTotal??1),new Set(A.questions.map(M=>M.index)).size>=A.questionTotal&&(e.push(A),T.delete(y));break}default:break}}return WS(e.sort((g,w)=>(g.ts??0)-(w.ts??0)))}var HS=2*6e4;function WS(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<=HS){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 Pc(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:qS(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??js(t)).filter(o=>o.kind==="message");return ep(n,r)}function qS(t){let e=String(t.summary||"").trim();return e&&e!==t.command&&e!==er(t.command)?e:Ne(t.command,t.description||"")}function We(t,e){let{ts:n,change:r,...o}=t.payload||{};return{id:t.id,kind:"event",type:e,...o,at:nt(t),ts:nt(t)}}function GS(t){let e=[],n=null;for(let r of String(t||"").split(`
|
|
83
|
-
|
|
84
|
-
`)}catch{}}function zm(){let t={};for(let[e,n]of jc)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:Us.slice(-20)}}var Gs=cv(iv),lv={timeout:3e4,maxBuffer:8*1024*1024};async function Ut(t,e,n={}){let{caller:r="prs",...o}=n,s=await re({cwd:t});we(r);try{let{stdout:a}=await Gs("gh",e,{cwd:t,...lv,...o,env:{...process.env,...s,...o.env||{}}});return String(a||"")}catch(a){throw je(a)&&Le(a),a}}var Ws=null;async function Uc(){if(Ws&&Date.now()-Ws.at<6e4)return Ws.data;let t;try{we("auth");let e=await Gs("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 zr();e.configured&&(t={installed:!0,authenticated:!0,login:e.username,via:"broker"})}catch{}return Ws={at:Date.now(),data:t},t}async function uv(t){try{let{stdout:e}=await Gs("git",["-C",t,"config","--get","remote.origin.url"],{timeout:5e3});return/github\.com/.test(e)}catch{return!1}}function Ym(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 Lc(t){let e=(t||[]).map(Ym),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 Hc="number,title,url,headRefName,headRefOid,baseRefName,isDraft,author,reviewDecision,mergeable,mergeStateStatus,statusCheckRollup,autoMergeRequest,additions,deletions,createdAt,updatedAt",Zm=Hc.replace("statusCheckRollup,","");function Wc(t){return/Resource not accessible by personal access token/.test(String(t?.stderr||t?.message||""))}async function Qm(t,e){if(!e)return null;let n=await Y(t);if(!n)return null;try{let r=await Ut(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 th(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 eh(t,e){let n=await Qm(t,e);return n===null?{...Lc([]),unavailable:!0}:Lc(th(n))}function qs(t,e=Lc(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 dv=9e4,ur=new Map;function fv(t,e,n){let r=new Set;for(let o of e){let s=`${t}#${o.number}`;if(o.mergeable!=="UNKNOWN"&&o.mergeStateStatus!=="UNKNOWN"){ur.delete(s);continue}r.add(s),ur.has(s)||ur.set(s,n),o.mergeabilityPending=n-ur.get(s)<dv}for(let o of ur.keys())o.startsWith(`${t}#`)&&!r.has(o)&&ur.delete(o);return e}async function dr(t,e){let n=await ge(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");try{let o=await Ut(n.path,["pr","view",String(r),"--json",Hc]);return qs(JSON.parse(o))}catch(o){if(!Wc(o))throw o;let s=await Ut(n.path,["pr","view",String(r),"--json",Zm]),a=JSON.parse(s);return qs(a,await eh(n.path,a.headRefOid))}}var Xm=(t,e)=>t.number-e.number;async function oo(t,{caller:e="prs"}={}){try{let n=await Ut(t,["pr","list","--json",Hc,"--limit","50"],{caller:e});return JSON.parse(n||"[]").map(r=>qs(r)).sort(Xm)}catch(n){if(!Wc(n))throw n;let r=await Ut(t,["pr","list","--json",Zm,"--limit","50"],{caller:e});return(await Promise.all(JSON.parse(r||"[]").map(async o=>qs(o,await eh(t,o.headRefOid))))).sort(Xm)}}var pv=3e4,mv=1500,ro=new Map;function se(){ro.clear()}async function hv(t,e){if(!t)return e;for(let n of e){let r=await vt(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 ws(t,n.number,{headSha:n.headSha}).catch(()=>{}):n.ciFix={delegatedAt:r.ciFix.delegatedAt,trackId:r.ciFix.trackId??null}),r.reconcile&&(o(r.reconcile)?await ys(t,n.number,{headSha:n.headSha}).catch(()=>{}):n.reconcile={delegatedAt:r.reconcile.delegatedAt,trackId:r.reconcile.trackId??null})}return e}async function nh(){let t=await Uc();if(!t.authenticated)return{auth:t,repos:[]};let e=[];for(let a of await me())await uv(a.path)&&e.push(a);let n=Date.now(),r=Ps(n)>0,o=await Promise.all(e.map(async a=>{let i=ro.get(a.path),c=i?.pending?mv:pv;if(i&&(r||n-i.at<c))return i.entry;try{let l=await Y(a.path).catch(()=>null),u=fv(a.path,await oo(a.path,{caller:"tab"}),n);if(await hv(l,u),i?.entry?.prs)for(let p of u)i.entry.prs.find(h=>h.number===p.number)?.checks?.pending>0&&p.checks.total>0&&p.checks.pending===0&&Hs({change:p.checks.failed>0?"checks-failed":"checks-green",repo:l||a.name,number:p.number,githubAt:p.checks.settledAt,source:"tab"});let d={name:a.name,path:a.path,prs:u},f=u.some(p=>p.mergeabilityPending||p.checks.pending>0||p.autoMergeArmed);return ro.set(a.path,{at:Date.now(),entry:d,pending:f}),d}catch(l){return i?i.entry:{name:a.name,path:a.path,prs:[],error:oe(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 ge(t){let e=(await me()).find(n=>n.path===t);if(!e)throw new Error(`unknown repo: ${t}`);return e}async function Sn(t){if(!t)return null;for(let e of await me())if(await Y(e.path).catch(()=>null)===t)return e.path;return null}var so=new Set(["squash","merge","rebase"]);async function fr(t,e,{method:n="squash"}={}){let r=await ge(t);if(!so.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 re({cwd:r.path})).GH_TOKEN){let a=await Fi().catch(()=>null);a&&s.push("--body",`Merged by bostrat on behalf of @${a}`)}try{await Ut(r.path,s)}catch(a){throw new Error(oe(a))}finally{se()}return{ok:!0,number:o}}async function ao(t,e){let n=await ge(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");try{await Ut(n.path,["api","-X","PUT",`repos/{owner}/{repo}/pulls/${r}/update-branch`])}catch(s){throw new Error(oe(s))}finally{se()}let o=await Y(n.path).catch(()=>null);return o&&await Ef(o,r).catch(()=>{}),{ok:!0,number:r}}async function qc(t,e){let n=await ge(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");try{await Ut(n.path,["pr","merge",String(r),"--disable-auto"])}catch(o){throw new Error(oe(o))}finally{se()}return{ok:!0,number:r}}var wv=2500,gv=9e4,yv=t=>new Promise(e=>setTimeout(e,t));function Gc(t){return t.green&&!t.isDraft}function bv(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 rh(t,{update:e=!0}={}){return t.isDraft?{action:"skip",reason:"draft"}:Gc(t)?{action:"merge"}:e&&t.mergeStateStatus==="BEHIND"&&t.mergeable!=="CONFLICTING"&&t.checks.failed===0?{action:"update-arm"}:{action:"arm"}}function oh(t){return[...t.filter(e=>e.plan.action!=="merge"),...t.filter(e=>e.plan.action==="merge")]}var Fc=new Map;function sh(){let t={};for(let[e,n]of Fc)t[e]={...n};return t}async function ah(t,{method:e="merge",onMerged:n=null}={}){let r=await ge(t);if(!so.has(e))throw new Error(`invalid merge method: ${e}`);if(Fc.get(r.path)?.running)throw new Error("a merge-all is already running for this repo");let o=(await oo(r.path)).filter(Gc).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 Fc.set(r.path,s),xv(s,o,{listPrs:()=>oo(r.path),merge:a=>fr(r.path,a,{method:e}),sleep:yv,onMerged:n}).catch(()=>{}),{ok:!0,repo:r.path,total:s.total,queue:o.map(a=>a.number)}}async function kv(t,e){let n=Date.now()+(e.settleMaxMs??gv),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(wv)}}async function xv(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 kv(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: ${oe(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(!Gc(a)){t.skipped.push({number:a.number,title:a.title,reason:bv(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:oe(i)})}}}finally{t.current=null,t.running=!1,t.finishedAt=Date.now(),se()}}async function Kc(t,{title:e,body:n="",base:r=null}={}){if(typeof e!="string"||!e.trim())throw new Error("title required");try{await Gs("git",["-C",t,"push","-u","origin","HEAD"],{timeout:12e4,maxBuffer:4*1024*1024})}catch(l){throw new Error(`push failed: ${oe(l)}`)}let o=n||"";if((await re({cwd:t})).GH_TOKEN){let l=await Fi().catch(()=>null);l&&(o=`${o?`${o}
|
|
85
|
-
|
|
86
|
-
|
|
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: ${T0(o)}.`:"")),await up(t.trackId,n,r.change)}break}case"mock.state.changed":{if(r.change==="ready"&&r.mockId){let o=await O0(n,r.mockId,r.version??null);if(!o)break;let s=(r.version??1)>1?` revised (v${r.version})`:"";await Qr(n,`**Bostrat** \u2014 mock **${r.name}**${s}${r.group?` (group \`${r.group}\`)`:""}:
|
|
53
|
+
|
|
54
|
+
`)}else if(r.change==="picked"){let o=(r.siblings??[]).map(s=>`**${s.name}**`).join(", ");await Qr(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 Qr(n,`**Bostrat** \u2014 live preview **${r.name}**: ${Ls(r.slug)}
|
|
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 co(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 lo(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 or=(t,e)=>t.length>e?`${t.slice(0,e).trimEnd()}\u2026`:t,At=t=>t&&!/[$`]/.test(t)?t:null,dt=t=>t.replace(/\/+$/,"").split("/").pop()||t,rr=t=>{let e=t.replace(/\/+$/,"").split("/").filter(Boolean);return e.length>3?e.slice(-2).join("/"):t},Kp=t=>t.startsWith("-"),qp=new Set(["-g","--glob","-t","--type","-A","-B","-C","-m","--max-count","-e","--regexp","-f","--file"]);function rt(t,e=new Set){let n=[];for(let r=1;r<t.length;r++){let o=t[r];if(o==="<<")break;if(Kp(o)){e.has(o)&&r++;continue}n.push(o)}return n}var yS={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 ${dt(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"},bS={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 Vp(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 kS(t){let e=Vp(t),n=At(rt(t).find(r=>!/^\d+(,\d+)?p$/.test(r)));return e&&n?e.from===e.to?`Read line ${e.from} of ${dt(n)}`:`Read lines ${e.from}\u2013${e.to} of ${dt(n)}`:t.includes("-i")&&n?`Edit ${dt(n)}`:null}function hc(t){if(t.includes("--files")){let s=At(rt(t,qp)[0]);return s&&s!=="."?`List files in ${rr(s)}`:"List files"}let e=rt(t,qp),n=e[0];if(!n)return null;let r=At(e.slice(1).find(s=>s!==".")),o=or(n,40);return r?`Search for \u201C${o}\u201D in ${rr(r)}`:`Search for \u201C${o}\u201D`}function wc(t){let e=rt(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 gc(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=rt(t)[0];return n?`Run ${dt(n)}`:null}function xS(t){let e=rt(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 Gp(t){return t.find(e=>/^https?:\/\//.test(e))||null}var SS={git:t=>{let e=rt(t,new Set(["-C","-c"])),n=yS[e[0]];return n?n(e,t):null},gh:t=>{let e=rt(t),n=bS[e[0]];return n?n(e):null},sed:kS,rg:hc,grep:hc,egrep:hc,nl:t=>{let e=At(rt(t)[0]);return e?`Read ${dt(e)}`:null},cat:t=>{let e=At(rt(t)[0]);return e?`Read ${dt(e)}`:null},head:t=>{let e=At(rt(t,new Set(["-n","-c"])).find(n=>!/^\d+$/.test(n)));return e?`Read the start of ${dt(e)}`:null},tail:t=>{let e=At(rt(t,new Set(["-n","-c"])).find(n=>!/^\d+$/.test(n)));return e?`Read the end of ${dt(e)}`:null},wc:t=>{let e=At(rt(t)[0]);return e?`Count lines in ${dt(e)}`:"Count input lines"},ls:t=>{let e=At(rt(t)[0]);return e&&e!=="."?`List files in ${rr(e)}`:"List files"},find:t=>At(t[1])&&!Kp(t[1])&&t[1]!=="."?`Find files in ${rr(t[1])}`:"Find files",tree:()=>"List the directory tree",mkdir:t=>{let e=At(rt(t)[0]);return e?`Create directory ${rr(e)}`:null},touch:t=>{let e=At(rt(t)[0]);return e?`Create ${dt(e)}`:null},rm:t=>{let e=At(rt(t)[0]);return e?`Delete ${rr(e)}`:null},mv:t=>{let e=At(rt(t)[0]);return e?`Move ${dt(e)}`:null},cp:t=>{let e=At(rt(t)[0]);return e?`Copy ${dt(e)}`:null},chmod:t=>{let e=At(rt(t)[1]);return e?`Change permissions on ${dt(e)}`:null},diff:t=>{let e=rt(t);return e.length>=2?`Compare ${dt(e[0])} and ${dt(e[1])}`:null},curl:t=>{let e=Gp(t);return e?`Fetch ${or(e,56)}`:"Fetch a URL"},wget:t=>{let e=Gp(t);return e?`Fetch ${or(e,56)}`:"Fetch a URL"},npm:wc,pnpm:wc,yarn:wc,npx:t=>{let e=rt(t)[0];return e?`Run ${e}`:null},node:t=>gc(t,"Node"),python:t=>gc(t,"Python"),python3:t=>gc(t,"Python"),make:t=>{let e=rt(t)[0];return e?`Run make ${e}`:"Run make"},systemctl:xS,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=rt(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=rt(t)[0];return e?`Locate ${e}`:null},command:t=>t[1]==="-v"&&t[2]?`Locate ${t[2]}`:null,fly:t=>{let e=rt(t)[0];return e==="deploy"?"Deploy to Fly":e?`Run fly ${e}`:null}},vS=new Set(["pwd","echo","cd","sleep","true","false","test","[","set","export","exit","read"]);function AS(t){let e=lo(co(t));if(!e.length)return null;if(/^(ba|z)?sh$/.test(dt(e[0]))&&(e[1]==="-lc"||e[1]==="-c")&&e[2])return yc(e[2]);let n=dt(e[0]);if(vS.has(n))return null;if(e.includes("--version"))return`Check ${n} version`;if(n.endsWith(".sh"))return`Run ${n}`;let r=SS[n];return r?r(e.map((o,s)=>s===0?n:o)):null}function yc(t){let e=String(t||"").trim();if(!e)return null;let n=e.indexOf("<<");if(n!==-1){let i=e.indexOf(`
|
|
57
|
+
`,n);i!==-1&&(e=e.slice(0,i))}let r=gS(e);for(let i=0;i<r.length-1;i++){let c=lo(co(r[i])),l=lo(co(r[i+1]));if(!(!c.length||!l.length)&&["nl","cat"].includes(dt(c[0]))&&dt(l[0])==="sed"){let u=Vp(l),f=rt(c)[0];if(u&&f)return or(u.from===u.to?`Read line ${u.from} of ${dt(f)}`:`Read lines ${u.from}\u2013${u.to} of ${dt(f)}`,88)}}let o=[];for(let i of r){let c=AS(i);c&&o.push({d:c,head:dt(lo(co(i))[0]||""),sub:rt(lo(co(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":or(s?s.d:o[0].d,88)}var kc=new Set(["Bash"]);function sr(t){let e=t.split(`
|
|
58
|
+
`).map(n=>n.trim()).find(Boolean)||t;return e.length>88?`${e.slice(0,88).trimEnd()}\u2026`:e}function Fe(t,e=""){let n=e.trim();return n||yc(t)||sr(t)}function bc(t,e){if(t){if(Array.isArray(t)){for(let n of t)bc(n,e);return}if(typeof t=="object"){if(t.type==="tool_use"&&kc.has(t.name)){let n=String(t.input?.command||"").trim();n&&e.push({id:t.id||null,tool:t.name,command:n,description:String(t.input?.description||"").trim()})}for(let n of Object.values(t))bc(n,e)}}}async function zp(t,{maxBytes:e=1048576,limit:n=400}={}){let r;try{r=await It(t,e)}catch{return[]}let o=[];for(let s of r.split(`
|
|
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(bc(c.content,l),!l.length)continue;let u=i.timestamp?Date.parse(i.timestamp):null;for(let f of l)o.push({...f,summary:Fe(f.command,f.description),ts:u})}return o.slice(-n)}function Jp(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 TS=/^Async (?:agent|task) launched/i,_S=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 Xp(t,{maxBytes:e=2097152,limit:n=100}={}){let r;try{r=await It(t,e)}catch{return[]}let o=new Map,s=new Set;for(let i of r.split(`
|
|
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?sr(m):null),detail:m?sr(m):null,ts:f,proven:!0})}}else d.type==="tool_result"&&d.tool_use_id&&TS.test(_S(d.content))&&s.add(d.tool_use_id)}let a=[];for(let i of o.values()){if(!i.proven&&!s.has(i.id))continue;let{proven:c,...l}=i;a.push(l)}return a.slice(-n)}import uo from"node:fs";import IS from"node:fs/promises";import yn from"node:path";import RS from"node:os";import $S from"node:crypto";import OS from"node:zlib";var Ys=new Set(["Shell","Read","Grep","Glob","StrReplace","Write","ReadLints","Delete","ApplyPatch","Await"]),Kt=(t,e=88)=>t.length>e?`${t.slice(0,e).trimEnd()}\u2026`:t,ar=t=>String(t).replace(/\/+$/,"").split("/").pop()||String(t);function Ue(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 xc=t=>`"${String(t).replace(/"/g,'\\"')}"`;function ES(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 Yp(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:Kt(`read ${Ue(r,n)}${o?` (lines ${o}\u2013${s??"end"})`:""}`,140),description:o?`Read lines ${o}\u2013${s??"end"} of ${ar(r)}`:`Read ${ar(r)}`}}case"Grep":{let r=e?.pattern;if(!r)return null;let o=e?.path?Ue(e.path,n):null,s=e?.glob?` --glob ${xc(e.glob)}`:"",a=Kt(String(r),40);return{command:Kt(`grep ${xc(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?Ue(e.target_directory,n):null;return{command:Kt(`glob ${xc(r)}${o&&o!=="."?` ${o}`:""}`,140),description:`Find files matching \u201C${Kt(String(r),40)}\u201D`}}case"StrReplace":return e?.path?{command:Kt(`edit ${Ue(e.path,n)}`,140),description:`Edit ${ar(e.path)}`}:null;case"Write":return e?.path?{command:Kt(`write ${Ue(e.path,n)}`,140),description:`Write ${ar(e.path)}`}:null;case"Delete":return e?.path?{command:Kt(`delete ${Ue(e.path,n)}`,140),description:`Delete ${ar(e.path)}`}:null;case"ReadLints":{let o=(Array.isArray(e?.paths)?e.paths:[]).map(s=>Ue(s,n)).filter(Boolean);return{command:Kt(o.length?`lints ${o.join(" ")}`:"lints",140),description:"Check lints"}}case"ApplyPatch":{let r=ES(e);if(!r.length)return null;let o=r.length>1?` (+${r.length-1} more)`:"";return{command:Kt(`apply_patch ${Ue(r[0],n)}${o}`,140),description:r.length>1?`Patch ${r.length} files`:`Patch ${ar(r[0])}`}}case"Await":{let r=e?.shell_id??e?.task_id;return{command:Kt(`await ${r??"background output"}`,140),description:"Wait for a background command"}}default:return null}}var CS=process.env.BOSTRAT_CURSOR_CHATS||yn.join(RS.homedir(),".cursor","chats");function tm(t){let e=$S.createHash("md5").update(String(t)).digest("hex");return yn.join(CS,e)}function em(t){try{let e=JSON.parse(uo.readFileSync(yn.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 MS(t){let e=tm(t),n;try{n=uo.readdirSync(e,{withFileTypes:!0}).filter(o=>o.isDirectory())}catch{return null}let r=null;for(let o of n){let s=yn.join(e,o.name),a=yn.join(s,"store.db");if(!uo.existsSync(a))continue;let i=em(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 Zs(t,e){if(!t)return null;if(e){let n=yn.join(tm(t),e),r=yn.join(n,"store.db");if(uo.existsSync(r))return{id:e,dir:n,store:r,...em(n)||{createdAtMs:null,updatedAtMs:null}}}return MS(t)}function Zp(t,e){let n=t.prepare("SELECT data FROM blobs WHERE id = ?").get(e);return n?Buffer.from(n.data):null}function NS(t){if(!t)return null;let e=t;if(e.length>1&&e[0]===31&&e[1]===139)try{e=OS.gunzipSync(e)}catch{}try{return JSON.parse(e.toString("utf8"))}catch{return null}}function PS(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 BS(t,e){if(!Array.isArray(t))return[];let n=[];for(let r of t){if(!r||r.type!=="tool-call"||!Ys.has(r.toolName))continue;let o=Yp(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 DS=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(`
|
|
61
|
+
`).trim():"",jS=t=>Array.isArray(t)?t.filter(e=>e&&e.type==="tool-call"&&e.toolName).map(e=>e.toolName):[],Qp=process.getBuiltinModule?.("node:sqlite"),ir={turns:[],trailingCommands:[],model:null,requestIds:[]};async function Sc(t,e){if(!Qp)return ir;let{DatabaseSync:n}=Qp,r;try{r=new n(t,{readOnly:!0})}catch{return ir}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 ir;let s;try{s=JSON.parse(Buffer.from(String(o.value),"hex").toString("utf8"))}catch{return ir}let a=s.lastUsedModel||null,i=s.latestRootBlobId?Zp(r,s.latestRootBlobId):null;if(!i)return{...ir,model:a};let c=[],l=[],u=[],f=null,d=[];for(let m of PS(i)){let h=NS(Zp(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 _=v?.providerOptions?.cursor?.modelName;_&&(f=_)}l.push(...BS(h.content,e).map((v,_)=>({...v,id:v.id||`${m}:${_}`}))),u.push(...jS(h.content));let g=DS(h.content);if(!g)continue;let k=[...new Set(u)];u=[],c.push({id:m,text:g,tools:k.length?k:void 0,commands:l,model:f}),l=[]}return{turns:c,trailingCommands:l,model:a,requestIds:d}}catch{return ir}finally{try{r.close()}catch{}}}function nm(t,e){let n=t.updatedAtMs;if(!n)try{n=Math.round(uo.statSync(t.store).mtimeMs)}catch{n=Date.now()}let r=t.createdAtMs;(!r||r>n)&&(r=n-e*1e3);let o=Math.max(0,n-r);return{end:n,tsAt:s=>Math.round(r+o*(s+1)/e)}}async function rm(t,e,{limit:n=80}={}){let r=Zs(t,e);if(!r)return[];let{turns:o,model:s}=await Sc(r.store,t);if(!o.length)return[];let a=o.slice(-n),{tsAt:i}=nm(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 om(t,e){let n=Zs(t,e);if(!n)return null;let{turns:r,requestIds:o,model:s}=await Sc(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 sm(t,e,{limit:n=80}={}){let r=Zs(t,e);if(!r)return[];let{turns:o,trailingCommands:s}=await Sc(r.store,t);if(!o.length&&!s.length)return[];let a=o.slice(-n),{end:i,tsAt:c}=nm(r,a.length||1),l=[];a.forEach((u,f)=>{for(let d of u.commands)l.push({...d,summary:Fe(d.command,d.description),ts:c(f)})});for(let u of s)l.push({...u,summary:Fe(u.command,u.description),ts:i+1});return l}async function am(t,e){let n=Zs(t,e);if(!n)return null;if(n.updatedAtMs)return n.updatedAtMs;try{return Math.round((await IS.stat(n.store)).mtimeMs)}catch{return null}}import im from"node:fs";import Ac from"node:fs/promises";import vc from"node:path";import LS from"node:os";var FS=process.env.BOSTRAT_CODEX_SESSIONS||vc.join(process.env.CODEX_HOME||vc.join(LS.homedir(),".codex"),"sessions");async function cm(t){try{return await Ac.realpath(t)}catch{return t}}function US(t=FS){let e=[],n=r=>{let o;try{o=im.readdirSync(r,{withFileTypes:!0})}catch{return}for(let s of o){let a=vc.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:im.statSync(a).mtimeMs})}catch{}}}};return n(t),e.sort((r,o)=>o.mtimeMs-r.mtimeMs)}async function HS(t,e=1048576){let n;try{n=await Ac.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(`
|
|
62
|
+
`);if(i!==-1)return o.slice(0,i);if(s+=a,o.length>=e)break}return o}finally{await n.close()}}async function WS(t){let e=await HS(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 bn(t,e){if(!t&&!e)return null;let n=US();if(e){let o=n.find(s=>s.id===e);return o||null}let r=await cm(t);for(let o of n){let s=await WS(o.file);if(s&&(s===t||await cm(s)===r))return o}return null}var qS=new Set(["output_text","text"]),GS=t=>typeof t=="string"?t.trim():Array.isArray(t)?t.filter(e=>e&&typeof e.text=="string"&&qS.has(e.type)).map(e=>e.text).join(`
|
|
63
|
+
`).trim():"";async function lm(t,e,{limit:n=80,maxBytes:r=524288}={}){let o=await bn(t,e);if(!o)return[];let s;try{s=await It(o.file,r)}catch{return[]}let a=[],i=null,c=[],l=!1,u=!1,f=null;for(let d of s.split(`
|
|
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:_c,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,Tc.has(w.name)||c.push(w.name);continue}if(w.type!=="message"||w.role!=="assistant")continue;u=!0;let g=GS(w.content),k=c.length?c.slice():null;c=[],!(!g&&!k)&&a.push({role:"assistant",text:g,tools:k||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 fo=new Set(["exec_command","shell","exec"]),Tc=new Set(["wait","write_stdin"]),_c="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 KS(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 VS(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+=`
|
|
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 zS(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=VS(i.slice(c.index+c[0].length).trimStart());l&&l.trim()&&o.push(l.trim())}),o}async function um(t,e,{limit:n=400,maxBytes:r=524288}={}){let o=await bn(t,e);if(!o)return[];let s;try{s=await It(o.file,r)}catch{return[]}let a=[];for(let i of s.split(`
|
|
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"&&fo.has(u.name)){let m;try{m=JSON.parse(u.arguments||"{}")}catch{continue}let h=KS(m);h&&(f=[h])}else u.type==="custom_tool_call"&&u.name==="exec"&&(f=zS(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:Fe(m),ts:l.timestamp?Date.parse(l.timestamp):null})})}return a.slice(-n)}async function dm(t,e){let n=await bn(t,e);if(!n)return null;try{return Math.round((await Ac.stat(n.file)).mtimeMs)}catch{return n.mtimeMs||null}}var cr=class{constructor(e,n=new Set){this.agent=e,this.cliTools=n}async readMessages(){return[]}async readCommands(){return[]}async readJobs(){return[]}async updatedAt(){return null}},Ec=class extends cr{transcript(e){return!e?.cwd||!e?.sessionId?null:pe(e.cwd,e.sessionId)}async readMessages(e,n){let r=this.transcript(e);return r?di(r,n):[]}async readCommands(e,n){let r=this.transcript(e);return r?zp(r,n):[]}async readJobs(e,n){let r=this.transcript(e);return r?Xp(r,n):[]}async updatedAt(e){let n=this.transcript(e);if(!n)return null;try{return Math.round((await JS.stat(n)).mtimeMs)}catch{return null}}},Ic=class extends cr{async readMessages(e,n){return rm(e.cwd,e.sessionId,n)}async readCommands(e,n){return sm(e.cwd,e.sessionId,n)}async updatedAt(e){return am(e.cwd,e.sessionId)}},Rc=class extends cr{sessionId(e){return e?.pinned?e.sessionId:null}async readMessages(e,n){return lm(e.cwd,this.sessionId(e),n)}async readCommands(e,n){return um(e.cwd,this.sessionId(e),n)}async updatedAt(e){return dm(e.cwd,this.sessionId(e))}},XS=new cr("unknown"),fm=new Map;function $c(t){return fm.set(t.agent,t),t}function po(t){return fm.get(t)||XS}$c(new Ec("claude",kc));$c(new Ic("cursor",Ys));$c(new Rc("codex",fo));var Oc=t=>e=>({...e,box:t.name,agent:t.agent,boxRole:t.role});async function Cc(t,e={}){return(await po(t.agent).readMessages(t,e)).map(Oc(t))}async function pm(t,e={}){return(await po(t.agent).readCommands(t,e)).map(Oc(t))}async function mm(t,e={}){return(await po(t.agent).readJobs(t,e)).map(Oc(t))}var gm=/to navigate\b.*\bEsc to cancel|Enter to select\b/,Pc=/Chat about this/i,kn=/^\s*(❯)?\s*(\d+)\.\s+(?:\[([ xX✔])\]\s+)?(.+?)\s*$/,hm=/^(type something|chat about this|submit)\b/i,xn=/^[─—-]{6,}\s*$/,Bc=/[\u2500-\u257F\u2550-\u256F┌┐└┘├┤│┬┴┼─✂]/;function Sn(t){let e=t.search(Bc);return(e>=0?t.slice(0,e):t).trim()}function YS(t){if(!t)return"";let e=t.replace(/[│┌└├─┘┤┬┴┼─✂].*$/,"").trim();return!e||/hidden|\d+\s+lines?/i.test(e)?"":e}function ZS(t){let e=String(t).trim();return!!(!e||xn.test(t)||Bc.test(e)||/hidden|\d+\s+lines?/i.test(e)||/notespress|press n to add/i.test(e)||/^\s*notes?\s*$/i.test(e))}function QS(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=tv(n,e);return n.forEach((a,i)=>{a.current=i===s}),{tabs:n,questionIndex:s,questionTotal:n.length,currentTabLabel:n[s].label}}function tv(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 wm(t){return t.replace(/[←→✔☐☒●▶]/g," ").replace(/\bSubmit\b/g," ").replace(/\s+/g," ").trim()}var ym=/^\s*Switch model\?\s*$/m;function ev(t,e=""){let n=String(t);if(!ym.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
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(kn);if(f)s.push({cursor:!!f[1],label:f[4].trim()});else if(s.length){if(r[u].trim()==="")break}else{let d=r[u].trim();d&&!xn.test(r[u])&&a.push(d)}}if(s.length<2)return null;let i=s.map(u=>({label:Sn(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 bm=/^\s*Enter to confirm\s*·\s*Esc to cancel\s*$/,nv=/^\s*[─—▔-]{6,}\s*$/;function rv(t,e=""){let n=String(t).replace(/\r/g,"").split(`
|
|
68
|
+
`),r=n.length-1;for(;r>=0&&n[r].trim()==="";)r--;if(r<0||!bm.test(n[r]))return null;let o=r-1;for(;o>=0&&n[o].trim()==="";)o--;let s=[];for(;o>=0;o--){let c=n[o].match(kn);if(!c)break;s.unshift({cursor:!!c[1],n:Number(c[2]),label:c[4].trim()})}if(s.length<2||s.some((c,l)=>c.n!==l+1))return null;let a=[];for(let c=o;c>=0&&a.length<6&&!(nv.test(n[c])||xn.test(n[c]));c--){let l=n[c].trim();l&&a.unshift(l)}let i=s.map(c=>({label:Sn(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 ov(t,e=""){let n=String(t);if(!/ready to submit your answers/i.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
69
|
+
`),o=[];for(let c=0;c<r.length;c++){let l=r[c].match(kn);l&&o.push({cursor:!!l[1],label:l[4].trim()})}let s=o.filter(c=>/^(submit answers|cancel)$/i.test(c.label));if(s.length<2)return null;let a=s.map(c=>({label:c.label,description:""})),i=Math.max(0,s.findIndex(c=>c.cursor));return{provider:"claude",box:e,multiSelect:!1,cursorIndex:i,review:!0,promptId:"claude:review:submit-answers",questions:[{question:"Ready to submit your answers?",header:"Review",multiSelect:!1,options:a}]}}var Mc=/Resume from summary/i,sv=/^\s*This session is\b/;function av(t,e=""){let n=String(t);if(!Mc.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
70
|
+
`),o=-1;for(let u=r.length-1;u>=0;u--)if(sv.test(r[u])){o=u;break}if(o<0)return null;let s=[],a=[r[o].trim()];for(let u=o+1;u<r.length;u++){let f=r[u].match(kn);if(f)s.push({cursor:!!f[1],label:f[4].trim()});else if(s.length){if(r[u].trim()==="")break}else{let d=r[u].trim();d&&!xn.test(r[u])&&a.push(d)}}if(s.length<2||!s.some(u=>Mc.test(u.label)))return null;let i=s.map(u=>({label:Sn(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 vn=/shift\+tab to cycle|⏵⏵ bypass permissions on|⏸ manual mode on/;function Dc(t){let e=[];for(let r=0;r<t.length;r++){let o=t[r].match(kn);o&&e.push({line:r,cursor:!!o[1],n:Number(o[2]),label:o[4].trim()})}let n=e.sort((r,o)=>r.n-o.n);return n.length<2||n.some((r,o)=>r.n!==o+1)?null:n}var km=/Choose the text style that looks best with your terminal/,xm=/Let's get started\./;function iv(t,e=""){let n=String(t);if(!km.test(n)||!xm.test(n)||vn.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
71
|
+
`),o=Dc(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:Sn(s.label),description:""}))}]}:null}var Sm=/Select login method:/;function cv(t,e=""){let n=String(t);if(!Sm.test(n)||vn.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
72
|
+
`),o=Dc(r);if(!o)return null;let s=o.map(a=>{let[i,...c]=a.label.split("\xB7");return{label:Sn(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 jc=/Paste code here if prompted/,lv=/^[A-Za-z0-9%&=+._~#?/:\-]+$/;function uv(t,e){let n=t[e].trim();for(let r=e+1;r<t.length;r++){let o=t[r].trim();if(!o||!lv.test(o)||jc.test(t[r]))break;n+=o}return n}function dv(t,e=""){let n=String(t);if(!jc.test(n)||vn.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
73
|
+
`),o="";for(let s=0;s<r.length;s++)if(/^\s*https:\/\//.test(r[s])){o=uv(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 Nc=/OAuth error:/,fv=/Press Enter to retry\./;function pv(t,e=""){let n=String(t);if(!Nc.test(n)||!fv.test(n)||vn.test(n))return null;let s=([...n.replace(/\r/g,"").split(`
|
|
74
|
+
`)].reverse().find(a=>Nc.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 vm=/Quick safety check: Is this a project you created or one you trust/;function mv(t,e=""){let n=String(t);if(!vm.test(n)||vn.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
75
|
+
`),o=Dc(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:Sn(s.label),description:""}))}]}:null}var Am=/^(.{0,60}?)Press\s+Enter\s+to\s+continue\s*[.…]{0,3}\s*$/;function hv(t,e=""){let n=String(t);if(vn.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
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(Am);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(xn.test(r[l])||Bc.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 wv(t){let e=-1;for(let r=t.length-1;r>=0;r--)if(Pc.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 gv(t,e=""){let n=String(t);if(!gm.test(n)||!Pc.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
77
|
+
`),o=wv(r);if(!o)return null;let{headerLine:s,chatLine:a,optStart:i,optEnd:c}=o,l=[],u=-1;for(let A=i;A<c;A++){let O=r[A].match(kn);O&&(u<0&&(u=A),l.push({line:A,cursor:!!O[1],checked:O[3]!=null&&O[3]!==" ",hasCheckbox:O[3]!=null,label:O[4].trim(),desc:""}))}if(!l.length)return null;for(let A of l)for(let O=A.line+1;O<Math.min(r.length,A.line+4)&&!(kn.test(r[O])||xn.test(r[O]));O++){let H=r[O].trim();if(!(!H||ZS(r[O])||hm.test(H))){A.desc=YS(H);break}}let f=l.filter(A=>!hm.test(A.label));if(!f.length)return null;let d=f.some(A=>A.hasCheckbox),m=Math.max(0,f.findIndex(A=>A.cursor)),h=s>=0?s+1:0,w=[];for(let A=h;A<u;A++){let O=r[A].trim();O&&!xn.test(r[A])&&!/[☐☒]/.test(r[A])&&w.push(O)}let g=w.join(" ")||(s>=0?wm(r[s]):"")||"The agent is asking a question",k=s>=0?QS(r[s],g):null,v=k?.currentTabLabel||(s>=0?wm(r[s]):""),_=f.map(A=>({label:Sn(A.label),description:A.desc})),M=_.map(A=>A.label).join("|"),$=`claude:${k?`q${k.questionIndex+1}/${k.questionTotal}`:"q1/1"}:${v}:${g}:${M}`.slice(0,240),L=k?`claude:ask:${k.tabs.map(A=>A.label).join("|")}`.slice(0,240):$;return{provider:"claude",box:e,multiSelect:d,cursorIndex:m,promptId:$,groupId:L,...k?{questionIndex:k.questionIndex,questionTotal:k.questionTotal,currentTabLabel:k.currentTabLabel,tabs:k.tabs}:{},questions:[{question:g,header:v,multiSelect:d,options:_}]}}function lr(t,e){let n=String(e||"");return n?t==="claude"?gm.test(n)&&Pc.test(n)||ym.test(n)||Mc.test(n)||bm.test(n.trimEnd().split(`
|
|
78
|
+
`).pop()||"")?!0:vn.test(n)?!1:km.test(n)&&xm.test(n)||Sm.test(n)||jc.test(n)||Nc.test(n)||vm.test(n)||Am.test(n.trimEnd().split(`
|
|
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 An(t,e){return!t||!e?null:t.agent==="claude"?ov(e,t.name)||gv(e,t.name)||ev(e,t.name)||av(e,t.name)||pv(e,t.name)||dv(e,t.name)||cv(e,t.name)||iv(e,t.name)||mv(e,t.name)||hv(e,t.name)||rv(e,t.name):t.agent==="cursor"||t.agent==="codex"?ld(t.agent,e,t.name):null}async function Lc(t){if(!t)return null;let e=await Re(t.name).catch(()=>"");return An(t,e)}function Tm(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 _m(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 ur(t){return typeof t=="string"?t.trim():""}var Em=/^\s*─{8,}\s*$/,yv=/^❯[ ]?(.*)$/;function mo(t,e){if(t!=="claude")return null;let n=String(e).replace(/\r/g,"").split(`
|
|
80
|
+
`).map(c=>c.replace(/\s+$/,"")),r=-1;for(let c=n.length-1;c>=0;c--)if(Em.test(n[c])){r=c;break}if(r<=0)return null;let o=-1;for(let c=r-1;c>=0;c--)if(Em.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(yv);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 Vt(t){return String(t||"").replace(/\s+/g," ").trim()}function Im(t,e,{slackMs:n=2e4}={}){let r=Vt(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&&Vt(o.text).includes(r))}function Rm(t,e,n,{busy:r=!1}={}){let o=mo(t,n);return o&&Fc(e,o)?"stuck":r||o?"submitted":"unknown"}function Fc(t,e){let n=Vt(t),r=Vt(e).replace(/^…\s*/,"").replace(/\s*…$/,"");return!n||!r||r.length<Math.min(n.length,12)?!1:n===r||n.includes(r)}import bv from"node:crypto";var Uc=new Set(["cursor","codex"]),Om=4,kv=4,xv=2,Sv=24,$m=/^[ \t]{0,3}(`{3,})bostrat-question[ \t]*\r?\n([\s\S]*?)\r?\n[ \t]{0,3}\1`*[ \t]*$/gm,ho=t=>typeof t=="string"?t.trim():"";function vv(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,Om)){let o=ho(r?.question);if(!o)return null;let s=Array.isArray(r?.options)?r.options:[],a=[];for(let i of s.slice(0,kv)){let c=ho(i?.label);if(!c)return null;let l=ho(i?.description);a.push(l?{label:c,description:l}:{label:c})}if(a.length<xv)return null;n.push({question:o,header:ho(r?.header).slice(0,Sv),multiSelect:r?.multiSelect===!0,options:a})}return n.length?n:null}function Hc(t){if(typeof t!="string"||!t.includes("```bostrat-question"))return null;$m.lastIndex=0;let e=null;for(let o;o=$m.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=vv(n);return r?{text:t.slice(0,e.index).trimEnd(),questions:r}:null}function Av(t,e,n){let r=bv.createHash("sha256").update(JSON.stringify(n)).digest("hex").slice(0,8);return`${t}:ask:${e||"turn"}:${r}`}function Wc(t,{leadBoxId:e,provider:n,isAnswered:r,maxQuestions:o=Om}={}){if(!Array.isArray(t)||!e||!Uc.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||Hc(s.text)?.questions;if(!a?.length)return null;let i=a.slice(0,Math.max(1,o)),c=Av(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 Cm(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=ho(n),s;return r.length?r.length===1?s=`${r[0].name}: ${r[0].picks}`:s=`Answers:
|
|
81
|
+
${r.map(a=>`- ${a.name}: ${a.picks}`).join(`
|
|
82
|
+
`)}`:s=o,r.length&&o&&(s+=`
|
|
83
|
+
${o}`),s||""}var Mm=`
|
|
84
|
+
When you are BLOCKED on a decision only the operator can make \u2014 an ambiguous
|
|
85
|
+
requirement, an irreversible choice, several valid approaches \u2014 do not bury the
|
|
86
|
+
question in prose. End your message with exactly ONE fenced code block tagged
|
|
87
|
+
bostrat-question, holding JSON of this exact shape:
|
|
88
|
+
|
|
89
|
+
\`\`\`bostrat-question
|
|
90
|
+
{"questions":[{"header":"Auth method","question":"Which auth should the API use?","multiSelect":false,"options":[{"label":"JWT","description":"Stateless; works for the mobile client"},{"label":"Sessions","description":"Simplest with the current middleware"}]}]}
|
|
91
|
+
\`\`\`
|
|
92
|
+
|
|
93
|
+
Rules: valid JSON only; 1\u20134 questions; each question needs a short header (max
|
|
94
|
+
24 chars), the question text, and 2\u20134 options with short labels (descriptions
|
|
95
|
+
optional); set "multiSelect": true only when several options can apply at once.
|
|
96
|
+
The block must be the LAST thing in your message \u2014 emit it, then STOP and end
|
|
97
|
+
your turn. The operator sees the questions as an interactive card in their chat
|
|
98
|
+
(never your raw block), and their choice arrives as a normal chat message, e.g.
|
|
99
|
+
"Auth method: JWT". They may instead reply in free text \u2014 treat that as the
|
|
100
|
+
answer. Never use the block for rhetorical questions, progress updates, or
|
|
101
|
+
anything you can decide yourself; keep asking in prose for questions without
|
|
102
|
+
enumerable options.
|
|
103
|
+
`;import{execFile as Tv}from"node:child_process";import{promisify as _v}from"node:util";var Gc=_v(Tv),Nm=64*1024*1024;async function ft(t,e,{input:n}={}){let r=Gc("git",["-C",t,...e],{maxBuffer:Nm});return n!==void 0&&r.child.stdin&&(r.child.stdin.on("error",()=>{}),r.child.stdin.end(n)),r}async function Qs(t){if(!t)return!1;try{let{stdout:e}=await ft(t,["rev-parse","--is-inside-work-tree"]);return e.trim()==="true"}catch{return!1}}async function Pm(t){let{stdout:e}=await ft(t,["rev-parse","--show-toplevel"]);return e.trim()}async function Ev(t){try{return await ft(t,["rev-parse","--verify","HEAD"]),!0}catch{return!1}}async function Kc(t,e="bostrat: subtask snapshot"){let{stdout:n}=await ft(t,["rev-parse","--absolute-git-dir"]),r=`${n.trim()}/bostrat-snapshot-index`,o={...process.env,GIT_INDEX_FILE:r},s=(a,i)=>Gc("git",["-C",t,...a],{maxBuffer:Nm,env:o,...i});try{let a=await Ev(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 Gc("rm",["-f",r])}catch{}}}async function Vc(t,e){let{stdout:n}=await ft(t,["status","--porcelain"]);return n.trim()?(await ft(t,["add","-A"]),await ft(t,["-c","user.email=agent@bostrat.local","-c","user.name=bostrat","commit","-m",e]),!0):!1}async function zc(t,{ignorePlumbing:e=!1}={}){let{stdout:n}=await ft(t,["status","--porcelain"]),r=n.split(`
|
|
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 Jc(t,e){if(!e)return 0;try{let{stdout:n}=await ft(t,["rev-list","--count",`${e}..HEAD`]);return Number(n.trim())||0}catch{return 0}}async function dr(t,e,n,r){return await ft(t,["worktree","add","--no-track","-b",n,e,r]),e}async function Bm(t,e){try{return await ft(t,["rev-parse","--verify","--quiet",`refs/heads/${e}`]),!0}catch{return!1}}async function Dm(t,e,n){return await ft(t,["worktree","add",e,n]),e}async function ta(t){if(process.env.BOSTRAT_TRACK_BASE_REF)return process.env.BOSTRAT_TRACK_BASE_REF;try{let{stdout:e}=await ft(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 ft(t,["rev-parse","--verify","--quiet",e]),n}catch{}return"HEAD"}async function jm(t){await ft(t,["init","-q"]),await ft(t,["-c","user.email=agent@bostrat.local","-c","user.name=bostrat","commit","--allow-empty","-qm","bostrat: track workspace"])}async function Ut(t,e){try{await ft(t,["worktree","remove","--force",e])}catch{try{await ft(t,["worktree","prune"])}catch{}}}function Iv(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 Xc(t,e){return!e||!e.length?t:[...t,"--",".",...e.map(n=>`:(exclude)${n}`)]}async function Yc(t,e,n,r){let{stdout:o}=await ft(t,Xc(["diff","--shortstat",e,n],r));return Iv(o)}async function Zc(t,e,n,r){let{stdout:o}=await ft(t,Xc(["diff","--name-only",e,n],r));return o.split(`
|
|
105
|
+
`).map(s=>s.trim()).filter(Boolean)}async function Qc(t,e,n,r){let{stdout:o}=await ft(t,Xc(["diff","--binary",e,n],r));if(!o.trim())return{applied:!0,empty:!0,conflicts:[]};if((await qc(t,o,["--whitespace=nowarn"])).ok)return{applied:!0,empty:!1,conflicts:[]};let s=await qc(t,o,["--3way","--whitespace=nowarn"]),a=await Rv(t);return a.length>0?{applied:!1,empty:!1,conflicts:a}:s.ok?{applied:!0,empty:!1,conflicts:[]}:(await qc(t,o,["--reject","--whitespace=nowarn"]),{applied:!1,empty:!1,conflicts:[`patch did not apply cleanly: ${s.err}`]})}async function qc(t,e,n){try{return await ft(t,["apply",...n,"-"],{input:e}),{ok:!0}}catch(r){return{ok:!1,err:r?.stderr?.trim()||r?.message||String(r)}}}async function Rv(t){try{let{stdout:e}=await ft(t,["diff","--name-only","--diff-filter=U"]);return e.split(`
|
|
106
|
+
`).map(n=>n.trim()).filter(Boolean)}catch{return[]}}import $v from"node:fs/promises";import Ov from"node:os";import Lm from"node:path";var ea=t=>new Promise(e=>setTimeout(e,t)),Cv=Number(process.env.BOSTRAT_MODEL_PICKER_TIMEOUT_MS)||8e3,Mv=300,Wm=[{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"}],Nv=new Set([...Wm.map(t=>t.alias),"best","opus[1m]","sonnet[1m]","claude-opus-4-8","claude-sonnet-4-6","claude-haiku-4-5","claude-fable-5"]),Pv=[{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"}],qm=new Map([["low","Low"],["medium","Medium"],["high","High"],["xhigh","Extra high"]]),Gm=[{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 Km(t){return t==="codex"?Pv:t==="cursor"?Gm:Wm}function Fm(t){let e=String(t||"").split(`
|
|
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 Um(t){let e=String(t||"").split(`
|
|
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 Bv(t){let e=String(t||"").split(`
|
|
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 Hm(t){let e=String(t||"").split(`
|
|
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 fl=new Map;async function FA(t,e){let n=`${t.id}:${e.name}`;if(!fl.has(n)){let r=await j(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);fl.set(n,o)}return fl.get(n)}async function Dh(t,e){try{if(e?.agent!=="codex"||!e.cwd)return;let n=await bn(e.cwd,e.pinned?e.sessionId:null);if(!n)return;let r=await FA(t,e),o=LA(await It(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 C(t,{type:"cost.recorded",actor:"agent",source:"transcript",boxId:e.name,provider:"codex",payload:{usd:Yt(c,i),model:c,tokens:i,reasoningOutput:a.last.reasoningOutput||void 0,codexTs:a.ts,ts:a.ts}}),a===s&&a.contextWindow&&await C(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 da from"node:fs";import ml from"node:path";import UA from"node:os";var HA=process.env.BOSTRAT_STATE_DIR||ml.join(process.env.HOME||UA.tmpdir(),".bostrat"),Lh=(t=process.env)=>!!t.BOSTRAT_CURSOR_USAGE_COOKIE,WA="https://cursor.com/api/dashboard/get-filtered-usage-events",jh=100;async function qA({sinceMs:t,cookie:e,fetchImpl:n=fetch}){let r=[];for(let o=1;o<=20;o++){let s=await n(WA,{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:jh})});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<jh)break}return r}function GA(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 KA=5*6e4,Je={at:0,rows:null,error:null},fa=()=>ml.join(HA,"cursor-usage.json");function Fh(){try{return JSON.parse(da.readFileSync(fa(),"utf8"))}catch{return{sinceMs:0,seen:[]}}}function VA(t){try{da.mkdirSync(ml.dirname(fa()),{recursive:!0});let e=fa()+".tmp";da.writeFileSync(e,JSON.stringify(t)),da.renameSync(e,fa())}catch{}}var zA=5e3;async function JA({fetchImpl:t,now:e=Date.now()}={}){if(Je.rows&&e-Je.at<KA)return Je.rows;let n=process.env.BOSTRAT_CURSOR_USAGE_COOKIE;if(!n)return null;let r=Fh(),o=Math.max(r.sinceMs-6e4,e-24*36e5);try{let s=await qA({sinceMs:o,cookie:n,fetchImpl:t});Je={at:e,rows:s.map(GA).filter(Boolean),error:null}}catch(s){Je={at:e,rows:null,error:String(s?.message||s)}}return Je.rows}var hl=()=>({enabled:Lh(),lastPollAt:Je.at||null,error:Je.error}),pl=new Map;async function XA(t,e){let n=`${t.id}:${e.name}`;if(!pl.has(n)){let r=await j(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);pl.set(n,o)}return pl.get(n)}async function Uh(t,e,{fetchImpl:n}={}){try{if(e?.agent!=="cursor"||!e.cwd||!Lh())return;let r=await JA({fetchImpl:n});if(!r||!r.length)return;let o=await om(e.cwd,e.pinned?e.sessionId:null);if(!o?.chatId)return;let s=await XA(t,e),a=Fh(),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=Yt(u,f)),!d&&l.chargedCents!=null&&(d=l.chargedCents/100),await C(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}})}VA({sinceMs:c,seen:[...i].slice(-zA)})}catch{}}import{execFile as eT}from"node:child_process";import{promisify as nT}from"node:util";import Hh from"node:fs/promises";import Wh from"node:path";import YA from"node:os";var qh=process.env.BOSTRAT_STATE_DIR||Wh.join(process.env.HOME||YA.tmpdir(),".bostrat"),ZA=Wh.join(qh,"github-latency.ndjson"),QA=100,pa=[],wl=new Map;function ma({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};pa.push(l),pa.length>QA&&pa.shift();let u=wl.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)),wl.set(t,u),tT(l),l}async function tT(t){try{await Hh.mkdir(qh,{recursive:!0}),await Hh.appendFile(ZA,JSON.stringify(t)+`
|
|
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 Ht(t,s)}catch(l){throw new Error(ce(l))}finally{le()}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 rw(t,e){let n=await ve(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");let o;try{let i;try{i=await Ht(n.path,["pr","checks",String(r),"--json","name,state,link,bucket"])}catch(c){if(i=String(c?.stdout||""),!i.trim())throw new Error(ce(c))}o=JSON.parse(i||"[]").filter(c=>c.bucket==="fail")}catch(i){if(!xl(i))throw i;let c=await Ht(n.path,["pr","view",String(r),"--json","headRefOid"]),l=await Jh(n.path,JSON.parse(c).headRefOid);if(l===null)throw new Error(ce(i));o=Xh(l).map(Vh).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=wT(i.link),l=null;c&&!s.has(c)&&(s.add(c),l=await Ht(n.path,["run","view",c,"--log-failed"]).then(u=>gT(u)).catch(()=>null)),a.push({name:i.name,link:i.link||null,log:l})}return a}function wT(t){return String(t||"").match(/\/actions\/runs\/(\d+)/)?.[1]??null}function gT(t,{maxLines:e=400,maxBytes:n=64*1024}={}){let r=String(t||""),o=r.split(`
|
|
87
115
|
`);return o.length>e&&(r=`\u2026 (${o.length-e} earlier lines truncated)
|
|
88
116
|
`+o.slice(-e).join(`
|
|
89
117
|
`)),Buffer.byteLength(r)>n&&(r=`\u2026 (truncated)
|
|
90
|
-
`+r.slice(-n)),r}function
|
|
91
|
-
`).find(n=>n.trim())||"gh failed"}var
|
|
118
|
+
`+r.slice(-n)),r}function ce(t){return String(t?.stderr||t?.message||t||"").trim().split(`
|
|
119
|
+
`).find(n=>n.trim())||"gh failed"}var yT=6e3,ya=new Map;function Tl(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 ow(t){let e=t?.workspace?.branch;if(!e)return;let n;try{n=await j(t)}catch{return}if(Tl(n)==null)return;let r=(t.boxes||[]).find(c=>c.name===t.leadBox);if(await ca(t.leadBox,r?.agent).catch(()=>!1))return;let o=Date.now();if(o-(ya.get(t.id)||0)<yT)return;ya.set(t.id,o);let s;try{s=await ko(t.cwd,{caller:"reconcile"})}catch{return}let a=(s||[]).find(c=>c.headRefName===e);if(!a)return;let i=await Z(t.cwd).catch(()=>null);if(i){let{link:c,created:l}=await Pt({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 no(i,a.number))){ya.delete(t.id);return}}await C(t,{type:"pr.state.changed",actor:"agent",source:"orchestrator",boxId:t.leadBox,payload:{ts:Date.now(),change:"opened",number:a.number,url:a.url,title:a.title,branch:e,...i?{repo:i}:{}}}).catch(()=>{}),ya.delete(t.id)}import _l from"node:fs/promises";import vo from"node:path";import{randomUUID as bT}from"node:crypto";var aw=".bostrat/uploads",El=t=>vo.join(t,aw),sw=Number(process.env.BOSTRAT_MAX_UPLOAD_BYTES)||24*1024*1024,kT=new Set(["png","jpg","jpeg","gif","webp","svg","bmp","tif","tiff","heic","heif","avif","ico"]),xT=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 Il(t){let e=String(t||"").toLowerCase().replace(/^\./,"");return kT.has(e)?"image":xT.has(e)?"doc":null}function ST(t){let r=vo.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 iw(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>sw)throw new Error(`file too large \u2014 max ${Math.floor(sw/(1024*1024))} MB`);let r=ST(e),o=vo.extname(r).slice(1),s=Il(o);if(!s)throw new Error(`unsupported file type "${o||"unknown"}" \u2014 attach an image or a document`);let a=bT().slice(0,8),i=El(t);await _l.mkdir(i,{recursive:!0});let c=`${a}-${r}`;return await _l.writeFile(vo.join(i,c),n,{mode:384}),await _l.chmod(vo.join(i,c),384).catch(()=>{}),{id:a,name:r,rel:`${aw}/${c}`,kind:s,size:n.length}}function cw(t){let e=t.map(r=>`\`${r}\``).join(", ");return`[Attached \u2014 please read ${t.length===1?"this file":`these ${t.length} files`}: ${e}]`}var vT=/\[Attached — please read (?:this file|these \d+ files): [^\]]*\]$/;function Rl(t){return t.role==="user"&&vT.test((t.text||"").trim())}function gr(t,e,n=Date.now()){return e?t.idleSince==null?(t.idleSince=n,!1):n-t.idleSince>=6500:(t.idleSince=null,!1)}var _T=TT(AT),Ae=process.env.BOSTRAT_STATE_DIR||U.join(process.env.HOME||"/tmp",".bostrat"),gw=U.join(Ae,"tracks.json"),ET=U.join(Ae,"events.jsonl"),Bl=U.join(Ae,"workspaces"),IT=process.env.BOSTRAT_DEFAULT_REPO||null,xa={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"}},Sa=Object.keys(xa);function RT(t,{skipPermissions:e=!0}={}){let n=xa[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 yw(t,e,{skipPermissions:n=!0}={}){let r=xa[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 Oo(t){let e=xa[t];if(e)return process.env[e.envVar]||e.base}var En={at:0,value:null};async function jl({maxAgeMs:t=6e4}={}){if(En.value&&Date.now()-En.at<t)return En.value;let e=await Promise.all(Sa.map(async n=>[n,await rs(Oo(n))]));return En.value=Object.fromEntries(e),En.at=Date.now(),En.value}async function va(t,e=!0){let n=RT(t,{skipPermissions:e});if(!await rs(n)){En.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.":oi[r]?`Install it on this node: ${oi[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 Aa=".bostrat/handoff/latest.md";function $T(t,e=Aa){return`${t.trim()}
|
|
92
120
|
|
|
93
|
-
(Read ${e} for handoff context from the prior lead \u2014 you are now the lead; none of the prior chat is in your context. Then respond to the user above.)`}var
|
|
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 OT=/\(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 lw(t){return t.role==="user"&&OT.test((t.text||"").trim())}var ba=".bostrat/handoff/subtask-result.md",To=".bostrat/handoff/lead-context.md",CT=(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 ${To} \u2014 read that first for context. Your scoped task:
|
|
94
122
|
|
|
95
123
|
${t}
|
|
96
124
|
|
|
97
|
-
Work ONLY inside this directory. cd into the repo a change belongs to before running git commands, and commit per repo with a clear message (repos you don't touch need nothing). When you are finished: (1) make your code changes, (2) commit them in each changed repo, (3) LAST, write a short markdown digest to ${
|
|
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 ${ba} (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 ${To} \u2014 read that first for context. Your scoped task:
|
|
98
126
|
|
|
99
127
|
${t}
|
|
100
128
|
|
|
101
|
-
Work ONLY inside this directory. When you are finished: (1) make your code changes, (2) commit them with git and a clear message, (3) LAST, write a short markdown digest to ${
|
|
102
|
-
`;return Zc=Zc.then(()=>D.mkdir(Ge,{recursive:!0})).then(()=>D.appendFile(Mv,n)).catch(()=>{}),Zc}var ot=(t,e,n={})=>{let r={at:Date.now(),type:e,...n};return t.events.push(r),Fv(t.id,r),Qp(t,r),r};yu(async({sessionName:t,from:e,to:n,version:r,status:o,error:s})=>{let i=(await H()).tracks.find(c=>c.leadBox===t||(c.boxes||[]).some(l=>l.name===t));i&&(ot(i,"codex-update",{box:t,from:e,to:r||n,status:o,...s?{error:s}:{}}),await V().catch(()=>{}))});var Qc=(t,e,n)=>e!=null&&n.some(r=>t===r.box&&e>=r.from&&e<=r.to),Uv=new Set(["stuck","wrong-approach","too-slow","want-different-model","context-refresh","cost","parallelize","other"]);function vh(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=>Uv.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 Ah(t,e,n,{kind:r}={}){let o=await z(e);if(!o)throw new Error(`box "${e}" is not running`);let s=ho(o.agent);await Te({cwd:o.cwd,sessionId:o.sessionId,sessionName:e,agent:o.agent,agentCmd:s}),await kt(e,n,{...le(o.agent),kind:r||"orchestration"}),await q({flow:"orchestration",phase:"sent",trackId:t,sessionName:e,agent:o.agent,kind:r||"orchestration",promptHead:n.slice(0,160)});let a=await H(),i=Z(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 V()}var An=new Set(["merged","conflict","canceled","failed","handed-off"]),hh=new Map;async function Hv(t){let e=j.join(zo(t),`${t.id}.ndjson`),n;try{n=await D.stat(e)}catch{return null}let r=hh.get(t.id);if(r&&r.mtimeMs===n.mtimeMs)return r.at;let o=null;for(let s of await F(t)){if(s.type!=="turn.agent")continue;let a=s.payload?.ts??s.ts;a&&(!o||a>o)&&(o=a)}return hh.set(t.id,{mtimeMs:n.mtimeMs,at:o}),o}async function Wv(t,e){let n=e.find(r=>r.name===t.leadBox);return n?.state!=="live"?{working:!1,prompted:!1}:Ec(n.name,n.agent)}async function Ke(t){let e=new Map((await Nn()).map(a=>[a.name,a])),n=t.boxes.map(a=>{let i=e.get(a.name),c=An.has(a.state)?a.state:i?a.role==="lead"?"live":a.state:"stopped";return{...a,state:c,cwd:i?.cwd||a.cwd,agent:i?.agent||a.agent,sessionId:i?.sessionId||a.sessionId}}),[r,{working:o,prompted:s}]=await Promise.all([Hv(t),Wv(t,n)]);return{...t,boxes:n,lastResponseAt:r,working:o,prompted:s}}var qv=5e3;function Gv(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),d=l.createdAt-qv,f=r[u+1]?.createdAt??1/0,p=o.find(m=>!s.has(m.id)&&m.birthMs>=d&&m.birthMs<f);p&&(s.add(p.id),c.push({box:l.name,sessionId:p.id}))}return c}async function Th(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(f=>f.box.sessionId),a=fu(r),i=new Set(a.map(f=>f.id)),c=s.every(f=>f&&i.has(f)),l=new Set(s).size===s.length;if(c&&l)continue;let u=o.map(f=>f.box),d=Gv(u,a,f=>i.has(f));for(let f of d){let p=u.find(m=>m.name===f.box);p&&(p.sessionId=f.sessionId),await nu(f.box,f.sessionId),n=!0}}return n}async function ut(){let t=await H();return await Th(t)&&await V(),Promise.all(t.tracks.map(Ke))}async function _h(t,e){let n=await H();if(t)for(let r of n.tracks||[]){let o=r.boxes.find(s=>s.sessionId&&s.sessionId===t);if(o)return{track:r,box:o}}if(e)for(let r of n.tracks||[]){let o=r.boxes.find(s=>s.cwd===e);if(o)return{track:r,box:o}}return null}async function et(t){let e=await H(),n=Z(e,t);return n?(await Th(e)&&await V(),Ke(n)):null}var Kv={claude:[/^\[Request interrupted by user(?: for tool use)?\]$/i],codex:[/^\[?Request interrupted/i,/^Conversation interrupted/i],cursor:[/^\[?Request interrupted/i]};function wh(t){if(t.role!=="user")return!1;let e=(t.text||"").trim();return(Kv[t.agent]||[]).some(n=>n.test(e))}var Vv=[/^\/model(?:\s|$)/i,/<command-(?:name|message|args)>[^<]*\bmodel\b/i,/^Model changed to\b/i];function gh(t){let e=(t.text||"").trim();return e?Vv.some(n=>n.test(e)):!1}var Jv=10*6e4;async function sl(t,e={}){let n=await et(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=wo(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=oi(n.id),c=!!(i&&!i.finishedAt),l=c&&i.startedAt&&i.box?[{box:i.box,from:i.startedAt,to:Date.now()+1e12}]:[],u=[...Id(n),...l],f={windows:[...Hu(n),...Wu(n,Vo),...a,...u]},m=(await Promise.all(n.boxes.map(k=>bp(k,e)))).flat(),h=m.map(k=>{let W={kind:"message",...k};if(W.role==="assistant"&&(W.text||"").trim()&&W.tools?.length){let En=to(W.agent).cliTools,Oo=W.tools.filter(In=>!En.has(In));return{...W,tools:Oo.length?Oo:void 0}}return W}).map(k=>{if(k.role!=="user")return k;let W=Ds(k.text);return W?W.kind==="suppress"?null:{...k,note:W,text:Dm(W)}:k}).filter(Boolean).filter(k=>!(k.role==="user"&&r.has((k.text||"").trim()))).filter(k=>!wh(k)).filter(k=>!gh(k)).filter(k=>!mh(k)).filter(k=>!Xc(k)).filter(k=>k.role!=="assistant"||!!(k.text||"").trim()||!!k.images?.length).filter(k=>!qu(k,f)),b=h.filter(k=>k.failed&&k.text===tc);if(b.length){let k=new Map;for(let W of b){if(!k.has(W.box)){let In=[...(await wt(W.box,{lines:_t}).catch(()=>"")).matchAll(/^\s*■\s+(.+?)\s*$/gm)].map(xa=>xa[1]);k.set(W.box,In.length?In[In.length-1]:null)}let En=k.get(W.box);En&&(W.text=`\u25A0 ${En}`)}}let x=await Promise.all(n.boxes.map(k=>xp(k,e)));await Nm(n,x.flat()),await Mm(n,h),await tm(n),await Pd(n,h).catch(()=>{});for(let k of n.boxes)k.agent==="codex"&&k.state==="live"&&await Fm(n,k),k.agent==="cursor"&&k.state==="live"&&await Gm(n,k);let v=await Promise.all(n.boxes.map(k=>kp(k,e)));await Pm(n,v.flat().filter(k=>!Qc(k.box,k.ts,u)));let E=await F(n),R=c&&i.viewId?i.viewId:null,st=dm(E),I=js(E).filter(k=>!(k.kind==="message"&&(wh(k)||gh(k)||mh(k)||Xc(k)))&&!(k.kind==="view-card"&&R&&k.viewId===R)),L=fm(I,st),T=I.filter(k=>!(k.kind==="message"&&k.role==="assistant"&&!L.has(k)&&Qc(k.box,k.ts,st))),U=Pc(E,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=>!Qc(k.anchorBox,k.anchorTs??k.ts,u)),J=[...T,...U].filter(k=>k.ts!=null).sort((k,W)=>k.ts-W.ts),g=zv(n),w=g.size?J.filter(k=>k.kind==="event"||!g.has(k.box??k.anchorBox)):J,y=n.boxes.find(k=>k.name===n.leadBox)||null,A=y?await Cr(y.cwd,y.sessionId,{fallbackNewest:!1}):null;if(!A&&y){let k=[...E].reverse().find(W=>W.type==="context.usage"&&W.boxId===y.name);k?.payload?.window&&(A={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 K=[...m].reverse().find(k=>k.role==="assistant"&&k.model),B=K?.model||null,$=[...n.events||[]].reverse().find(k=>k.type==="model-change"),_=A?.model||B;$&&(!K?.ts||$.at>=K.ts)&&(_=$.to);let M=y?await wt(y.name,{lines:_t}).catch(()=>""):"",at=y&&sr(y.agent,M)?await ve(y.name).catch(()=>M):M,Q=y?bn(y,at):null;Q&&em(n,y,Q);let ht=!!y&&!Q&&jn(y.agent,M);y&&!y.readyAt&&(ht||Q||M&&Wo(y.agent,M))&&(y.readyAt=Date.now(),await V());let Tt=!!y&&!y.readyAt&&Date.now()-(n.createdAt||0)<Jv;if(n.kind==="setup"&&n.setup?.provider&&!n.setup.connectedAt&&y&&Wo(y.agent,M)&&!Q){let k=await H(),W=Z(k,n.id);W?.setup&&!W.setup.connectedAt&&(W.setup.connectedAt=Date.now(),n.setup.connectedAt=W.setup.connectedAt,await O(W,{type:"provider.connected",actor:"system",source:"pane",boxId:y.name,provider:y.agent,payload:{provider:y.agent,ts:W.setup.connectedAt}}).catch(()=>{}),await V())}let Dt=s?{running:!0,step:o.step,toAgent:o.toAgent||null}:null,Ht=null;if(c){let k=Date.now()-i.startedAt;if(i.viewId&&!ht){let W=ns(n.id);W&&(ot(n,"view-spinup",{box:i.box,startedAt:i.startedAt,endedAt:W.finishedAt}),await V())}else if(Q||k>_d&&!ht&&!i.viewId||k>ri){let W=Q?"needs-input":k>ri?"timeout":"no-view";ns(n.id,{error:W})&&await O(n,{type:"view.spinup.failed",actor:"orchestrator",source:"orchestrator",boxId:i.box,provider:y?.agent||null,payload:{ts:i.startedAt,name:i.name,reason:W}}).catch(()=>{})}else Ht={running:!0,step:i.step,name:i.name}}return{trackId:n.id,leadBox:n.leadBox,items:w,context:A?{pct:A.pct,occupancy:A.occupancy,window:A.window,windowKnown:A.windowKnown,model:A.model}:null,models:{current:_,options:Jp(y?.agent)},pendingPrompt:Q,working:ht,leadStarting:Tt,handoff:Dt,view:Ht,queued:Yv(n.id).filter(k=>k.kind!=="ship-notify")}}function zv(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 Eh(t,e){let n=await et(t);if(!n)return null;let r=br(t).find(d=>d.id===e)||null;if(!r)return null;let o=r.box||null;if(!o)return{items:[],working:!1,box:null,agent:r.agent,task:r.task};let s=await F(n),a=js(s).filter(d=>d.box===o),i=Pc(s.filter(d=>d.boxId===o),a.filter(d=>d.kind==="message")).map(d=>({kind:"command-batch",id:d.id,ts:d.pending?d.ts:(d.anchorTs??d.ts)-1,anchorTs:d.anchorTs,anchorBox:d.anchorBox,agent:d.agent,boxRole:d.boxRole,commands:d.commands,pending:d.pending})),c=[...a,...i].filter(d=>d.ts!=null).sort((d,f)=>d.ts-f.ts),l=n.boxes.find(d=>d.name===o)||null,u=!1;if(l&&l.state==="live"){let d=await wt(o,{lines:_t}).catch(()=>"");u=jn(l.agent||r.agent,d)}return{items:c,working:u,box:o,agent:r.agent,task:r.task}}async function al(t,{promptId:e,answers:n,text:r}={}){let o=Z(await H(),t);if(!o)throw new Error(`track "${t}" not found`);let s=o.boxes.find(f=>f.name===o.leadBox);if(!s)throw new Error(`track "${t}" has no lead box`);if(!await z(o.leadBox))throw new Error(`lead box "${o.leadBox}" is not running`);let i=await fc(s);if(!i)throw new Error("no pending prompt to answer");if(e&&e!==i.promptId)throw new Error("prompt changed since it was shown \u2014 reload and answer the current prompt");let c;if(i.answerMode==="text"){let f=typeof r=="string"?r.trim():"";if(!f)throw new Error("this prompt needs a code \u2014 paste it into the card");if(/\s/.test(f)||f.length>2048)throw new Error("that doesn't look like a sign-in code \u2014 paste the code exactly as shown");await kt(o.leadBox,f,{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.provider==="claude"||i.answerMode==="keys"){let f=Cp(i,n||[]);await bt(o.leadBox,f,{kind:"answer-prompt"}),c={mode:"keys",value:f.join(" "),count:f.length}}else{let f=Tu(i,n||[]);await kt(o.leadBox,f,{enter:!0,kind:"answer-prompt"}),c={mode:"text",value:f,count:1}}let l=i.questions?.[0]||{},u=(Array.isArray(n?.[0])?n[0]:[]).map(f=>l.options?.[f]).filter(Boolean).map(f=>({label:f.label,description:f.description||""})),d=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,...d?{control:d}:{}},await q({flow:"prompt",phase:"answered",sessionName:o.leadBox,agent:s.agent,provider:i.provider,promptId:i.promptId,...c}).catch(()=>{}),nm(o,s,i,c),{ok:!0,promptId:i.promptId,sent:c.count}}async function Ih(t,{promptId:e,questionIndex:n}={}){let r=Z(await H(),t);if(!r)throw new Error(`track "${t}" not found`);let o=r.boxes.find(l=>l.name===r.leadBox);if(!o)throw new Error(`track "${t}" has no lead box`);if(!await z(r.leadBox))throw new Error(`lead box "${r.leadBox}" is not running`);let a=await fc(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=Mp(a,n);return c.length?(await bt(r.leadBox,c,{kind:"navigate-prompt"}),await q({flow:"prompt",phase:"navigated",sessionName:r.leadBox,agent:o.agent,provider:a.provider,promptId:a.promptId,fromIndex:a.questionIndex??0,toIndex:n,mode:"keys",value:c.join(" "),count:c.length}).catch(()=>{}),{ok:!0,promptId:a.promptId,sent:c.length}):{ok:!0,promptId:a.promptId,sent:0}}var Xv=1500,lo=new Map;function il(t){let e=lo.get(t);return e||(e={items:[],timer:null,flushing:!1,idleSince:null},lo.set(t,e)),e}function Yv(t){return(lo.get(t)?.items??[]).map(e=>({...e}))}function ta(t){let e=lo.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 Rh(t){let e=il(t);e.timer||(e.timer=setInterval(()=>Zv(t).catch(()=>{}),Xv))}async function Zv(t){let e=lo.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=Z(await H(),t);if(!n||n.archivedAt){ta(t);return}let r=wo(t);if(r&&!r.finishedAt&&r.step!=="done"&&!r.error){pr(e,!1);return}let o=await z(n.leadBox);if(!o){pr(e,!1);return}let s=n.boxes.find(c=>c.name===n.leadBox),a=await wt(n.leadBox,{lines:_t}).catch(()=>"");if(Ho(s?.agent,a)){pr(e,!1);return}if(sr(s?.agent,a)){let c=await ve(n.leadBox).catch(()=>a);if(bn(s,c)){pr(e,!1);return}}if(!pr(e,!0))return;let i=e.items.shift();e.idleSince=null,await $h(n,s,o,i.text,i.kind??null,i.attachments??null),await V()}finally{e.flushing=!1}}}async function $h(t,e,n,r,o=null,s=null){let a=r,i=!1;t.pendingHandoffHydrate&&(a=Bv(r,t.pendingHandoffHydrate.artifact||Qs),delete t.pendingHandoffHydrate,i=!0);let c=Array.isArray(s)?s.filter(u=>u&&u.rel):[];if(c.length){let u=ph(c.map(d=>d.rel));a=a.trim()?`${a.trim()}
|
|
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 ${ba} 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.`,MT=(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."),ue=null,$l=null;async function W(){return ue||($l||=(async()=>{try{ue=JSON.parse(await F.readFile(gw,"utf8"))}catch{ue={tracks:[]}}finally{$l=null}return ue})(),$l)}async function K(){await Qt(gw,ue)}var tt=(t,e)=>t.tracks.find(n=>n.id===e)||null,Ol=Promise.resolve();function NT(t,e){let n=JSON.stringify({trackId:t,...e})+`
|
|
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()}
|
|
103
131
|
|
|
104
|
-
${u}`:u}let l=ot(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
|
|
132
|
+
${u}`:u}let l=ot(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 Ce({cwd:n.cwd,sessionId:n.sessionId,sessionName:t.leadBox,agent:n.agent,agentCmd:Oo(n.agent)}),await xt(t.leadBox,a,{...me(e?.agent),kind:i?"lead-handoff-first-prompt":"say"})}catch(u){throw ot(t,"unsaid",{box:t.leadBox,agent:e?.agent,boxRole:e?.role,saidTs:l.at,text:r}),u}await r_(t,e,a)||(await $w(t.leadBox,e?.agent,a.length+8).catch(()=>{}),ot(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 Iw(t,{actions:e}={}){Ul(t).items.push({id:$o(),text:uh(e),ts:Date.now(),kind:"ship-notify"}),_w(t)}async function de(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"||!ur(e))&&!o.length)throw new Error("text (string) or an attachment required");let s=tt(await W(),t);if(!s)throw new Error(`track "${t}" not found`);if(s.archivedAt)throw new Error(`track "${t}" is archived \u2014 restore it before driving it`);let a=await J(s.leadBox);if(!a&&await Gl(s)&&(await K(),a=await J(s.leadBox)),!a)throw new Error(`lead box "${s.leadBox}" is not running`);let i=s.boxes.find(d=>d.name===s.leadBox),c=ur(e),l=Ul(t),u=await mt(s.leadBox,{lines:St}).catch(()=>""),f=l.items.length>0||us(i?.agent,u);if(!f&&lr(i?.agent,u)){let d=await Re(s.leadBox).catch(()=>u);f=!!An(i,d)}if(f){let d={id:$o(),text:c,ts:Date.now(),...n?{kind:n}:{},...o.length?{attachments:o}:{}};return l.items.push(d),_w(t),delete s.draft,await K(),{ok:!0,leadBox:s.leadBox,queued:!0,id:d.id,position:l.items.length}}return await Ew(s,i,a,c,n,o),await K(),{ok:!0,leadBox:s.leadBox}}var mw="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 Rw(t,{hint:e}={}){let n=tt(await W(),t);if(!n)throw new Error(`track "${t}" not found`);if(n.archivedAt)throw new Error(`track "${t}" is archived \u2014 restore it first`);let r=Ci(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";Sf(n.id,{name:s,box:n.leadBox});let a=String(e||"").trim()?`${mw}
|
|
105
133
|
|
|
106
|
-
What to preview: ${String(e).trim()}`:
|
|
134
|
+
What to preview: ${String(e).trim()}`:mw;try{await de(t,a,{kind:"spin-up-view"})}catch(i){throw Es(n.id,{error:"drive-failed"}),i}return{ok:!0,running:!0}}async function Hl(t){let e=tt(await W(),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=Ta(t),o=await J(e.leadBox),s=o?await JT(e.leadBox,n?.agent):{stopped:!1,sends:0},a=o&&s.stopped?await ZT(e,n):null,i=!a&&o&&s.stopped?await e_(e,n):null,c=o?await XT(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(`
|
|
135
|
+
|
|
136
|
+
`),e.draft={text:u,updatedAt:Date.now()},await K()),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 VT=[300,350,400],zT=3;async function JT(t,e){let n=0;for(let r=0;r<zT;r++){await bt(t,["Escape"],{kind:"interrupt"}),n++;for(let o of VT){await Ar(o);let s=await mt(t,{lines:St}).catch(()=>"");if(!Oe(e,s))return{stopped:!0,sends:n}}}return{stopped:!1,sends:n}}async function XT(t){let e=Tl(await j(t));return e?.offerId?(await C(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 YT=[250,400,500,600];async function ZT(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 YT){await Ar(r);let o=await mt(t.leadBox,{lines:St}).catch(()=>""),s=mo(e.agent,o);if(s&&Fc(n.text,s))return await $w(t.leadBox,e.agent,n.text.length+8),t.draft={text:n.text,updatedAt:Date.now()},ot(t,"unsaid",{box:t.leadBox,agent:e.agent,boxRole:e.role,saidTs:n.at,text:n.text}),await K(),n.text}return null}var QT=3e4,t_=[350,650,900];async function e_(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>QT||n.some(s=>s.type==="unsaid"&&s.saidTs===r.at)||!Vt(r.text))return null;for(let s of t_){await Ar(s);let a;try{a=await Cc(e,{})}catch{return null}if(Im(a,r))return null;let i=await mt(t.leadBox,{lines:St}).catch(()=>"");if(mo(e.agent,i))return null}return t.draft={text:r.text,updatedAt:Date.now()},ot(t,"unsaid",{box:t.leadBox,agent:e.agent,boxRole:e.role,saidTs:r.at,text:r.text,reason:"lost"}),await K(),r.text}var n_=[250,450,700],Ml=2;async function r_(t,e,n){if(e?.agent!=="claude")return!0;for(let r=0;r<=Ml;r++){let o=!1;for(let s of n_){await Ar(s);let a=await mt(t.leadBox,{lines:St}).catch(()=>""),i=Rm(e.agent,n,a,{busy:Oe(e.agent,a)});if(i==="stuck"){o=!0;break}if(i==="submitted")return!0}if(!o)return!0;r<Ml&&(await bt(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:Ml,textHead:n.slice(0,120)}).catch(()=>{}),!1}async function $w(t,e,n){for(let r=0;r<3;r++){await ss(t,n,{kind:"unsend-wipe"}).catch(()=>{}),await Ar(200);let o=await mt(t,{lines:St}).catch(()=>""),s=mo(e,o);if(!s)return!0;n=s.length+8}return!1}async function Ow(t,e){if(typeof e!="string")throw new Error("text (string) required");let n=tt(await W(),t);if(!n)throw new Error(`track "${t}" not found`);let r=ur(e);return r?n.draft={text:r,updatedAt:Date.now()}:delete n.draft,await K(),{ok:!0,draft:n.draft||null}}async function Cw(t,e){if(typeof e!="string"||!e.trim())throw new Error("model (string) required");let n=e.trim(),r=tt(await W(),t);if(!r)throw new Error(`track "${t}" not found`);let o=await J(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=Vm(a,n),l=[...r.events||[]].reverse().find(f=>f.type==="model-change")?.to??null;l||(l=(await Fr(o.cwd,o.sessionId,{fallbackNewest:!1}).catch(()=>null))?.model??null),await Ce({cwd:o.cwd,sessionId:o.sessionId,sessionName:r.leadBox,agent:o.agent,agentCmd:Oo(o.agent)});let u=await zm(a,r.leadBox,i);return ot(r,"model-change",{to:n,from:l,box:r.leadBox,agent:a,verified:!!u.verified}),await K(),{ok:!0,requested:n,leadBox:r.leadBox,...u}}async function Mw(t){for(let e of t){if(!e||!await Qs(e))continue;let n=await Pm(e).catch(()=>null);if(!n)continue;if((await F.stat(U.join(n,".git")).catch(()=>null))?.isDirectory())return n}return null}function Ot(t){let e=t;return e?e.kind==="multi"?Array.isArray(e.repos)?e.repos.filter(Boolean):[]:e.base?[{name:U.basename(e.base),dir:e.dir??null,base:e.base,baseRef:e.baseRef??null,repo:e.repo??null}]:[]:[]}async function go(t){let e=[];for(let n of Ot(t?.workspace)){if(!n?.dir)continue;let r=await zc(n.dir,{ignorePlumbing:!0}).catch(()=>!1),o=await Jc(n.dir,n.baseRef).catch(()=>0);(r||o>0)&&e.push({...n,dirty:r,ahead:o})}return e}async function Nw(t){let e=t;typeof e=="string"&&!U.isAbsolute(e)&&/^[^/\s]+\/[^/\s]+$/.test(e)&&(e=await so(e)??await mc(e));let n=await Mw([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 o_(t,e,n,r){let o=`track/${zl(e)}-${t}`,s=[];try{await F.mkdir(n,{recursive:!0});let a=new Set([".bostrat"]);for(let i of r){let c=await Nw(i);if(s.some(m=>m.base===c))throw new Error(`repo tagged twice: ${i}`);let l=await Z(c).catch(()=>null),u=U.basename(c);a.has(u)&&l&&(u=l.replace("/","-"));for(let m=2;a.has(u);m++)u=`${U.basename(c)}-${m}`;a.add(u);let f=U.join(n,u),d=await ta(c);await dr(c,f,o,d),s.push({name:u,dir:f,base:c,baseRef:d,repo:l})}return await _a(n,s,o),{dir:n,kind:"multi",branch:o,repos:s}}catch(a){for(let i of s)await Ut(i.base,i.dir).catch(()=>{});throw await F.rm(n,{recursive:!0,force:!0}).catch(()=>{}),a}}var Pw=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 Bw(t,e){let n=await W(),r=tt(n,t);if(!r)throw new Error(`track "${t}" not found`);if(r.archivedAt)throw new Error("track is archived \u2014 unarchive it first");let o=r.workspace;if(o?.kind!=="multi")throw new Error("live re-tagging needs a multi-repo Track \u2014 a single-repo Track's workspace IS its repo checkout; create the Track with multiple repos tagged instead");let s=await Nw(typeof e=="string"?e.trim():e);if((o.repos??[]).some(d=>d?.base===s))throw new Error(`repo already tagged: ${e}`);let a=await Z(s).catch(()=>null),i=new Set([".bostrat",...(o.repos??[]).map(d=>d?.name).filter(Boolean)]),c=U.basename(s);i.has(c)&&a&&(c=a.replace("/","-"));for(let d=2;i.has(c);d++)c=`${U.basename(s)}-${d}`;let l=U.join(o.dir,c),u=await ta(s);await Bm(s,o.branch)?await Dm(s,l,o.branch):await dr(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 _a(o.dir,o.repos,o.branch),ot(r,"retagged",{action:"add",repo:a??s,name:c,workspace:Pw(o)}),await K(),f}async function Dw(t,e){let n=await W(),r=tt(n,t);if(!r)throw new Error(`track "${t}" not found`);let o=r.workspace;if(o?.kind!=="multi")throw new Error("live re-tagging needs a multi-repo Track");let s=(o.repos??[]).find(c=>c&&(c.name===e||c.repo===e||c.base===e));if(!s)throw new Error(`repo "${e}" is not tagged on this Track`);if((o.repos??[]).filter(Boolean).length<=1)throw new Error("can't untag the last repo \u2014 archive or delete the Track instead");for(let c of 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 zc(s.dir,{ignorePlumbing:!0}).catch(()=>!1),i=await Jc(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 Ut(s.base,s.dir).catch(()=>{}),await F.rm(s.dir,{recursive:!0,force:!0}).catch(()=>{}),o.repos=(o.repos??[]).filter(c=>c&&c!==s),await _a(o.dir,o.repos,o.branch),ot(r,"retagged",{action:"remove",repo:s.repo??s.base,name:s.name,workspace:Pw(o)}),await K(),{removed:s.name}}async function s_(t,e,{repo:n,repos:r,cwd:o}={}){await F.mkdir(Bl,{recursive:!0});let s=U.join(Bl,t),a=Array.isArray(r)?r.map(c=>typeof c=="string"?c.trim():"").filter(Boolean):[];if(a.length>=2)return o_(t,e,s,a);a.length===1&&!n&&(n=a[0]),typeof n=="string"&&!U.isAbsolute(n)&&/^[^/\s]+\/[^/\s]+$/.test(n)&&(n=await so(n)??await mc(n));let i=await Mw([n,o,IT]);if(i){let c=`track/${zl(e)}-${t}`;try{let l=await ta(i);await dr(i,s,c,l),await Dl(s);let u=await Z(i).catch(()=>null);return{dir:s,base:i,branch:c,kind:"worktree",baseRef:l,repo:u}}catch{await F.rm(s,{recursive:!0,force:!0}).catch(()=>{})}}return await F.mkdir(s,{recursive:!0}),await jm(s),await Dl(s),{dir:s,base:null,branch:null,kind:"repo"}}var Eo="# bostrat workspace",jw=`${Eo}
|
|
107
137
|
|
|
108
138
|
When asked to run, preview, or demo this app so the operator can review it: do NOT
|
|
109
139
|
start an ad-hoc dev server and do NOT share a localhost URL (the operator is on
|
|
@@ -242,9 +272,9 @@ which steps to run (and which to skip). An offer buried mid-message, phrased as
|
|
|
242
272
|
statement, or omitted entirely renders no controls \u2014 so close EVERY completed
|
|
243
273
|
implementation turn this way, every time. When the operator has already asked you
|
|
244
274
|
to commit / push / open the PR, just do it; do not re-offer.
|
|
245
|
-
|
|
246
|
-
`)),
|
|
247
|
-
`);return`${
|
|
275
|
+
`,a_=jw,Ao=jw+Mm;async function Dl(t){try{await F.writeFile(U.join(t,"AGENTS.md"),Ao,{flag:"wx"})}catch{return}await l_(t,["/AGENTS.md"])}var Nl=t=>typeof t=="string"&&(t===Eo||t.startsWith(`${Eo}
|
|
276
|
+
`)),i_="# bostrat multi-repo workspace";function c_(t,e){let n=t.map(r=>`- \`${r.name}/\`${r.repo?` \u2014 github.com/${r.repo}`:""}${r.baseRef?` (forked from ${r.baseRef})`:""}`).join(`
|
|
277
|
+
`);return`${i_}
|
|
248
278
|
|
|
249
279
|
This directory is NOT a git repo. It is a bostrat Track workspace holding one
|
|
250
280
|
git worktree per tagged repo, ALL on the same branch \`${e}\`:
|
|
@@ -256,25 +286,25 @@ push / open PRs) in each repo separately, always on that shared branch. Work
|
|
|
256
286
|
only needs to land in the repos it actually touches \u2014 an untouched repo's
|
|
257
287
|
branch simply stays at its fork point and never gets pushed. When asked to run
|
|
258
288
|
or preview an app, run it from that app's own repo directory.
|
|
259
|
-
`}async function
|
|
260
|
-
`,`${
|
|
289
|
+
`}async function _a(t,e,n){let r=c_(e,n);for(let o of["CLAUDE.md","AGENTS.md"])await F.writeFile(U.join(t,o),r).catch(()=>{})}var Pl=Ao.replace(`${Eo}
|
|
290
|
+
`,`${Eo}
|
|
261
291
|
|
|
262
292
|
(Global copy managed by bostrat \u2014 rewritten on service start; edits will be lost.)
|
|
263
293
|
SCOPE: these instructions apply ONLY when your working directory is under
|
|
264
294
|
~/.bostrat/workspaces/ or ~/.bostrat/worktrees/ \u2014 a bostrat-orchestrated box.
|
|
265
295
|
When working anywhere else, ignore everything below.
|
|
266
|
-
`);async function
|
|
267
|
-
`).includes(s));o.length&&await
|
|
296
|
+
`);async function Lw(t){let{stdout:e}=await _T("git",["-C",t,"rev-parse","--git-path","info/exclude"]);return U.resolve(t,e.trim())}async function l_(t,e){try{let n=await Lw(t);await F.mkdir(U.dirname(n),{recursive:!0});let r=await F.readFile(n,"utf8").catch(()=>""),o=e.filter(s=>!r.split(`
|
|
297
|
+
`).includes(s));o.length&&await F.appendFile(n,(r.endsWith(`
|
|
268
298
|
`)||!r?"":`
|
|
269
299
|
`)+o.join(`
|
|
270
300
|
`)+`
|
|
271
|
-
`)}catch{}}async function
|
|
301
|
+
`)}catch{}}async function u_(t,e){try{let n=await Lw(t),r=await F.readFile(n,"utf8"),o=r.split(`
|
|
272
302
|
`).filter(s=>s!==e);o.length!==r.split(`
|
|
273
|
-
`).length&&await
|
|
274
|
-
`))}catch{}}async function
|
|
303
|
+
`).length&&await F.writeFile(n,o.join(`
|
|
304
|
+
`))}catch{}}async function Fw(){let t={refreshed:0,removed:0,codexGlobal:null};try{await F.mkdir(Ae,{recursive:!0}),await F.writeFile(U.join(Ae,"CLAUDE.md"),a_).catch(()=>{}),await F.writeFile(U.join(Ae,"AGENTS.md"),Ao).catch(()=>{});let e=process.env.CODEX_HOME||U.join(process.env.HOME||"/tmp",".codex");if((await F.stat(e).catch(()=>null))?.isDirectory()){let r=U.join(e,"AGENTS.md"),o=await F.readFile(r,"utf8").catch(()=>null);o===null?(await F.writeFile(r,Pl,{flag:"wx"}).catch(()=>{}),t.codexGlobal="created"):Nl(o)&&o!==Pl&&(await F.writeFile(r,Pl).catch(()=>{}),t.codexGlobal="refreshed")}for(let r of[Bl,kr]){let o=await F.readdir(r,{withFileTypes:!0}).catch(()=>[]);for(let s of o){if(!s.isDirectory())continue;let a=U.join(r,s.name),i=U.join(a,"AGENTS.md"),c=await F.readFile(i,"utf8").catch(()=>null);c!==null&&Nl(c)&&c!==Ao&&(await F.writeFile(i,Ao).catch(()=>{}),t.refreshed++);let l=U.join(a,"CLAUDE.md"),u=await F.readFile(l,"utf8").catch(()=>null);u!==null&&Nl(u)&&(await F.rm(l,{force:!0}).catch(()=>{}),await u_(a,"/CLAUDE.md"),t.removed++)}}}catch{}return t}async function d_(t){let e=t.workspace;if(!(!e||!e.dir))try{if(e.kind==="multi"){for(let n of Ot(e))n.base&&n.dir&&await Ut(n.base,n.dir).catch(()=>{});await F.rm(e.dir,{recursive:!0,force:!0})}else e.kind==="worktree"&&e.base?await Ut(e.base,e.dir):await F.rm(e.dir,{recursive:!0,force:!0})}catch{}}async function Wl({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 va(o,s),i=await W(),c=$o().slice(0,8),l=await s_(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(si(f))&&(f=`${t.trim().slice(0,50).trim()} ${c} lead`);let d=await sn(f,l.dir,{agentCmd:a,agent:o});fs({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:[Xw(d,"lead","live")],events:[],createdAt:Date.now()};return ot(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:k})=>({name:h,base:w,baseRef:g,repo:k}))}:{}}}),i.tracks.push(m),await K(),Xe(m)}var f_=new Set(["claude","codex","cursor"]);async function Uw({provider:t="claude",fresh:e=!1}={}){if(!f_.has(t))throw new Error(`unknown setup provider "${t}" (claude | codex | cursor)`);let r=(await W()).tracks.find(u=>u.kind==="setup"&&u.setup?.provider===t&&!u.archivedAt);if(r){if(!e){let u=await Xe(r);if(u.boxes.find(d=>d.name===u.leadBox)?.state==="live")return u}await Sr(r.id).catch(()=>{})}let o=(await va(t,!1)).replace(/\s+--(?:dangerously-skip-permissions|dangerously-bypass-approvals-and-sandbox)\b/g,""),s=$o().slice(0,8),a=U.join(Ae,"setup",s);await F.mkdir(a,{recursive:!0});let i=await sn(`Connect ${t} ${s}`,a,{agentCmd:o,agent:t,firstRun:!0});t==="codex"&&fs({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:[Xw(i,"lead","live")],events:[],createdAt:Date.now()};return ot(c,"opened",{agent:t,box:i.name,skipPermissions:!1,trackKind:"setup",name:c.name,cwd:c.cwd,workspace:{base:null,branch:null,kind:"scratch"}}),(await W()).tracks.push(c),await K(),Xe(c)}async function Hw(t,e,n){await Rf(t).catch(()=>{});let r=Io.get(t);r&&!r.finishedAt&&(r.error=n,r.finishedAt=Date.now()),Io.delete(t),e.handoff&&!e.handoff.finishedAt&&(e.handoff.step="error",e.handoff.error=n,e.handoff.finishedAt=Date.now());for(let a of vr(t))a.finishedAt||(a.canceled=!0,a.finishedAt=Date.now()),await Xl(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 W(),s=new Set((o.tracks||[]).filter(a=>a.id!==t&&!a.archivedAt).flatMap(a=>(a.boxes||[]).map(i=>i.name)));for(let a of e.boxes)s.has(a.name)||await Xt(a.name).catch(()=>{})}async function Ww(t,{cascadeMocks:e=!0,cascadeRunbooks:n=!1}={}){let r=await W(),o=r.tracks.findIndex(a=>a.id===t);if(o===-1)throw new Error(`track "${t}" not found`);let s=r.tracks[o];return e||await df(s),await Hw(t,s,"track deleted"),n?await Uf(t).catch(()=>{}):await Ff(t).catch(()=>{}),await Xm(),await d_(s),e&&await ff(t).catch(()=>{}),ot(s,"track-deleted",{name:s.name,boxes:s.boxes.length,ageMs:s.createdAt?Date.now()-s.createdAt:null}),r.tracks.splice(o,1),await K(),{ok:!0,id:t,name:s.name,boxes:s.boxes.length}}async function Sr(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 already archived`);return n.archivedAt=Date.now(),ot(n,"archived",{boxes:n.boxes.length}),await K(),n.kind==="setup"?await qw(t):m_(t).catch(()=>{}),Xe(n)}var p_=9e4;async function m_(t){try{await h_(t)}catch{}await qw(t).catch(()=>{})}async function qw(t){let e=await W(),n=tt(e,t);if(!(!n||!n.archivedAt)){await Hw(t,n,"track archived");for(let r of n.boxes)In.has(r.state)||(r.state="stopped");await K()}}async function h_(t){let e=await W(),n=tt(e,t);if(!n||!n.archivedAt)return;let r=null,o=[],s="derived",a=(n.events||[]).some(l=>l.type==="said"),i=n.leadBox?await J(n.leadBox).catch(()=>null):null;if(i?.cwd&&a)try{({text:r,items:o}=await Nd(n.leadBox,i.cwd,{timeoutMs:p_})),s="lead-cli"}catch{}if(r||(r=ac(n,await j(n).catch(()=>[]))),!r)return;let c={text:r,source:s,at:Date.now()};o.length&&(c.items=o),n.beat=c,await C(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 K()}async function Gw(){let t=await W(),e=0,n=0,r=0;for(let o of t.tracks){if(!o.archivedAt||o.kind==="setup"||o.beat?.text){n++;continue}let s=ac(o,await j(o).catch(()=>[]));if(!s){r++;continue}o.beat={text:s,source:"derived",at:Date.now()},await C(o,{type:"track.summary.recorded",actor:"orchestrator",source:"orchestrator",payload:{text:s,source:"derived",backfill:!0}}).catch(()=>{}),e++}return await K(),{recorded:e,skipped:n,empty:r}}async function Ke(t,e){let n=await Tt(t,e).catch(()=>null);if(!n?.archiveOnMerge||!n.trackId)return!1;let r=await Kw(n);if(r.length){for(let o of r)await tr(o.repo,o.number,!0).catch(()=>{});return!1}try{return await Sr(n.trackId),!0}catch(o){if(/already archived/i.test(String(o?.message||o)))return!1;throw o}}async function Kw(t){return(await ae().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 Ve(t,e){return ql(t,e,"merged")}async function ql(t,e,n="merged"){let r=await Tt(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 Kw(r);if(s.length){let i=[...r.notifyPending||[],o];for(let c of s)await Tp(c.repo,c.number,i).catch(()=>{});return!1}let a=[...r.notifyPending||[],o];try{return await de(r.trackId,w_(a),{kind:"merge-notify"}),!0}catch{return!1}}var hw={merged:"merged",closed:"closed on GitHub WITHOUT merging",lost:"no longer found on GitHub (deleted, or its repo left the grant)"};function w_(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 ${hw[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 ${hw[i.outcome]??i.outcome}`).join(`
|
|
275
305
|
`),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}
|
|
276
306
|
${s}
|
|
277
|
-
${a}`}async function dl(t){if(t.archivedAt)return!1;let e=t.boxes.find(r=>r.name===t.leadBox);if(!e||An.has(e.state)||await z(e.name))return!1;let n=Sh(e.agent,e.sessionId);if(!await No(n))return ot(t,"rehydrate-failed",{box:e.name,agent:e.agent,reason:`agent "${e.agent}" not available on this box`}),!1;try{return await Qe(e.name,e.cwd,{agentCmd:n,agent:e.agent,resumeSessionId:e.sessionId,pinned:e.pinned===!0}),qo({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 Jh(t){let e=await H(),n=Z(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 dl(n),await V(),Ke(n)}async function zh(){let t=await H(),e=new Set((await Nn()).map(s=>s.name)),n=new Set((t.tracks||[]).filter(s=>!s.archivedAt).flatMap(s=>(s.boxes||[]).map(a=>a.name))),r=0,o=!1;for(let s of t.tracks||[])if(s.archivedAt)for(let a of s.boxes)e.has(a.name)&&!n.has(a.name)&&(await Jt(a.name).catch(()=>{}),ot(s,"orphan-reaped",{box:a.name,agent:a.agent}),r++,o=!0),!An.has(a.state)&&a.state!=="stopped"&&(a.state="stopped",o=!0);return o&&await V(),r}async function Xh(t){let e=await H(),n=Z(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(An.has(r.state))throw new Error("lead box is terminal \u2014 nothing to restart");if(await z(n.leadBox)){let a=await wt(n.leadBox,{lines:_t}).catch(()=>"");if(Ho(r.agent,a))throw new Error("lead is mid-turn \u2014 retry when it goes idle");await Jt(n.leadBox),r.state="stopped"}let s=await dl(n);if(await V(),!s)throw new Error("rehydrate failed \u2014 see the track's rehydrate-failed event");return Ke(n)}var Yh=(t,e,n)=>({name:t.name,agent:t.agent,role:e,state:n,cwd:t.cwd,sessionId:t.sessionId,pinned:t.pinned===!0,createdAt:Date.now()});function Zh(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 fo=new Map,wo=t=>fo.get(t)||(ae?Z(ae,t)?.handoff:null)||null;async function zs(t,e){let n=await H(),r=Z(n,t);r&&(r.handoff={...r.handoff||{},...e},await V())}function yA(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 Qh(){let t=await H(),e=!1;for(let n of t.tracks||[]){let r=n.handoff,o=yA(n);if(!o)continue;e=!0;let s=!!r.toBox&&r.toBox===r.fromBox;if(s&&o.step==="error"){let c=await z(r.fromBox).catch(()=>null),l=n.boxes.find(u=>u.name===r.fromBox);if(c&&l&&c.agent===r.toAgent&&r.toAgent!==l.agent){let u=Date.now(),d=Zh(l),f=d[d.length-1],p=f.agent,m=f.sessionId??null;f.endedAt=u,r.reason&&(f.endReason=r.reason);let h=c.agent==="claude"&&!!c.sessionId;d.push({agent:r.toAgent,sessionId:c.sessionId||null,pinned:h,startedAt:u,endedAt:null}),l.agent=r.toAgent,l.sessionId=c.sessionId||null,l.pinned=h,l.state="live",n.pendingHandoffHydrate={artifact:Qs,at:u},ot(n,"lead-handoff",{from:r.fromBox,to:r.fromBox,fromAgent:p,toAgent:r.toAgent,fromSessionId:m,toSessionId:c.sessionId||null,epoch:d.length-1,singleBox:!0,skipPermissions:r.skipPermissions,reason:r.reason??null,handoffStartedAt:r.startedAt,recovered:!0}),n.handoff={...r,step:"done",finishedAt:u,recovered:"swap adopted from live tmux tags after restart"},await q({flow:"lead-handoff",phase:"reconciled",trackId:n.id,outcome:"done",recovered:n.handoff.recovered,fromBox:r.fromBox,toBox:r.toBox}).catch(()=>{});continue}}let a=!s&&o.recovered==="rolled-back"&&r.toBox&&r.toBox!==n.leadBox?r.toBox:null,i=!s&&o.step==="done"?r.fromBox:null;n.handoff=o,await q({flow:"lead-handoff",phase:"reconciled",trackId:n.id,outcome:o.step,recovered:o.recovered,fromBox:o.fromBox,toBox:o.toBox}).catch(()=>{}),i&&await Jt(i).catch(()=>{}),a&&await Jt(a).catch(()=>{})}return e&&await V(),e}async function fl(t,e,n=!0,r=null){let o=fo.get(t);if(o&&!o.finishedAt)throw new Error("a lead handoff is already running for this Track");let s=Z(await H(),t);if(!s)throw new Error(`track "${t}" not found`);let a=await Zs(e,n),i=await z(s.leadBox);if(!i)throw new Error(`lead box "${s.leadBox}" is not running`);let c={trackId:t,step:"checkpoint",fromBox:s.leadBox,toAgent:e,skipPermissions:n,reason:vh(r),toBox:s.leadBox,startedAt:Date.now(),finishedAt:null,error:null};return fo.set(t,c),await zs(t,{step:c.step,fromBox:c.fromBox,toAgent:e,toBox:c.toBox,skipPermissions:n,startedAt:c.startedAt,finishedAt:null,error:null}),bA(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 zs(t,{step:c.step,error:c.error,finishedAt:c.finishedAt}).catch(()=>{}),q({flow:"lead-handoff",phase:"failed",trackId:t,step:c.step,error:c.error,fromBox:c.fromBox,toAgent:c.toAgent,toBox:c.toBox}).catch(()=>{})}),q({flow:"lead-handoff",phase:"started",trackId:t,fromBox:c.fromBox,toAgent:e,cwd:i.cwd}).catch(()=>{}),c}async function bA(t,e,n,r,o){let s=async(E,R={})=>{t.step=E,console.log(`[lead-handoff ${t.trackId}] step \u2192 ${E}`),await zs(t.trackId,{step:E,toBox:t.toBox}),await q({flow:"lead-handoff",phase:E,trackId:t.trackId,fromBox:t.fromBox,toBox:t.toBox,toAgent:o,sessionName:R.sessionName,...R})};console.log(`[lead-handoff ${t.trackId}] ${t.fromBox}: ${n.agent} \u2192 ${o} IN PLACE`);let a=await H(),i=Z(a,t.trackId)?.boxes.find(E=>E.name===t.fromBox)||null,c=i?.agent||n.agent,l=i?.sessionId??n.sessionId??null,u=i?.pinned===!0;await s("checkpoint",{sessionName:t.fromBox}),await Vu(t.fromBox),await s("swap",{cwd:n.cwd,agentCmd:r});let d=await Ni(t.fromBox,{agentCmd:r,agent:o});await s("hydrate",{sessionName:t.fromBox}),await Te({cwd:n.cwd,sessionId:d.sessionId,sessionName:t.fromBox,agent:o,agentCmd:ho(o)});let f=await wt(t.fromBox,{lines:_t}).catch(()=>"");if(!Wo(o,f)){await s("rollback",{sessionName:t.fromBox,fromAgent:c});let E=Sh(c,l,{skipPermissions:t.skipPermissions});await Ni(t.fromBox,{agentCmd:E,agent:c,resumeSessionId:l,pinned:u});let R=await H(),st=Z(R,t.trackId);if(st){let I=st.boxes.find(L=>L.name===t.fromBox);I&&(I.state="live"),ot(st,"lead-handoff-failed",{box:t.fromBox,fromAgent:c,toAgent:o,reason:t.reason,error:`incoming ${o} CLI never drew its composer`,rolledBack:!0,handoffStartedAt:t.startedAt}),await V()}throw new Error(`incoming ${o} CLI never drew its composer \u2014 rolled back to ${c} (--resume, context intact)`)}await s("commit");let p=await H(),m=Z(p,t.trackId);if(!m)throw new Error(`track "${t.trackId}" not found during handoff commit \u2014 was it deleted or archived mid-flight?`);let h=m.boxes.find(E=>E.name===t.fromBox);if(!h)throw new Error(`lead box row "${t.fromBox}" missing during handoff commit`);let b=Date.now(),x=Zh(h),v=x[x.length-1];v.endedAt=b,t.reason&&(v.endReason=t.reason),x.push({agent:o,sessionId:d.sessionId,pinned:d.pinned,startedAt:b,endedAt:null}),h.agent=o,h.sessionId=d.sessionId,h.pinned=d.pinned,h.state="live",h.role="lead",m.pendingHandoffHydrate={artifact:Qs,at:b},ot(m,"lead-handoff",{from:t.fromBox,to:t.fromBox,fromAgent:c,toAgent:o,fromSessionId:l,toSessionId:d.sessionId,epoch:x.length-1,singleBox:!0,forced:d.forced||void 0,skipPermissions:t.skipPermissions,reason:t.reason,handoffStartedAt:t.startedAt}),await V(),await s("done",{sessionName:t.fromBox}),await zs(t.trackId,{finishedAt:Date.now(),error:null}),t.finishedAt=Date.now()}var kA=Number(process.env.BOSTRAT_SUBTASK_TIMEOUT_MS)||18e5,xA=Number(process.env.BOSTRAT_SUBTASK_POLL_MS)||3e3,wr=j.join(Ge,"worktrees"),mr=[co,Js],Kt=new Map;var br=t=>{let e=new Map([...Kt.values()].filter(r=>r.trackId===t).map(r=>[r.id,r])),n=ae?Z(ae,t):null;if(n?.subtasks)for(let[r,o]of Object.entries(n.subtasks))e.has(r)||e.set(r,o);return[...e.values()].sort((r,o)=>r.startedAt-o.startedAt)},pl=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 SA(t,e,n){let r=await H(),o=Z(r,t);o&&(o.subtasks=o.subtasks||{},o.subtasks[e]={...o.subtasks[e]||{},...n},await V())}var gr=t=>SA(t.trackId,t.id,pl(t));async function hr(t,e){t.step=e,await gr(t)}function vA(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 AA(t){return!!t&&!t.finishedAt&&!t.canceled&&t.step==="working"&&!!t.box&&!!t.dir&&!!t.base&&!!t.branch}async function TA(t,e,n){if(!AA(n)||Kt.has(e)||!await z(n.box).catch(()=>null))return!1;try{await D.access(n.dir)}catch{return!1}let o=await z(t.leadBox).catch(()=>null);if(!o?.cwd)return!1;let s={...n,id:e,leadBox:t.leadBox,step:"working",canceled:!1,forceMerge:!1};return Kt.set(e,s),ow(s,o).catch(a=>nw(s,a)),!0}async function tw(){let t=await H(),e=!1;for(let n of t.tracks||[]){for(let[r,o]of Object.entries(n.subtasks||{})){if(Kt.has(r))continue;if(await TA(n,r,o)){e=!0,ot(n,"subtask-resumed",{box:o.box,agent:o.agent,task:o.task,subtaskId:r,resumedStep:o.step}),await q({flow:"subtask",phase:"resumed",trackId:n.id,subtaskId:r,box:o.box,branch:o.branch}).catch(()=>{});continue}let s=o.step,a=vA(o);if(!a)continue;e=!0,n.subtasks[r]=a;let i=n.boxes.find(c=>c.subtaskId===r);i&&!An.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 q({flow:"subtask",phase:"reconciled",trackId:n.id,subtaskId:r,outcome:a.step,recovered:a.recovered,box:o.box,branch:o.branch}).catch(()=>{}),await kh(n,o.box,o.dir)}for(let r of n.boxes){if(r.role!=="subtask"||An.has(r.state)||Kt.has(r.subtaskId)||n.subtasks?.[r.subtaskId])continue;e=!0,r.state="canceled";let o=r.subtaskId?j.join(wr,`${n.id}-${r.subtaskId}`):null;await q({flow:"subtask",phase:"reaped-legacy-orphan",trackId:n.id,subtaskId:r.subtaskId,box:r.name}).catch(()=>{}),await kh(n,r.name,o)}}return e=await _A(t)||e,e&&await V(),e}async function kh(t,e,n){if(e&&await Jt(e).catch(()=>{}),!n)return;if(t.workspace?.kind==="multi"){for(let o of $t(t.workspace)){let s=j.join(n,o.name);await Ft(o.base,s).then(()=>!0,()=>!1)||await Ft(o.dir,s).catch(()=>{})}await D.rm(n,{recursive:!0,force:!0}).catch(()=>{});return}let r=await z(t.leadBox).catch(()=>null);r?.cwd&&await Ft(r.cwd,n).catch(()=>{})}async function _A(t){let e;try{e=await D.readdir(wr)}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 Kt.values())o.dir&&n.add(j.basename(o.dir));let r=!1;for(let o of e){if(n.has(o))continue;let s=j.join(wr,o),a=(t.tracks||[]).find(i=>o.startsWith(`${i.id}-`));if(a?.workspace?.kind==="multi"){for(let i of $t(a.workspace))await Ft(i.base,j.join(s,i.name)).catch(()=>{});await D.rm(s,{recursive:!0,force:!0}).catch(()=>{})}else{let i=a?await z(a.leadBox).catch(()=>null):null;i?.cwd?await Ft(i.cwd,s).catch(()=>{}):await D.rm(s,{recursive:!0,force:!0}).catch(()=>{})}await q({flow:"subtask",phase:"swept-orphan-worktree",dir:s}).catch(()=>{}),r=!0}return r}var ml=t=>t.trim().replace(/\s+/g,"-").replace(/[^A-Za-z0-9._-]/g,"").toLowerCase()||"track",po=class extends Error{};async function ew(t,{agent:e,task:n,skipPermissions:r=!0,reason:o=null}={}){if(typeof n!="string"||!ar(n))throw new Error("task (string) required");let s=await Zs(e,r),a=Z(await H(),t);if(!a)throw new Error(`track "${t}" not found`);let i=await z(a.leadBox);if(!i)throw new Error(`lead box "${a.leadBox}" is not running`);if(!i.cwd)throw new Error("can't resolve the lead's working directory");let c=a.workspace?.kind==="multi",l=null;if(c){let f=$t(a.workspace);if(!f.length)throw new Error("this multi-repo Track has no tagged repos to delegate into");for(let p of f)if(!p?.dir||!await _s(p.dir))throw new Error(`tagged checkout "${p?.name??"?"}" is missing from the lead workspace (${p?.dir??"no dir"})`);l=f.map(p=>({name:p.name,leadDir:p.dir,base:null}))}else if(!await _s(i.cwd))throw new Error(`subtask delegation needs the lead's workspace to be a git repo (got ${i.cwd})`);let u=mo().slice(0,8),d={id:u,trackId:t,trackName:a.name,agent:e,task:ar(n),skipPermissions:r,reason:vh(o),step:"snapshot",leadBox:a.leadBox,box:null,branch:`bostrat/${ml(a.name)}-${u}`,base:null,repos:l,dir:j.join(wr,`${t}-${u}`),stat:null,conflicts:[],applied:null,startedAt:Date.now(),finishedAt:null,error:null,canceled:!1,forceMerge:!1};return Kt.set(u,d),await gr(d),EA(d,i,s).catch(f=>nw(d,f)),d}async function nw(t,e){t.failedStep=t.step,t.error=e instanceof po?"canceled":e?.message||String(e),t.step=e instanceof po?"canceled":"error",t.finishedAt=Date.now(),await wl(t).catch(()=>{}),await gr(t).catch(()=>{}),await OA(t).catch(()=>{})}function hl(t){let e=Kt.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 rw(t){let e=Kt.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 EA(t,e,n){if(await hr(t,"snapshot"),t.repos){for(let s of t.repos)s.base=await wc(s.leadDir,`bostrat: snapshot for subtask ${t.id}`);await gr(t)}else t.base=await wc(e.cwd,`bostrat: snapshot for subtask ${t.id}`);if(await hr(t,"spawn"),await D.mkdir(wr,{recursive:!0}),t.repos){await D.mkdir(t.dir,{recursive:!0});for(let a of t.repos)await ir(a.leadDir,j.join(t.dir,a.name),t.branch,a.base);let s=$t((Z(await H(),t.trackId)||{}).workspace);await ea(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 ir(e.cwd,t.dir,t.branch,t.base),await rl(t.dir);await IA(t,e);let r=t.trackName.slice(0,38).trim(),o=await Qe(`${r} ${t.agent} sub ${t.id}`,t.dir,{agentCmd:n,agent:t.agent});t.box=o.name,await RA(t,o),await Ah(t.trackId,o.name,jv(t.task,!!t.repos),{kind:"subtask"}),await ow(t,e)}async function ow(t,e){await hr(t,"working");let n=j.join(t.dir,Js);if(await MA(n,t),t.canceled)throw new po;if(await hr(t,"merge"),t.repos){let o={files:0,insertions:0,deletions:0},s=[],a=[],i=!0;for(let c of t.repos){let l=j.join(t.dir,c.name);await gc(l,`bostrat: subtask ${t.id} result (auto-committed)`),c.stat=await xc(c.leadDir,c.base,t.branch,mr),c.files=await Sc(c.leadDir,c.base,t.branch,mr);let u=await vc(c.leadDir,c.base,t.branch,mr);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(d=>j.join(c.name,d))),a.push(...c.conflicts.map(d=>d.startsWith("patch did not apply cleanly")?`${c.name}: ${d}`:j.join(c.name,d))),u.applied||(i=!1)}t.stat=o,t.files=s,t.conflicts=a,t.applied=i}else{await gc(t.dir,`bostrat: subtask ${t.id} result (auto-committed)`),t.stat=await xc(e.cwd,t.base,t.branch,mr),t.files=await Sc(e.cwd,t.base,t.branch,mr);let o=await vc(e.cwd,t.base,t.branch,mr);t.applied=o.applied,t.conflicts=o.conflicts}await gr(t);let r=await CA(t,e,n);await hr(t,"resume"),await Ah(t.trackId,t.leadBox,Lv(t.task,t.stat,t.conflicts,r),{kind:"subtask-resume"}),await hr(t,"reap"),await wl(t),t.step="done",t.finishedAt=Date.now(),await $A(t),await gr(t)}async function IA(t,e){let n=j.join(t.dir,j.dirname(co));await D.mkdir(n,{recursive:!0});let r="";try{r=await mu(e.cwd,e.sessionId)}catch{}if(!r)try{r=await D.readFile(j.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}
|
|
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}
|
|
278
308
|
|
|
279
309
|
**Delegated task:** ${t.task}
|
|
280
310
|
|
|
@@ -285,43 +315,47 @@ ${a}`}async function dl(t){if(t.archivedAt)return!1;let e=t.boxes.find(r=>r.name
|
|
|
285
315
|
## Conversation so far (lead \u2192 you)
|
|
286
316
|
|
|
287
317
|
${r}
|
|
288
|
-
`;await
|
|
289
|
-
${n}`}},{type:"section",text:{type:"mrkdwn",text:r}},{type:"context",elements:[{type:"mrkdwn",text:`<${
|
|
318
|
+
`;await F.writeFile(U.join(t.dir,To),o)}async function I_(t,e){let n=await W(),r=tt(n,t.trackId);r&&(r.boxes.push({name:e.name,agent:e.agent,role:"subtask",state:"live",cwd:e.cwd,sessionId:e.sessionId,hookTokenHash:Nr(e.hookToken),createdAt:Date.now(),subtaskId:t.id,branch:t.branch,task:t.task}),ot(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]||{},...Vl(t)},await K())}async function R_(t){let e=await W(),n=tt(e,t.trackId);if(!n)return;let r=n.boxes.find(o=>o.subtaskId===t.id);r&&(r.state=t.conflicts.length?"conflict":"merged",r.stat=t.stat),ot(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 K()}async function $_(t){let e=await W(),n=tt(e,t.trackId);if(!n)return;let r=t.canceled||t.error==="canceled",o=n.boxes.find(s=>s.subtaskId===t.id);o&&(o.state=r?"canceled":"failed"),ot(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]||{},...Vl(t)},await K()}async function O_(t,e,n){let r=`.bostrat/handoff/subtask-${t.id}.md`;try{let o=U.join(e.cwd,r);await F.mkdir(U.dirname(o),{recursive:!0}),await F.copyFile(n,o)}catch{}return r}async function Xl(t){if(t.box&&await Xt(t.box).catch(()=>{}),t.repos&&t.dir){for(let e of t.repos)await Ut(e.leadDir,U.join(t.dir,e.name)).catch(()=>{});await F.rm(t.dir,{recursive:!0,force:!0}).catch(()=>{})}else if(t.dir){let n=(await J(t.leadBox).catch(()=>null))?.cwd;n&&await Ut(n,t.dir).catch(()=>{})}}async function C_(t,e){let n=(e.startedAt||Date.now())+b_;for(;;){if(e.canceled||e.forceMerge)return;try{await F.access(t);return}catch{}if(Date.now()>n)throw new Error("subtask timed out without signaling completion (no result digest written)");await Ar(k_)}}function Ar(t){return new Promise(e=>setTimeout(e,t))}import Mo from"node:fs/promises";import Ra from"node:path";import D_ from"node:os";import M_ from"ws";var og=()=>process.env.BOSTRAT_SLACK_API_BASE||"https://slack.com/api";async function Ea(t,e,n={}){let r=await fetch(`${og()}/${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 Yl=(t,e)=>Ea("chat.postMessage",t,e),Tr=(t,e)=>Ea("chat.update",t,e);async function Ct(t,e){await fetch(t,{method:"POST",headers:{"content-type":"application/json; charset=utf-8"},body:JSON.stringify(e)}).catch(()=>{})}async function N_(t,e,n={}){let r=await fetch(`${og()}/${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 sg(t,{filename:e,buffer:n,title:r,channelId:o,threadTs:s,initialComment:a}){let{upload_url:i,file_id:c}=await N_("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 Ea("files.completeUploadExternal",t,{files:[{id:c,title:r}],channel_id:o,thread_ts:s,...a?{initial_comment:a}:{}})}var P_=2200;function ag({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 Ea("apps.connections.open",u))}catch(u){return n(`connections.open failed: ${u?.message||u}`),c()}o=new M_(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()},P_);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 lg=()=>process.env.BOSTRAT_STATE_DIR||Ra.join(process.env.HOME||D_.tmpdir(),".bostrat"),nu=()=>Ra.join(lg(),"slack.json"),Ia=null,Zl=null,Ql=Promise.resolve();async function ou(){return Ia||(Zl??=Mo.readFile(nu(),"utf8").then(t=>JSON.parse(t)?.threads??{}).catch(()=>({})).then(t=>Ia=t).finally(()=>Zl=null),Zl)}async function ig(){let t=JSON.stringify({threads:Ia},null,2),e=`${nu()}.${process.pid}.tmp`;await Mo.mkdir(lg(),{recursive:!0}),await Mo.writeFile(e,t),await Mo.rename(e,nu())}async function Te(){Ql=Ql.then(ig,ig),await Ql.catch(()=>{})}async function su(t){return(await ou())[t]??null}async function j_(t,e){let n=await ou();for(let[r,o]of Object.entries(n))if(o.channel===t&&o.ts===e)return{trackId:r,rec:o};return null}var z=t=>String(t??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");function No(t,e){let n=String(t??"").trim();return n.length<=e?n:`${n.slice(0,e-1).trimEnd()}\u2026`}function L_(t,e=500){let n=String(t??"").trim().split(/\n\s*\n/)[0]??"";return No(n.replace(/\n+/g," "),e)}function ug(t){let e=`*${z(t.name)}*${t.archivedAt?" \xB7 _archived_":""}`,n=`${z(t.repoLabel)} \xB7 \`${z(t.branch||"?")}\` \xB7 lead *${z(t.lead)}*`,r=t.kickoff?`> ${z(No(t.kickoff,300))}`:"_no kickoff yet \u2014 reply in this thread to steer the lead_";return[{type:"section",text:{type:"mrkdwn",text:`${e}
|
|
319
|
+
${n}`}},{type:"section",text:{type:"mrkdwn",text:r}},{type:"context",elements:[{type:"mrkdwn",text:`<${Bs(t.trackId)}|open the Track> \xB7 reachable on the operator's private network only`}]}]}async function ru(t,e){await Tr(t.botToken,{channel:e.channel,ts:e.ts,text:`Track: ${e.name}`,blocks:ug(e)})}async function $n(t,e,n,r=void 0){return Yl(t.botToken,{channel:e.channel,thread_ts:e.ts,text:n,...r?{blocks:r}:{},unfurl_links:!1})}var cg=new Set;function Rn(t){cg.add(t),t.catch(()=>{}).finally(()=>cg.delete(t))}var ut=t=>({response_type:"ephemeral",text:t}),F_="*/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._",U_=["claude","cursor","codex"];function tu(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 eu=(t,e)=>ut(`No live Track matches \u201C${z(e)}\u201D. Live: ${t.map(n=>`\`${z(n.name)}\``).join(" \xB7 ")||"none"}`);async function dg(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 Ct(o,{...ut(`At capacity \u2014 ${a.length} live Tracks (limit ${e.maxConcurrent}). Archive or stop something first.`),replace_original:!0});return}if(!e.channel){await Ct(o,{...ut("No designated channel configured \u2014 set `slack.channel` in integrations.json."),replace_original:!0});return}let i=await s.createTrack({name:n,...r?{repo:r}:{}}),c=i.workspace||{},l={trackId:i.id,channel:e.channel,name:i.name,branch:c.branch||null,repoLabel:c.repo||(c.base?Ra.basename(c.base):"fresh workspace"),lead:"claude"},u=await Yl(e.botToken,{channel:e.channel,text:`Track started: ${l.name}`,blocks:ug(l),unfurl_links:!1});l.ts=u.ts,(await ou())[i.id]=l,await Te(),await Ct(o,{...ut(`\u2713 *${z(l.name)}* started \u2014 root message posted.`),replace_original:!0})}catch(a){await Ct(o,{...ut(`Start failed: ${a?.message||a}`),replace_original:!0})}}async function H_(t,e,{track:n,responseUrl:r}){let o=t.deps;try{await o.archiveTrack(n.id);let s=await su(n.id);s&&(s.archivedAt=Date.now(),await Te(),await ru(e,s).catch(()=>{})),await Ct(r,{...ut(`\u2713 *${z(n.name)}* archived.`),replace_original:!0})}catch(s){await Ct(r,{...ut(`Archive failed: ${s?.message||s}`),replace_original:!0})}}async function W_(t,e,{track:n,agent:r,responseUrl:o}){try{await t.deps.startLeadHandoff(n.id,r),await Ct(o,{...ut(`\u2713 Handing *${z(n.name)}* to ${z(r)} \u2014 the root message updates when the baton lands.`),replace_original:!0})}catch(s){await Ct(o,{...ut(`Handoff failed: ${s?.message||s}`),replace_original:!0})}}async function q_(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 Ct(r,{...ut(`\u2713 Stopped *${z(n.name)}*${s.length?` (+ ${s.length} subtask${s.length>1?"s":""} canceled)`:""} \u2014 Track parked, resume from the Track page.`),replace_original:!0})}catch(s){await Ct(r,{...ut(`Stop failed: ${s?.message||s}`),replace_original:!0})}}function G_(t,e){let n=[...e.slice(0,99).map(r=>({text:{type:"plain_text",text:No(`${r.name} (${r.branch})`,75)},value:r.path})),{text:{type:"plain_text",text:"fresh workspace"},value:"__fresh__"}];return[{type:"section",block_id:JSON.stringify({name:t}),text:{type:"mrkdwn",text:`Repo for *${z(t)}*:`},accessory:{type:"static_select",action_id:"bostrat-start-repo",placeholder:{type:"plain_text",text:"Choose a repo"},options:n}}]}async function K_(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?Ra.basename(a.base):"fresh"),c=[...s.boxes||[]].reverse().find(u=>u.role==="lead")?.agent||"?",l=s.prompted?"\u26A0 needs an answer":s.working?"working":"idle";return`\u2022 *${z(s.name)}* \u2014 ${z(i)}${a.branch?` \`${z(a.branch)}\``:""} \xB7 lead ${z(c)} \xB7 ${l}`}),o=e.length-n.length;return r.join(`
|
|
290
320
|
`)+(o?`
|
|
291
|
-
_${o} archived_`:"")}async function
|
|
292
|
-
\u2713 *${
|
|
293
|
-
\u2713 _answered_`}}]})}async function
|
|
294
|
-
_runs on the Track's branch; reachable on the operator's private network only_`}}]);return}case"mock.state.changed":return
|
|
295
|
-
`)){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 dT(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 fT(t,e){return t?.transcript_path?t.transcript_path:e?.cwd&&e?.sessionId?ce(e.cwd,e.sessionId):e?.cwd?Na(e.cwd):null}async function mw(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 pT(r,o,t,i);break;default:break}return a}async function pT(t,e,n,r){let o=[],s=fT(n,e);if(s)try{o=uT(await lT.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=dT(await F(t)),c=o.filter(u=>!i.has(u.messageId));if(c.length){let u=new Map;for(let d of c){let f=d.model||"unknown",p=u.get(f)||{tokens:{input:0,output:0,cacheWrite:0,cacheRead:0},messageIds:[]};for(let m of Object.keys(p.tokens))p.tokens[m]+=d.tokens[m];p.messageIds.push(d.messageId),u.set(f,p)}for(let[d,f]of u)await r({type:"cost.recorded",actor:"system",source:"transcript",payload:{model:d,usd:zt(d,f.tokens),tokens:f.tokens,messageId:f.messageIds[f.messageIds.length-1],messageIds:f.messageIds,apiCalls:f.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 wT from"node:fs";import _l from"node:fs/promises";import gT from"node:http";import xr from"node:path";import ia from"node:os";import{createRequire as yT}from"node:module";import El from"ws";import mT from"node:path";import hT from"node:os";var qM=process.env.BOSTRAT_STATE_DIR||mT.join(process.env.HOME||hT.tmpdir(),".bostrat");function hw(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 ww(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 aa=(()=>{try{return yT(import.meta.url)("../package.json").version||null}catch{return null}})(),bT=process.env.BOSTRAT_STATE_DIR||xr.join(process.env.HOME||ia.tmpdir(),".bostrat");function kT(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 xT({home:t=process.env.HOME||ia.homedir(),env:e=process.env}={}){let n=r=>{try{return wT.statSync(r),!0}catch{return!1}};return{claude:n(xr.join(t,".claude",".credentials.json")),codex:n(xr.join(e.CODEX_HOME||xr.join(t,".codex"),"auth.json"))}}var At=1,gw=1e3,ST=3e4,yw=100,vT=2e3,AT=25e3;function bw({url:t,token:e,nodeId:n=ia.hostname(),name:r=ia.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=ww(t);let c=a.replace(/^http/,"ws"),l=new Map,u=new Map,d=xr.join(bT,"plane-link","cursor.json"),f={},p=new Map,m=null,h=!1,b=!1,x=gw,v=null,E=null,R=null,st=!1;async function I(){try{f=JSON.parse(await _l.readFile(d,"utf8"))}catch{f={}}}function L(){st=!0,!E&&(E=setTimeout(async()=>{if(E=null,!!st){st=!1;try{await _l.mkdir(xr.dirname(d),{recursive:!0}),await _l.writeFile(d,JSON.stringify(f))}catch(B){i.error("[plane-link] cursor flush failed:",B?.message||B)}}},vT),E.unref?.())}function T(B){return!m||m.readyState!==El.OPEN?!1:(m.send(JSON.stringify(B)),!0)}function U(B){for(let{trackId:$,event:_}of B)p.set(_.id,{trackId:$,seq:_.seq});return T({v:At,ch:"events",type:"append",events:B})}async function J(){let B;try{B=await o()}catch($){i.error("[plane-link] backfill listTracks failed:",$?.message||$);return}T({v:At,ch:"ctl",type:"tracks",ids:B.map($=>$.id).filter($=>typeof $=="string")});for(let $ of B){if(!h)return;let _;try{_=await F($)}catch{continue}let M=f[$.id]??0,at=_.filter(Q=>Q.seq>M&&typeof Q.id=="string");for(let Q=0;Q<at.length;Q+=yw){let ht=at.slice(Q,Q+yw).map(Tt=>({trackId:$.id,event:Tt}));if(!U(ht))return}}}function g(B){!h||!B?.trackId||typeof B.id!="string"||U([{trackId:B.trackId,event:B}])}let w=new Map;async function y(B){let{reqId:$,method:_,path:M,headers:at={},body:Q}=B,ht=Object.fromEntries(Object.entries(at).filter(([,Dt])=>Dt!=null)),Tt=new AbortController;w.set($,Tt);try{if(B.host){await A(B,ht,Tt);return}let Dt=await fetch(a+M,{method:_,headers:ht,body:Q?Buffer.from(Q,"base64"):void 0,signal:Tt.signal}),Ht=Object.fromEntries(Dt.headers);for(let k of["connection","keep-alive","transfer-encoding","upgrade","content-encoding","content-length"])delete Ht[k];if(Ht["content-type"]||(Ht["content-type"]="application/octet-stream"),T({v:At,ch:"control",type:"http_head",reqId:$,status:Dt.status,headers:Ht}),Dt.body){for await(let k of Dt.body)if(!T({v:At,ch:"control",type:"http_data",reqId:$,data:Buffer.from(k).toString("base64")}))break}T({v:At,ch:"control",type:"http_end",reqId:$,error:null})}finally{w.delete($)}}function A(B,$,_){let{reqId:M,method:at,path:Q,body:ht}=B,Tt=new URL(a);return new Promise(Dt=>{let Ht=gT.request({host:Tt.hostname,port:Tt.port,method:at,path:Q,headers:{...$,host:B.host},signal:_.signal},k=>{let{connection:W,"keep-alive":En,"transfer-encoding":Oo,upgrade:In,...xa}=k.headers;T({v:At,ch:"control",type:"http_head",reqId:M,status:k.statusCode||502,headers:xa}),k.on("data",Rg=>{T({v:At,ch:"control",type:"http_data",reqId:M,data:Buffer.from(Rg).toString("base64")})||k.destroy()}),k.on("end",()=>{T({v:At,ch:"control",type:"http_end",reqId:M,error:null}),Dt()}),k.on("error",()=>{T({v:At,ch:"control",type:"http_end",reqId:M,error:"view upstream error"}),Dt()})});Ht.on("error",k=>{T({v:At,ch:"control",type:"http_end",reqId:M,error:k?.message||"view request failed"}),Dt()}),ht&&Ht.write(Buffer.from(ht,"base64")),Ht.end()})}function K(){if(b)return;m=new El(t,{headers:{authorization:`Bearer ${e}`}}),m.on("open",async()=>{let $=null;try{$=await s?.()||null}catch{$=null}T({v:At,ch:"ctl",type:"hello",nodeId:n,name:r,platform:process.platform,version:aa,providers:xT(),...$?{agents:$}:{}})}),m.on("message",$=>{let _;try{_=JSON.parse($)}catch{return}if(_.ch==="ctl"&&_.type==="hello_ok"){h=!0,x=gw,i.log(`[plane-link] connected to plane as ${n}`),_.minNodeVersion&&aa&&kT(aa,_.minNodeVersion)&&i.error(`[plane-link] this node is bostrat-node ${aa}; the plane asks for \u2265${_.minNodeVersion} \u2014 update with: npm install -g bostrat-node`),R&&clearInterval(R),R=setInterval(()=>T({v:At,ch:"ctl",type:"ping"}),AT),R.unref?.(),J().catch(M=>i.error("[plane-link] backfill failed:",M?.message||M));return}if(_.ch==="events"&&_.type==="ack"&&Array.isArray(_.ids)){for(let M of _.ids){let at=p.get(M);at&&(p.delete(M),(f[at.trackId]??0)<at.seq&&(f[at.trackId]=at.seq))}L();return}if(_.ch==="control"&&_.type==="http_open"){y(_).catch(M=>T({v:At,ch:"control",type:"http_end",reqId:_.reqId,error:M?.message||"tunnel error"}));return}if(_.ch==="control"&&_.type==="http_cancel"){w.get(_.reqId)?.abort();return}if(_.ch==="attach"&&_.type==="open"){let M=new El(c+_.path,Array.isArray(_.protocols)?_.protocols:[],_.host?{headers:{host:_.host}}:void 0);l.set(_.streamId,M),u.set(_.streamId,[]),M.on("open",()=>{let Q=u.get(_.streamId)||[];u.delete(_.streamId);for(let{buf:ht,bin:Tt}of Q)M.send(ht,{binary:Tt})}),M.on("message",(Q,ht)=>{let Tt=ht?Q:Buffer.from(String(Q));T({v:At,ch:"attach",type:"data",streamId:_.streamId,data:Tt.toString("base64"),...ht?{bin:!0}:{}})});let at=()=>{u.delete(_.streamId),l.delete(_.streamId)&&T({v:At,ch:"attach",type:"close",streamId:_.streamId})};M.on("close",at),M.on("error",at);return}if(_.ch==="attach"&&_.type==="data"){let M=l.get(_.streamId);if(!M)return;let at=Buffer.from(_.data,"base64"),Q=_.bin===!0,ht=u.get(_.streamId);ht?ht.push({buf:at,bin:Q}):M.send(at,{binary:Q});return}if(_.ch==="attach"&&_.type==="close"){let M=l.get(_.streamId);l.delete(_.streamId),u.delete(_.streamId),M?.close();return}});let B=()=>{h&&i.log("[plane-link] disconnected from plane"),h=!1,R&&(clearInterval(R),R=null),p.clear();for(let $ of l.values())$.close();l.clear(),u.clear();for(let $ of w.values())$.abort();w.clear(),!(b||v)&&(v=setTimeout(()=>{v=null,K()},x),v.unref?.(),x=Math.min(x*2,ST))};m.on("close",B),m.on("error",B)}return jt.on("event",g),I().then(K),{stop:()=>{b=!0,jt.off("event",g),v&&clearTimeout(v),E&&clearTimeout(E),R&&clearInterval(R),m?.close()},status:()=>({connected:h,nodeId:n,cursor:{...f},inflight:p.size})}}function TT(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 kw=()=>({input:0,output:0,cacheRead:0,cacheCreation:0}),xw=(t,e)=>{for(let n of Object.keys(t))t[n]+=e?.[n]||0},Il=t=>Math.round(t*1e4)/1e4;async function Sw(t){let e=[],n={},r={},o=0,s=0,a=0,i=0,c=new Set,l=kw(),u=0,d=[];for(let f of t||[]){let p=await F(f),m=_e(p,{boxLabels:TT(f)});if(m.turns===0){f.archivedAt?u++:d.push(f.name||f.id);continue}o+=m.totalUsd,s+=m.turns,a+=m.repricedTurns||0,i+=m.unpricedTurns||0;for(let h of m.unpricedModels||[])c.add(h);xw(l,m.totalTokens);for(let[h,b]of Object.entries(m.byModel))n[h]||={usd:0,events:0,tokens:kw()},n[h].usd=Il(n[h].usd+b.usd),n[h].events+=b.events,xw(n[h].tokens,b.tokens);for(let[h,b]of Object.entries(m.byProvider))r[h]||={usd:0,events:0},r[h].usd=Il(r[h].usd+b.usd),r[h].events+=b.events;e.push({id:f.id,name:f.name,usd:m.totalUsd,rollup:m})}return e.sort((f,p)=>p.usd-f.usd),{generatedAt:new Date().toISOString(),pricingBasis:"api-equivalent (subscription usage priced at public API rates)",fleet:{totalUsd:Il(o),turns:s,repricedTurns:a,unpricedTurns:i,unpricedModels:[...c],totalTokens:l,byModel:n,byProvider:r,tracksWithCost:e.length,tracksSilent:u+d.length,tracksSilentArchived:u,tracksSilentLive:d},tracks:e}}var bo=t=>`$${t.toFixed(4)}`,Sr=(t,e)=>String(t).padEnd(e);function vw(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 ${bo(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(` ${Sr(a,26)} ${Sr(bo(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(` ${Sr(a.name||a.id,30)} ${bo(a.usd)}`);let i=Object.values(a.rollup.byBox).sort((c,l)=>l.usd-c.usd);for(let c of i)if(r.push(` ${Sr(c.label,40)} ${Sr(bo(c.usd),10)} ${c.models.join(",")||"?"}`),c.epochs)for(let[l,u]of Object.entries(c.epochs).sort((d,f)=>Number(d[0])-Number(f[0])))r.push(` \u21B3 tenure ${l} (${u.provider||"?"})${" ".repeat(Math.max(1,24-String(u.provider||"?").length))}${Sr(bo(u.usd),10)} ${u.models.join(",")||"?"}`)}return r.join(`
|
|
296
|
-
`)}import PT from"node:fs/promises";import NT from"node:path";var _T=["bug-fix","orchestration","design","ops-infra","docs-writing","research","feature-build"],ET={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 ca(t){let e=String(t||"").toLowerCase();if(!e.trim())return null;let n=null;for(let r of _T){let o=0;for(let s of ET[r])s.test(e)&&o++;o>0&&(!n||o>n.score)&&(n={category:r,score:o})}return n}var IT=new Set(["Read","Grep","Glob","LS","WebSearch","WebFetch","NotebookRead"]),RT=new Set(["Edit","Write","MultiEdit","NotebookEdit"]);function Aw(t){let e=0,n=0;for(let[r,o]of Object.entries(t||{}))IT.has(r)&&(e+=o||0),RT.has(r)&&(n+=o||0);return e>=5&&n===0?{category:"research",score:1}:n>=3?{category:"feature-build",score:1}:null}function Rl(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 la=t=>t.payload?.ts??t.ts,$T=12e4,Ve=t=>Math.round(t*1e4)/1e4,OT=()=>({usd:0,activeMs:0,turns:0,toolCalls:0,models:{},providers:{},byTrack:new Map});function CT(t,{since:e}){let{track:n,events:r,brief:o}=t,s=v=>la(v)>=e,a=ca([Rl(n?.name),Rl(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 E=ca(v.payload.task);E&&i.set(v.boxId,E)}let c=new Map,l=v=>{let E=v||"unattributed";return c.has(E)||c.set(E,{segments:[u(null)],lastTs:null,provider:null}),c.get(E)},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}),d=[...r].sort((v,E)=>la(v)-la(E)),f=0,p=0,m=0,h={turns:0};for(let v of d){let E=l(v.boxId),R=E.segments[E.segments.length-1],st=la(v);switch(E.lastTs!=null&&st>E.lastTs&&s(v)&&(R.activeMs+=Math.min(st-E.lastTs,$T)),E.lastTs=st,v.provider&&(E.provider=v.provider),v.type){case"turn.user":{let I=v.payload?.text;if(v.source==="hook"||!String(I||"").trim())break;let L=u(ca(I));E.segments.push(L),s(v)&&(L.turns+=1);break}case"tool.called":{if(v.source==="transcript")break;let I=v.payload?.tool;I&&(R.tools[I]=(R.tools[I]||0)+1),s(v)&&(R.toolCalls+=1);break}case"cost.recorded":{if(!s(v))break;let I=v.payload||{},L={input:I.tokens?.input||0,output:I.tokens?.output||0,cacheRead:I.tokens?.cacheRead||0,cacheWrite:I.tokens?.cacheWrite??I.tokens?.cacheCreation??0},T=Number(I.usd)||0;!T&&I.tokens&&(T=zt(I.model,L)),!T&&I.tokens&&!Xo(I.model)&&(h.turns+=1),R.usd+=T,R.tokens.input+=L.input,R.tokens.output+=L.output,R.tokens.cacheRead+=L.cacheRead,R.tokens.cacheWrite+=L.cacheWrite;let U=I.model||"unknown";R.models[U]=(R.models[U]||0)+T,v.provider&&(R.provider=v.provider);break}case"handoff.done":s(v)&&(f+=1);break;case"subtask.merged":s(v)&&(p+=1);break;case"error":s(v)&&v.payload?.kind==="subtask-failed"&&(m+=1);break;default:break}}let b=new Map,x=!1;for(let[v,E]of c)for(let R of E.segments){if(!(R.usd>0||R.activeMs>0||R.turns>0||R.toolCalls>0))continue;let I=R.textCat?.category??i.get(v)?.category??Aw(R.tools)?.category??a?.category??"uncategorized";b.has(I)||b.set(I,{usd:0,activeMs:0,turns:0,toolCalls:0,models:{},providers:{}});let L=b.get(I);L.usd+=R.usd,L.activeMs+=R.activeMs,L.turns+=R.turns,L.toolCalls+=R.toolCalls;for(let[U,J]of Object.entries(R.models))L.models[U]=(L.models[U]||0)+J;let T=R.provider||E.provider;T&&(L.providers[T]=(L.providers[T]||0)+R.usd),T==="cursor"&&R.usd===0&&(x=!0)}return{perCategory:b,handoffs:f,merged:p,failed:m,sawCursorActivity:x,unpricedTurns:h.turns}}function _w(t,{windowMs:e=null,now:n=Date.now()}={}){let r=e==null?0:n-e,o=new Map,s=[],a=0,i=!1,c=0;for(let h of t||[]){let{track:b,events:x}=h;if(!x?.length){a+=1;continue}let{perCategory:v,handoffs:E,merged:R,failed:st,sawCursorActivity:I,unpricedTurns:L}=CT(h,{since:r});I&&(i=!0),c+=L;let T={trackId:b?.id,name:b?.name||b?.id,archived:!!b?.archivedAt,usd:0,activeMs:0,turns:0,categories:{},models:{},providers:{},handoffs:E,subtasksMerged:R,subtasksFailed:st};for(let[U,J]of v){T.usd+=J.usd,T.activeMs+=J.activeMs,T.turns+=J.turns,T.categories[U]={usd:Ve(J.usd),activeMs:J.activeMs};for(let[y,A]of Object.entries(J.models))T.models[y]=Ve((T.models[y]||0)+A);for(let[y,A]of Object.entries(J.providers))T.providers[y]=Ve((T.providers[y]||0)+A);o.has(U)||o.set(U,OT());let g=o.get(U);g.usd+=J.usd,g.activeMs+=J.activeMs,g.turns+=J.turns,g.toolCalls+=J.toolCalls;for(let[y,A]of Object.entries(J.models))g.models[y]=(g.models[y]||0)+A;for(let[y,A]of Object.entries(J.providers))g.providers[y]=(g.providers[y]||0)+A;let w=g.byTrack.get(b?.id)||{usd:0,activeMs:0};w.usd+=J.usd,w.activeMs+=J.activeMs,g.byTrack.set(b?.id,w)}(T.usd>0||T.activeMs>0||T.turns>0)&&(T.usd=Ve(T.usd),T.dominantCategory=MT(T.categories),s.push(T))}let l=[...o.values()].reduce((h,b)=>h+b.usd,0),u=[...o.values()].reduce((h,b)=>h+b.activeMs,0),d=l>0?"usd":"time",f=d==="usd"?l:u,p=new Map((t||[]).map(h=>[h.track?.id,h.track?.name||h.track?.id])),m=[...o.entries()].map(([h,b])=>({category:h,sharePct:f>0?Math.round((d==="usd"?b.usd:b.activeMs)/f*100):0,shareBasis:d,usd:Ve(b.usd),activeMs:b.activeMs,turns:b.turns,toolCalls:b.toolCalls,models:Tw(b.models),providers:Tw(b.providers),topWorkstreams:[...b.byTrack.entries()].sort((x,v)=>v[1].usd-x[1].usd||v[1].activeMs-x[1].activeMs).slice(0,2).map(([x,v])=>({trackId:x,name:p.get(x),usd:Ve(v.usd),activeMs:v.activeMs}))})).sort((h,b)=>b.usd-h.usd||b.activeMs-h.activeMs);return s.sort((h,b)=>b.usd-h.usd||b.activeMs-h.activeMs),{since:r,capturedAt:n,totalUsd:Ve(l),activeMs:u,turns:[...o.values()].reduce((h,b)=>h+b.turns,0),bullets:m,workstreams:s,gaps:{tracksWithoutEvents:a,cursorCostUnknown:i,unpricedTurns:c}}}var MT=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},Tw=t=>Object.fromEntries(Object.entries(t).map(([e,n])=>[e,Ve(n)]));var $l={"24h":24*36e5,"7d":168*36e5,"30d":720*36e5,all:null};function BT(t){let e=String(t||"7d");return e in $l?{key:e,ms:$l[e]}:{key:"7d",ms:$l["7d"]}}function DT(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=$s(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 jT(t){if(!t?.cwd)return null;try{let n=(await PT.readFile(NT.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 Ew(t,{window:e="7d",now:n=Date.now()}={}){let{key:r,ms:o}=BT(e),s=[];for(let a of t||[]){let i=await F(a);s.push({track:a,events:DT(a,i),brief:await jT(a)})}return{window:r,..._w(s,{windowMs:o,now:n})}}import vr from"node:fs/promises";import LT from"node:fs";import mt from"node:path";import FT from"node:os";import{execFile as UT}from"node:child_process";import{promisify as HT}from"node:util";var WT=HT(UT),Iw=process.env.BOSTRAT_STATE_DIR||mt.join(process.env.HOME||FT.tmpdir(),".bostrat"),Ol=!1;async function ko(t,e){let n=await vr.stat(t).catch(()=>null);if(!n?.isFile())return!1;let r=await vr.stat(e).catch(()=>null);return r&&r.size===n.size&&r.mtimeMs>=n.mtimeMs?!1:(await vr.mkdir(mt.dirname(e),{recursive:!0}),await vr.copyFile(t,e),!0)}var qT=new Set([".claude",".codex",".credentials.json","auth.json",".claude.json","integrations.json"]);async function xo(t,e){let n=0,r=await vr.readdir(t,{withFileTypes:!0}).catch(()=>[]);for(let o of r){if(qT.has(o.name))continue;let s=mt.join(t,o.name);o.isDirectory()?n+=await xo(s,mt.join(e,o.name)):o.isFile()&&(n+=await ko(s,mt.join(e,o.name))?1:0)}return n}async function Rw({stateDir:t=Iw,dest:e=process.env.BOSTRAT_SYNC_DIR||`${Iw}-sync`,remote:n=process.env.BOSTRAT_STATE_REMOTE||null,tracks:r=null}={}){try{await vr.mkdir(e,{recursive:!0});let o=0;o+=await ko(mt.join(t,"tracks.json"),mt.join(e,"tracks.json"))?1:0,o+=await ko(mt.join(t,"mocks.json"),mt.join(e,"mocks.json"))?1:0,o+=await ko(mt.join(t,"runbooks.json"),mt.join(e,"runbooks.json"))?1:0,o+=await xo(mt.join(t,"runbooks"),mt.join(e,"runbooks")),o+=await xo(mt.join(t,"mock-archive"),mt.join(e,"mock-archive")),o+=await ko(mt.join(t,"events.jsonl"),mt.join(e,"events.jsonl"))?1:0,o+=await xo(mt.join(t,"events"),mt.join(e,"events"));for(let l of r||[])!l?.cwd||!l?.id||(o+=await xo(mt.join(l.cwd,".bostrat"),mt.join(e,"workspaces",l.id,".bostrat")));let s=(...l)=>WT("git",["-C",e,...l]);LT.existsSync(mt.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 Ui(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||Ol))try{await s("push","-q",n,"HEAD:refs/heads/main"),c=!0,Ol=!1}catch{Ol=!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 GT}from"node:child_process";import{promisify as KT}from"node:util";import{randomUUID as VT}from"node:crypto";import JT from"node:fs";import zT from"node:os";import XT from"node:path";var ze=KT(GT),ua=t=>new Promise(e=>setTimeout(e,t)),Xe=Ea,Cl=process.env.BOSTRAT_USAGE_PROBE_CWD||XT.join(zT.homedir(),".bostrat","usage-probe"),YT=Number(process.env.BOSTRAT_USAGE_IDLE_REAP_MS)||5*6e4,ZT=Number(process.env.BOSTRAT_USAGE_PANEL_TIMEOUT_MS)||15e3,QT=Number(process.env.BOSTRAT_USAGE_READY_TIMEOUT_MS)||45e3,$w=Number(process.env.BOSTRAT_USAGE_CACHE_MS)||2e4,Je=null,So=null,vo=null;async function t_(){try{return await ze(C,P("has-session","-t",Xe)),!0}catch{return!1}}function e_(){Je&&clearTimeout(Je),Je=setTimeout(()=>n_().catch(()=>{}),YT),Je.unref&&Je.unref()}async function n_(){Je&&(clearTimeout(Je),Je=null);try{await ze(C,P("kill-session","-t",Xe))}catch{}}async function r_(){let t=Date.now()+QT;for(;;){let e=await Ml().catch(()=>"");if(/for agents|for shortcuts|bypass permissions on/i.test(e)){await ua(500);return}if(Date.now()>t)throw new Error("usage probe didn't reach the prompt (trust/onboarding may be blocking)");await ua(500)}}async function o_(){if(await t_())return;if(!await Se())throw new Error("host tmux server not running \u2014 start globo-tmux.service");JT.mkdirSync(Cl,{recursive:!0}),await xe(Cl);let t=VT();await ze(C,P("new-session","-d","-s",Xe,"-c",Cl,"-x","200","-y","60"));let e=`${_a} --session-id ${t}`;await ze(C,P("send-keys","-t",Xe,e,"Enter")),await r_()}async function Ml(){let{stdout:t}=await ze(C,P("capture-pane","-t",Xe,"-p"));return t}async function s_(){await o_(),await ze(C,P("send-keys","-t",Xe,"-l","/usage")),await ze(C,P("send-keys","-t",Xe,"Enter"));let t=Date.now()+ZT,e="";for(;await ua(400),e=await Ml(),!(/Current\s+(session|week)/i.test(e)||/\d+%\s*used/i.test(e)||Date.now()>t););return await ua(900),e=await Ml(),await ze(C,P("send-keys","-t",Xe,"Escape")).catch(()=>{}),e_(),e}function a_(t){let e=String(t||"").split(`
|
|
297
|
-
`).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
|
|
298
|
-
`).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
|
|
299
|
-
`),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:
|
|
321
|
+
_${o} archived_`:"")}async function V_(t,e){let n=await pn();if(!n)return ut("Slack integration not configured.");let r=t.deps,o=(e.text||"").trim().split(/\s+/).filter(Boolean),s=(o[0]||"help").toLowerCase(),a=o.slice(1);switch(s){case"start":{if(!a.length)return ut("Usage: `/bostrat start <name> [repo]`");let i=await r.listRepos(),c=a[a.length-1]?.toLowerCase(),l=a.length>1?i.find(f=>f.name.toLowerCase()===c):null,u=(l?a.slice(0,-1):a).join(" ");return l?(Rn(dg(t,n,{name:u,repo:l.path,responseUrl:e.response_url})),ut(`Starting *${z(u)}* on ${z(l.name)}\u2026`)):{response_type:"ephemeral",text:`Pick a repo for \u201C${u}\u201D`,blocks:G_(u,i)}}case"archive":{if(!a.length)return ut("Usage: `/bostrat archive <track>`");let{match:i,live:c}=tu(await r.listTracks(),a.join(" "));return i?(Rn(H_(t,n,{track:i,responseUrl:e.response_url})),ut(`Archiving *${z(i.name)}*\u2026`)):eu(c,a.join(" "))}case"handoff":{let i=r.knownAgents??U_,c=a.at(-1)?.toLowerCase();if(a.length<2||!i.includes(c))return ut(`Usage: \`/bostrat handoff <track> <${i.join("|")}>\``);let l=a.slice(0,-1).join(" "),{match:u,live:f}=tu(await r.listTracks(),l);return u?(Rn(W_(t,n,{track:u,agent:c,responseUrl:e.response_url})),ut(`Handing *${z(u.name)}* to ${z(c)}\u2026`)):eu(f,l)}case"stop":{if(!a.length)return ut("Usage: `/bostrat stop <track>`");let{match:i,live:c}=tu(await r.listTracks(),a.join(" "));return i?(Rn(q_(t,n,{track:i,responseUrl:e.response_url})),ut(`Stopping *${z(i.name)}*\u2026`)):eu(c,a.join(" "))}case"list":return ut(await K_(r));default:return ut(F_)}}async function z_(t,e,n,r){let{trackId:o,promptId:s,label:a}=r,i=await su(o);try{if(await t.deps.answerPrompt(o,{promptId:s,answers:[a]}),i?.prompt?.promptId===s){let{ts:c,question:l}=i.prompt;delete i.prompt,await Te(),await Tr(e.botToken,{channel:i.channel,ts:c,text:`\u2713 ${a}`,blocks:[{type:"section",text:{type:"mrkdwn",text:`~${z(l||"question")}~
|
|
322
|
+
\u2713 *${z(a)}*`}}]}).catch(()=>{})}}catch(c){await Ct(n.response_url,ut(`Couldn't answer: ${c?.message||c}`))}}async function J_(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,oa(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 Ct(n.response_url,ut(`Couldn't act on the offer: ${c?.message||c}`))}}async function X_(t,e){let n=e.actions?.[0];if(!n?.action_id)return;let r=await pn();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 Ct(e.response_url,{...ut(`Starting *${z(s)}*\u2026`),replace_original:!0}),Rn(dg(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"?Rn(z_(t,r,e,o)):n.action_id==="bostrat-ship"&&Rn(J_(t,r,e,o))}async function Y_(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 j_(e.channel,e.thread_ts);if(!n)return;let r=await pn();if(r)try{await t.deps.sayToLead(n.trackId,e.text.trim())}catch(o){await $n(r,n.rec,`Couldn't deliver that to the lead: ${o?.message||o}`).catch(()=>{})}}function Z_(t){return async e=>{if(e.type==="slash_commands")return V_(t,e.payload);if(e.type==="interactive"){await X_(t,e.payload);return}if(e.type==="events_api"){await Y_(t,e.payload?.event);return}}}var Q_=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 tE(t,e){let n=[{type:"section",text:{type:"mrkdwn",text:`\u2753 ${z(e.question||"The lead needs an answer")}`}}],r=(e.options||[]).slice(0,12);return r.length?n.push({type:"actions",elements:r.map(o=>({type:"button",action_id:"bostrat-prompt",text:{type:"plain_text",text:No(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 eE(t,e,n){let r=n.payload||{};if(!r.promptId||e.prompt?.promptId===r.promptId)return;e.prompt&&await Tr(t.botToken,{channel:e.channel,ts:e.prompt.ts,text:"superseded",blocks:[{type:"section",text:{type:"mrkdwn",text:`~${z(e.prompt.question||"question")}~ _superseded_`}}]}).catch(()=>{});let o=await $n(t,e,r.question||"The lead needs an answer",tE(n.trackId,r));e.prompt={promptId:r.promptId,ts:o.ts,question:r.question||null},await Te()}async function nE(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 Te(),await Tr(t.botToken,{channel:e.channel,ts:o,text:"answered",blocks:[{type:"section",text:{type:"mrkdwn",text:`~${z(s||"question")}~
|
|
323
|
+
\u2713 _answered_`}}]})}async function rE(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 $n(t,e,r.excerpt||"Ship it?",[{type:"section",text:{type:"mrkdwn",text:`\u{1F6A2} ${z(No(r.excerpt||"The lead offered to ship.",300))}`}},{type:"actions",elements:s}]);e.offers=e.offers||{},e.offers[r.offerId]=a.ts,await Te()}async function oE(t,e,n){let r=n.payload||{},o=e.offers?.[r.offerId];if(!o)return;delete e.offers[r.offerId],await Te();let s=r.action==="dismiss"?"dismissed":r.action==="pr"?"\u2713 opening a PR":"\u2713 pushing";await Tr(t.botToken,{channel:e.channel,ts:o,text:s,blocks:[{type:"section",text:{type:"mrkdwn",text:`\u{1F6A2} ${s}`}}]})}async function sE(t,e,n,r){let o=n.payload||{};if(o.change==="ready"&&o.mockId){let s=await(r.readMockPng??aE)(o.mockId,o.version??null);if(!s)return;let a=(o.version??1)>1?` (v${o.version})`:"";await sg(t.botToken,{filename:s.filename,buffer:s.buffer,title:`${o.name}${a}`,channelId:e.channel,threadTs:e.ts,initialComment:`\u{1F3A8} mock *${z(o.name)}*${a}${o.group?` (group \`${z(o.group)}\`)`:""}`})}else if(o.change==="picked"){let s=(o.siblings??[]).map(a=>`*${z(a.name)}*`).join(", ");await $n(t,e,`Design decision: ${o.name} picked`,[{type:"section",text:{type:"mrkdwn",text:`\u{1F3A8} design decision: *${z(o.name)}* picked${s?` over ${s}`:""}`}}])}}async function aE(t,e){let n=await Me(t);if(!n)return null;let r=ln(n,e);if(!r)return null;let o=await Mo.readFile(Ft(n,r.v).png).catch(()=>null);return o?{filename:`${n.name.replace(/[^\w.-]+/g,"-")}.v${r.v}.png`,buffer:o}:null}async function iE(t,e){if(!Q_.has(e.type))return;let n=await su(e.trackId);if(!n)return;let r=await pn();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 Te(),await ru(r,n);return;case"turn.agent":{if(!o.text)return;let s=L_(o.text);if(!s)return;await $n(r,n,s,[{type:"section",text:{type:"mrkdwn",text:z(s)}},{type:"context",elements:[{type:"mrkdwn",text:`<${Bs(e.trackId)}|full turn \u2192>`}]}]);return}case"prompt.raised":return eE(r,n,e);case"prompt.answered":return nE(r,n,e);case"pr.offered":return rE(r,n,e);case"pr.offer.answered":return oE(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 $n(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 Te(),await ru(r,n);return;case"view.state.changed":{if(o.change!=="created"||o.restarted||!o.slug)return;await $n(r,n,`Live preview ${o.name}: ${Ls(o.slug)}`,[{type:"section",text:{type:"mrkdwn",text:`\u{1F50D} live preview *${z(o.name)}*: ${Ls(o.slug)}
|
|
324
|
+
_runs on the Track's branch; reachable on the operator's private network only_`}}]);return}case"mock.state.changed":return sE(r,n,e,t.deps)}}function fg(t){let e={deps:t};Lt.on("event",s=>{iE(e,s).catch(()=>{})});let n=null,r=null,o=async()=>{n||!await pn()||(r&&clearInterval(r),r=null,n=ag({getToken:async()=>(await pn())?.appToken,onEnvelope:Z_(e),log:s=>console.log(`[slack] ${s}`)}))};return r=setInterval(()=>o().catch(()=>{}),3e4),r.unref?.(),o().catch(()=>{}),()=>{r&&clearInterval(r),n?.()}}import cE from"node:fs/promises";function lE(t){let e=new Map;for(let n of String(t||"").split(`
|
|
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 uI(t){let e=String(t||"").split(`
|
|
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 Pg({fresh:t=!1}={}){return!t&&jo&&Date.now()-jo.at<Ng?jo.data:Lo||(Lo=(async()=>{try{let e=await cI(),n=lI(e),r={...n,contributors:uI(e),subscription:!!(n.session||n.week||n.weekModels),capturedAt:Date.now()};return jo={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 jo={at:Date.now()-(Ng-3e3),data:n},n}finally{Lo=null}})(),Lo)}var dI=262144;function fI(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 Bg(t){if(!t||typeof t!="object")return null;let e=Number(t.used_percent);return Number.isFinite(e)?{pct:Math.round(e),resets:fI(t.resets_in_seconds)}:null}function pI(t){let e=String(t||"").split(`
|
|
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:Bg(i.primary),week:Bg(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 pu(t){let e={provider:"codex",supported:!0,contributors:null};try{let n=await bn(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 It(n.file,dI),o=pI(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 Dg(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 Na({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 pu(i);if(c.subscription){a=c;break}(!a||a.error&&!c.error)&&(a=c)}r.push(a||await pu(null))}return n.has("cursor")&&r.push(await Na({agent:"cursor"})),{providers:r,capturedAt:Date.now()}}async function Na(t,{fresh:e=!1}={}){let n=t?.agent||"claude";return n==="codex"?pu(t):n==="cursor"?{provider:"cursor",supported:!1,subscription:!1,session:null,week:null,contributors:null,capturedAt:Date.now()}:{provider:"claude",supported:!0,...await Pg({fresh:e})}}function mI(t=process.env){return!!(nr(t)&&t.BOSTRAT_PLANE_TOKEN)}var hI=t=>({authorization:`Bearer ${t.BOSTRAT_PLANE_TOKEN||""}`}),Pa=(t,e)=>Object.assign(new Error(t),{status:e});async function jg(t,e){if(!mI(e))throw Pa("node is not plane-linked \u2014 Gmail unavailable",503);let n;try{n=await fetch(`${nr(e)}${t}`,{headers:hI(e)})}catch(o){throw Pa(`plane unreachable \u2014 Gmail unavailable (${o?.message||o})`,503)}let r=await n.json().catch(()=>({}));if(!n.ok)throw Pa(r?.error||`plane replied ${n.status}`,n.status);return r}async function Lg(t,{max:e=10,env:n=process.env}={}){let r=new URLSearchParams({q:t||""});return e&&r.set("max",String(e)),(await jg(`/v1/gmail/messages?${r.toString()}`,n)).messages||[]}async function Fg(t,{env:e=process.env}={}){if(!t)throw Pa("message id required",400);return(await jg(`/v1/gmail/messages/${encodeURIComponent(t)}`,e)).message}function Ug(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 wI(t){let[e,n]=t.split("/"),r=Ug(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 gI(t){return(t||"").split(",").map(e=>e.trim()).filter(Boolean).map(wI).filter(Boolean)}function yI(t){let e=t.headers["fly-client-ip"];return typeof e=="string"&&e.trim()?e.trim():(t.socket&&t.socket.remoteAddress||"").replace(/^::ffff:/,"")}function Hg(t){let e=gI(t),n=String(t||"").trim().toLowerCase()==="open";return{open:n,clientIp:yI,allowed(r){if(n)return!0;let o=Ug((r||"").replace(/^::ffff:/,""));return o==null?!1:e.some(s=>s(o))}}}import Wg from"node:crypto";import Ba from"node:fs";import bI from"node:path";function qg(t=process.env,{stateDir:e=mg,log:n=console}={}){let r=String(t.BOSTRAT_NODE_API_TOKEN||"").trim();if(r)return r;let o=bI.join(e,"api-token");try{let a=Ba.readFileSync(o,"utf8").trim();if(a)return a}catch{}let s=Wg.randomBytes(32).toString("base64url");try{Ba.mkdirSync(e,{recursive:!0}),Ba.writeFileSync(o,s+`
|
|
330
|
+
`,{mode:384}),Ba.chmodSync(o,384)}catch(a){n.error?.(`[bostrat-agent] could not persist the node API token: ${a?.message||a}`)}return s}function Fo(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 Gg(t,e){if(!t||!e)return!1;let n=Buffer.from(String(t)),r=Buffer.from(String(e));return n.length!==r.length?!1:Wg.timingSafeEqual(n,r)}function Kg(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 kI=[/^\/api\/github\/token\b/],xI=t=>kI.some(e=>e.test(t));function Vg({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=Kg(o);return Gg(c,t)?a():xI(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||Fo(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 zg(t,e,{enforce:n=!0}={}){return!n||Gg(Kg(t),e)?!0:Fo(t)}import SI from"node:http";import vI from"node:net";function Jg(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 Xg(t){let e=Jg(t.headers.host);return e?{slug:e,view:Pi(e)}:null}var AI=t=>`<!doctype html><meta charset="utf-8">
|
|
300
331
|
<meta http-equiv="refresh" content="3">
|
|
301
332
|
<title>${t.name} \u2014 starting</title>
|
|
302
333
|
<body style="font-family:system-ui;background:#0b0e14;color:#dce3f0;display:grid;place-items:center;height:100vh;margin:0">
|
|
303
334
|
<div style="text-align:center"><p style="font-size:2rem;margin:0">\u23F3</p>
|
|
304
|
-
<p><b>${t.name}</b> is waking up\u2026</p><p style="opacity:.6">auto-refreshing</p></div>`;function
|
|
335
|
+
<p><b>${t.name}</b> is waking up\u2026</p><p style="opacity:.6">auto-refreshing</p></div>`;function Yg(t,e,n){let r=Xg(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(AI(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=SI.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 Zg(t,e,n){let r=Xg(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
|
|
305
336
|
\r
|
|
306
337
|
bostrat: no live view "${s}"\r
|
|
307
|
-
`),e.destroy(),!0;let a=
|
|
338
|
+
`),e.destroy(),!0;let a=vI.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
|
|
308
339
|
`)+`\r
|
|
309
340
|
\r
|
|
310
|
-
`),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 Fw(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}.${y_(n.host)}/`}function y_(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 Bw(e)&&ii(n[0])?n.slice(1).join("."):e}import{execFile as b_}from"node:child_process";import{promisify as k_}from"node:util";var Nl=k_(b_),Uw=3e4,x_=30*6e4,S_=Uw,v_=10*6e4,A_=10080*6e4,T_=60*6e4,__=8,E_=1440*6e4,I_=3,Hw="number,url,title,headRefName,headRefOid,state,mergedAt,closedAt",Pl=60*6e4,R_=15*6e4,da=new Map,fa=new Map,$_=/could not resolve|not found|no pull requests?/i,O_=t=>$_.test(String(t?.message||t||""));function C_(t,e){return[t.ciFix?.delegatedAt,t.reconcile?.delegatedAt,t.ciFixAwait?.since,t.reconcileAwait?.since].some(n=>n&&e-n<Pl)}var Ww={fast:S_,slow:v_,decay:T_};function qw(t,e){return t.armedAt&&e-t.armedAt<x_||C_(t,e)?"fast":e-(t.firstSeen||0)>A_?"decay":"slow"}function M_(t,e){return Ww[qw(t,e)]}function Eo(t,e,n,r){let o=t.lanes?.get(it(e.repo,e.number))||{};return Hs({change:n,repo:e.repo,number:Number(e.number),githubAt:r||null,lane:o.lane||null,intervalMs:o.intervalMs||null,source:"watch"})}async function P_(t,e){we("watch");try{let{stdout:n}=await Nl("gh",["pr","view",String(e),"--repo",t,"--json",Hw],{timeout:3e4,maxBuffer:4194304,env:{...process.env,...await re({slug:t})}});return JSON.parse(n||"null")}catch(n){throw je(n)&&Le(n),n}}async function N_(t){we("watch");try{let{stdout:e}=await Nl("gh",["pr","list","--repo",t,"--state","all","--limit","100","--json",Hw],{timeout:3e4,maxBuffer:8388608,env:{...process.env,...await re({slug:t})}}),n=new Map;for(let r of JSON.parse(e||"[]"))n.set(Number(r.number),r);return n}catch(e){throw je(e)&&Le(e),e}}async function B_(){let t=new Map;for(let e of await ut().catch(()=>[]))t.set(e.id,e);return t}async function D_(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 j_(t,e){let n=await Sn(t);return n?dr(n,e):null}async function L_(t,e,n){let r=await Sn(t);if(!r)throw new Error(`no local checkout for ${t}`);return fr(r,e,{method:n})}async function F_(t,e){let n=await Sn(t);if(!n)throw new Error(`no local checkout for ${t}`);return ao(n,e)}async function U_(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 Ar(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 H_=10*6e4;async function W_(t,e,n){let r=t.autoMerge?.armedAt||0;if(r&&(n.now??Date.now())-r>E_){await Ar(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 Ar(t,o,e,n,`check${o.checks.failed===1?"":"s"} failed: ${a}`);return}if(o.mergeable==="CONFLICTING"){await Ar(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<H_)return;await Ar(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 Ar(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 Ar(t,o,e,n,String(a?.message||a).trim()||"merge failed");return}await Gw(t,{...o,state:"MERGED"},e,n,{change:"auto-merged",githubAt:o.checks.settledAt||null})}}async function Ao(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 q_(t,e){try{return await Nl("git",["-C",t.cwd,"cat-file","-e",`${e}^{commit}`],{timeout:1e4}),!0}catch{return!1}}async function To(t,e,n,r=()=>!0){return(await F(t).catch(()=>[])).some(s=>s.type==="pr.state.changed"&&s.payload?.change===e&&s.payload?.number===Number(n)&&r(s))}var _o=(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 G_(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 ws(t.repo,t.number,{headSha:s}).catch(()=>{}),t.reconcile?.headSha&&s!==t.reconcile.headSha&&await ys(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<R_;await Rf(t.repo,t.number,s).catch(()=>{}),t.baseFoldAt&&await If(t.repo,t.number).catch(()=>{}),!i&&!c&&!l&&a&&!await r.localHasCommit(a,s).catch(()=>!1)&&!await To(a,"pushed",t.number,d=>d.payload?.sha===s)&&await Ao(a,"pushed",{..._o(t,e),sha:s}).catch(()=>{})}if(t.ciFixAwait)if(o-(t.ciFixAwait.since||0)>Pl)await gs(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 gs(t.repo,t.number).catch(()=>{});else if(i.checks.total>0&&i.checks.pending===0){let c=Eo(r,t,"ci-fixed",i.checks.settledAt||null),l=t.ciFixAwait.since||0;a&&!await To(a,"ci-fixed",t.number,u=>(u.payload?.ts||0)>=l)&&await Ao(a,"ci-fixed",{..._o(t,e),passed:i.checks.passed,githubAt:c.githubAt,latencyMs:c.latencyMs,lane:c.lane}).catch(()=>{}),await gs(t.repo,t.number).catch(()=>{})}}}if(t.reconcileAwait)if(o-(t.reconcileAwait.since||0)>Pl)await bs(t.repo,t.number).catch(()=>{});else{let i=await r.detail(t.repo,t.number).catch(()=>null);if(i){if(i.mergeable==="CONFLICTING")await bs(t.repo,t.number).catch(()=>{});else if(i.mergeable==="MERGEABLE"){Eo(r,t,"reconciled",null);let c=t.reconcileAwait.since||0;a&&!await To(a,"reconciled",t.number,l=>(l.payload?.ts||0)>=c)&&await Ao(a,"reconciled",{..._o(t,e),base:i.baseRefName||null}).catch(()=>{}),await bs(t.repo,t.number).catch(()=>{})}}}}async function K_(t,e,n,r){let o=Eo(r,t,"closed",Date.parse(e?.closedAt||"")||null),s=n.get(t.trackId);s&&!await To(s,"closed",t.number)&&await Ao(s,"closed",{..._o(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 V_(t,e,n){Eo(n,t,"lost",null);let r=e.get(t.trackId);r&&!await To(r,"lost",t.number)&&await Ao(r,"lost",_o(t,null)).catch(()=>{}),await n.notifyIfResolved(t.repo,t.number,"lost").catch(()=>{}),await n.mark(t.repo,t.number,"lost").catch(()=>{})}async function Gw(t,e,n,r,o={}){let s=Eo(r,t,o.change||"merged",o.githubAt??(Date.parse(e.mergedAt||"")||null)),a=n.get(t.trackId);a&&((await F(a).catch(()=>[])).some(l=>l.type==="pr.state.changed"&&l.payload?.change==="merged"&&l.payload?.number===Number(t.number))||await r.emitMerged(a,{number:Number(t.number),url:e.url||t.url,title:e.title||t.title,branch:e.headRefName||t.headRef,repo:t.repo,githubAt:s.githubAt,latencyMs:s.latencyMs,lane:s.lane}).catch(()=>{})),await r.notifyIfArmed(t.repo,t.number).catch(()=>{}),await r.archiveIfArmed(t.repo,t.number).catch(()=>{}),await r.mark(t.repo,t.number,"merged").catch(()=>{})}async function J_(t={}){let{now:e=Date.now(),auth:n=Uc,view:r=P_,list:o=N_,links:s=Of,trackMap:a=B_,emitMerged:i=D_,mark:c=Ce,archiveIfArmed:l=Ue,notifyIfArmed:u=He,notifyIfResolved:d=ul,batchThreshold:f=I_,detail:p=j_,merge:m=L_,updateBranch:h=F_,lookup:b=vt,disarm:x=Vr,emitDisarmed:v=U_,localHasCommit:E=q_}=t;if(!(await n().catch(()=>null))?.authenticated||Ps()>0)return 0;let I=(await s().catch(()=>[])).filter(y=>e-(da.get(it(y.repo,y.number))||0)>=M_(y,e));if(!I.length)return 0;let L=Date.now(),T=new Map,U=new Map;for(let y of I){let A=qw(y,e);T.set(it(y.repo,y.number),{lane:A,intervalMs:Ww[A]}),da.set(it(y.repo,y.number),e),U.has(y.repo)||U.set(y.repo,[]),U.get(y.repo).push(y)}let J=await a().catch(()=>new Map),g={now:e,lanes:T,emitMerged:i,mark:c,archiveIfArmed:l,notifyIfArmed:u,notifyIfResolved:d,detail:p,merge:m,updateBranch:h,lookup:b,disarm:x,emitDisarmed:v,localHasCommit:E};for(let[y,A]of U){let K,B=new Set;if(A.length>f)K=await o(y).catch(()=>null);else{K=new Map;for(let $ of A)try{let _=await r(y,$.number);_?.number!=null&&K.set(Number(_.number),_)}catch(_){O_(_)&&B.add(it($.repo,$.number))}}if(K)for(let $ of A){let _=it($.repo,$.number),M=K.get(Number($.number));if(!M){if(B.has(_)){let at=(fa.get(_)||0)+1;fa.set(_,at),at>=__&&(await V_($,J,g),fa.delete(_),da.delete(_))}continue}fa.delete(_),M.state==="MERGED"||M.state==="CLOSED"?(M.state==="MERGED"?await Gw($,M,J,g):await K_($,M,J,g),da.delete(_)):(await G_($,M,J,g).catch(()=>{}),$.autoMerge&&await W_($,J,g).catch(()=>{}))}}let w={};for(let{lane:y}of T.values())w[y]=(w[y]||0)+1;return console.log(`[pr-watch] probed ${I.length} link(s) (${Object.entries(w).map(([y,A])=>`${y} ${A}`).join(", ")}) in ${Date.now()-L}ms`),I.length}function Kw(){let t=!1;setInterval(async()=>{if(!t){t=!0;try{await J_()}catch{}finally{t=!1}}},Uw)}import ke from"node:fs/promises";import Tr from"node:path";import Qw from"node:os";import Vw from"node:fs/promises";import pa from"node:path";import{execFile as z_}from"node:child_process";import{promisify as X_}from"node:util";import{fileURLToPath as Y_}from"node:url";var Z_=X_(z_),zw=pa.dirname(Y_(import.meta.url)),Q_=pa.resolve(zw,"..","..","client","dist"),tE="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",eE=new Map([...tE].map((t,e)=>[t,e]));function nE(t){let e=[],n=0,r=0;for(let o of t){let s=eE.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 rE(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=nE(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 oE(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 Jw=new Map;async function sE(t){let e;try{e=await Vw.stat(t)}catch{return null}let n=Jw.get(t);if(n&&n.mtimeMs===e.mtimeMs)return n;try{let r=JSON.parse(await Vw.readFile(t,"utf8")),o={mtimeMs:e.mtimeMs,sources:r.sources||[],lines:rE(r.mappings||"")};return Jw.set(t,o),o}catch{return null}}var Bl=null;function aE(){return Bl||(Bl=process.env.BOSTRAT_RELEASE?Promise.resolve(process.env.BOSTRAT_RELEASE):Z_("git",["rev-parse","--short","HEAD"],{cwd:pa.resolve(zw,"..","..")}).then(t=>t.stdout.trim()).catch(()=>null)),Bl}var iE=/(?:https?:\/\/[^\s()]*)?\/assets\/([^\s()/]+\.js):(\d+):(\d+)/g;async function cE(t,{distDir:e=Q_}={}){let n=[],r=0,o=!1;for(let s of t.matchAll(iE)){let[a,i,c,l]=s,u=await sE(pa.join(e,"assets",`${i}.map`)),d=u?oE(u.lines[Number(c)-1],Number(l)-1):null;if(n.push(t.slice(r,s.index)),d&&u.sources[d[1]]){let f=u.sources[d[1]].replace(/^(\.\.\/)+/,"");n.push(`${f}:${d[2]+1}:${d[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 Xw(t,e={}){try{if(!t?.error?.stack||!t.release||t.release==="unknown")return t;let n=e.release??await aE();if(!n||t.release!==n)return t;let{stack:r,symbolicated:o}=await cE(t.error.stack,e);o&&(t.error.stack=r,t.error.symbolicated=!0)}catch{}return t}var lE=()=>process.env.BOSTRAT_STATE_DIR||Tr.join(process.env.HOME||Qw.tmpdir(),".bostrat"),Ye=()=>Tr.join(lE(),"telemetry"),tg=()=>Tr.join(Ye(),".relay-offsets.json"),uE=50,dE=14,fE=3e4,Yw=500,Bt=(t,e)=>typeof t=="string"?t.slice(0,e):"";function pE(t,e){if(!t||typeof t!="object")return null;let n=Bt(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:Bt(t.anonId,64),sessionId:Bt(t.sessionId,64),userId:Bt(t.userId,64),release:Bt(t.release,64),appVersion:Bt(t.appVersion,32),route:Bt(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:Bt(t.error.kind,256),message:Bt(t.error.message,8192),stack:Bt(t.error.stack,32768),fingerprint:Bt(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:Bt(s?.n,64),r:Bt(s?.r,128)}))),o}var eg=t=>new Date(t).toISOString().slice(0,10),Zw=0;async function ng(t,e=Date.now()){let r=(Array.isArray(t?.events)?t.events.slice(0,uE):[]).map(s=>pE(s,e)).filter(Boolean);if(!r.length)return 0;for(let s of r)s.error?.stack&&await Xw(s);await ke.mkdir(Ye(),{recursive:!0});let o=Tr.join(Ye(),`${eg(e)}.ndjson`);return await ke.appendFile(o,r.map(s=>JSON.stringify(s)+`
|
|
311
|
-
`).join(""),"utf8"),e-Zw>6*3600*1e3&&(Zw=e,mE(e).catch(()=>{})),r.length}async function mE(t=Date.now()){let e=eg(t-dE*24*3600*1e3),n=[];try{n=await ke.readdir(Ye())}catch{return}let r=n.filter(s=>s.endsWith(".ndjson")&&s.slice(0,10)<e);if(!r.length)return;for(let s of r)await ke.rm(Tr.join(Ye(),s),{force:!0});let o=await og();for(let s of r)delete o[s];await sg(o)}var ma=new Map;function rg(t,e=Date.now()){let n=t||"unknown",r=ma.get(n);return r||(ma.size>512&&ma.clear(),r={tokens:40,at:e},ma.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 og(){try{return JSON.parse(await ke.readFile(tg(),"utf8"))||{}}catch{return{}}}async function sg(t){await ke.mkdir(Ye(),{recursive:!0}),await ke.writeFile(tg(),JSON.stringify(t),"utf8")}function hE(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||Qw.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 wE({fetchImpl:t=fetch}={}){let e=await Xd();if(!e)return{sent:0,disabled:!0};let n;try{n=(await ke.readdir(Ye())).filter(s=>s.endsWith(".ndjson")).sort()}catch{return{sent:0,disabled:!1}}let r=await og(),o=0;for(let s of n){let a=Tr.join(Ye(),s),i=r[s]||0,c;try{let f=await ke.open(a,"r");try{let p=(await f.stat()).size;if(p<=i)continue;c=Buffer.alloc(p-i),await f.read(c,0,c.length,i)}finally{await f.close()}}catch{continue}let l=c.lastIndexOf(10);if(l<0)continue;let u=c.toString("utf8",0,l+1).split(`
|
|
312
|
-
`).filter(Boolean),d=[];for(let f of u)try{d.push(JSON.parse(f))}catch{}for(let f=0;f<d.length;f+=Yw){let p=d.slice(f,f+Yw).map(h=>hE(h)),m=await t(`${e.url.replace(/\/$/,"")}/v1/logs`,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${e.ingestKey}`},body:JSON.stringify({events:p})}).catch(()=>null);if(!m||!m.ok)return{sent:o,disabled:!1};o+=p.length}r[s]=i+l+1,await sg(r)}return{sent:o,disabled:!1}}var Dl=null,jl=!1;function ag(){Dl||(Dl=setInterval(async()=>{if(!jl){jl=!0;try{await wE()}catch(t){console.error("[telemetry] relay failed:",t?.message||t)}finally{jl=!1}}},fE),Dl.unref?.())}import wa from"node:fs/promises";import dg from"node:path";import gE from"node:os";var fg=()=>process.env.BOSTRAT_STATE_DIR||dg.join(process.env.HOME||gE.tmpdir(),".bostrat"),pg=()=>dg.join(fg(),"dictation-glossary.json"),yE=100,bE=500,kE=["bostrat","globopi","codex","tmux","worktree","Deepgram","Linear","Firecracker","Tigris","WireGuard","misous","postyx","cedar","gerbil","Anthropic","Opus","Haiku","Sonnet","keyterm","monorepo","dogfood"],ga=null,Ll=null,Fl=Promise.resolve();function ig(){let t={},e=Date.now();for(let n of kE)t[n.toLowerCase()]={term:n,count:1,lastUsed:e,source:"seed"};return t}async function mg(){return ga||(Ll??=wa.readFile(pg(),"utf8").then(t=>JSON.parse(t)?.terms??ig()).catch(()=>ig()).then(t=>ga=t).finally(()=>Ll=null),Ll)}async function cg(){let t=JSON.stringify({terms:ga},null,2),e=pg(),n=`${e}.${process.pid}.tmp`;await wa.mkdir(fg(),{recursive:!0}),await wa.writeFile(n,t),await wa.rename(n,e)}function xE(){return Fl=Fl.then(cg,cg),Fl.catch(()=>{})}async function Ul(){return Object.values(await mg()).sort((e,n)=>n.count-e.count||n.lastUsed-e.lastUsed)}var SE=t=>Math.max(1,Math.ceil(t.length/4));async function vE(){let t=[],e=0;for(let{term:n}of await Ul()){let r=SE(n);if(!(e+r>bE)&&(t.push(n),e+=r,t.length>=yE))break}return t}async function AE(t,e){let n=await mg(),r=t.toLowerCase(),o=n[r];n[r]={term:t,count:(o?.count??0)+1,lastUsed:Date.now(),source:o?.source??e}}var lg=t=>(t||"").split(/\s+/).map(e=>e.replace(/^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu,"")).filter(Boolean),ha=t=>t.toLowerCase();function ug(t){return!new RegExp("\\p{L}","u").test(t)||t.length<3?!1:/^[a-z]+$/.test(t)?t.length>=4:!0}async function hg(t,e){let n=lg(t),r=lg(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 d=s-1;d>=0;d--)a[u][d]=ha(n[u])===ha(r[d])?a[u+1][d+1]+1:Math.max(a[u+1][d],a[u][d+1]);let i=new Set,c=0,l=0;for(;c<o&&l<s;)ha(n[c])===ha(r[l])?(c++,l++):a[c+1][l]>=a[c][l+1]?c++:(ug(r[l])&&i.add(r[l]),l++);for(;l<s;l++)ug(r[l])&&i.add(r[l]);if(!i.size)return[];for(let u of i)await AE(u,"learned");return await xE(),[...i]}var Io=null;function TE(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 wg(){if(!Io)return null;let t=await Ur();if(!t||t.apiKey.slice(-6)!==Io.keyTail)return null;let{keyTail:e,...n}=Io;return n}async function gg(t,e){let n=await Ur();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 vE())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=TE(o.status,i);c.persistent&&(Io={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 Io=null,{transcript:(await o.json())?.results?.channels?.[0]?.alternatives?.[0]?.transcript??""}}var Sg=lt.dirname(EE(import.meta.url)),yg=Number(process.env.PORT)||3362,ya=hw(process.env),vg=Number(process.env.BOSTRAT_AUTO_TRIGGER_PCT)||80,_r=Nw(process.env.BOSTRAT_ALLOW_IPS),S=ie();S.disable("x-powered-by");S.use((t,e,n)=>{if(_r.allowed(_r.clientIp(t)))return n();e.status(403).type("text/plain").send("bostrat: access restricted to the allowlist")});S.use(jw);S.use("/api/mocks",ie.json({limit:"1mb"}));S.use("/api/mocks",ie.raw({type:"image/png",limit:"24mb"}));S.use("/api/runbooks",ie.json({limit:"1mb"}));S.use("/api/telemetry",ie.json({limit:"256kb"}));S.use("/api/hook",ie.json({limit:"2mb"}));S.use("/api/dictation",ie.raw({type:["audio/*","video/mp4","application/octet-stream"],limit:"32mb"}));S.use("/api/tracks/:id/upload",ie.raw({type:()=>!0,limit:"25mb"}));S.use(ie.json({limit:"64kb"}));S.get("/api/health",async(t,e)=>{e.json({ok:!0,tmuxSocket:Mt,hostTmuxAlive:await Se(),autoTriggerPct:vg})});S.get("/api/sessions",async(t,e)=>{e.json({sessions:await Nn()})});S.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 Qe(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})}});S.delete("/api/sessions/:name",async(t,e)=>{let n=decodeURIComponent(t.params.name);if(!pt(n))return e.status(400).json({error:"invalid session name"});if(!await z(n))return e.status(404).json({error:"session not found"});try{await Jt(n),e.json({ok:!0})}catch(r){e.status(500).json({error:`failed to kill: ${r?.message||r}`})}});S.get("/api/context/:name",async(t,e)=>{let n=decodeURIComponent(t.params.name);if(!pt(n))return e.status(400).json({error:"invalid session name"});let r=await z(n);if(!r)return e.status(404).json({error:"session not found"});let o=await Cr(r.cwd,r.sessionId);if(!o)return e.json({available:!1,cwd:r.cwd,sessionId:r.sessionId||null});e.json({available:!0,...o})});S.post("/api/handoff/:name",async(t,e)=>{let n=decodeURIComponent(t.params.name);if(!pt(n))return e.status(400).json({error:"invalid session name"});try{let r=await Gu(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)})}});S.get("/api/handoff/:name",(t,e)=>{let n=decodeURIComponent(t.params.name),r=Vo(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})});S.get("/api/tracks",async(t,e)=>{e.json({tracks:await ut(),agents:Ys,agentAvailability:await ol(),tickets:await fs()})});S.get("/api/cost",async(t,e)=>{try{let n=await Sw(await ut());if(n.cursorUsage=qm(),t.query.format==="text")return e.type("text/plain").send(vw(n));e.json(n)}catch(n){e.status(500).json({error:n?.message||String(n)})}});S.get("/api/usage/roundup",async(t,e)=>{try{e.json(await Ew(await ut(),{window:t.query.window}))}catch(n){e.status(500).json({error:n?.message||String(n)})}});S.get("/api/arcs",async(t,e)=>{try{e.json(await Df(await ut()))}catch(n){e.status(500).json({error:n?.message||String(n)})}});S.get("/api/arcs/:key",async(t,e)=>{try{let n=await jf(await ut(),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)})}});S.post("/api/arcs/backfill-beats",async(t,e)=>{try{e.json({ok:!0,...await Kh()})}catch(n){e.status(500).json({error:n?.message||String(n)})}});S.post("/api/hook",async(t,e)=>{try{let n=await mw(t.body||{},_h);e.json({ok:!0,events:n.length})}catch(n){e.json({ok:!1,error:n?.message||String(n)})}});S.post("/api/telemetry",async(t,e)=>{try{if(!rg(_r.clientIp(t)))return e.json({ok:!0,dropped:!0});let n=await ng(t.body||{});e.json({ok:!0,events:n})}catch(n){e.json({ok:!1,error:n?.message||String(n)})}});S.get("/api/repos",async(t,e)=>{try{let n=await me(),r=new Set((await Promise.all(n.map(a=>Y(a.path)))).filter(Boolean).map(a=>a.toLowerCase())),s=((await zr()).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)})}});S.get("/api/github/token",async(t,e)=>{try{e.json(await vs(String(t.query.repo||"")))}catch(n){e.status(n?.status||502).json({error:n?.message||String(n)})}});S.get("/api/github/status",(t,e)=>{e.json({configured:Ss(),calls:Sm(),latency:zm()})});S.post("/api/github/refresh",(t,e)=>{zf(),e.json({ok:!0})});function Vl(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 ba({repo:t=null,number:e=null,branch:n=null}){let r=t?await Y(t):null;if(r&&e!=null){let s=await vt(r,e),a=s&&await et(s.trackId);if(a)return a}return n?(await ut()).find(s=>s.workspace?.branch===n&&Vl(s,t,r))??null:null}async function Ag({repo:t=null,number:e=null,branch:n=null}){let r=await ba({repo:t,number:e,branch:n}).catch(()=>null);if(r&&!r.archivedAt)return r;if(!n)return null;let o=t?await Y(t).catch(()=>null):null;return(await ut()).find(a=>!a.archivedAt&&a.workspace?.branch===n&&Vl(a,t,o))??null}async function Vt(t,{number:e,url:n=null,title:r=null,branch:o=null,repo:s=null,track:a=null,extra:i={}}){let c=a||await ba({repo:s,number:e,branch:o}).catch(()=>null);if(!c)return;let l=i.repo??(s?await Y(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 OE(t){let e=(await ut()).filter(n=>!n.archivedAt);for(let n of t??[]){if(!n.prs?.length)continue;let r=await Y(n.path);if(r)for(let o of n.prs){if(await vt(r,o.number))continue;let s=e.find(a=>a.workspace?.branch===o.headRefName&&Vl(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"})}}}S.get("/api/git/prs",async(t,e)=>{try{let n=await nh();await OE(n.repos).catch(()=>{}),e.json(n)}catch(n){e.status(500).json({error:n?.message||String(n)})}});S.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:d}=t.body||{};try{if(s){let h=o||"merge";if(!so.has(h))throw new Error(`invalid merge method: ${h}`);let b=Number(r);if(!Number.isInteger(b)||b<=0)throw new Error("invalid PR number");let x=await Y((await ge(n)).path);if(!x)throw new Error("repo has no GitHub remote");let v=await ba({repo:n,number:b,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 ao(n,b),await Pt({repo:x,number:b,trackId:v.id,url:d,headRef:l,title:u,source:"matched"}).catch(()=>{}),!await Kr(x,b,{merge:{method:h}}))throw new Error("couldn't record the auto-merge arm");return await Yn(x,b,i===!0),await ks(x,b,c===!0&&i!==!0),se(),await Vt("auto-armed",{number:b,url:d,title:u,branch:l,repo:n,track:v,...a?{extra:{updated:!0}}:{}}),e.json({ok:!0,number:b,auto:!0})}let f=await fr(n,r,{method:o||"squash"});await Vt("merged",{number:f.number,url:d,title:u,branch:l,repo:n});let p=await Y(n).catch(()=>null),m=!1;if(p&&(i===!0&&await Yn(p,f.number,!0).catch(()=>{}),c===!0&&i!==!0&&await ks(p,f.number,!0).catch(()=>{}),await He(p,f.number).catch(()=>{}),m=await Ue(p,f.number).catch(()=>!1),await Ce(p,f.number,"merged").catch(()=>{})),i===!0&&!m){let h=await ba({repo:n,number:f.number,branch:l}).catch(()=>null);h&&!h.archivedAt&&await yr(h.id).catch(()=>{})}e.json(f)}catch(f){let p=f?.message||String(f),m=/invalid|unknown repo|required|needs a Track|no GitHub remote/i.test(p)?400:502;e.status(m).json({error:p})}});S.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 Y((await ge(n)).path).catch(()=>null),l=c?await Vr(c,i).catch(()=>null):null;try{await qc(n,i)}catch(u){if(!l)throw u}se(),await Vt("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})}});S.post("/api/git/prs/merge-all",async(t,e)=>{let{repo:n,method:r}=t.body||{};try{let o=await Y(n).catch(()=>null),s=await ah(n,{method:r||"merge",onMerged:a=>{Vt("merged",{number:a.number,url:a.url,title:a.title,branch:a.headRefName,repo:n}),o&&He(o,a.number).catch(()=>{}).then(()=>Ue(o,a.number)).catch(()=>{}).then(()=>Ce(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})}});S.get("/api/git/prs/merge-all",(t,e)=>{e.json({jobs:sh()})});S.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(!so.has(i))throw new Error(`invalid merge method: ${i}`);let c=n?await et(n):null;if(!c)return e.status(400).json({error:"trackId required"});let l=(await Me()).filter(f=>f.trackId===c.id&&f.state!=="merged"&&f.state!=="closed");if(!l.length)return e.status(409).json({error:"no open PRs on this Track"});for(let f of l)await Yn(f.repo,f.number,o===!0).catch(()=>{}),await ks(f.repo,f.number,s===!0&&o!==!0).catch(()=>{});let u=[],d=[];for(let f of l){let p={repo:f.repo,number:f.number,ok:!0,action:"armed"};u.push(p);try{let m=await Sn(f.repo);if(!m)throw new Error(`no local checkout for ${f.repo}`);let h=await dr(m,f.number);d.push({r:p,l:f,repoPath:m,pr:h,plan:rh(h,{update:r!==!1})})}catch(m){p.ok=!1,p.action="failed",p.reason=m?.message||String(m)}}for(let{r:f,l:p,repoPath:m,pr:h,plan:b}of oh(d))try{if(b.action==="skip")f.action="skipped",f.reason=b.reason;else if(b.action==="merge")await fr(m,p.number,{method:i}),await Vt("merged",{number:p.number,url:h.url,title:h.title,branch:h.headRefName,repo:m,track:c}),await He(p.repo,p.number).catch(()=>{}),await Ue(p.repo,p.number).catch(()=>{}),await Ce(p.repo,p.number,"merged").catch(()=>{}),f.action="merged";else{if(b.action==="update-arm"&&(await ao(m,p.number).catch(()=>{}),f.action="updated-armed"),!await Kr(p.repo,p.number,{merge:{method:i}}))throw new Error("couldn't record the auto-merge arm");await Vt("auto-armed",{number:p.number,url:h.url,title:h.title,branch:h.headRefName,repo:m,track:c,...f.action==="updated-armed"?{extra:{updated:!0}}:{}})}}catch(x){f.ok=!1,f.action="failed",f.reason=x?.message||String(x)}if(se(),!u.some(f=>f.ok))return e.status(502).json({error:u.map(f=>`${f.repo}#${f.number}: ${f.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})}});S.post("/api/git/prs/disarm-track",async(t,e)=>{let{trackId:n}=t.body||{};try{let r=n?await et(n):null;if(!r)return e.status(400).json({error:"trackId required"});let o=(await Me()).filter(a=>a.trackId===r.id&&a.state!=="merged"&&a.state!=="closed"),s=[];for(let a of o){let i=await Vr(a.repo,a.number).catch(()=>null),c=await Sn(a.repo).catch(()=>null);c&&await qc(c,a.number).catch(()=>{}),i&&(await Vt("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}))}se(),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})}});S.post("/api/git/prs/open",async(t,e)=>{let{trackId:n,title:r,body:o,base:s,repos:a}=t.body||{};try{let i=n?await et(n):null;if(!i)return e.status(400).json({error:"trackId required"});if(i.workspace?.kind==="multi"){let u=$t(i.workspace),d,f=Array.isArray(a)?a.filter(Boolean):[];if(f.length)d=f.map(h=>{let b=u.find(x=>x.name===h||x.repo===h||x.base===h||x.dir===h);if(!b)throw new Error(`invalid repo "${h}" \u2014 not tagged on this Track`);return b});else if(d=await no(i),!d.length)return e.status(409).json({error:"no tagged repo has work to ship"});let p=[];for(let h of d)try{let b=await Kc(h.dir,{title:r,body:o,base:s}),x=h.repo||await Y(h.dir).catch(()=>null);x&&b.number&&await Pt({repo:x,number:b.number,trackId:i.id,url:b.url,headRef:i.workspace?.branch??null,title:r,source:"api"}),await Vt("opened",{number:b.number,url:b.url,title:r,track:i,branch:i.workspace?.branch,extra:{repo:x??h.name}}),p.push({repo:h.name,slug:x??null,ok:!0,url:b.url,number:b.number})}catch(b){p.push({repo:h.name,slug:h.repo??null,ok:!1,error:b?.message||String(b)})}let m=p.filter(h=>h.ok);return m.length?e.json({ok:!0,multi:!0,results:p,url:m[0].url,number:m[0].number}):e.status(502).json({error:p.map(h=>`${h.repo}: ${h.error}`).join("; "),results:p})}let c=await Kc(i.cwd,{title:r,body:o,base:s}),l=await Y(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 Vt("opened",{number:c.number,url:c.url,title:r,track:i,branch:i.workspace?.branch}),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})}});S.get("/api/tracks/:id/repos",async(t,e)=>{try{let n=await et(t.params.id);if(!n)return e.status(404).json({error:"track not found"});let r=await no(n),o=new Map(r.map(a=>[a.base,a])),s=$t(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)})}});S.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 jh(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})}});S.delete("/api/tracks/:id/repos/:name",async(t,e)=>{try{let n=await Lh(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})}});S.post("/api/git/prs/fix",async(t,e)=>{let{repo:n,number:r}=t.body||{};try{let o=await dr(n,r),s=await Y(n).catch(()=>null),a=await Ag({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=$t(a.workspace).find(x=>x.base===n||s&&x.repo===s)??null,c=a.workspace?.kind==="multi",l=await ih(n,o.number);if(!l.length)return e.status(409).json({error:"no failing checks on this PR"});let u=c&&i?lt.join("ci",i.name):"ci",d=lt.join(a.cwd,".bostrat",u),f=lt.join(".bostrat",u,`pr-${o.number}-fail.log`);await Ct.promises.mkdir(d,{recursive:!0}),await Ct.promises.writeFile(lt.join(a.cwd,f),l.map(x=>`\u2550\u2550\u2550\u2550 ${x.name}${x.link?` \xB7 ${x.link}`:""} \u2550\u2550\u2550\u2550
|
|
313
|
-
${x.log??"(no fetchable log \u2014 see link)"}
|
|
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 Qg(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}.${TI(n.host)}/`}function TI(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 Jg(e)&&Pi(n[0])?n.slice(1).join("."):e}var ty=10,ey=200,_I=120,EI=24,II=/^\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 RI=(t=Date.now())=>new Date(t).toISOString().slice(0,10);function ny(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 $I(t){if(typeof t!="string"||!t.trim())throw new Error("md (markdown string) required");let e=Yr(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(`
|
|
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-wy>6*3600*1e3&&(wy=e,_R(e).catch(()=>{})),r.length}async function _R(t=Date.now()){let e=by(t-vR*24*3600*1e3),n=[];try{n=await _e.readdir(nn())}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 _e.rm(Cr.join(nn(),s),{force:!0});let o=await Sy();for(let s of r)delete o[s];await vy(o)}var Fa=new Map;function xy(t,e=Date.now()){let n=t||"unknown",r=Fa.get(n);return r||(Fa.size>512&&Fa.clear(),r={tokens:40,at:e},Fa.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 Sy(){try{return JSON.parse(await _e.readFile(yy(),"utf8"))||{}}catch{return{}}}async function vy(t){await _e.mkdir(nn(),{recursive:!0}),await _e.writeFile(yy(),JSON.stringify(t),"utf8")}function ER(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||gy.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 IR({fetchImpl:t=fetch}={}){let e=await Kf();if(!e)return{sent:0,disabled:!0};let n;try{n=(await _e.readdir(nn())).filter(s=>s.endsWith(".ndjson")).sort()}catch{return{sent:0,disabled:!1}}let r=await Sy(),o=0;for(let s of n){let a=Cr.join(nn(),s),i=r[s]||0,c;try{let d=await _e.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(`
|
|
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
|
+
${k.log??"(no fetchable log \u2014 see link)"}
|
|
314
346
|
`).join(`
|
|
315
|
-
`));let p=l.map(x=>x.name).join(", "),m=c&&i?` in the ${i.name}/ checkout`:"",h=await ye(a.id,`CI is red on PR #${o.number} "${o.title}" (${o.url}) \u2014 branch ${o.headRefName}${m}. Failing check${l.length===1?"":"s"}: ${p}. The failure log is at ${f} 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"}),b=s;b&&(await Pt({repo:b,number:o.number,trackId:a.id,url:o.url,headRef:o.headRefName,title:o.title,source:"matched"}),await Tf(b,o.number,{headSha:o.headSha,trackId:a.id}).catch(()=>{})),await Vt("fix-delegated",{number:o.number,url:o.url,title:o.title,branch:o.headRefName,repo:n,track:a,extra:{checks:l.map(x=>x.name)}}),e.json({ok:!0,leadBox:h.leadBox,queued:!!h.queued,checks:l.map(x=>x.name),log:f})}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})}});S.post("/api/git/prs/reconcile",async(t,e)=>{let{repo:n,number:r}=t.body||{};try{let o=await dr(n,r);if(o.mergeable!=="CONFLICTING")return e.status(409).json({error:"this PR does not conflict with its base branch"});let s=await Y(n).catch(()=>null),a=await Ag({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=$t(a.workspace).find(d=>d.base===n||s&&d.repo===s)??null,c=a.workspace?.kind==="multi"&&i?` In the ${i.name}/ checkout:`:"",l=await ye(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 _f(u,o.number,{headSha:o.headSha,trackId:a.id}).catch(()=>{})),await Vt("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})}});S.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=lm.filter(d=>t.body.actions.includes(d)),!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 et(t.params.id);if(!a)return e.status(404).json({error:"track not found"});let i=await F(a),c=i.find(d=>d.type==="pr.offered"&&d.payload?.offerId===n);if(!c)return e.status(404).json({error:"unknown offer"});if(i.some(d=>d.type==="pr.offer.answered"&&d.payload?.offerId===n))return e.status(409).json({error:"offer already answered"});let l=null;o&&(l=await ye(a.id,Os(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&&Oh(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 Ro=t=>e=>({...e,url:Fw(e,t.headers)});S.get("/api/views",async(t,e)=>{try{let n=await os({trackId:t.query.track||void 0});e.json({views:n.map(Ro(t)),maxLive:Ie})}catch(n){e.status(500).json({error:n?.message||String(n)})}});S.get("/api/views/:id",async(t,e)=>{let n=await Rd(t.params.id);if(!n)return e.status(404).json({error:"view not found"});e.json({view:Ro(t)(n),session:Wn(n.slug)})});S.post("/api/views",async(t,e)=>{let{trackId:n,name:r,cmd:o,cwd:s,port:a}=t.body||{};try{let i=n?await et(n):null;if(!i&&s&&(i=(await ut()).find(u=>u.cwd&&(s===u.cwd||s.startsWith(u.cwd+lt.sep)))||null),!i)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});let c=await ci({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:Ro(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})}});S.post("/api/views/:id/stop",async(t,e)=>{try{e.json({ok:!0,view:Ro(t)(await $d(t.params.id))})}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});S.post("/api/views/:id/restart",async(t,e)=>{try{e.json({ok:!0,view:Ro(t)(await Od(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})}});S.delete("/api/views/:id",async(t,e)=>{try{e.json(await Cd(t.params.id))}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});var CE=[["linear",an,gi],["jira",cs,hi]],Hl=new Map;S.get("/api/integrations/ticketing",async(t,e)=>{try{let n=await mi(),r={};for(let[o,s,a]of CE){let i=await s();if(!i){Hl.delete(o),r[o]={configured:!1};continue}let c=Hl.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},Hl.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)})}});S.get("/api/tickets/flow",async(t,e)=>{try{e.json({flow:await mi()})}catch(n){e.status(500).json({error:n?.message||String(n)})}});S.post("/api/tickets/flow",async(t,e)=>{let{trackId:n,cwd:r,set:o,actor:s}=t.body||{};try{let a=await zd(o),i=n?await et(n):null;!i&&r&&(i=(await ut()).find(l=>l.cwd&&(r===l.cwd||r.startsWith(l.cwd+lt.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})}});S.post("/api/tickets/link",async(t,e)=>{let{trackId:n,cwd:r,ref:o}=t.body||{};try{let s=n?await et(n):null;if(!s&&r&&(s=(await ut()).find(c=>c.cwd&&(r===c.cwd||r.startsWith(c.cwd+lt.sep)))||null),!s)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});if(!o)return e.status(400).json({error:"ref (issue URL or key) required"});let a=await Xn(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})}});S.get("/api/tickets/status",async(t,e)=>{let{cwd:n,track:r}=t.query;try{let o=r?await et(r):null;if(!o&&n&&(o=(await ut()).find(a=>a.cwd&&(n===a.cwd||String(n).startsWith(a.cwd+lt.sep)))||null),!o)return e.status(400).json({error:"track (or a cwd inside a Track workspace) required"});e.json({trackId:o.id,configured:await ff(),ticket:await ne(o.id)})}catch(o){e.status(500).json({error:o?.message||String(o)})}});S.get("/api/tracks/:id/ticket",async(t,e)=>{e.json({ticket:await ne(t.params.id)})});S.post("/api/tracks/:id/ticket",async(t,e)=>{try{let n=await et(t.params.id);if(!n)return e.status(404).json({error:"track not found"});let r=await Xn(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})}});S.delete("/api/tracks/:id/ticket",async(t,e)=>{e.json({ok:!0,removed:await Ii(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;S.get("/api/tracks/:id/ticket/states",async(t,e)=>{try{e.json(await wf(t.params.id))}catch(n){let r=n?.message||String(n);e.status(ka(r)).json({error:r})}});S.post("/api/tracks/:id/ticket/state",async(t,e)=>{try{e.json({ok:!0,...await Ri(t.params.id,(t.body||{}).stateId)})}catch(n){let r=n?.message||String(n);e.status(ka(r)).json({error:r})}});S.post("/api/tracks/:id/ticket/comment",async(t,e)=>{try{e.json({ok:!0,...await gf(t.params.id,(t.body||{}).body)})}catch(n){let r=n?.message||String(n);e.status(ka(r)).json({error:r})}});S.post("/api/tracks/:id/ticket-offer",async(t,e)=>{let{offerId:n,accept:r}=t.body||{};try{let o=await et(t.params.id);if(!o)return e.status(404).json({error:"track not found"});e.json({ok:!0,...await yf(o,n,r===!0)})}catch(o){let s=o?.message||String(o);e.status(ka(s)).json({error:s})}});S.post("/api/tickets/propose",async(t,e)=>{let{trackId:n,cwd:r,plan:o}=t.body||{};try{let s=n?await et(n):null;if(!s&&r&&(s=(await ut()).find(i=>i.cwd&&(r===i.cwd||r.startsWith(i.cwd+lt.sep)))||null),!s)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});e.json({ok:!0,trackId:s.id,proposal:await kf(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})}});S.get("/api/tickets/teams",async(t,e)=>{try{e.json({teams:await bf()})}catch(n){let r=n?.message||String(n);e.status(/no ticketing/i.test(r)?400:502).json({error:r})}});S.post("/api/tracks/:id/ticket-create-offer",async(t,e)=>{let{proposalId:n,accept:r,teamId:o}=t.body||{};try{let s=await et(t.params.id);if(!s)return e.status(404).json({error:"track not found"});e.json({ok:!0,...await xf(s,n,r===!0,{teamId:o||null})})}catch(s){let a=s?.message||String(s);e.status(/unknown proposal|unknown team/i.test(a)?404:/already answered/i.test(a)?409:/required|no ticketing|pick one/i.test(a)?400:502).json({error:a})}});S.get("/api/mocks",async(t,e)=>{try{e.json({mocks:await Qo({trackId:t.query.track||void 0,group:t.query.group||void 0})})}catch(n){e.status(500).json({error:n?.message||String(n)})}});S.get("/api/mocks/:id",async(t,e)=>{let n=await Ee(t.params.id);if(!n)return e.status(404).json({error:"mock not found"});e.json({mock:n})});S.get("/api/mocks/:id/png",async(t,e)=>{let n=await Ee(t.params.id);if(!n)return e.status(404).json({error:"mock not found"});let r=nn(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(Lt(n,r.v).png,o=>{o&&!e.headersSent&&e.status(404).json({error:"rendered artifact missing"})})});S.get("/api/mocks/:id/html",async(t,e)=>{let n=await Ee(t.params.id);if(!n)return e.status(404).json({error:"mock not found"});let r=nn(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(Lt(n,r.v).html,o=>{o&&!e.headersSent&&e.status(404).json({error:"source artifact missing"})})});S.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 et(o):null;if(!l&&a&&(l=(await ut()).find(p=>p.cwd&&(a===p.cwd||a.startsWith(p.cwd+lt.sep)))||null),!l)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});let u=(l.boxes||[]).find(f=>f.name===l.leadBox),d=await dd({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:d})}catch(l){let u=l?.message||String(l);e.status(/required|invalid|must be/i.test(u)?400:500).json({error:u})}});S.post("/api/mocks/:id/update",async(t,e)=>{let n=Buffer.isBuffer(t.body)?t.body:null;try{let r=await fd(t.params.id,{html:n?null:(t.body||{}).html,image:n,width:n?t.query.width:(t.body||{}).width});e.json({ok:!0,mock:r})}catch(r){let o=r?.message||String(r),s=/not found/i.test(o)?404:/required|invalid|must be/i.test(o)?400:500;e.status(s).json({error:o})}});S.post("/api/mocks/:id/pick",async(t,e)=>{try{let{mock:n,siblings:r}=await md(t.params.id),o=r.length?` over ${r.map(s=>`"${s.name}"`).join(", ")}`:"";await ye(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})}});S.delete("/api/mocks/:id",async(t,e)=>{try{e.json(await hd(t.params.id))}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});S.get("/api/runbooks",async(t,e)=>{try{let n=t.query.track||void 0;if(!n&&t.query.cwd){let r=await ut(),o=String(t.query.cwd);if(n=r.find(s=>s.cwd&&(o===s.cwd||o.startsWith(s.cwd+lt.sep)))?.id,!n)return e.json({runbooks:[]})}e.json({runbooks:await ss({trackId:n,status:t.query.status||void 0})})}catch(n){e.status(500).json({error:n?.message||String(n)})}});S.get("/api/runbooks/:id",async(t,e)=>{let n=await fi(t.params.id);if(!n)return e.status(404).json({error:"runbook not found"});e.json({runbook:n})});S.get("/api/runbooks/:id/md",async(t,e)=>{let n=await fi(t.params.id);if(!n)return e.status(404).json({error:"runbook not found"});let r=Dd(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(qn(n,r.v),o=>{o&&!e.headersSent&&e.status(404).json({error:"runbook artifact missing"})})});S.post("/api/runbooks",async(t,e)=>{let{trackId:n,name:r,md:o,cwd:s}=t.body||{};try{let a=n?await et(n):null;if(!a&&s&&(a=(await ut()).find(u=>u.cwd&&(s===u.cwd||s.startsWith(u.cwd+lt.sep)))||null),!a)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});let i=(a.boxes||[]).find(l=>l.name===a.leadBox),c=await Fd({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})}});S.post("/api/runbooks/:id/update",async(t,e)=>{try{let{md:n,note:r}=t.body||{};e.json({ok:!0,runbook:await Ud(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})}});S.post("/api/runbooks/:id/status",async(t,e)=>{try{e.json({ok:!0,runbook:await Hd(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})}});S.delete("/api/runbooks/:id",async(t,e)=>{try{e.json(await Wd(t.params.id))}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});var ME=120,Wl=400,Kl=null;S.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>Wl)return e.status(400).json({error:`message is ${i.length} chars \u2014 over the ${Wl} cap; a notification is a nudge, put the detail in chat`});let c=di(`${n||""}
|
|
316
|
-
${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
|
|
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 Y(a):null;if(!l&&s&&(l=(await at()).find(h=>h.cwd&&(s===h.cwd||s.startsWith(h.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(m=>m.name===l.leadBox),f=(m,h)=>String(m).replace(/\s+/g," ").trim().slice(0,h),d={title:n?f(n,YR):l.name,body:f(i,Tu),urgency:o==="high"?"high":"normal",trackName:l.name,ts:Date.now()};await C(l,{type:"notify.raised",actor:"agent",boxId:l.leadBox??null,provider:u?.agent||null,payload:d}),e.json({ok:!0,notification:d,delivered:Iu?Iu.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 Py(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 Zr(),terms:await Su(),problem:await Ny()})});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 My(n,r)})});x.get("/api/tracks/:id/voice-chat",async(t,e)=>{if(!await Y(t.params.id))return e.status(404).json({error:"track not found"});e.json(await By())});x.post("/api/tracks/:id/voice-chat/session",async(t,e)=>{try{let n=await Y(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 Dy())}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 Y(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 jy(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 Wl({name:n,cwd:r,repo:o,repos:s,agent:a,skipPermissions:i!==!1}),u=null;if(c)try{await Qn(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 Y(t.params.id);if(!n)return e.status(404).json({error:"track not found"});e.json({track:n,handoff:Jy(Co(t.params.id)),subtasks:vr(t.params.id).map(zo)})});x.delete("/api/tracks/:id",async(t,e)=>{try{let n=await Ww(t.params.id,{cascadeMocks:t.query.mocks!=="0",cascadeRunbooks:t.query.runbooks==="1"});await ec(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 Sr(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 Vw(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 Ll(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 Y(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}
|
|
317
349
|
data: ${JSON.stringify(i)}
|
|
318
350
|
|
|
319
|
-
`),o=Number(t.query.since??t.headers["last-event-id"])||0;for(let i of await
|
|
351
|
+
`),o=Number(t.query.since??t.headers["last-event-id"])||0;for(let i of await j(n))i.seq>o&&r(i);let s=Gd(n.id,r),a=setInterval(()=>e.write(`: hb
|
|
320
352
|
|
|
321
|
-
`),25e3);t.on("close",()=>{clearInterval(a),s()})});
|
|
353
|
+
`),25e3);t.on("close",()=>{clearInterval(a),s()})});x.get("/api/tracks/:id/search",async(t,e)=>{let n=await Y(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=Wr(await j(n)),a=ps(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.post("/api/tracks/:id/lead/restart",async(t,e)=>{try{e.json({ok:!0,track:await Jw(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 de(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 Y(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 iw(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 Y(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=ct.basename(String(t.params.file||""));if(!r||!Il(ct.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(ct.join(El(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 ZR=/^[0-9a-fA-F-]{8,64}$/;x.get("/api/tracks/:id/chat-images/:box/:uuid/:idx",async(t,e)=>{try{let n=await Y(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(!ZR.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=pe(a.cwd,a.sessionId);if(!i)return e.status(404).json({error:"transcript not found"});let c=`"${o}"`,l=null,u=qR.createInterface({input:Mt.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?ui(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 Hl(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:Ta(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 Fl(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 Uw({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 Tw(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 Ow(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 Y(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 Na(r,{fresh:o}))});x.post("/api/tracks/:id/model",async(t,e)=>{let{model:n}=t.body||{};try{e.json(await Cw(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 tg(t.params.id,{agent:n,task:r,skipPermissions:o!==!1,reason:s});e.json({ok:!0,subtask:zo(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:vr(t.params.id).map(zo)})});x.get("/api/tracks/:id/subtask/:sid/messages",async(t,e)=>{let n=await Aw(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:zo(Jl(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:zo(ng(t.params.sid))})}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:409).json({error:r})}});function zo(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 Kl(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 Rw(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(Jy(Co(t.params.id))??{running:!1})});function Jy(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:es(),paths:Fu,trace:await Lu({limit:n})})});var _u=ct.resolve(qy,"..","..","client","dist");Mt.existsSync(_u)&&(x.use(fe.static(_u)),x.get(/^(?!\/api\/).*/,(t,e)=>e.sendFile(ct.join(_u,"index.html"))));var Xy=KR.createServer(x),QR=new zR({noServer:!0}),Yy=(t,e,n)=>{if(!(Fo(t)||Mr.allowed(Mr.clientIp(t)))){e.write(`HTTP/1.1 403 Forbidden\r
|
|
354
|
+
\r
|
|
355
|
+
`),e.destroy();return}if(Zg(t,e,n))return;if(!zg(t,Ky,{enforce:Vy})){e.write(`HTTP/1.1 401 Unauthorized\r
|
|
322
356
|
\r
|
|
323
|
-
`),e.destroy();return}
|
|
357
|
+
`),e.destroy();return}let r=Ju(t.url);if(!r.ok){e.write(`HTTP/1.1 ${r.status} ${r.status===400?"Bad Request":"Not Found"}\r
|
|
324
358
|
\r
|
|
325
|
-
`),e.destroy();return}let o=r.sessionName;if(!
|
|
359
|
+
`),e.destroy();return}let o=r.sessionName;if(!ht(o)){e.write(`HTTP/1.1 400 Bad Request\r
|
|
326
360
|
\r
|
|
327
|
-
`),e.destroy();return}let{cols:s,rows:a}=r;
|
|
361
|
+
`),e.destroy();return}let{cols:s,rows:a}=r;QR.handleUpgrade(t,e,n,i=>Ku(i,o,{cols:s,rows:a}))};Xy.on("upgrade",Yy);var Zy=process.env.BOSTRAT_CERT_DIR||ct.join(process.env.HOME||Eu.tmpdir(),".bostrat","certs"),Uy=Number(process.env.BOSTRAT_TLS_PORT)||3443,Hy=ct.join(Zy,"bostrat.globopi.key"),Wy=ct.join(Zy,"bostrat.globopi.crt");if(Mt.existsSync(Hy)&&Mt.existsSync(Wy)){let t=VR.createServer({key:Mt.readFileSync(Hy),cert:Mt.readFileSync(Wy)},x);t.on("upgrade",Yy),t.on("error",e=>{console.error(`[bostrat-agent] https listener failed (${e?.code||e?.message}) \u2014 continuing HTTP-only`)}),t.listen(Uy,qa,()=>{console.log(`[bostrat-agent] https listening on ${qa||"0.0.0.0"}:${Uy}`)})}Xy.listen(Ly,qa,()=>{console.log(`[bostrat-agent] listening on ${qa||"0.0.0.0"}:${Ly}`),console.log(`[bostrat-agent] tmux socket: ${Nt}`),console.log(`[bostrat-agent] auto-trigger default: ${Gy}%`),console.log(`[bostrat-agent] ip allowlist: ${Mr.open?"OPEN (BOSTRAT_ALLOW_IPS unset)":process.env.BOSTRAT_ALLOW_IPS}`),Zw().then(t=>t&&console.log("[bostrat-agent] reconciled interrupted handoff(s)")).catch(t=>console.error("[bostrat-agent] handoff reconcile failed:",t?.message||t)),Qw().then(t=>t&&console.log("[bostrat-agent] reconciled interrupted subtask(s)")).catch(t=>console.error("[bostrat-agent] subtask reconcile failed:",t?.message||t)),zw().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)),Fw().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)),r$(),n$(),uy(),gp({resolveTrack:Y}),fg({listTracks:at,createTrack:Wl,archiveTrack:Sr,listRepos:ke,resolveTrack:Y,sayToLead:de,answerPrompt:Fl,startLeadHandoff:Kl,interruptLead:Hl,listSubtaskJobs:vr,cancelSubtask:Jl,readEvents:j,appendEvent:C,knownAgents:Sa}),t$(),jr().then(t=>{t||console.warn(`[boot] no host tmux server on ${Nt} and auto-start failed \u2014 boxes cannot spawn until one runs`)}),Ay(),process.env.BOSTRAT_PLANE_URL&&process.env.BOSTRAT_PLANE_TOKEN&&(Iu=bg({url:process.env.BOSTRAT_PLANE_URL,token:process.env.BOSTRAT_PLANE_TOKEN,nodeId:process.env.BOSTRAT_NODE_ID||Eu.hostname(),name:process.env.BOSTRAT_NODE_NAME||Eu.hostname(),listTracks:at,detectAgents:jl}),console.log(`[bostrat-agent] plane link \u2192 ${process.env.BOSTRAT_PLANE_URL}`))});function t$(){e$(),Is().catch(()=>{}),setInterval(()=>Is().catch(()=>{}),1e4)}function e$(){for(let t of["bostrat-view","bostrat-mock","bostrat-ticket","bostrat-runbook","bostrat-notify","bostrat-gmail","bostrat-changelog","gh"])try{let e=ct.resolve(qy,"..","bin",t),n=ct.join(process.env.HOME||"",".local","bin");if(!process.env.HOME||!Mt.existsSync(e))continue;Mt.mkdirSync(n,{recursive:!0});let r=ct.join(n,t);(Mt.existsSync(r)?Mt.readlinkSync(r):null)!==e&&(Mt.rmSync(r,{force:!0}),Mt.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 n$(){let t=!1,e=async()=>{if(!t){t=!0;try{let n=await at(),r=await Mg({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 r$(){let t=!1;setInterval(async()=>{if(!t){t=!0;try{let e=await at();for(let n of e)n.archivedAt||!n.boxes?.some(r=>r.state==="live")||(await Ll(n.id).catch(()=>{}),await ow(n).catch(()=>{}))}catch{}finally{t=!1}}},4e3)}
|