bostrat-node 0.2.4 → 0.2.6
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/bostrat-notify +2 -0
- package/bin/gh +13 -2
- package/dist/cli.js +35 -55
- package/dist/index.js +139 -113
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,107 +1,111 @@
|
|
|
1
|
-
var
|
|
1
|
+
var $y=Object.defineProperty;var bu=(t,e)=>{for(var n in e)$y(t,n,{get:e[n],enumerable:!0})};import ct from"node:path";import Mt from"node:fs";import iR from"node:readline";import{fileURLToPath as cR}from"node:url";import ue from"express";import lR from"node:http";import uR from"node:https";import hu from"node:os";import{WebSocketServer as dR}from"ws";import{execFile as Gy}from"node:child_process";import{promisify as Ky}from"node:util";import Ka from"node:fs";import{randomUUID as Vy}from"node:crypto";import On from"node:fs/promises";import vu from"node:os";import en from"node:path";import{createHash as Oy,randomBytes as Cy,timingSafeEqual as My}from"node:crypto";var Wo="BOSTRAT_HOOK_TOKEN",qo="x-bostrat-hook";function ku(){return Cy(32).toString("hex")}function Or(t){let e=typeof t=="string"?t.trim():"";return e?Oy("sha256").update(e).digest("hex"):null}function xu(t,e){return typeof t!="string"||typeof e!="string"||t.length!==e.length||!t?!1:My(Buffer.from(t),Buffer.from(e))}var Ua=new Map,Ha=new Map,Ny=6e4;function Go(t,e={},{now:n=Date.now(),log:r=console.warn}={}){if(Ua.set(t,(Ua.get(t)||0)+1),Ha.has(t)&&n-Ha.get(t)<Ny)return!1;Ha.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=${Ua.get(t)}${o?` ${o}`:""}`),!0}async function Su(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 Ko(){return/^(1|true|yes|on)$/i.test(process.env.BOSTRAT_CLAUDE_USE_API_KEY||"")&&process.env.ANTHROPIC_API_KEY?.trim()||null}function Py(){return process.env.CLAUDE_CONFIG_DIR?en.join(process.env.CLAUDE_CONFIG_DIR,".claude.json"):en.join(vu.homedir(),".claude.json")}function By(){return process.env.CLAUDE_CONFIG_DIR?en.join(process.env.CLAUDE_CONFIG_DIR,"settings.json"):en.join(vu.homedir(),".claude","settings.json")}async function Te(t){if(!t||typeof t!="string")return;let e=en.resolve(t),n=Py(),r=By(),o={};try{o=JSON.parse(await On.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 On.mkdir(en.dirname(n),{recursive:!0}),await On.writeFile(n,JSON.stringify(o,null,2));let i={};try{i=JSON.parse(await On.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 "${qo}: $${Wo}" --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 On.mkdir(en.dirname(r),{recursive:!0}),await On.writeFile(r,JSON.stringify(i,null,2))}import Wa from"node:fs/promises";import Dy from"node:os";import Vo from"node:path";function jy(){let t=process.env.CODEX_HOME||Vo.join(Dy.homedir(),".codex");return Vo.join(t,"config.toml")}function Au(t){return`[projects."${t}"]`}function Ly(t,e){let n=Au(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 Cn(t){if(!t||typeof t!="string")return;let e=Vo.resolve(t),n=jy(),r="";try{r=await Wa.readFile(n,"utf8")}catch{}if(Ly(r,e))return;let o=Au(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 Wa.mkdir(Vo.dirname(n),{recursive:!0}),await Wa.writeFile(n,r)}catch(s){console.warn(`[codex-config] could not seed folder trust for ${e}: ${s?.message||s}`)}}import Tu from"node:fs/promises";import Fy from"node:os";import Cr from"node:path";function Uy(t){let n=Cr.resolve(t).slice(1).replace(/\//g,"-").replace(/\./g,""),r=process.env.CURSOR_CONFIG_DIR||Cr.join(Fy.homedir(),".cursor");return Cr.join(r,"projects",n)}async function Mn(t){if(!t||typeof t!="string")return;let e=Cr.resolve(t),n=Uy(e),r=Cr.join(n,".workspace-trusted");await Tu.mkdir(n,{recursive:!0}),await Tu.writeFile(r,`${JSON.stringify({trustedAt:new Date().toISOString(),workspacePath:e},null,2)}
|
|
9
|
+
`,"utf8")}import Mr from"node:fs/promises";import Hy from"node:os";import Eu from"node:path";var Wy=[/[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 qa(t){let e=String(t??"");for(let n of Wy)e=e.replace(n,"\u2039redacted\u203A");return e}var Ga=process.env.BOSTRAT_DEBUG_DIR||Eu.join(Hy.homedir(),".bostrat","debug"),Nn=Eu.join(Ga,"handoff.jsonl"),_u=Number(process.env.BOSTRAT_HANDOFF_DEBUG_MAX)||500;function zo(){let t=process.env.BOSTRAT_HANDOFF_DEBUG;return t==="1"||t==="true"}async function qy(t){try{await Mr.mkdir(Ga,{recursive:!0}),await Mr.appendFile(Nn,`${JSON.stringify(t)}
|
|
10
|
+
`,"utf8");let n=(await Mr.readFile(Nn,"utf8")).split(`
|
|
11
|
+
`).filter(Boolean);n.length>_u&&await Mr.writeFile(Nn,`${n.slice(-_u).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 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")continue;if(u==="assistant"&&Array.isArray(l.content))for(let h of l.content){let
|
|
21
|
-
|
|
22
|
-
`):""}import
|
|
23
|
-
`))py.test(n)&&(e=Math.max(e,n.trimEnd().length));return e}function Da(t,e){let n=_t[t];if(!n?.busy)return"idle";if(ja(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=my(e);return t==="claude"&&r>0&&r<fy?"unknown":"idle"}var hy=/\bWaiting for \d+\b[^\n]*\bto finish\b|\/workflows to (?:stop|monitor)\b/;function hu(t,e){return t==="claude"&&hy.test(String(e||""))}function Bn(t,e){return Da(t,e)==="busy"}function Do(t,e){return Da(t,e)!=="idle"}function jo(t,e){let n=_t[t];return n?.up?n.up.test(e):!0}function wy(t,e){let n=_t[t];return n?ja(t,e)?!1:n.up.test(e)&&Da(t,e)==="idle":!0}function ja(t,e){let n=_t[t];return n?n.auth?.test(e)&&!n.up.test(e)?!0:n.block?.test(e)?t==="cursor"?!n.up.test(e):!0:!1:!1}async function mu(t,e,n){let r=Date.now()+n,o=0;for(await en(t).catch(()=>{});;){let s=await ht(t);if(wy(e,s)){await ve(Or);return}if(e==="cursor"&&cy.test(s)&&!_t.cursor.up.test(s)&&o<3){if(!pu(t,"cursor-trust")){await ve(Ba);continue}o+=1,await G({flow:"agent-ready",phase:"gate-accept",sessionName:t,agent:e,attempt:o}).catch(()=>{}),await kt(t,"a",{enter:!1,kind:"cursor-trust-accept"}),await ve(Or);continue}if(e==="codex"&&o<3){let a=uy(s);if(a){if(!pu(t,`codex-update:${a.from}->${a.to}`)){await ve(Ba);continue}o+=1,await G({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"}),fu({sessionName:t,from:a.from,to:a.to}),await ve(Or);continue}}if(Date.now()>r){if(ja(e,s)){let a=`${e} is blocked on an interactive prompt`;throw e==="codex"?a=_t.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"&&_t.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"&&_t.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"),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:!!_t[e]?.block?.test(s),paneUp:!!_t[e]?.up?.test(s),paneBusy:!!_t[e]?.busy?.test(s)}).catch(()=>{}),await ve(Or);return}await ve(Ba)}}function wu({agent:t,agentCmd:e}={}){let n=e?Mn(e):Mn(t||"");return{label:n,realClaude:e?Cn(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 Lo({cwd:t,sessionName:e,agent:n,agentCmd:r}={}){let{realCodex:o,realCursor:s}=wu({agent:n,agentCmd:r});return!e||!o&&!s?null:Ae({cwd:t,sessionName:e,agent:n,agentCmd:r}).catch(()=>{})}async function Ae({cwd:t,sessionId:e,sessionName:n,agent:r,agentCmd:o,timeoutMs:s=iy()}={}){let{label:a,realClaude:i,realCodex:c,realCursor:l}=wu({agent:r,agentCmd:o});if(i&&t&&await ke(t),c&&t&&await Rn(t),l&&t&&await $n(t),(i||c||l)&&n){await mu(n,a,s);return}let u=_t[r]?r:_t[a]?a:null;if(process.env.BOSTRAT_FAKE_POLL_READY==="1"&&n&&u){await mu(n,u,s);return}await ve(Or)}var gy=(()=>{let t=Number(process.env.BOSTRAT_CODEX_SUBMIT_SETTLE_MS??120);return Number.isFinite(t)&&t>=0?t:120})();function ce(t){return t==="codex"?{enter:!0,submitDelayMs:gy}:{enter:!0}}var yu=".bostrat/handoff",bu="latest.md",ku="Checkpoint: write a work-in-progress handoff",xu="HANDOFF WRITTEN",Su="Resume from .bostrat/handoff/latest.md",by="/clear",ky=t=>`${ku} 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 ${xu} and take no further action.`,xy=`${Su}: read it, restate the Objective and the single next step in one line, then continue the work from 'Next steps'.`,vu="Beat: write a 1-2 sentence summary",Au="BEAT WRITTEN",Sy=".bostrat/arc",vy="beat.txt",gu=320,Ay=t=>`${vu} of what this workstream accomplished and its outcome (plain prose, past tense, no headings or bullets \u2014 the way a changelog line reads) 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). When the file is written, reply only with ${Au} and take no further action.`;async function Tu(t,e,{timeoutMs:n=9e4}={}){let r=Dn.join(e,Sy),o=Dn.join(r,vy);await Cr.mkdir(r,{recursive:!0});let s=await Fo(o),a=await z(t);if(!a)throw new Error(`session "${t}" not found`);await Uo(t,a,Ay(o)),await Cu(o,s,n);let i=(await Cr.readFile(o,"utf8")).trim().replace(/\s+/g," ");if(!i)throw new Error("beat file was written empty");return i.length>gu?`${i.slice(0,gu-1).trimEnd()}\u2026`:i}function Ty(t,e){let n=(e||"").trim();return n?t==="assistant"?n===xu||n===Au:n.startsWith(ku)||n===by||n.startsWith(Su)||n.startsWith(vu):!1}var _y=6e5;function _u(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-_y;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 Eu(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 Iu(t,{windows:e=[]}={}){if(Ty(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 Ey=3500,Iy=3e5,La=new Map;function Ho(t){return La.get(t)||null}async function Ru(t){let e=La.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:Dn.join(n.cwd,yu,bu)};return La.set(t,r),Ry(r,n.cwd).catch(o=>{r.error=o?.message||String(o),r.finishedAt=Date.now()}),r}async function Ry(t,e){t.step="summarize",await $u(t.name,e),t.step="clear";let n=await z(t.name);await Uo(t.name,n,"/clear"),await Mu(Ey),t.step="resume",await Uo(t.name,n,xy),t.step="done",t.finishedAt=Date.now()}async function $u(t,e){let n=Dn.join(e,yu),r=Dn.join(n,bu);await Cr.mkdir(n,{recursive:!0});let o=await Fo(r),s=await z(t);return await G({flow:"checkpoint",phase:"summarize-start",sessionName:t,cwd:e,file:r,beforeMtime:o}),await Uo(t,s,ky(r)),await Cu(r,o,Iy),await G({flow:"checkpoint",phase:"summarize-done",sessionName:t,file:r,afterMtime:await Fo(r)}),await $y(n,r),r}async function Ou(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 $u(t,e.cwd)}async function Fo(t){try{return(await Cr.stat(t)).mtimeMs}catch{return 0}}async function Cu(t,e,n){let r=Date.now()+n;for(;;){if(await Fo(t)>e)return;if(Date.now()>r)throw new Error(`timed out waiting for the handoff file to be written (${t})`);await Mu(2e3)}}async function $y(t,e){try{if(!yy.existsSync(e))return;let n=new Date().toISOString().replace(/[:.]/g,"-");await Cr.copyFile(e,Dn.join(t,`${n}.md`))}catch{}}function Mu(t){return new Promise(e=>setTimeout(e,t))}async function Uo(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 Ae({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",...ce(r.agent)})}import j from"node:fs/promises";import L from"node:path";import{randomUUID as fo}from"node:crypto";import{execFile as mv}from"node:child_process";import{promisify as hv}from"node:util";import wf from"node:path";import Fa from"node:fs/promises";import jn from"node:path";import Oy from"node:os";import{EventEmitter as Cy}from"node:events";import{randomUUID as My}from"node:crypto";var Py=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","provider.connected","track.summary.recorded","stall.detected","error"]),Ny=new Set(["user","agent","orchestrator","system"]),By=new Set(["hook","otel","statusline","transcript","pane","orchestrator","vendor"]),Dt=new Cy;Dt.setMaxListeners(0);var Ua=process.env.BOSTRAT_STATE_DIR||jn.join(process.env.HOME||Oy.tmpdir(),".bostrat");function qo(t){return t?.cwd?jn.join(t.cwd,".bostrat","events"):jn.join(Ua,"events")}var Pu=(t,e)=>jn.join(t,`${e}.ndjson`),Wo=new Map;async function Dy(t,e){if(!Wo.has(e)){let o=(await F(t,e)).reduce((s,a)=>a.seq>s?a.seq:s,0);Wo.set(e,o)}let n=Wo.get(e)+1;return Wo.set(e,n),n}function jy(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||!Py.has(e.type))throw new Error(`appendEvent: unknown event type "${e?.type}"`);if(e.actor&&!Ny.has(e.actor))throw new Error(`appendEvent: bad actor "${e.actor}"`);if(e.source&&!By.has(e.source))throw new Error(`appendEvent: bad source "${e.source}"`);let r=typeof t=="string"?jn.join(Ua,"events"):qo(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=jy(c,Number(e.payload?.ts??e.ts)||Date.now())}let s=await Dy(r,n),a=e.userId??process.env.BOSTRAT_OWNER??null,i={id:e.id||My(),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 Fa.mkdir(r,{recursive:!0}),await Fa.appendFile(Pu(r,n),JSON.stringify(i)+`
|
|
24
|
-
`)
|
|
25
|
-
`)
|
|
26
|
-
|
|
13
|
+
`,"utf8")}catch(e){console.warn(`[handoff-debug] failed to write ${Nn}: ${e?.message||e}`)}}async function V(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&&zo())try{e.paneTail=qa((await wt(e.sessionName,{lines:35})).slice(-4e3))}catch{e.paneTail=null}(zo()||e.error||/^(checkpoint|spawn|hydrate|commit|done|summarize|clear|resume|ready|send|failed)/.test(String(e.phase||e.step||"")))&&await qy(e)}async function Iu({limit:t=80}={}){try{return(await Mr.readFile(Nn,"utf8")).split(`
|
|
14
|
+
`).filter(Boolean).slice(-t).map(r=>{try{let o=JSON.parse(r);return typeof o?.paneTail=="string"&&(o.paneTail=qa(o.paneTail)),o}catch{return{raw:qa(r)}}})}catch{return[]}}var Ru={DEBUG_DIR:Ga,LOG_FILE:Nn};var it=Ky(Gy);function Nr(t){return new Promise(e=>setTimeout(e,t))}function Jo(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",Pt=process.env.BOSTRAT_TMUX_SOCKET||"/tmp/tmux-1000/default",zy=process.env.BOSTRAT_DEFAULT_CWD||process.env.HOME||"/",Va=process.env.BOSTRAT_AGENT_CMD||"claude --dangerously-skip-permissions",za="bostrat-usage-probe",Jy=new Set([za]);function Ja(t,e){let n=Pn(t),r=/(^|\s)(--session-id|--resume|--continue|-r|-c)(\s|=|$)/.test(t);return!n||r?t:`${t} --session-id ${e}`}function Pn(t){return/(^|\/|\s)claude(\s|$)/.test(String(t||""))}function Bn(t){return/(^|\/|\s)claude(\s|$)/.test(t)?"claude":/cursor/.test(t)?"cursor":/codex/.test(t)?"codex":/gemini/.test(t)?"gemini":"agent"}async function Xa(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 Xo(t){return Xa(String(t||"").trim().split(/\s+/)[0])}var Ya={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",Pt,...t]}async function _e(){if(!Ka.existsSync(Pt))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 Pr(){if(await _e())return!0;try{Ka.mkdirSync(Pt.replace(/\/[^/]+$/,""),{recursive:!0}),await it(N,B("new-session","-d","-s","keeper","sleep 2147483647"))}catch{}return await _e()}var Xy=/^[A-Za-z0-9_.-]{1,64}$/;function mt(t){return typeof t=="string"&&Xy.test(t)}async function nn(){if(!Ka.existsSync(Pt))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=>!Jy.has(r.name)&&!r.name.startsWith("bostrat-view-"))}catch{return[]}}async function X(t){return(await nn()).find(n=>n.name===t)||null}var Yy=/^[A-Za-z0-9 ._↻-]+$/,Zy=64;function Qy(t){if(typeof t!="string")return!1;let e=t.trim();return e.length>=1&&e.length<=Zy&&Yy.test(e)}function Za(t){return t.trim().replace(/\s+/g,"-").toLowerCase().replace(/[^A-Za-z0-9_.-]/g,"").replace(/^-+|-+$/g,"")}async function tb(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 rn(t,e=zy,n={}){if(!Qy(t))throw new Error("displayName must be 1\u201364 chars (letters, digits, space . _ -)");let r=t.trim(),o=Za(r);if(!mt(o))throw new Error("resulting tmux session name is invalid");if(!await Pr())throw new Error(`host tmux server not running on ${Pt} and auto-start failed \u2014 start one: ${N} -S ${Pt} new-session -d -s keeper "sleep 2147483647"`);if(await tb(o))throw new Error(`tmux session "${o}" already exists`);let s=n.agentCmd||Va,a=n.agent||Bn(s),i=!!n.resumeSessionId,c=n.resumeSessionId||Vy(),l=i?s:Ja(s,c),u=i?n.pinned===!0:/(^|\s)--session-id(\s|=)/.test(l),f=a==="claude"&&Pn(s),d=n.firstRun===!0;d||(f&&await Te(e),a==="codex"&&await Cn(e),a==="cursor"&&await Mn(e));let m=f&&!d?Ko():null,h=ku(),w=["-e",`${Wo}=${h}`];m&&w.push("-e",`ANTHROPIC_API_KEY=${m}`);try{await it(N,B("new-session","-d","-s",o,"-c",e,...w))}catch(y){throw Jo(m?Jo(y,m):y,h)}return await on(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 Jt(t){if(!mt(t))throw new Error("invalid session name");await it(N,B("kill-session","-t",t))}async function $u(t,e){if(mt(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 wt(t,{lines:e=40}={}){if(!mt(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 eb=Number(process.env.BOSTRAT_PROMPT_CAPTURE_COLS)||100,nb=Number(process.env.BOSTRAT_PROMPT_CAPTURE_ROWS)||40,At=Number(process.env.BOSTRAT_PROMPT_CAPTURE_LINES)||80,rb=Number(process.env.BOSTRAT_BOX_COLS)||120,ob=Number(process.env.BOSTRAT_BOX_ROWS)||40;async function Ou(t){if(!mt(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 on(t,{cols:e=rb,rows:n=ob}={}){if(mt(t)&&!await Ou(t))try{let r=await Mu(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 Nr(80)}catch{}}async function Cu(t){if(!mt(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 Mu(t){if(!mt(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 Yo(t,{cols:e=eb,rows:n=nb}={}){if(mt(t)&&!await Ou(t))try{let r=await Mu(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 Nr(80))}catch{}}async function Ee(t){return await Yo(t),wt(t,{lines:At})}var sb=new Set(["Up","Down","Left","Right","Space","Enter","Tab","BTab","Escape","BSpace"]);async function kt(t,e,{settleMs:n=40,kind:r}={}){if(!mt(t))throw new Error("invalid session name");let o=(Array.isArray(e)?e:[e]).filter(s=>sb.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 Nr(n);return V({flow:"tmux",phase:"send-keys",sessionName:t,kind:r||"keys",keys:o.join(" ")}).catch(()=>{}),o.length}async function Zo(t,e,{kind:n}={}){if(!mt(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")),V({flow:"tmux",phase:"send-keys",sessionName:t,kind:n||"wipe",keys:`BSpace x${r}`}).catch(()=>{}),r}async function Qo(t,e,{perCharDelayMs:n=120,kind:r}={}){if(!mt(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 Nr(n);V({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(!mt(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 Nr(s),await it(N,B("send-keys","-t",t,"Enter"))}V({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 ab from"node-pty";async function Pu(t,e,n={}){if(!mt(e)){ts(t,`invalid session name: ${e}`),t.close(1008,"invalid session name");return}if(!await _e()){ts(t,`host tmux server not running on ${Pt}`),t.close(1011,"tmux server unavailable");return}if(!await X(e)){ts(t,`session "${e}" not found`),t.close(1008,"session not found");return}let r=Nu(n.cols,120,Bu),o=Nu(n.rows,32,Du),s;try{s=ab.spawn(N,["-S",Pt,"attach-session","-d","-t",e],{name:"xterm-256color",cols:r,rows:o,cwd:process.env.HOME||"/",env:{...process.env,TERM:"xterm-256color"}})}catch(a){ts(t,`failed to spawn tmux attach: ${a.message}`),t.close(1011,"spawn failed");return}s.onData(a=>{t.readyState===t.OPEN&&Qa(t,{type:"data",data:a})}),s.onExit(({exitCode:a,signal:i})=>{t.readyState===t.OPEN&&(Qa(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"&&ib(i.cols,i.rows))try{s.resize(Math.min(500,i.cols|0),Math.min(500,i.rows|0))}catch{}}}),t.on("close",()=>{try{s.kill()}catch{}on(e).catch(()=>{})})}var Bu=20,Du=6;function ib(t,e){return Number.isFinite(Number(t))&&Number.isFinite(Number(e))&&Number(t)>=Bu&&Number(e)>=Du}function Nu(t,e,n){let r=Number(t);return!Number.isFinite(r)||r<n?e:Math.min(500,r|0)}function Qa(t,e){try{t.send(JSON.stringify(e))}catch{}}function ts(t,e){Qa(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 Br from"node:fs";import cb from"node:fs/promises";import Dn from"node:path";import lb from"node:os";var Lu={"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},ub=2e5,ti=process.env.BOSTRAT_CLAUDE_PROJECTS||Dn.join(lb.homedir(),".claude","projects");function ei(t){return t.replace(/[^A-Za-z0-9]/g,"-")}function ni(t){let e=Dn.join(ti,ei(t)),n;try{n=Br.readdirSync(e).filter(o=>o.endsWith(".jsonl"))}catch{return null}let r=null;for(let o of n){let s=Dn.join(e,o);try{let{mtimeMs:a}=Br.statSync(s);(!r||a>r.mtimeMs)&&(r={full:s,mtimeMs:a})}catch{}}return r?r.full:null}function de(t,e){if(!e)return null;let n=Dn.join(ti,ei(t),`${e}.jsonl`);return Br.existsSync(n)?n:null}function Fu(t){if(!t)return[];let e=Dn.join(ti,ei(t)),n;try{n=Br.readdirSync(e).filter(o=>o.endsWith(".jsonl"))}catch{return[]}let r=[];for(let o of n)try{let s=Br.statSync(Dn.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 db(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 Dr(t,e=null,{fallbackNewest:n=!0}={}){if(!t)return null;let r,o;if(e){if(r=de(t,e),o=!0,!r)return null}else if(n){if(r=ni(t),o=!1,!r)return null}else return null;let s;try{s=db(await It(r))}catch{return null}if(!s)return null;let a=Object.prototype.hasOwnProperty.call(Lu,s.model),i=Lu[s.model]||ub;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 fb(t,e){return!t||!e?null:de(t,e)}function pb(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 mb(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 hb(t){return Array.isArray(t)&&t.length>0&&t.every(e=>e&&e.type==="tool_result")}function ri(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 Uu(t,e){if(!t.uuid)return null;let n=ri(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 wb=/\.(png|jpe?g|gif|webp|bmp|svg|tiff?|avif|heic|heif|ico)$/i;async function oi(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"&&wb.test(w)&&s.set(h.id,w.split("/").pop())}if(hb(l.content)){let h=Uu(c,s);h&&o.push({role:"assistant",text:"",images:h,ts:c.timestamp?Date.parse(c.timestamp):null,model:null});continue}let f=pb(l.content),d=u==="assistant"?mb(l.content):null,m=u==="user"?Uu(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 gb=(t,e)=>t.length>e?`${t.slice(0,e).trimEnd()}\u2026`:t;async function Hu(t,e,{turns:n=16}={}){let r=fb(t,e);if(!r)return"";let o=await oi(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}:** ${gb(s.text||c,700)} ${i}`.trim()}).join(`
|
|
21
|
+
|
|
22
|
+
`):""}import zb from"node:fs";import Lr from"node:fs/promises";import Fn from"node:path";import yb from"node:fs/promises";import{execFile as bb}from"node:child_process";import{promisify as kb}from"node:util";var si=kb(bb),xb=Number(process.env.BOSTRAT_CODEX_UPDATE_TIMEOUT_MS)||5*6e4,Wu=new Set,qu=Promise.resolve(),ai=null;function Ku(t){ai=typeof t=="function"?t:null}function Sb(t){let e=String(t||""),n=e.indexOf("/lib/node_modules/");return n>0?e.slice(0,n):null}function Vu({sessionName:t,from:e,to:n}={}){let r=String(n||"").trim();!r||Wu.has(r)||(Wu.add(r),qu=qu.then(()=>vb({sessionName:t,from:e,to:r})).catch(()=>{}))}async function vb({sessionName:t,from:e,to:n}){await V({flow:"codex-update",phase:"start",sessionName:t,from:e,to:n}).catch(()=>{});try{let r=(await si("/bin/sh",["-c","command -v codex"])).stdout.trim();if(!r)throw new Error("codex not on PATH");let o=await yb.realpath(r),s=Sb(o),a=s?{...process.env,PATH:`${s}/bin:${process.env.PATH||""}`}:process.env;await si("npm",["install","-g","@openai/codex"],{env:a,timeout:xb});let i=(await si(r,["--version"])).stdout.trim().replace(/^codex-cli\s*/,"")||n;await V({flow:"codex-update",phase:"done",sessionName:t,from:e,to:n,version:i}).catch(()=>{}),await Gu({sessionName:t,from:e,to:n,version:i,status:"updated"})}catch(r){let o=String(r?.message||r).slice(0,300);await V({flow:"codex-update",phase:"failed",sessionName:t,from:e,to:n,error:o}).catch(()=>{}),await Gu({sessionName:t,from:e,to:n,status:"failed",error:o})}}async function Gu(t){if(ai)try{await ai(t)}catch{}}var Ab=/^\s*[›>*\-❯]?\s*(\d+)[.)]\s+(.+?)\s*$/,zu=/\b(allow|approve|apply (this )?patch|run (this )?command|do you want to run|grant)\b/i;function Tb(t,{provider:e,box:n}){let r=String(t).replace(/\r/g,"").split(`
|
|
23
|
+
`);if(!zu.test(t))return null;let o=[],s=!1;for(let l=r.length-1;l>=0;l--){let u=r[l].match(Ab);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"&&zu.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 es=/(^|\n)\s*›/,Yu=/^\s*Welcome to Codex\b/;function jn(t){let e=String(t??"").replace(/\r/g,""),n=e.split(`
|
|
24
|
+
`);for(let r=n.length-1;r>=0;r--)if(Yu.test(n[r]))return n.slice(r).join(`
|
|
25
|
+
`);return e}function _b(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 Eb(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 Ju=/^\s*(>)?\s*(\d+)\.\s+(.+?)\s*$/;function Ib(t,e=""){let n=jn(t);if(es.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(Ju);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(Ju.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&&!Yu.test(r[l])&&_b(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=Eb(l.label);return{label:l.label,description:[l.desc,u].filter(Boolean).join(" \xB7 ")}})}]}}var Xu=/Enter this one-time code/,Rb=/^\s*([A-Z0-9]{2,10}-[A-Z0-9]{2,10})\s*$/;function $b(t,e=""){let n=jn(t);if(es.test(n)||!Xu.test(n))return null;let r=n.split(`
|
|
27
|
+
`),o=r.findIndex(i=>Xu.test(i)),s="";for(let i=o+1;i<r.length;i++){let c=r[i].match(Rb);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 Ob(t,e=""){let n=jn(t);return es.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 Cb(t,e=""){let n=jn(t);return es.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 ii=/Run Everything|Add a follow-up|Plan, search, build anything/;function Mb(t,e=""){let n=String(t);if(!/Press any key to log in/.test(n)||ii.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 Nb=/click this link to log in|Signing in with the browser/;function Pb(t,e=""){let n=String(t);if(!Nb.test(n)||ii.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 Bb(t,e=""){let n=String(t);return!/Scan this QR code to log in/.test(n)||ii.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 Zu(t,e,n=""){if(t!=="cursor"&&t!=="codex")return null;if(t==="codex"){let r=$b(e,n)||Ob(e,n)||Cb(e,n)||Ib(e,n);if(r)return r}if(t==="cursor"){let r=Pb(e,n)||Bb(e,n)||Mb(e,n);if(r)return r}return Tb(e,{provider:t,box:n})}function Qu(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 Db=()=>Number(process.env.BOSTRAT_AGENT_READY_TIMEOUT_MS)||9e4,ci=Number(process.env.BOSTRAT_AGENT_READY_POLL_MS)||500,jr=Number(process.env.BOSTRAT_AGENT_READY_SETTLE_MS)||800,St={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/}},jb=/Trust this workspace|\[a\]\s*Trust/,Lb=/Update available!\s*([\d][\w.-]*)\s*->\s*([\d][\w.-]*)/;function Fb(t){let e=String(t||"");if(!St.codex.block.test(e))return null;let n=jn(e);if(!St.codex.block.test(n))return null;let r=n.match(Lb);return{from:r?.[1]||null,to:r?.[2]||null}}function Ie(t){return new Promise(e=>setTimeout(e,t))}var Ub=()=>Number(process.env.BOSTRAT_GATE_RESEND_MS)||3e3,ns=new Map;function td(t,e){let n=Date.now(),r=Ub();for(let[s,a]of ns)n-a>=r&&ns.delete(s);let o=`${t}::${e}`;return ns.has(o)?!1:(ns.set(o,n),!0)}var Hb=68,Wb=/^\s*[─━]{10,}\s*$/;function qb(t){let e=0;for(let n of String(t).split(`
|
|
30
|
+
`))Wb.test(n)&&(e=Math.max(e,n.trimEnd().length));return e}function li(t,e){let n=St[t];if(!n?.busy)return"idle";if(ui(t,e))return"unknown";if(n.up&&!n.up.test(e))return"idle";if(n.busy.test(e)||n.busyAlt&&n.busyAlt.test(e))return"busy";let r=qb(e);return t==="claude"&&r>0&&r<Hb?"unknown":"idle"}var Gb=/\bWaiting for \d+\b[^\n]*\bto finish\b|\/workflows to (?:stop|monitor)\b/;function nd(t,e){return t==="claude"&&Gb.test(String(e||""))}function Ln(t,e){return li(t,e)==="busy"}function rs(t,e){return li(t,e)!=="idle"}function os(t,e){let n=St[t];return n?.up?n.up.test(e):!0}function Kb(t,e){let n=St[t];return n?ui(t,e)?!1:n.up.test(e)&&li(t,e)==="idle":!0}function ui(t,e){let n=St[t];return n?n.auth?.test(e)&&!n.up.test(e)?!0:n.block?.test(e)?t==="codex"?!0:!n.up.test(e):!1:!1}async function ed(t,e,n){let r=Date.now()+n,o=0;for(await on(t).catch(()=>{});;){let s=await wt(t);if(Kb(e,s)){await Ie(jr);return}if(e==="cursor"&&jb.test(s)&&!St.cursor.up.test(s)&&o<3){if(!td(t,"cursor-trust")){await Ie(ci);continue}o+=1,await V({flow:"agent-ready",phase:"gate-accept",sessionName:t,agent:e,attempt:o}).catch(()=>{}),await xt(t,"a",{enter:!1,kind:"cursor-trust-accept"}),await Ie(jr);continue}if(e==="codex"&&o<3){let a=Fb(s);if(a){if(!td(t,`codex-update:${a.from}->${a.to}`)){await Ie(ci);continue}o+=1,await V({flow:"agent-ready",phase:"gate-accept",sessionName:t,agent:e,attempt:o,update:`${a.from} -> ${a.to}`}).catch(()=>{}),await xt(t,"3",{enter:!1,kind:"codex-update-skip"}),Vu({sessionName:t,from:a.from,to:a.to}),await Ie(jr);continue}}if(Date.now()>r){if(ui(e,s)){let a=`${e} is blocked on an interactive prompt`;throw e==="codex"?a=St.codex.auth.test(s)?"codex isn't signed in on this box \u2014 answer the Sign in card in chat (Device Code works from any device) or finish signing in from the Terminal tab, then send again":"codex is stuck on its update prompt (auto-skip didn't clear it) \u2014 open the terminal and choose Skip, then send again":e==="cursor"&&St.cursor.auth.test(s)?a="cursor isn't signed in on this box \u2014 answer the Sign in card in chat (the login link works from any device) or finish signing in from the Terminal tab, then send again":e==="claude"&&St.claude.auth.test(s)?a="claude isn't signed in on this box \u2014 answer the setup cards in chat (or finish signing in from the Terminal tab), then send again":e==="claude"&&St.claude.block.test(s)&&(a='claude is waiting on its "Press Enter to continue" screen \u2014 answer the Continue card in chat (or press Enter in the Terminal tab), then send again'),V({flow:"agent-ready",phase:"blocked",sessionName:t,agent:e,timeoutMs:n,gateAccepts:o,error:a}).catch(()=>{}),new Error(a)}console.warn(`[agent-ready] ${e} readiness unconfirmed in ${t} after ${n}ms \u2014 driving the box anyway.`),V({flow:"agent-ready",phase:"timeout",sessionName:t,agent:e,timeoutMs:n,gateAccepts:o,paneBlocked:!!St[e]?.block?.test(s),paneUp:!!St[e]?.up?.test(s),paneBusy:!!St[e]?.busy?.test(s)}).catch(()=>{}),await Ie(jr);return}await Ie(ci)}}function rd({agent:t,agentCmd:e}={}){let n=e?Bn(e):Bn(t||"");return{label:n,realClaude:e?Pn(e):n==="claude",realCodex:e?/codex/.test(e)&&!/fake-codex|fake-lead/.test(e):n==="codex",realCursor:e?/cursor/.test(e)&&!/fake-cursor|fake-box/.test(e):n==="cursor"}}function ss({cwd:t,sessionName:e,agent:n,agentCmd:r}={}){let{realCodex:o,realCursor:s}=rd({agent:n,agentCmd:r});return!e||!o&&!s?null:Re({cwd:t,sessionName:e,agent:n,agentCmd:r}).catch(()=>{})}async function Re({cwd:t,sessionId:e,sessionName:n,agent:r,agentCmd:o,timeoutMs:s=Db()}={}){let{label:a,realClaude:i,realCodex:c,realCursor:l}=rd({agent:r,agentCmd:o});if(i&&t&&await Te(t),c&&t&&await Cn(t),l&&t&&await Mn(t),(i||c||l)&&n){await ed(n,a,s);return}let u=St[r]?r:St[a]?a:null;if(process.env.BOSTRAT_FAKE_POLL_READY==="1"&&n&&u){await ed(n,u,s);return}await Ie(jr)}var Vb=(()=>{let t=Number(process.env.BOSTRAT_CODEX_SUBMIT_SETTLE_MS??120);return Number.isFinite(t)&&t>=0?t:120})();function fe(t){return t==="codex"?{enter:!0,submitDelayMs:Vb}:{enter:!0}}var ad=".bostrat/handoff",id="latest.md",cd="Checkpoint: write a work-in-progress handoff",ld="HANDOFF WRITTEN",ud="Resume from .bostrat/handoff/latest.md",Jb="/clear",Xb=t=>`${cd} to the EXACT absolute path ${t} (write it there specifically \u2014 NOT into any sub-project or other directory; create the directory if needed, overwrite the file) so a FRESH Claude context with none of this conversation can resume seamlessly. Use these sections: Objective; Done (with file paths); In progress (exact state); Next steps (ordered); Key files & commands; Open decisions/blockers; Resume instruction (one line). Be concrete and self-contained \u2014 assume the reader has none of this chat. When the file is written, reply only with ${ld} and take no further action.`,Yb=`${ud}: read it, restate the Objective and the single next step in one line, then continue the work from 'Next steps'.`,dd="Beat: write a",fd="BEAT WRITTEN",Zb=".bostrat/arc",Qb="beat.txt",od=200,pd=4,md=5,tk=36,ek=t=>`${dd} short summary of what this workstream accomplished, to the EXACT absolute path ${t} (write it there specifically \u2014 NOT into any sub-project or other directory; create the directory if needed, overwrite the file). Format the file as: FIRST LINE = one sentence of at most 18 words, plain past tense, saying the outcome \u2014 no heading, no bullet, no trailing list. THEN, only if this workstream covered several separable pieces of work, up to ${pd} further lines, each starting with "- " and naming ONE piece in at most ${md} words \u2014 a label, not a sentence (e.g. "- stale working flag on poll"). Write nothing else in the file. When the file is written, reply only with ${fd} and take no further action.`,sd=/^(?:[-–—*•·]|\d+[.)])\s+/,nk=(t,e,n)=>{let r=t.split(/\s+/).filter(Boolean).slice(0,e).join(" ");return r.length>n?`${r.slice(0,n-1).trimEnd()}\u2026`:r};function rk(t){let e=String(t||"").split(/\r?\n/).map(s=>s.trim()).filter(Boolean),n=[],r=[];for(let s of e)if(sd.test(s)){if(r.length>=pd)continue;let a=nk(s.replace(sd,"").replace(/[.;,:]+$/,""),md,tk);a&&r.push(a)}else r.length||n.push(s);let o=n.join(" ").replace(/\s+/g," ").replace(/[:\-–—]+$/,"").trim();if(!o)throw new Error("beat file has no summary line");return o.length>od&&(o=`${o.slice(0,od-1).trimEnd()}\u2026`),{text:o,items:r.length>1?r:[]}}async function hd(t,e,{timeoutMs:n=9e4}={}){let r=Fn.join(e,Zb),o=Fn.join(r,Qb);await Lr.mkdir(r,{recursive:!0});let s=await as(o),a=await X(t);if(!a)throw new Error(`session "${t}" not found`);return await is(t,a,ek(o)),await Sd(o,s,n),rk(await Lr.readFile(o,"utf8"))}function ok(t,e){let n=(e||"").trim();return n?t==="assistant"?n===ld||n===fd:n.startsWith(cd)||n===Jb||n.startsWith(ud)||n.startsWith(dd):!1}var sk=6e5;function wd(t){let e=[];for(let r of t?.events||[]){if(r.type!=="lead-handoff"||!r.from)continue;let o=r.at??Date.now(),s=r.handoffStartedAt??o-sk;e.push({box:r.from,from:s,to:o}),r.to&&r.to!==r.from&&e.push({box:r.to,from:s,to:o})}let n=t?.handoff;return n?.fromBox&&n.startedAt&&n.finishedAt&&e.push({box:n.fromBox,from:n.startedAt,to:n.finishedAt}),n?.toBox&&n.startedAt&&n.finishedAt&&e.push({box:n.toBox,from:n.startedAt,to:n.finishedAt}),e}function gd(t,e){let n=[];for(let r of t?.boxes||[]){let o=e?.(r.name);o?.startedAt&&n.push({box:r.name,from:o.startedAt,to:o.finishedAt||Date.now()+1e12})}return n}function yd(t,{windows:e=[]}={}){if(ok(t.role,t.text))return!0;if(t.role!=="assistant"||!t.ts)return!1;for(let n of e)if(t.box===n.box&&t.ts>=n.from&&t.ts<=n.to)return!0;return!1}var ak=3500,ik=3e5,di=new Map;function cs(t){return di.get(t)||null}async function bd(t){let e=di.get(t);if(e&&!e.finishedAt)throw new Error("a handoff is already running for this session");let n=await X(t);if(!n)throw new Error(`session "${t}" not found`);if(!n.cwd)throw new Error(`can't resolve cwd for "${t}"`);let r={name:t,step:"summarize",startedAt:Date.now(),finishedAt:null,error:null,file:Fn.join(n.cwd,ad,id)};return di.set(t,r),ck(r,n.cwd).catch(o=>{r.error=o?.message||String(o),r.finishedAt=Date.now()}),r}async function ck(t,e){t.step="summarize",await kd(t.name,e),t.step="clear";let n=await X(t.name);await is(t.name,n,"/clear"),await vd(ak),t.step="resume",await is(t.name,n,Yb),t.step="done",t.finishedAt=Date.now()}async function kd(t,e){let n=Fn.join(e,ad),r=Fn.join(n,id);await Lr.mkdir(n,{recursive:!0});let o=await as(r),s=await X(t);return await V({flow:"checkpoint",phase:"summarize-start",sessionName:t,cwd:e,file:r,beforeMtime:o}),await is(t,s,Xb(r)),await Sd(r,o,ik),await V({flow:"checkpoint",phase:"summarize-done",sessionName:t,file:r,afterMtime:await as(r)}),await lk(n,r),r}async function xd(t){let e=await X(t);if(!e)throw new Error(`session "${t}" not found`);if(!e.cwd)throw new Error(`can't resolve cwd for "${t}"`);return kd(t,e.cwd)}async function as(t){try{return(await Lr.stat(t)).mtimeMs}catch{return 0}}async function Sd(t,e,n){let r=Date.now()+n;for(;;){if(await as(t)>e)return;if(Date.now()>r)throw new Error(`timed out waiting for the handoff file to be written (${t})`);await vd(2e3)}}async function lk(t,e){try{if(!zb.existsSync(e))return;let n=new Date().toISOString().replace(/[:.]/g,"-");await Lr.copyFile(e,Fn.join(t,`${n}.md`))}catch{}}function vd(t){return new Promise(e=>setTimeout(e,t))}async function is(t,e,n){if(e){let o=e.agent==="claude"?process.env.BOSTRAT_AGENT_CMD||"claude":e.agent==="cursor"?process.env.BOSTRAT_CURSOR_CMD||"cursor-agent":e.agent==="codex"?process.env.BOSTRAT_CODEX_CMD||"codex":"";await Re({cwd:e.cwd,sessionId:e.sessionId,sessionName:t,agent:e.agent,agentCmd:o})}let r=await X(t);if(!r)throw new Error(`session "${t}" disappeared before the orchestration prompt could be sent`);await xt(t,n,{kind:"orchestration-drive",...fe(r.agent)})}import U from"node:fs/promises";import H from"node:path";import{randomUUID as So}from"node:crypto";import{execFile as LA}from"node:child_process";import{promisify as FA}from"node:util";import ip from"node:path";import fi from"node:fs/promises";import Un from"node:path";import uk from"node:os";import{EventEmitter as dk}from"node:events";import{randomUUID as fk}from"node:crypto";var pk=new Set(["session.ready","turn.user","turn.agent","turn.retracted","tool.called","tool.result","prompt.raised","prompt.answered","context.usage","cost.recorded","handoff.started","handoff.step","handoff.done","handoff.failed","subtask.delegated","subtask.merged","job.started","pr.state.changed","pr.offered","pr.offer.answered","box.state.changed","view.state.changed","view.spinup.failed","mock.state.changed","runbook.state.changed","ticket.linked","ticket.sync.failed","ticket.move.offered","ticket.move.answered","ticket.state.changed","ticket.commented","ticket.flow.changed","ticket.create.offered","ticket.create.answered","ticket.created","notify.raised","provider.connected","track.summary.recorded","changelog.published","stall.detected","error"]),mk=new Set(["user","agent","orchestrator","system"]),hk=new Set(["hook","otel","statusline","transcript","pane","orchestrator","vendor"]),Lt=new dk;Lt.setMaxListeners(0);var pi=process.env.BOSTRAT_STATE_DIR||Un.join(process.env.HOME||uk.tmpdir(),".bostrat");function us(t){return t?.cwd?Un.join(t.cwd,".bostrat","events"):Un.join(pi,"events")}var Ad=(t,e)=>Un.join(t,`${e}.ndjson`),ls=new Map;async function wk(t,e){if(!ls.has(e)){let o=(await F(t,e)).reduce((s,a)=>a.seq>s?a.seq:s,0);ls.set(e,o)}let n=ls.get(e)+1;return ls.set(e,n),n}function gk(t,e){let n=Array.isArray(t?.epochs)?t.epochs:null;if(!(!n||n.length===0)){if(!Number.isFinite(e))return n.length-1;for(let r=n.length-1;r>=0;r--){let o=n[r].startedAt;if(o==null||o<=e)return r}return 0}}async function O(t,e){let n=typeof t=="string"?t:t?.id;if(!n)throw new Error("appendEvent: a track (or trackId) is required");if(!e||!pk.has(e.type))throw new Error(`appendEvent: unknown event type "${e?.type}"`);if(e.actor&&!mk.has(e.actor))throw new Error(`appendEvent: bad actor "${e.actor}"`);if(e.source&&!hk.has(e.source))throw new Error(`appendEvent: bad source "${e.source}"`);let r=typeof t=="string"?Un.join(pi,"events"):us(t),o=e.epoch;if(o==null&&typeof t=="object"&&e.boxId&&Array.isArray(t?.boxes)){let c=t.boxes.find(l=>l.name===e.boxId);o=gk(c,Number(e.payload?.ts??e.ts)||Date.now())}let s=await wk(r,n),a=e.userId??process.env.BOSTRAT_OWNER??null,i={id:e.id||fk(),trackId:n,boxId:e.boxId??null,...o!=null?{epoch:o}:{},...a?{userId:a}:{},seq:s,ts:e.ts??Date.now(),type:e.type,actor:e.actor??"system",provider:e.provider??null,source:e.source??"orchestrator",payload:e.payload??{},...e.causationId?{causationId:e.causationId}:{},...e.correlationId?{correlationId:e.correlationId}:{}};return await fi.mkdir(r,{recursive:!0}),await fi.appendFile(Ad(r,n),JSON.stringify(i)+`
|
|
31
|
+
`),Lt.emit("event",i),Lt.emit(`track:${n}`,i),i}async function F(t,e){let n,r;typeof t=="string"&&e?(n=t,r=e):(r=typeof t=="string"?t:t?.id,n=typeof t=="string"?Un.join(pi,"events"):us(t));let o;try{o=await fi.readFile(Ad(n,r),"utf8")}catch{return[]}let s=[];for(let a of o.split(`
|
|
32
|
+
`))if(a.trim())try{s.push(JSON.parse(a))}catch{}return s}function Td(t,e){let n=`track:${t}`;return Lt.on(n,e),()=>Lt.off(n,e)}var mi={"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}},_d={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 Ed(t){if(!t)return null;let e=String(t).trim();return mi[e]?e:_d[e]?_d[e]:Object.keys(mi).find(r=>e.startsWith(r))||null}var ds=t=>Ed(t)!==null;function Xt(t,e={}){let n=mi[Ed(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 Id=()=>({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 Rd(t,e){e&&(t.input+=e.input,t.output+=e.output,t.cacheRead+=e.cacheRead,t.cacheCreation+=e.cacheWrite)}function ps(t){let e=t.payload?.model||"unknown",n=yk(t.payload?.tokens),r=Number(t.payload?.usd)||0,o=!1;!r&&n&&(r=Xt(e,n),r>0&&(o=!0));let s=n?n.input+n.output+n.cacheRead+n.cacheWrite:0,a=!r&&s>0&&!ds(e);return{usd:r,model:e,tokens:n,repriced:o,unpriced:a}}function Yt(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=Id();for(let f of n){let{usd:d,model:m,tokens:h,repriced:w,unpriced:y}=ps(f),b=f.provider||"unknown",A=f.boxId||"unattributed";if(w&&i++,y&&(c++,l.add(m)),a+=d,Rd(u,h),r[m]||={usd:0,tokens:Id(),events:0},r[m].usd+=d,r[m].events+=1,Rd(r[m].tokens,h),o[b]||={usd:0,events:0},o[b].usd+=d,o[b].events+=1,s[A]||={usd:0,events:0,provider:b,models:new Set,label:e[A]||A},s[A].usd+=d,s[A].events+=1,m!=="unknown"&&s[A].models.add(m),f.epoch!=null){let _=(s[A].epochs||={})[f.epoch]||={usd:0,events:0,provider:b,models:new Set};_.usd+=d,_.events+=1,_.provider=b,m!=="unknown"&&_.models.add(m)}}return{totalUsd:Fr(a),totalTokens:u,turns:n.length,repricedTurns:i,unpricedTurns:c,unpricedModels:[...l],byModel:fs(r,f=>({...f,usd:Fr(f.usd)})),byProvider:fs(o,f=>({...f,usd:Fr(f.usd)})),byBox:fs(s,f=>({usd:Fr(f.usd),events:f.events,provider:f.provider,label:f.label,models:[...f.models],...f.epochs?{epochs:fs(f.epochs,d=>({usd:Fr(d.usd),events:d.events,provider:d.provider,models:[...d.models]}))}:{}}))}}var Fr=t=>Math.round(t*1e4)/1e4,fs=(t,e)=>Object.fromEntries(Object.entries(t).map(([n,r])=>[n,e(r)]));import yt from"node:fs/promises";import Rt from"node:path";import Ur from"node:os";import{randomUUID as xk}from"node:crypto";import{execFile as Sk}from"node:child_process";import{promisify as vk}from"node:util";import{chromium as Ak}from"playwright-core";import hi from"node:fs/promises";import bk from"node:path";var kk=0;async function Zt(t,e,{mode:n}={}){await hi.mkdir(bk.dirname(t),{recursive:!0});let r=`${t}.${process.pid}.${++kk}.tmp`;await hi.writeFile(r,JSON.stringify(e,null,2)+`
|
|
33
|
+
`,n!=null?{mode:n}:void 0),await hi.rename(r,t)}var Od=vk(Sk),Cd=process.env.BOSTRAT_STATE_DIR||Rt.join(process.env.HOME||Ur.tmpdir(),".bostrat"),Md=Rt.join(Cd,"mocks.json"),Tk=process.env.BOSTRAT_MOCK_ENGINE||"chromium",_k=process.env.BOSTRAT_CHROMIUM_BIN||"/usr/bin/chromium",Ek=process.env.BOSTRAT_FIREFOX_BIN||"firefox",Ik=1300,Hn=2,Rk=32,Nd=1500,Wn=Number(process.env.BOSTRAT_MOCK_TIMEOUT_MS)||75e3,$k=700*1024*1024;async function Ok(){if(process.platform!=="darwin")return Ur.freemem();try{let{stdout:t}=await Od("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:Ur.freemem()}catch{return Ur.freemem()}}var Wt=class extends Error{constructor(e,n=""){super(n?`${e}
|
|
27
34
|
${n.trim().split(`
|
|
28
35
|
`).slice(-6).join(`
|
|
29
|
-
`)}`:e),this.name="MockRenderError"}},nn=null,qa=null;async function le(){return nn||(qa||=(async()=>{try{nn=JSON.parse(await gt.readFile(Wu,"utf8"))}catch{nn={mocks:[]}}finally{qa=null}return nn.mocks||=[],nn})(),qa)}async function Xt(){await zt(Wu,nn)}function Wt(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 Vo=t=>Et.join(t,".bostrat","mocks");function jt(t,e){let n=Vo(t.cwd);return{html:Et.join(n,`${t.id}.v${e}.html`),png:Et.join(n,`${t.id}.v${e}.png`)}}function rn(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 Ka(t){return t.length<24||t.readUInt32BE(12)!==1229472850?null:{width:t.readUInt32BE(16),height:t.readUInt32BE(20)}}var Qy=Buffer.from([137,80,78,71,13,10,26,10]);function Gu(t){return Buffer.isBuffer(t)&&t.length>24&&t.subarray(0,8).equals(Qy)}async function tb(t){let e=Buffer.alloc(24),n=await gt.open(t,"r");try{await n.read(e,0,24,0)}finally{await n.close()}return e}function eb({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 nb(t,e){return t.evaluate(async n=>{let r=new Image;await new Promise((h,m)=>{r.onload=h,r.onerror=()=>m(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 h=0;h<s;h++)for(let m=h*o*4,b=0;b<o;b++,m+=4)Math.abs(c[m]-l[0])+Math.abs(c[m+1]-l[1])+Math.abs(c[m+2]-l[2])>24&&(b<u&&(u=b),b>f&&(f=b),h<d&&(d=h),p=h);return f<0?null:{imgW:o,imgH:s,bg:l,bbox:{left:u,top:d,right:f,bottom:p}}},e.toString("base64"))}async function rb(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 ob({htmlPath:t,outPath:e,width:n}){let r=Date.now(),o;try{o=await Gy.launch({executablePath:Vy,args:["--disable-gpu"],timeout:3e4})}catch(s){throw new Ht("chromium failed to launch",s?.message||"")}try{let a=await(await o.newContext({viewport:{width:n,height:800},deviceScaleFactor:Ln})).newPage();a.setDefaultTimeout(Fn),await a.goto(`file://${t}`,{waitUntil:"load",timeout:Fn}),await a.waitForTimeout(250);let i;for(let l=1;;l++)try{i=await a.screenshot({fullPage:!0,timeout:Fn});break}catch(u){if(l>=3||!/captureScreenshot|Unable to capture/i.test(u?.message||""))throw u;await a.waitForTimeout(500)}try{let l=await nb(a,i),u=l&&eb({...l,pad:Xy*Ln,align:Ln});u&&(i=await rb(a,i,{bg:l.bg,crop:u}))}catch{}if(i.length<qu)throw new Ht(`capture produced a ${i.length}-byte image`);let c=Ka(i);if(!c)throw new Ht("output is not a PNG");return await gt.writeFile(e,i),{width:Math.round(c.width/Ln),height:Math.round(c.height/Ln),dpr:Ln,renderMs:Date.now()-r}}catch(s){throw s instanceof Ht?s:new Ht(/timeout/i.test(s?.message||"")?`render timed out after ${Fn}ms`:"chromium render failed",s?.message||"")}finally{await o.close().catch(()=>{})}}async function Fu({htmlPath:t,outPath:e,width:n}){let r=await gt.mkdtemp(Et.join(Pr.tmpdir(),"bostrat-mock-profile-")),o=Date.now(),s="";try{try{s=(await Uu(Jy,["--headless","--no-remote","--profile",r,`--window-size=${n}`,"--screenshot",e,`file://${t}`],{env:{...process.env,MOZ_HEADLESS:"1"},timeout:Fn,killSignal:"SIGKILL",maxBuffer:4194304})).stderr||""}catch(c){throw s=c?.stderr||"",new Ht(c?.killed?`render timed out after ${Fn}ms`:"firefox exited non-zero",s)}let a=await gt.stat(e).catch(()=>null);if(!a||a.size<qu)throw new Ht(`render produced ${a?`a ${a.size}-byte file`:"no file"} (silent no-op \u2014 another firefox holding the profile?)`,s);let i=Ka(await tb(e));if(!i)throw new Ht("output is not a PNG",s);return{width:i.width,height:i.height,dpr:1,renderMs:Date.now()-o}}finally{await gt.rm(r,{recursive:!0,force:!0}).catch(()=>{})}}async function sb(t){if(Ky==="firefox")return Fu(t);try{return await ob(t)}catch(e){if(/failed to launch/i.test(e?.message||""))return Fu(t);throw e}}var ab=sb;var Ga=Promise.resolve();function Ku(t){return Ga=Ga.then(t,t),Ga}async function Vu(t,e){let n=await le(),r=n.mocks.find(o=>o.id===t);if(r){r.state="rendering",await Xt(),await Wt(r,"rendering",{version:e});try{let o=await Zy();if(o<Yy)throw new Ht(`box is low on memory (${Math.round(o/1e6)} MB available) \u2014 not spawning a browser`);let s=jt(r,e),a=await ab({htmlPath:s.html,outPath:s.png,width:r.width});if(!n.mocks.some(i=>i.id===t)){await gt.rm(s.png,{force:!0}).catch(()=>{});return}Ju(r,{v:e,source:"html",htmlRel:Et.relative(r.cwd,s.html),pngRel:Et.relative(r.cwd,s.png),width:a.width,height:a.height,dpr:a.dpr,renderMs:a.renderMs,createdAt:Date.now()}),await Xt(),await Wt(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 Xt(),await Wt(r,"error",{version:e,error:r.error})}}}function Ju(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 ib({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?zy:Number(r);if(!Number.isInteger(o)||o<320||o>4e3)throw new Error("invalid width (320-4000)");return o}async function Jo({trackId:t,group:e}={}){let n=await le(),r=t?n.mocks.filter(o=>o.trackId===t):n.mocks;return e&&(r=r.filter(o=>o.group===e)),r.map(o=>({...o}))}async function _e(t){let n=(await le()).mocks.find(r=>r.id===t);return n?{...n}:null}async function zu({trackId:t,name:e,html:n,image:r,cwd:o,width:s,group:a,box:i,agent:c,prompt:l}){let u=ib({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&&!Gu(r))throw new Error("image must be a PNG");let f=await le(),p={id:Hy().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 gt.mkdir(Vo(o),{recursive:!0}),f.mocks.push(p),d?(await gt.writeFile(jt(p,1).html,n),await Xt(),await Wt(p,"created"),Ku(()=>Vu(p.id,1))):(await Yu(p,1,r),await Xt(),await Wt(p,"created"),await Wt(p,"ready",{version:1,width:p.width,height:p.height,dpr:p.dpr})),{...p}}async function Xu(t,{html:e,image:n,width:r}={}){let s=(await le()).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&&!Gu(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 gt.mkdir(Vo(s.cwd),{recursive:!0}),a?(s.state="queued",await gt.writeFile(jt(s,i).html,e),await Xt(),await Wt(s,"updated",{version:i}),Ku(()=>Vu(s.id,i))):(await Yu(s,i,n),await Xt(),await Wt(s,"updated",{version:i}),await Wt(s,"ready",{version:i,width:s.width,height:s.height,dpr:s.dpr})),{...s}}async function Yu(t,e,n){let r=Ka(n);if(!r)throw new Error("image must be a PNG");let o=jt(t,e);await gt.writeFile(o.png,n),Ju(t,{v:e,source:"image",htmlRel:null,pngRel:Et.relative(t.cwd,o.png),width:r.width,height:r.height,dpr:1,renderMs:null,createdAt:Date.now()}),t.width=r.width}async function Zu(t){let e=await le(),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 Wt(n,"picked",{version:n.readyVersion,siblings:r}),{mock:{...n},siblings:r}}async function Qu(t){let e=await le(),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=jt(r,o);await gt.rm(s.html,{force:!0}).catch(()=>{}),await gt.rm(s.png,{force:!0}).catch(()=>{})}return await Xt(),await Wt(r,"deleted"),{ok:!0,id:t,name:r.name}}async function td(t){let n=(await le()).mocks.filter(i=>i.trackId===t.id);if(!n.length)return 0;let r=Et.join(Hu,"mock-archive",t.id),o=Vo(t.cwd),s=Et.join(r,".bostrat","mocks");await gt.mkdir(s,{recursive:!0});let a=await gt.readdir(o).catch(()=>[]);for(let i of a)await gt.copyFile(Et.join(o,i),Et.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 gt.stat(Et.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 Xt(),n.length}async function ed(t){let e=await le(),n=e.mocks.filter(r=>r.trackId===t);return n.length&&(e.mocks=e.mocks.filter(r=>r.trackId!==t),await Xt()),n.length}import Br from"node:fs/promises";import It from"node:path";import cb from"node:os";import lb from"node:net";import{randomUUID as ub}from"node:crypto";import{execFile as db}from"node:child_process";import{promisify as fb}from"node:util";var Zt=fb(db),pb=process.env.BOSTRAT_STATE_DIR||It.join(process.env.HOME||cb.tmpdir(),".bostrat"),ad=It.join(pb,"views.json"),[nd,rd]=(process.env.BOSTRAT_VIEW_PORTS||"4300-4349").split("-").map(t=>Number(t)||0),Ee=Number(process.env.BOSTRAT_VIEW_MAX)||3,mb="bostrat-view-",Un=t=>`${mb}${t}`,od=new Set(["bostrat","www","api","views","app"]),zo=t=>Object.assign(new Error(t),{status:400}),hb=new Set(["npm","pnpm","yarn","bun"]),za="package.json",wb=/[|;&`]|\$\(/,gb=new Set(["sh","bash","zsh","dash","env","exec","nohup","time"]),yb=/(^|\s)(-C|--cwd|--manifest-path|--dir)(=|\s)/,id=t=>String(t).trim().split(/\s+/).filter(Boolean),bb=/^[A-Za-z_][A-Za-z0-9_]*=/;function kb(t){for(let e of id(t))if(!bb.test(e))return e;return null}function cd(t){let e=id(t),n=e.indexOf("--");return n===-1?e:e.slice(0,n)}function xb(t,e){let n=cd(e);for(let r=0;r<n.length;r++){let o=/^--prefix=(.+)$/.exec(n[r]);if(o)return It.resolve(t,o[1]);if(n[r]==="--prefix"&&n[r+1])return It.resolve(t,n[r+1])}return t}var Sb=t=>Br.stat(t).then(e=>e.isDirectory(),()=>!1),ld=t=>Br.stat(t).then(e=>e.isFile(),()=>!1),Xo=t=>Br.realpath(t).catch(()=>It.resolve(t));async function vb(t,e){if(It.resolve(t)===It.resolve(e))return!0;let[n,r]=await Promise.all([Xo(t),Xo(e)]);return n===r}async function Ab(t){let n=(await Br.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 ld(It.join(t,o.name,za))?`./${o.name}`:null))).filter(Boolean)}async function Tb(t,e){let[n,r]=await Promise.all([Xo(t),Xo(e)]),o=It.relative(n,r);if(o&&(o.startsWith("..")||It.isAbsolute(o)))throw zo(`serve directory escapes the Track workspace: ${e}`)}async function _b(t,e){if(!await Sb(t))throw zo(`serve directory does not exist: ${t}`);let n=kb(e);if(!n)return;if(!n.includes("/")&&!await Ta(n,{loginShell:!0,failOpen:!0}))throw zo(`\`${n}\` is not on the box's PATH \u2014 install it in the box first`);if(wb.test(e)||gb.has(n)||!hb.has(n)||!cd(e).includes("run")||yb.test(e))return;let r=xb(t,e);if(await ld(It.join(r,za)))return;let o=await Ab(r),s=o.length?` \u2014 found one in ${o.join(", ")}; run bostrat-view from there`:"";throw zo(`no ${za} in ${r}${s}`)}var Yt=null,Va=null;async function fe(){return Yt||(Va||=(async()=>{try{Yt=JSON.parse(await Br.readFile(ad,"utf8"))}catch{Yt={views:[],adoptWatermarks:{}}}finally{Va=null}return Yt.views||=[],Yt.adoptWatermarks||={},Yt})(),Va)}async function Qt(){await zt(ad,Yt)}function de(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 Nr=new Map,Za=Number(process.env.BOSTRAT_VIEW_JOB_TIMEOUT)||3e5,ud=12e3;function Qa(t){return Nr.get(t)||null}function dd(t,{name:e,box:n}={}){let r=Nr.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 Nr.set(t,o),o}function Xa(t,e){let n=Nr.get(t);n&&!n.finishedAt&&!n.viewId&&(n.viewId=e,n.step="booting")}function Yo(t,{error:e=null}={}){let n=Nr.get(t);return!n||n.finishedAt?null:(n.finishedAt=Date.now(),e&&(n.error=e),n)}function fd(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 Ya(t){return String(t||"").trim().toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,40).replace(/-+$/g,"")||"view"}function Eb(t,e){let n=Ya(e),r=new Set(t.views.map(o=>o.slug));if(!r.has(n)&&!od.has(n))return n;for(let o=2;;o++){let s=`${n}-${o}`;if(!r.has(s)&&!od.has(s))return s}}function Dr(t,{timeoutMs:e=1200}={}){return new Promise(n=>{let r=lb.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 ti(t){if(process.platform!=="darwin"){await Zt("fuser",["-k",`${t}/tcp`]).catch(()=>{});return}let e="";try{({stdout:e}=await Zt("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 Ib(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=nd;r<=rd;r++)if(!n.has(r)&&!await Dr(r,{timeoutMs:250}))return r;throw new Error(`no free view port in ${nd}-${rd}`)}async function Rb(t){try{let{stdout:e}=await Zt(P,N("list-sessions","-F","#{session_name}"));return e.split(`
|
|
30
|
-
`).some(n=>n.trim()===
|
|
36
|
+
`)}`:e),this.name="MockRenderError"}},sn=null,wi=null;async function pe(){return sn||(wi||=(async()=>{try{sn=JSON.parse(await yt.readFile(Md,"utf8"))}catch{sn={mocks:[]}}finally{wi=null}return sn.mocks||=[],sn})(),wi)}async function Qt(){await Zt(Md,sn)}function qt(t,e,n={}){return O({id:t.trackId,cwd:t.cwd},{type:"mock.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box||null,provider:t.agent||null,payload:{ts:Date.now(),change:e,mockId:t.id,name:t.name,...t.group?{group:t.group}:{},...n}}).catch(()=>{})}var ms=t=>Rt.join(t,".bostrat","mocks");function Ft(t,e){let n=ms(t.cwd);return{html:Rt.join(n,`${t.id}.v${e}.html`),png:Rt.join(n,`${t.id}.v${e}.png`)}}function an(t,e=null){let n=t.versions||[];return e!=null?n.find(r=>r.v===Number(e))||null:n.length?n[n.length-1]:null}function yi(t){return t.length<24||t.readUInt32BE(12)!==1229472850?null:{width:t.readUInt32BE(16),height:t.readUInt32BE(20)}}var Ck=Buffer.from([137,80,78,71,13,10,26,10]);function Pd(t){return Buffer.isBuffer(t)&&t.length>24&&t.subarray(0,8).equals(Ck)}async function Mk(t){let e=Buffer.alloc(24),n=await yt.open(t,"r");try{await n.read(e,0,24,0)}finally{await n.close()}return e}function Nk({imgW:t,imgH:e,bbox:n,pad:r,align:o=1}){if(!n||!(t>0)||!(e>0))return null;let s=n.right-n.left+1,a=n.bottom-n.top+1;if(s<=0||a<=0||s>=t*.9&&a>=e*.9)return null;let i=s+2*r,c=a+2*r;return i+=(o-i%o)%o,c+=(o-c%o)%o,{sx:n.left,sy:n.top,sw:s,sh:a,outW:i,outH:c,dx:Math.floor((i-s)/2),dy:Math.floor((c-a)/2)}}async function Pk(t,e){return t.evaluate(async n=>{let r=new Image;await new Promise((h,w)=>{r.onload=h,r.onerror=()=>w(new Error("decode failed")),r.src=`data:image/png;base64,${n}`});let o=r.naturalWidth,s=r.naturalHeight;if(!o||!s||o*s>24e6)return null;let a=document.createElement("canvas");a.width=o,a.height=s;let i=a.getContext("2d",{willReadFrequently:!0});i.drawImage(r,0,0);let c=i.getImageData(0,0,o,s).data,l=[c[0],c[1],c[2]],u=o,f=s,d=-1,m=-1;for(let h=0;h<s;h++)for(let w=h*o*4,y=0;y<o;y++,w+=4)Math.abs(c[w]-l[0])+Math.abs(c[w+1]-l[1])+Math.abs(c[w+2]-l[2])>24&&(y<u&&(u=y),y>d&&(d=y),h<f&&(f=h),m=h);return d<0?null:{imgW:o,imgH:s,bg:l,bbox:{left:u,top:f,right:d,bottom:m}}},e.toString("base64"))}async function Bk(t,e,{bg:n,crop:r}){let o=await t.evaluate(async({b64:s,bg:a,crop:i})=>{let c=new Image;await new Promise((f,d)=>{c.onload=f,c.onerror=()=>d(new Error("decode failed")),c.src=`data:image/png;base64,${s}`});let l=document.createElement("canvas");l.width=i.outW,l.height=i.outH;let u=l.getContext("2d");return u.fillStyle=`rgb(${a[0]},${a[1]},${a[2]})`,u.fillRect(0,0,i.outW,i.outH),u.drawImage(c,i.sx,i.sy,i.sw,i.sh,i.dx,i.dy,i.sw,i.sh),l.toDataURL("image/png").split(",")[1]},{b64:e.toString("base64"),bg:n,crop:r});return Buffer.from(o,"base64")}async function Dk({htmlPath:t,outPath:e,width:n}){let r=Date.now(),o;try{o=await Ak.launch({executablePath:_k,args:["--disable-gpu"],timeout:3e4})}catch(s){throw new Wt("chromium failed to launch",s?.message||"")}try{let a=await(await o.newContext({viewport:{width:n,height:800},deviceScaleFactor:Hn})).newPage();a.setDefaultTimeout(Wn),await a.goto(`file://${t}`,{waitUntil:"load",timeout:Wn}),await a.waitForTimeout(250);let i;for(let l=1;;l++)try{i=await a.screenshot({fullPage:!0,timeout:Wn});break}catch(u){if(l>=3||!/captureScreenshot|Unable to capture/i.test(u?.message||""))throw u;await a.waitForTimeout(500)}try{let l=await Pk(a,i),u=l&&Nk({...l,pad:Rk*Hn,align:Hn});u&&(i=await Bk(a,i,{bg:l.bg,crop:u}))}catch{}if(i.length<Nd)throw new Wt(`capture produced a ${i.length}-byte image`);let c=yi(i);if(!c)throw new Wt("output is not a PNG");return await yt.writeFile(e,i),{width:Math.round(c.width/Hn),height:Math.round(c.height/Hn),dpr:Hn,renderMs:Date.now()-r}}catch(s){throw s instanceof Wt?s:new Wt(/timeout/i.test(s?.message||"")?`render timed out after ${Wn}ms`:"chromium render failed",s?.message||"")}finally{await o.close().catch(()=>{})}}async function $d({htmlPath:t,outPath:e,width:n}){let r=await yt.mkdtemp(Rt.join(Ur.tmpdir(),"bostrat-mock-profile-")),o=Date.now(),s="";try{try{s=(await Od(Ek,["--headless","--no-remote","--profile",r,`--window-size=${n}`,"--screenshot",e,`file://${t}`],{env:{...process.env,MOZ_HEADLESS:"1"},timeout:Wn,killSignal:"SIGKILL",maxBuffer:4194304})).stderr||""}catch(c){throw s=c?.stderr||"",new Wt(c?.killed?`render timed out after ${Wn}ms`:"firefox exited non-zero",s)}let a=await yt.stat(e).catch(()=>null);if(!a||a.size<Nd)throw new Wt(`render produced ${a?`a ${a.size}-byte file`:"no file"} (silent no-op \u2014 another firefox holding the profile?)`,s);let i=yi(await Mk(e));if(!i)throw new Wt("output is not a PNG",s);return{width:i.width,height:i.height,dpr:1,renderMs:Date.now()-o}}finally{await yt.rm(r,{recursive:!0,force:!0}).catch(()=>{})}}async function jk(t){if(Tk==="firefox")return $d(t);try{return await Dk(t)}catch(e){if(/failed to launch/i.test(e?.message||""))return $d(t);throw e}}var Lk=jk;var gi=Promise.resolve();function Bd(t){return gi=gi.then(t,t),gi}async function Dd(t,e){let n=await pe(),r=n.mocks.find(o=>o.id===t);if(r){r.state="rendering",await Qt(),await qt(r,"rendering",{version:e});try{let o=await Ok();if(o<$k)throw new Wt(`box is low on memory (${Math.round(o/1e6)} MB available) \u2014 not spawning a browser`);let s=Ft(r,e),a=await Lk({htmlPath:s.html,outPath:s.png,width:r.width});if(!n.mocks.some(i=>i.id===t)){await yt.rm(s.png,{force:!0}).catch(()=>{});return}jd(r,{v:e,source:"html",htmlRel:Rt.relative(r.cwd,s.html),pngRel:Rt.relative(r.cwd,s.png),width:a.width,height:a.height,dpr:a.dpr,renderMs:a.renderMs,createdAt:Date.now()}),await Qt(),await qt(r,"ready",{version:e,width:a.width,height:a.height,dpr:a.dpr})}catch(o){if(!n.mocks.some(s=>s.id===t))return;r.state="error",r.error=o?.message||String(o),await Qt(),await qt(r,"error",{version:e,error:r.error})}}}function jd(t,e){t.versions=(t.versions||[]).filter(r=>r.v!==e.v).concat(e),t.versions.sort((r,o)=>r.v-o.v);let n=t.versions[t.versions.length-1];t.readyVersion=n.v,t.height=n.height,t.dpr=n.dpr,t.renderMs=n.renderMs??null,n.v===t.version&&(t.state="ready",t.error=null)}function Fk({trackId:t,name:e,cwd:n,width:r}){if(!t)throw new Error("trackId required");if(typeof e!="string"||!e.trim())throw new Error("name (string) required");if(!n)throw new Error("cwd required");let o=r==null?Ik:Number(r);if(!Number.isInteger(o)||o<320||o>4e3)throw new Error("invalid width (320-4000)");return o}async function hs({trackId:t,group:e}={}){let n=await pe(),r=t?n.mocks.filter(o=>o.trackId===t):n.mocks;return e&&(r=r.filter(o=>o.group===e)),r.map(o=>({...o}))}async function $e(t){let n=(await pe()).mocks.find(r=>r.id===t);return n?{...n}:null}async function Ld({trackId:t,name:e,html:n,image:r,cwd:o,width:s,group:a,box:i,agent:c,prompt:l}){let u=Fk({trackId:t,name:e,cwd:o,width:s}),f=typeof n=="string"&&n.trim();if(!f&&!r)throw new Error("html (string) or image (png) required");if(r&&!Pd(r))throw new Error("image must be a PNG");let d=await pe(),m={id:xk().slice(0,8),trackId:t,box:i||null,agent:c||null,name:e.trim(),prompt:l?String(l).slice(0,500):null,group:a?String(a).trim():null,cwd:o,width:u,height:null,dpr:null,state:"queued",source:f?"html":"image",version:1,readyVersion:null,versions:[],error:null,renderMs:null,createdAt:Date.now(),updatedAt:Date.now()};return await yt.mkdir(ms(o),{recursive:!0}),d.mocks.push(m),f?(await yt.writeFile(Ft(m,1).html,n),await Qt(),await qt(m,"created"),Bd(()=>Dd(m.id,1))):(await Ud(m,1,r),await Qt(),await qt(m,"created"),await qt(m,"ready",{version:1,width:m.width,height:m.height,dpr:m.dpr})),{...m}}async function Fd(t,{html:e,image:n,width:r}={}){let s=(await pe()).mocks.find(c=>c.id===t);if(!s)throw new Error("mock not found");let a=typeof e=="string"&&e.trim();if(!a&&!n)throw new Error("html (string) or image (png) required");if(n&&!Pd(n))throw new Error("image must be a PNG");if(r!=null){let c=Number(r);if(!Number.isInteger(c)||c<320||c>4e3)throw new Error("invalid width (320-4000)");s.width=c}let i=s.version+1;return s.version=i,s.updatedAt=Date.now(),s.source=a?"html":"image",await yt.mkdir(ms(s.cwd),{recursive:!0}),a?(s.state="queued",await yt.writeFile(Ft(s,i).html,e),await Qt(),await qt(s,"updated",{version:i}),Bd(()=>Dd(s.id,i))):(await Ud(s,i,n),await Qt(),await qt(s,"updated",{version:i}),await qt(s,"ready",{version:i,width:s.width,height:s.height,dpr:s.dpr})),{...s}}async function Ud(t,e,n){let r=yi(n);if(!r)throw new Error("image must be a PNG");let o=Ft(t,e);await yt.writeFile(o.png,n),jd(t,{v:e,source:"image",htmlRel:null,pngRel:Rt.relative(t.cwd,o.png),width:r.width,height:r.height,dpr:1,renderMs:null,createdAt:Date.now()}),t.width=r.width}async function Hd(t){let e=await pe(),n=e.mocks.find(o=>o.id===t);if(!n)throw new Error("mock not found");if(n.readyVersion==null)throw new Error(`mock is ${n.state} \u2014 nothing to pick yet`);let r=n.group?e.mocks.filter(o=>o.trackId===n.trackId&&o.group===n.group&&o.id!==t&&o.readyVersion!=null).map(o=>({mockId:o.id,name:o.name})):[];return await qt(n,"picked",{version:n.readyVersion,siblings:r}),{mock:{...n},siblings:r}}async function Wd(t){let e=await pe(),n=e.mocks.findIndex(o=>o.id===t);if(n===-1)throw new Error("mock not found");let r=e.mocks[n];e.mocks.splice(n,1);for(let o=1;o<=r.version;o++){let s=Ft(r,o);await yt.rm(s.html,{force:!0}).catch(()=>{}),await yt.rm(s.png,{force:!0}).catch(()=>{})}return await Qt(),await qt(r,"deleted"),{ok:!0,id:t,name:r.name}}async function qd(t){let n=(await pe()).mocks.filter(i=>i.trackId===t.id);if(!n.length)return 0;let r=Rt.join(Cd,"mock-archive",t.id),o=ms(t.cwd),s=Rt.join(r,".bostrat","mocks");await yt.mkdir(s,{recursive:!0});let a=await yt.readdir(o).catch(()=>[]);for(let i of a)await yt.copyFile(Rt.join(o,i),Rt.join(s,i));for(let i of n){for(let c of i.versions||[])for(let l of[c.htmlRel,c.pngRel]){if(!l)continue;if(!(await yt.stat(Rt.join(r,l)).catch(()=>null))?.isFile())throw new Error(`mock evacuation incomplete: ${l} missing from the archive`)}i.cwd=r,i.trackDeletedAt=Date.now(),i.trackName=t.name||t.id}return await Qt(),n.length}async function Gd(t){let e=await pe(),n=e.mocks.filter(r=>r.trackId===t);return n.length&&(e.mocks=e.mocks.filter(r=>r.trackId!==t),await Qt()),n.length}import Wr from"node:fs/promises";import $t from"node:path";import Uk from"node:os";import Hk from"node:net";import{randomUUID as Wk}from"node:crypto";import{execFile as qk}from"node:child_process";import{promisify as Gk}from"node:util";var ee=Gk(qk),Kk=process.env.BOSTRAT_STATE_DIR||$t.join(process.env.HOME||Uk.tmpdir(),".bostrat"),Xd=$t.join(Kk,"views.json"),[Kd,Vd]=(process.env.BOSTRAT_VIEW_PORTS||"4300-4349").split("-").map(t=>Number(t)||0),Oe=Number(process.env.BOSTRAT_VIEW_MAX)||3,Vk="bostrat-view-",qn=t=>`${Vk}${t}`,zd=new Set(["bostrat","www","api","views","app"]),ws=t=>Object.assign(new Error(t),{status:400}),zk=new Set(["npm","pnpm","yarn","bun"]),xi="package.json",Jk=/[|;&`]|\$\(/,Xk=new Set(["sh","bash","zsh","dash","env","exec","nohup","time"]),Yk=/(^|\s)(-C|--cwd|--manifest-path|--dir)(=|\s)/,Yd=t=>String(t).trim().split(/\s+/).filter(Boolean),Zk=/^[A-Za-z_][A-Za-z0-9_]*=/;function Qk(t){for(let e of Yd(t))if(!Zk.test(e))return e;return null}function Zd(t){let e=Yd(t),n=e.indexOf("--");return n===-1?e:e.slice(0,n)}function tx(t,e){let n=Zd(e);for(let r=0;r<n.length;r++){let o=/^--prefix=(.+)$/.exec(n[r]);if(o)return $t.resolve(t,o[1]);if(n[r]==="--prefix"&&n[r+1])return $t.resolve(t,n[r+1])}return t}var ex=t=>Wr.stat(t).then(e=>e.isDirectory(),()=>!1),Qd=t=>Wr.stat(t).then(e=>e.isFile(),()=>!1),gs=t=>Wr.realpath(t).catch(()=>$t.resolve(t));async function nx(t,e){if($t.resolve(t)===$t.resolve(e))return!0;let[n,r]=await Promise.all([gs(t),gs(e)]);return n===r}async function rx(t){let n=(await Wr.readdir(t,{withFileTypes:!0}).catch(()=>[])).filter(o=>o.isDirectory()&&!o.name.startsWith(".")&&o.name!=="node_modules");return(await Promise.all(n.map(async o=>await Qd($t.join(t,o.name,xi))?`./${o.name}`:null))).filter(Boolean)}async function ox(t,e){let[n,r]=await Promise.all([gs(t),gs(e)]),o=$t.relative(n,r);if(o&&(o.startsWith("..")||$t.isAbsolute(o)))throw ws(`serve directory escapes the Track workspace: ${e}`)}async function sx(t,e){if(!await ex(t))throw ws(`serve directory does not exist: ${t}`);let n=Qk(e);if(!n)return;if(!n.includes("/")&&!await Xa(n,{loginShell:!0,failOpen:!0}))throw ws(`\`${n}\` is not on the box's PATH \u2014 install it in the box first`);if(Jk.test(e)||Xk.has(n)||!zk.has(n)||!Zd(e).includes("run")||Yk.test(e))return;let r=tx(t,e);if(await Qd($t.join(r,xi)))return;let o=await rx(r),s=o.length?` \u2014 found one in ${o.join(", ")}; run bostrat-view from there`:"";throw ws(`no ${xi} in ${r}${s}`)}var te=null,bi=null;async function we(){return te||(bi||=(async()=>{try{te=JSON.parse(await Wr.readFile(Xd,"utf8"))}catch{te={views:[],adoptWatermarks:{}}}finally{bi=null}return te.views||=[],te.adoptWatermarks||={},te})(),bi)}async function ne(){await Zt(Xd,te)}function he(t,e,n={}){return O({id:t.trackId,cwd:t.cwd},{type:"view.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box||null,payload:{ts:Date.now(),change:e,viewId:t.id,name:t.name,slug:t.slug,port:t.port,...n}}).catch(()=>{})}var Hr=new Map,Ai=Number(process.env.BOSTRAT_VIEW_JOB_TIMEOUT)||3e5,tf=12e3;function Ti(t){return Hr.get(t)||null}function ef(t,{name:e,box:n}={}){let r=Hr.get(t);if(r&&!r.finishedAt)throw new Error("a view is already being spun up for this Track");let o={trackId:t,name:e||"preview",box:n||null,step:"spinning-up",startedAt:Date.now(),finishedAt:null,viewId:null,error:null};return Hr.set(t,o),o}function Si(t,e){let n=Hr.get(t);n&&!n.finishedAt&&!n.viewId&&(n.viewId=e,n.step="booting")}function ys(t,{error:e=null}={}){let n=Hr.get(t);return!n||n.finishedAt?null:(n.finishedAt=Date.now(),e&&(n.error=e),n)}function nf(t){let e=[];for(let n of t?.events||[])n.type!=="view-spinup"||!n.box||!n.startedAt||!n.endedAt||e.push({box:n.box,from:n.startedAt,to:n.endedAt});return e}function vi(t){return String(t||"").trim().toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,40).replace(/-+$/g,"")||"view"}function ax(t,e){let n=vi(e),r=new Set(t.views.map(o=>o.slug));if(!r.has(n)&&!zd.has(n))return n;for(let o=2;;o++){let s=`${n}-${o}`;if(!r.has(s)&&!zd.has(s))return s}}function qr(t,{timeoutMs:e=1200}={}){return new Promise(n=>{let r=Hk.connect({port:t,host:"127.0.0.1"}),o=s=>{r.destroy(),n(s)};r.setTimeout(e,()=>o(!1)),r.once("connect",()=>o(!0)),r.once("error",()=>o(!1))})}async function _i(t){if(process.platform!=="darwin"){await ee("fuser",["-k",`${t}/tcp`]).catch(()=>{});return}let e="";try{({stdout:e}=await ee("lsof",["-t","-sTCP:LISTEN","-i",`tcp:${t}`]))}catch{return}for(let n of e.split(/\s+/).filter(Boolean)){let r=Number(n);if(!(!r||r===process.pid))try{process.kill(r,"SIGKILL")}catch{}}}function rf(t=process.env){return new Set([Number(t.PORT)||3362,Number(t.BOSTRAT_TLS_PORT)||3443])}async function ix(t,e){let n=new Set(t.views.map(o=>o.port)),r=rf();if(e){let o=Number(e);if(!Number.isInteger(o)||o<1024||o>65535)throw new Error("invalid port");if(r.has(o))throw new Error(`port ${o} is the node's own API port`);if(n.has(o))throw new Error(`port ${o} is already owned by a view`);return o}for(let o=Kd;o<=Vd;o++)if(!(n.has(o)||r.has(o))&&!await qr(o,{timeoutMs:250}))return o;throw new Error(`no free view port in ${Kd}-${Vd}`)}async function cx(t){try{let{stdout:e}=await ee(N,B("list-sessions","-F","#{session_name}"));return e.split(`
|
|
37
|
+
`).some(n=>n.trim()===qn(t))}catch{return!1}}async function Gr(t){try{await ee(N,B("kill-session","-t",qn(t)))}catch{}}async function Ei(t){if(!await Pr())throw new Error("host tmux server not running and auto-start failed \u2014 cannot spawn a view server");let e=qn(t.slug),n=t.serveCwd??t.cwd,r=$t.dirname(process.execPath);await ee(N,B("new-session","-d","-s",e,"-c",n)),await ee(N,B("send-keys","-t",e,"-l",`export PORT=${t.port} PATH="${r}:$PATH"`)),await ee(N,B("send-keys","-t",e,"Enter")),await ee(N,B("send-keys","-t",e,"-l",t.cmd)),await ee(N,B("send-keys","-t",e,"Enter"))}async function Jd(t,e=12){try{let{stdout:n}=await ee(N,B("capture-pane","-t",qn(t),"-p"));return String(n||"").trimEnd().split(`
|
|
31
38
|
`).filter(Boolean).slice(-e).join(`
|
|
32
|
-
`)}catch{return""}}var ue=new Map,$b=12e4;async function Ob(t){if(t.state==="stopped"||t.state==="error")return!1;let e=t.origin!=="detected",n=e?await Rb(t.slug):!0;if(e&&!n){let s=t.state==="starting";return t.state=s?"error":"stopped",s&&(t.error=await sd(t.slug)||"server exited before listening"),ue.delete(t.id),await de(t,t.state,t.error?{error:t.error}:{}),!0}if(await Dr(t.port))return ue.delete(t.id),t.lastHealthyAt=Date.now(),t.state!=="live"?(t.state="live",t.error=null,await de(t,"live"),!0):!1;if(t.state==="starting")return Date.now()-t.createdAt>$b?(t.state="error",t.error=await sd(t.slug)||"never started listening",await de(t,"error",{error:t.error}),!0):!1;let o=(ue.get(t.id)||0)+1;return ue.set(t.id,o),o>=3&&t.state==="live"?(t.state="unhealthy",!0):!1}var Ja=!1;async function Zo(){if(!Ja){Ja=!0;try{let t=await fe(),e=!1;for(let n of t.views)await Ob(n)&&(e=!0);e&&await Qt()}finally{Ja=!1}}}async function Qo({trackId:t}={}){await Zo();let e=await fe();return(t?e.views.filter(r=>r.trackId===t):e.views).map(r=>({...r}))}async function pd(t){let n=(await fe()).views.find(r=>r.id===t);return n?{...n}:null}function ni(t){return Yt&&Yt.views.find(e=>e.slug===t)||null}async function ri({trackId:t,name:e,cmd:n,cwd:r,serveCwd:o,port:s,box:a,origin:i="agent",branch:c=null}){if(!t)throw new Error("trackId required");if(typeof e!="string"||!e.trim())throw new Error("name (string) required");if(i!=="detected"&&(typeof n!="string"||!n.trim()))throw new Error("cmd (string) required");if(!r)throw new Error("cwd required");let l=o!=null,u=It.resolve(r,o??"."),d=i==="detected"?null:async()=>{await Tb(r,u),await _b(u,n)},f=await fe();if(i!=="detected"){let m=Ya(e),b=String(n).trim(),k=f.views.find(v=>v.trackId===t&&(v.slug===m||Ya(v.name)===m||v.cmd===b&&It.resolve(v.serveCwd??v.cwd)===u));if(k)return Cb(f,k,{cmd:b,box:a,branch:c,serveCwd:l?u:void 0,validate:d})}if(d&&await d(),f.views.filter(m=>m.state==="live"||m.state==="starting").length>=Ee)throw new Error(`view cap reached (${Ee} live) \u2014 stop one first`);let h={id:ub().slice(0,8),trackId:t,box:a||null,name:e.trim(),slug:Eb(f,e),cmd:n?String(n).trim():null,cwd:r,serveCwd:u,branch:c,port:await Ib(f,s),origin:i,state:"starting",error:null,createdAt:Date.now(),lastHealthyAt:null};return i!=="detected"&&await ei(h),f.views.push(h),await Qt(),await de(h,"created",{origin:i}),Xa(t,h.id),i==="detected"&&await Dr(h.port)&&(h.state="live",h.lastHealthyAt=Date.now(),await Qt(),await de(h,"live")),{...h}}async function Cb(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 vb(e.serveCwd??e.cwd,s);if((e.state==="live"||e.state==="starting")&&!i&&!c&&await Dr(e.port))return await Qt(),Xa(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>=Ee)throw new Error(`view cap reached (${Ee} live) \u2014 stop one first`);return e.origin==="detected"?(await ti(e.port),e.origin="agent"):await jr(e.slug),e.cmd=n,s!==void 0&&(e.serveCwd=s),e.state="starting",e.error=null,e.createdAt=Date.now(),ue.delete(e.id),await ei(e),await Qt(),await de(e,"created",{restarted:!0,reused:!0}),Xa(e.trackId,e.id),{...e,reused:!0}}async function md(t){let n=(await fe()).views.find(r=>r.id===t);if(!n)throw new Error("view not found");return n.origin==="detected"?await ti(n.port):await jr(n.slug),n.state!=="stopped"&&(n.state="stopped",ue.delete(n.id),await Qt(),await de(n,"stopped")),{...n}}async function hd(t){let e=await fe(),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>=Ee)throw new Error(`view cap reached (${Ee} live) \u2014 stop one first`);return await jr(n.slug),n.state="starting",n.error=null,n.createdAt=Date.now(),ue.delete(n.id),await ei(n),await Qt(),await de(n,"created",{restarted:!0}),{...n}}async function wd(t){let e=await fe(),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 ti(r.port):await jr(r.slug),e.views.splice(n,1),ue.delete(r.id),await Qt(),await de(r,"deleted"),{ok:!0,id:t,slug:r.slug}}async function gd(t){let e=await fe(),n=e.views.filter(r=>r.trackId===t);for(let r of n)r.origin!=="detected"&&await jr(r.slug),ue.delete(r.id);return n.length&&(e.views=e.views.filter(r=>r.trackId!==t),await Qt()),n.length}var Mb=/https?:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0)(?::(\d{2,5}))?(?:\/|\b)/g;async function yd(t,e){if(!t?.id||!Array.isArray(e)||!e.length)return[];let n=await fe(),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 Qt()),!o.length)return[];let a=new Set;for(let c of o)for(let l of String(c.text||"").matchAll(Mb)){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 Dr(c))try{i.push(await ri({trackId:t.id,name:`${t.name} preview`,cmd:null,cwd:t.cwd,port:c,origin:"detected",branch:t.workspace?.branch??null}))}catch{}return i}import sn from"node:fs/promises";import Ie from"node:path";import Pb from"node:os";import{randomUUID as Nb}from"node:crypto";var Wn=process.env.BOSTRAT_STATE_DIR||Ie.join(process.env.HOME||Pb.tmpdir(),".bostrat"),kd=Ie.join(Wn,"runbooks.json"),si=Ie.join(Wn,"runbooks"),bd=256*1024,Bb=[{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 Db(t){return Bb.filter(({re:e})=>e.test(t)).map(({label:e})=>e)}var on=null,oi=null;async function Re(){return on||(oi||=(async()=>{try{on=JSON.parse(await sn.readFile(kd,"utf8"))}catch{on={runbooks:[]}}finally{oi=null}return on.runbooks||=[],on})(),oi)}async function qn(){await zt(kd,on)}function Gn(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 Hn(t,e){return Ie.join(si,`${t.id}.v${e}.md`)}function xd(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 Sd(t){if(typeof t!="string"||!t.trim())throw new Error("md (markdown string) required");let e=Buffer.byteLength(t,"utf8");if(e>bd)throw new Error(`runbook is ${Math.round(e/1024)} KB \u2014 over the ${bd/1024} KB cap; a doc that size belongs in the repo, not a runbook`);let n=Db(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 vd(t,e){if(t.trackDeletedAt)throw new Error(`runbook is orphaned (its Track was deleted) \u2014 cannot ${e} it`)}async function ts({trackId:t,status:e}={}){let n=await Re(),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 ai(t){let n=(await Re()).runbooks.find(r=>r.id===t);return n?{...n}:null}async function Ad({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=Sd(r),c=await Re(),l={id:Nb().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 sn.mkdir(si,{recursive:!0}),await sn.writeFile(Hn(l,1),r),l.versions.push({v:1,mdRel:Ie.relative(Wn,Hn(l,1)),bytes:i,note:null,createdAt:Date.now()}),c.runbooks.push(l),await qn(),await Gn(l,"created",{version:1}),{...l}}async function Td(t,{md:e,note:n}={}){let o=(await Re()).runbooks.find(i=>i.id===t);if(!o)throw new Error("runbook not found");vd(o,"update");let s=Sd(e),a=o.version+1;return o.version=a,o.updatedAt=Date.now(),await sn.mkdir(si,{recursive:!0}),await sn.writeFile(Hn(o,a),e),o.versions.push({v:a,mdRel:Ie.relative(Wn,Hn(o,a)),bytes:s,note:n?String(n).slice(0,200):null,createdAt:Date.now()}),await qn(),await Gn(o,"updated",{version:a,...n?{note:String(n).slice(0,200)}:{}}),{...o}}async function _d(t,e){if(e!=="open"&&e!=="done")throw new Error('status must be "open" or "done"');let r=(await Re()).runbooks.find(o=>o.id===t);if(!r)throw new Error("runbook not found");return vd(r,e==="done"?"complete":"reopen"),r.status===e?{...r}:(r.status=e,r.updatedAt=Date.now(),await qn(),await Gn(r,e==="done"?"done":"reopened"),{...r})}async function Ed(t){let e=await Re(),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 sn.rm(Ie.join(Wn,o.mdRel),{force:!0}).catch(()=>{});return await qn(),r.trackDeletedAt||await Gn(r,"deleted"),{ok:!0,id:t,name:r.name}}async function Id(t){let n=(await Re()).runbooks.filter(r=>r.trackId===t&&!r.trackDeletedAt);for(let r of n)r.trackDeletedAt=Date.now(),await Gn(r,"orphaned"),r.cwd=null;return n.length&&await qn(),n.length}async function Rd(t){let e=await Re(),n=e.runbooks.filter(r=>r.trackId===t);for(let r of n){await Gn(r,"deleted");for(let o of r.versions||[])await sn.rm(Ie.join(Wn,o.mdRel),{force:!0}).catch(()=>{})}return n.length&&(e.runbooks=e.runbooks.filter(r=>r.trackId!==t),await qn()),n.length}import Ur from"node:fs/promises";import qd from"node:path";import dk from"node:os";import{randomUUID as Gd}from"node:crypto";import jb from"node:fs/promises";import $d from"node:path";import Lb from"node:os";var Od=()=>$d.join(process.env.BOSTRAT_STATE_DIR||$d.join(process.env.HOME||Lb.tmpdir(),".bostrat"),"integrations.json");async function an(){try{return JSON.parse(await jb.readFile(Od(),"utf8"))||{}}catch{return{}}}var es=["linked","prOpened","prMerged","create"],ii=["prompt","auto","off"],Fb={create:["prompt","off"]},Cd=t=>Fb[t]??ii;function ns(t){let e=t.linear||{},n=e.transitions===!0?"auto":"prompt",r={};for(let o of es){let s=Cd(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 cn(){let t=await an(),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:ns(t),defaultTeam:n}:null}async function rs(){let t=await an(),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:ns(t)}}async function ci(){return ns(await an())}async function Md(t){let e=Object.entries(t||{});if(!e.length)throw new Error(`nothing to set \u2014 phases: ${es.join(", ")}`);for(let[r,o]of e){if(!es.includes(r))throw new Error(`unknown phase "${r}" \u2014 configurable phases: ${es.join(", ")}`);let s=Cd(r);if(!s.includes(o))throw new Error(s.length<ii.length?`mode "${o}" not allowed for "${r}" \u2014 ${r} is always confirmed (modes: ${s.join(", ")})`:`unknown mode "${o}" \u2014 modes: ${ii.join(", ")}`)}let n=await an();return n.ticketFlow={...ns(n),...Object.fromEntries(e)},n.linear&&(delete n.linear.ticketFlow,delete n.linear.transitions),await zt(Od(),n,{mode:384}),n.ticketFlow}async function Lr(){let t=(await an()).deepgram||{},e=process.env.DEEPGRAM_API_KEY||t.apiKey;return e?{apiKey:e}:null}async function Pd(){let t=(await an()).cedar||{},e=process.env.BOSTRAT_CEDAR_INGEST_KEY||t.ingestKey,n=process.env.BOSTRAT_CEDAR_URL||t.url||"https://cedar-api.fly.dev";return e?{url:n,ingestKey:e}:null}async function ln(){let t=(await an()).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 ui={};Ul(ui,{createComment:()=>zb,findIssueUrl:()=>qb,issueStates:()=>Zb,mdToAdf:()=>Dd,moveIssue:()=>Qb,parseIssueRef:()=>Wb,resolveIssue:()=>Gb,uploadImage:()=>Xb,upsertCard:()=>Kb,whoami:()=>li});var Kn=t=>String(t.baseUrl||"").replace(/\/$/,""),Bd=t=>`Basic ${Buffer.from(`${t.email}:${t.apiToken}`).toString("base64")}`;async function un(t,e,n,r){let o=await fetch(`${Kn(t)}${n}`,{method:e,headers:{authorization:Bd(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 Ub=/\/browse\/([A-Za-z][A-Za-z0-9]{0,9}-\d+)/,Hb=/^([A-Z][A-Z0-9]{0,9}-\d+)$/;function Wb(t){let e=String(t||"").trim();if(/^https?:\/\//i.test(e)){let r=e.match(Ub);return r?r[1].toUpperCase():null}let n=e.match(Hb);return n?n[1]:null}function qb(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 Gb(t,e){let n;try{n=await un(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:`${Kn(t)}/browse/${n.key}`,state:n.fields?.status?.name??null}}async function Kb(t,e,{url:n,title:r,subtitle:o}){await un(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 Vb=/(!\[[^\]]*\]\([^)\s]+\)|\*\*[^*\n]+\*\*|`[^`\n]+`|_[^_\n]+_|https?:\/\/[^\s)]+)/g;function Jb(t){let e=[],n=0;for(let r of t.matchAll(Vb)){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 Dd(t){let e=String(t??"").split(/\n{2,}/).map(n=>n.trim()).filter(Boolean).map(n=>{let r=[];return n.split(`
|
|
33
|
-
`).forEach((o,s)=>{s>0&&r.push({type:"hardBreak"}),r.push(...
|
|
39
|
+
`)}catch{return""}}var me=new Map,lx=12e4;async function ux(t){if(t.state==="stopped"||t.state==="error")return!1;let e=t.origin!=="detected",n=e?await cx(t.slug):!0;if(e&&!n){let s=t.state==="starting";return t.state=s?"error":"stopped",s&&(t.error=await Jd(t.slug)||"server exited before listening"),me.delete(t.id),await he(t,t.state,t.error?{error:t.error}:{}),!0}if(await qr(t.port))return me.delete(t.id),t.lastHealthyAt=Date.now(),t.state!=="live"?(t.state="live",t.error=null,await he(t,"live"),!0):!1;if(t.state==="starting")return Date.now()-t.createdAt>lx?(t.state="error",t.error=await Jd(t.slug)||"never started listening",await he(t,"error",{error:t.error}),!0):!1;let o=(me.get(t.id)||0)+1;return me.set(t.id,o),o>=3&&t.state==="live"?(t.state="unhealthy",!0):!1}var ki=!1;async function bs(){if(!ki){ki=!0;try{let t=await we(),e=!1;for(let n of t.views)await ux(n)&&(e=!0);e&&await ne()}finally{ki=!1}}}async function ks({trackId:t}={}){await bs();let e=await we();return(t?e.views.filter(r=>r.trackId===t):e.views).map(r=>({...r}))}async function of(t){let n=(await we()).views.find(r=>r.id===t);return n?{...n}:null}function Ii(t){return te&&te.views.find(e=>e.slug===t)||null}async function Ri({trackId:t,name:e,cmd:n,cwd:r,serveCwd:o,port:s,box:a,origin:i="agent",branch:c=null}){if(!t)throw new Error("trackId required");if(typeof e!="string"||!e.trim())throw new Error("name (string) required");if(i!=="detected"&&(typeof n!="string"||!n.trim()))throw new Error("cmd (string) required");if(!r)throw new Error("cwd required");let l=o!=null,u=$t.resolve(r,o??"."),f=i==="detected"?null:async()=>{await ox(r,u),await sx(u,n)},d=await we();if(i!=="detected"){let w=vi(e),y=String(n).trim(),b=d.views.find(A=>A.trackId===t&&(A.slug===w||vi(A.name)===w||A.cmd===y&&$t.resolve(A.serveCwd??A.cwd)===u));if(b)return dx(d,b,{cmd:y,box:a,branch:c,serveCwd:l?u:void 0,validate:f})}if(f&&await f(),d.views.filter(w=>w.state==="live"||w.state==="starting").length>=Oe)throw new Error(`view cap reached (${Oe} live) \u2014 stop one first`);let h={id:Wk().slice(0,8),trackId:t,box:a||null,name:e.trim(),slug:ax(d,e),cmd:n?String(n).trim():null,cwd:r,serveCwd:u,branch:c,port:await ix(d,s),origin:i,state:"starting",error:null,createdAt:Date.now(),lastHealthyAt:null};return i!=="detected"&&await Ei(h),d.views.push(h),await ne(),await he(h,"created",{origin:i}),Si(t,h.id),i==="detected"&&await qr(h.port)&&(h.state="live",h.lastHealthyAt=Date.now(),await ne(),await he(h,"live")),{...h}}async function dx(t,e,{cmd:n,box:r,branch:o,serveCwd:s,validate:a}){r&&(e.box=r),o&&(e.branch=o);let i=e.cmd!==n,c=s!==void 0&&!await nx(e.serveCwd??e.cwd,s);if((e.state==="live"||e.state==="starting")&&!i&&!c&&await qr(e.port))return await ne(),Si(e.trackId,e.id),{...e,reused:!0};if(a&&await a(),t.views.filter(f=>f.id!==e.id&&(f.state==="live"||f.state==="starting")).length>=Oe)throw new Error(`view cap reached (${Oe} live) \u2014 stop one first`);return e.origin==="detected"?(await _i(e.port),e.origin="agent"):await Gr(e.slug),e.cmd=n,s!==void 0&&(e.serveCwd=s),e.state="starting",e.error=null,e.createdAt=Date.now(),me.delete(e.id),await Ei(e),await ne(),await he(e,"created",{restarted:!0,reused:!0}),Si(e.trackId,e.id),{...e,reused:!0}}async function sf(t){let n=(await we()).views.find(r=>r.id===t);if(!n)throw new Error("view not found");return n.origin==="detected"?await _i(n.port):await Gr(n.slug),n.state!=="stopped"&&(n.state="stopped",me.delete(n.id),await ne(),await he(n,"stopped")),{...n}}async function af(t){let e=await we(),n=e.views.find(o=>o.id===t);if(!n)throw new Error("view not found");if(!n.cmd)throw new Error("a detected view has no known command \u2014 recreate it as a managed view");if(e.views.filter(o=>o.id!==t&&(o.state==="live"||o.state==="starting")).length>=Oe)throw new Error(`view cap reached (${Oe} live) \u2014 stop one first`);return await Gr(n.slug),n.state="starting",n.error=null,n.createdAt=Date.now(),me.delete(n.id),await Ei(n),await ne(),await he(n,"created",{restarted:!0}),{...n}}async function cf(t){let e=await we(),n=e.views.findIndex(o=>o.id===t);if(n===-1)throw new Error("view not found");let r=e.views[n];return r.origin==="detected"?await _i(r.port):await Gr(r.slug),e.views.splice(n,1),me.delete(r.id),await ne(),await he(r,"deleted"),{ok:!0,id:t,slug:r.slug}}async function lf(t){let e=await we(),n=e.views.filter(r=>r.trackId===t);for(let r of n)r.origin!=="detected"&&await Gr(r.slug),me.delete(r.id);return n.length&&(e.views=e.views.filter(r=>r.trackId!==t),await ne()),n.length}var fx=/https?:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0)(?::(\d{2,5}))?(?:\/|\b)/g;async function uf(t,e){if(!t?.id||!Array.isArray(e)||!e.length)return[];let n=await we(),r=n.adoptWatermarks[t.id]??null,o=e.filter(c=>c.role==="assistant"&&c.ts!=null&&(r==null?!1:c.ts>r)),s=e.reduce((c,l)=>l.ts>c?l.ts:c,r??0);if(n.adoptWatermarks[t.id]!==s&&(n.adoptWatermarks[t.id]=s,await ne()),!o.length)return[];let a=new Set;for(let c of o)for(let l of String(c.text||"").matchAll(fx)){let u=Number(l[1]||80);u>=1024&&u<=65535&&a.add(u)}if(!a.size)return[];let i=[];for(let c of a)if(!rf().has(c)&&!n.views.some(l=>l.port===c)&&await qr(c))try{i.push(await Ri({trackId:t.id,name:`${t.name} preview`,cmd:null,cwd:t.cwd,port:c,origin:"detected",branch:t.workspace?.branch??null}))}catch{}return i}import ln from"node:fs/promises";import Ce from"node:path";import px from"node:os";import{randomUUID as mx}from"node:crypto";var Kn=process.env.BOSTRAT_STATE_DIR||Ce.join(process.env.HOME||px.tmpdir(),".bostrat"),ff=Ce.join(Kn,"runbooks.json"),Oi=Ce.join(Kn,"runbooks"),df=256*1024,hx=[{label:"Anthropic API key",re:/\bsk-ant-[A-Za-z0-9_-]{20,}/},{label:"OpenAI-style API key",re:/\bsk-(?:proj-|svcacct-)?[A-Za-z0-9_-]{32,}\b/},{label:"Stripe secret key",re:/\bsk_(?:live|test)_[A-Za-z0-9]{16,}/},{label:"GitHub token",re:/\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{36,}|\bgithub_pat_[A-Za-z0-9_]{22,}/},{label:"AWS access key id",re:/\bAKIA[0-9A-Z]{16}\b/},{label:"Slack token",re:/\bxox[baprs]-[A-Za-z0-9-]{10,}/},{label:"private key block",re:/-----BEGIN [A-Z ]*PRIVATE KEY-----/},{label:"JWT",re:/\beyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/}];function Kr(t){return hx.filter(({re:e})=>e.test(t)).map(({label:e})=>e)}var cn=null,$i=null;async function Me(){return cn||($i||=(async()=>{try{cn=JSON.parse(await ln.readFile(ff,"utf8"))}catch{cn={runbooks:[]}}finally{$i=null}return cn.runbooks||=[],cn})(),$i)}async function Vn(){await Zt(ff,cn)}function zn(t,e,n={}){return O({id:t.trackId,cwd:t.cwd},{type:"runbook.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box||null,provider:t.agent||null,payload:{ts:Date.now(),change:e,runbookId:t.id,name:t.name,status:t.status,...n}}).catch(()=>{})}function Gn(t,e){return Ce.join(Oi,`${t.id}.v${e}.md`)}function pf(t,e=null){let n=t.versions||[];return e!=null?n.find(r=>r.v===Number(e))||null:n.length?n[n.length-1]:null}function mf(t){if(typeof t!="string"||!t.trim())throw new Error("md (markdown string) required");let e=Buffer.byteLength(t,"utf8");if(e>df)throw new Error(`runbook is ${Math.round(e/1024)} KB \u2014 over the ${df/1024} KB cap; a doc that size belongs in the repo, not a runbook`);let n=Kr(t);if(n.length)throw new Error(`runbook appears to contain a secret VALUE (${n.join(", ")}) \u2014 runbooks ship off-box; name the secret and where it goes, never its value. Replace it with a placeholder like <your key> and retry.`);return e}function hf(t,e){if(t.trackDeletedAt)throw new Error(`runbook is orphaned (its Track was deleted) \u2014 cannot ${e} it`)}async function xs({trackId:t,status:e}={}){let n=await Me(),r=t?n.runbooks.filter(o=>o.trackId===t):n.runbooks;return e&&(r=r.filter(o=>o.status===e)),r.map(o=>({...o}))}async function Ci(t){let n=(await Me()).runbooks.find(r=>r.id===t);return n?{...n}:null}async function wf({trackId:t,trackName:e,name:n,md:r,cwd:o,box:s,agent:a}){if(!t)throw new Error("trackId required");if(typeof n!="string"||!n.trim())throw new Error("name (string) required");let i=mf(r),c=await Me(),l={id:mx().slice(0,8),trackId:t,trackName:e||t,box:s||null,agent:a||null,name:n.trim(),status:"open",version:1,versions:[],trackDeletedAt:null,cwd:o||null,createdAt:Date.now(),updatedAt:Date.now()};return await ln.mkdir(Oi,{recursive:!0}),await ln.writeFile(Gn(l,1),r),l.versions.push({v:1,mdRel:Ce.relative(Kn,Gn(l,1)),bytes:i,note:null,createdAt:Date.now()}),c.runbooks.push(l),await Vn(),await zn(l,"created",{version:1}),{...l}}async function gf(t,{md:e,note:n}={}){let o=(await Me()).runbooks.find(i=>i.id===t);if(!o)throw new Error("runbook not found");hf(o,"update");let s=mf(e),a=o.version+1;return o.version=a,o.updatedAt=Date.now(),await ln.mkdir(Oi,{recursive:!0}),await ln.writeFile(Gn(o,a),e),o.versions.push({v:a,mdRel:Ce.relative(Kn,Gn(o,a)),bytes:s,note:n?String(n).slice(0,200):null,createdAt:Date.now()}),await Vn(),await zn(o,"updated",{version:a,...n?{note:String(n).slice(0,200)}:{}}),{...o}}async function yf(t,e){if(e!=="open"&&e!=="done")throw new Error('status must be "open" or "done"');let r=(await Me()).runbooks.find(o=>o.id===t);if(!r)throw new Error("runbook not found");return hf(r,e==="done"?"complete":"reopen"),r.status===e?{...r}:(r.status=e,r.updatedAt=Date.now(),await Vn(),await zn(r,e==="done"?"done":"reopened"),{...r})}async function bf(t){let e=await Me(),n=e.runbooks.findIndex(o=>o.id===t);if(n===-1)throw new Error("runbook not found");let r=e.runbooks[n];e.runbooks.splice(n,1);for(let o of r.versions||[])await ln.rm(Ce.join(Kn,o.mdRel),{force:!0}).catch(()=>{});return await Vn(),r.trackDeletedAt||await zn(r,"deleted"),{ok:!0,id:t,name:r.name}}async function kf(t){let n=(await Me()).runbooks.filter(r=>r.trackId===t&&!r.trackDeletedAt);for(let r of n)r.trackDeletedAt=Date.now(),await zn(r,"orphaned"),r.cwd=null;return n.length&&await Vn(),n.length}async function xf(t){let e=await Me(),n=e.runbooks.filter(r=>r.trackId===t);for(let r of n){await zn(r,"deleted");for(let o of r.versions||[])await ln.rm(Ce.join(Kn,o.mdRel),{force:!0}).catch(()=>{})}return n.length&&(e.runbooks=e.runbooks.filter(r=>r.trackId!==t),await Vn()),n.length}import Jr from"node:fs/promises";import Bf from"node:path";import Gx from"node:os";import{randomUUID as Df}from"node:crypto";import wx from"node:fs/promises";import Sf from"node:path";import gx from"node:os";var vf=()=>Sf.join(process.env.BOSTRAT_STATE_DIR||Sf.join(process.env.HOME||gx.tmpdir(),".bostrat"),"integrations.json");async function ge(){try{return JSON.parse(await wx.readFile(vf(),"utf8"))||{}}catch{return{}}}var Ss=["linked","prOpened","prMerged","create"],Mi=["prompt","auto","off"],yx={create:["prompt","off"]},Af=t=>yx[t]??Mi;function vs(t){let e=t.linear||{},n=e.transitions===!0?"auto":"prompt",r={};for(let o of Ss){let s=Af(o),a=s.includes(n)?n:"prompt",i=t.ticketFlow?.[o]??e.ticketFlow?.[o];r[o]=s.includes(i)?i:a}return r}async function un(){let t=await ge(),e=process.env.BOSTRAT_LINEAR_API_KEY||t.linear?.apiKey,n=process.env.BOSTRAT_LINEAR_DEFAULT_TEAM||t.linear?.defaultTeam||null;return e?{apiKey:e,ticketFlow:vs(t),defaultTeam:n}:null}async function As(){let t=await ge(),e=t.jira||{},n=process.env.BOSTRAT_JIRA_BASE_URL||e.baseUrl,r=process.env.BOSTRAT_JIRA_EMAIL||e.email,o=process.env.BOSTRAT_JIRA_API_TOKEN||e.apiToken;return!n||!r||!o?null:{baseUrl:String(n).replace(/\/$/,""),email:r,apiToken:o,tokenExpiresAt:e.tokenExpiresAt??null,ticketFlow:vs(t)}}async function Ni(){return vs(await ge())}async function Tf(t){let e=Object.entries(t||{});if(!e.length)throw new Error(`nothing to set \u2014 phases: ${Ss.join(", ")}`);for(let[r,o]of e){if(!Ss.includes(r))throw new Error(`unknown phase "${r}" \u2014 configurable phases: ${Ss.join(", ")}`);let s=Af(r);if(!s.includes(o))throw new Error(s.length<Mi.length?`mode "${o}" not allowed for "${r}" \u2014 ${r} is always confirmed (modes: ${s.join(", ")})`:`unknown mode "${o}" \u2014 modes: ${Mi.join(", ")}`)}let n=await ge();return n.ticketFlow={...vs(n),...Object.fromEntries(e)},n.linear&&(delete n.linear.ticketFlow,delete n.linear.transitions),await Zt(vf(),n,{mode:384}),n.ticketFlow}async function Vr(){let t=(await ge()).deepgram||{},e=process.env.DEEPGRAM_API_KEY||t.apiKey;return e?{apiKey:e}:null}async function bx(){let t=(await ge()).openai||{},e=process.env.BOSTRAT_OPENAI_API_KEY||t.apiKey;return e?{apiKey:e}:null}var kx={openai:{model:"gpt-realtime-2.1-mini",voice:"marin"}};async function Ts(){let e=(await ge()).voiceChat||{},n=e.provider||"openai",r=kx[n];if(!r)return null;let o=n==="openai"?await bx():null;return o?{provider:n,model:e.model||r.model,voice:e.voice||r.voice,apiKey:o.apiKey}:null}async function _f(){let t=(await ge()).cedar||{},e=process.env.BOSTRAT_CEDAR_INGEST_KEY||t.ingestKey,n=process.env.BOSTRAT_CEDAR_URL||t.url||"https://cedar-api.fly.dev";return e?{url:n,ingestKey:e}:null}async function dn(){let t=(await ge()).slack||{},e=process.env.BOSTRAT_SLACK_BOT_TOKEN||t.botToken,n=process.env.BOSTRAT_SLACK_APP_TOKEN||t.appToken;return!e||!n?null:{botToken:e,appToken:n,channel:process.env.BOSTRAT_SLACK_CHANNEL||t.channel||null,maxConcurrent:Number.isInteger(t.maxConcurrent)&&t.maxConcurrent>0?t.maxConcurrent:2}}var Bi={};bu(Bi,{createComment:()=>Rx,findIssueUrl:()=>Ax,issueStates:()=>Cx,mdToAdf:()=>Rf,moveIssue:()=>Mx,parseIssueRef:()=>vx,resolveIssue:()=>Tx,uploadImage:()=>$x,upsertCard:()=>_x,whoami:()=>Pi});var Jn=t=>String(t.baseUrl||"").replace(/\/$/,""),If=t=>`Basic ${Buffer.from(`${t.email}:${t.apiToken}`).toString("base64")}`;async function fn(t,e,n,r){let o=await fetch(`${Jn(t)}${n}`,{method:e,headers:{authorization:If(t),accept:"application/json",...r!==void 0?{"content-type":"application/json"}:{}},body:r!==void 0?JSON.stringify(r):void 0}),s=await o.text(),a=null;try{a=s?JSON.parse(s):null}catch{a=null}if(!o.ok){let i=a?.errorMessages?.[0]??Object.values(a?.errors??{})[0]??`HTTP ${o.status}`,c=new Error(`jira: ${i}`);throw c.status=o.status,c}return a}var xx=/\/browse\/([A-Za-z][A-Za-z0-9]{0,9}-\d+)/,Sx=/^([A-Z][A-Z0-9]{0,9}-\d+)$/;function vx(t){let e=String(t||"").trim();if(/^https?:\/\//i.test(e)){let r=e.match(xx);return r?r[1].toUpperCase():null}let n=e.match(Sx);return n?n[1]:null}function Ax(t,e=null){let n=null;try{n=e?.baseUrl?new URL(e.baseUrl).host:null}catch{n=null}let r=/https?:\/\/[^\s)]+\/browse\/[A-Za-z][A-Za-z0-9]{0,9}-\d+[^\s)]*/g;for(let o of String(t||"").matchAll(r))try{let s=new URL(o[0]).host;if(s===n||/\.atlassian\.net$/i.test(s))return o[0]}catch{}return null}async function Tx(t,e){let n;try{n=await fn(t,"GET",`/rest/api/3/issue/${encodeURIComponent(e)}?fields=summary,status`)}catch(r){throw r.status===404?new Error(`jira: issue "${e}" not found`):r}return{issueId:n.id,key:n.key,title:n.fields?.summary??null,url:`${Jn(t)}/browse/${n.key}`,state:n.fields?.status?.name??null}}async function _x(t,e,{url:n,title:r,subtitle:o}){await fn(t,"POST",`/rest/api/3/issue/${encodeURIComponent(e.key)}/remotelink`,{globalId:n,application:{type:"com.bostrat.tracks",name:"Bostrat"},object:{url:n,title:r,summary:o,icon:{title:"Bostrat"}}})}var Ex=/(!\[[^\]]*\]\([^)\s]+\)|\*\*[^*\n]+\*\*|`[^`\n]+`|_[^_\n]+_|https?:\/\/[^\s)]+)/g;function Ix(t){let e=[],n=0;for(let r of t.matchAll(Ex)){r.index>n&&e.push({type:"text",text:t.slice(n,r.index)});let o=r[0];if(o.startsWith("![")){let[,s,a]=o.match(/^!\[([^\]]*)\]\(([^)\s]+)\)$/);e.push({type:"text",text:s||a,marks:[{type:"link",attrs:{href:a}}]})}else o.startsWith("**")?e.push({type:"text",text:o.slice(2,-2),marks:[{type:"strong"}]}):o.startsWith("`")?e.push({type:"text",text:o.slice(1,-1),marks:[{type:"code"}]}):o.startsWith("_")?e.push({type:"text",text:o.slice(1,-1),marks:[{type:"em"}]}):e.push({type:"text",text:o,marks:[{type:"link",attrs:{href:o}}]});n=r.index+o.length}return n<t.length&&e.push({type:"text",text:t.slice(n)}),e}function Rf(t){let e=String(t??"").split(/\n{2,}/).map(n=>n.trim()).filter(Boolean).map(n=>{let r=[];return n.split(`
|
|
40
|
+
`).forEach((o,s)=>{s>0&&r.push({type:"hardBreak"}),r.push(...Ix(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 Rx(t,e,n){let r=await fn(t,"POST",`/rest/api/3/issue/${encodeURIComponent(e.key)}/comment`,{body:Rf(n)});return r?.id?`${Jn(t)}/browse/${e.key}?focusedCommentId=${r.id}`:null}async function Pi(t){let e=await fn(t,"GET","/rest/api/3/myself"),n=null;try{n=new URL(Jn(t)).host}catch{n=t.baseUrl??null}return{user:e?.displayName??e?.emailAddress??null,org:n}}async function $x(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(`${Jn(t)}/rest/api/3/issue/${encodeURIComponent(e)}/attachments`,{method:"POST",headers:{authorization:If(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??`${Jn(t)}/rest/api/3/attachment/content/${c.id}`}var Ox={new:"unstarted",indeterminate:"started",done:"completed"},Ef=t=>Ox[t?.key]??"started";async function Cx(t,e){let n=encodeURIComponent(e.key),[r,o]=await Promise.all([fn(t,"GET",`/rest/api/3/issue/${n}?fields=status`),fn(t,"GET",`/rest/api/3/issue/${n}/transitions`)]),s=r?.fields?.status??null,a=s?{id:`status:${s.id}`,name:s.name,type:Ef(s.statusCategory)}:null,i=(o?.transitions??[]).map((c,l)=>({id:c.id,name:c.to?.name??c.name,type:Ef(c.to?.statusCategory),position:l}));return{current:a,states:i}}async function Mx(t,e,n){await fn(t,"POST",`/rest/api/3/issue/${encodeURIComponent(e.key)}/transitions`,{transition:{id:String(n)}})}var Fi={};bu(Fi,{createComment:()=>Ux,createIssue:()=>Li,createProject:()=>ji,findIssueUrl:()=>jx,issueStates:()=>Wx,moveIssue:()=>qx,parseIssueRef:()=>Dx,resolveIssue:()=>Lx,teams:()=>Xn,uploadImage:()=>Hx,upsertCard:()=>Fx,whoami:()=>Di});var Nx=()=>process.env.BOSTRAT_LINEAR_API_URL||"https://api.linear.app/graphql";async function re(t,e,n={}){let r=await fetch(Nx(),{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 Px=/linear\.app\/[^/\s]+\/issue\/([A-Za-z][A-Za-z0-9]{0,9}-\d+)/,Bx=/^([A-Z][A-Z0-9]{0,9}-\d+)$/;function Dx(t){let e=String(t||"").trim(),n=e.match(Px);if(n)return n[1].toUpperCase();let r=e.match(Bx);return r?r[1]:null}function jx(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 Lx(t,e){let r=(await re(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 Fx(t,e,{url:n,title:r,subtitle:o}){let s=await re(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 Ux(t,e,n){let r=await re(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 Di(t){let e=await re(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 Hx(t,{filename:e,contentType:n,buffer:r}){let o=await re(t,`mutation($contentType: String!, $filename: String!, $size: Int!) {
|
|
34
41
|
fileUpload(contentType: $contentType, filename: $filename, size: $size) {
|
|
35
42
|
success
|
|
36
43
|
uploadFile { uploadUrl assetUrl headers { key value } }
|
|
37
44
|
}
|
|
38
|
-
}`,{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 lk(t,e){let n=await te(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 uk(t,e,n){if(!(await te(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 Vn(t){return((await te(t,"query { teams(first: 100) { nodes { id key name } } }")).teams?.nodes??[]).map(n=>({id:n.id,key:n.key,name:n.name}))}var jd=255;async function fi(t,{teamId:e,name:n,description:r}){let o=r||null,s=!!o&&o.length>jd,a=s?`${o.slice(0,jd-1).trimEnd()}\u2026`:o,i=await te(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 pi(t,{teamId:e,title:n,description:r,projectId:o,priority:s}){let a=await te(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 Kd={linear:{api:mi,config:cn},jira:{api:ui,config:rs}};async function ki(){let t=[];for(let[e,{api:n,config:r}]of Object.entries(Kd)){let o=await r();o&&t.push({name:e,api:n,cfg:o})}return t}async function dn(t){let e=Kd[t?.provider];if(!e)return null;let n=await e.config();return n?{name:t.provider,api:e.api,cfg:n}:null}function xi(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 Vd=()=>process.env.BOSTRAT_STATE_DIR||qd.join(process.env.HOME||dk.tmpdir(),".bostrat"),yi=()=>qd.join(Vd(),"tickets.json"),Jd=()=>(process.env.BOSTRAT_PUBLIC_URL||"http://bostrat.globopi").replace(/\/$/,""),ss=t=>`${Jd()}/track/${t}`,os=null,hi=null,wi=Promise.resolve();async function as(){return os||(hi??=Ur.readFile(yi(),"utf8").then(t=>JSON.parse(t)?.tickets??{}).catch(()=>({})).then(t=>os=t).finally(()=>hi=null),hi)}async function Ld(){let t=JSON.stringify({tickets:os},null,2),e=`${yi()}.${process.pid}.tmp`;await Ur.mkdir(Vd(),{recursive:!0}),await Ur.writeFile(e,t),await Ur.rename(e,yi())}async function $e(){wi=wi.then(Ld,Ld),await wi.catch(()=>{})}async function ee(t){return(await as())[t]??null}async function is(){return{...await as()}}async function zd(){return(await ki()).length>0}async function Jn(t,e,{source:n="api"}={}){let r=await ki();if(!r.length)throw new Error("no ticketing integration configured \u2014 add a linear or jira entry to ~/.bostrat/integrations.json");let o=null,s=null,a=null;for(let u of r){let 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 as())[t.id]=c,await $e(),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 Hr(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 Fr(c,`**Bostrat** \u2014 Track **${c.track.name}** linked${l.length?` (${l.join(", ")})`:""}.`),await Zd(t.id,c,"linked"),c}async function Si(t){let e=await as();return e[t]?(delete e[t],await $e(),!0):!1}var bi=t=>`$${t>=100?Math.round(t):t.toFixed(2)}`;async function Xd(t,e){if(!t.track?.cwd)return null;let n=Te(await F({id:e,cwd:t.track.cwd}));return!n.totalUsd||n.totalUsd<.01?null:n}function fk(t){let e=Object.entries(t.byProvider).sort((r,o)=>o[1].usd-r[1].usd).map(([r,o])=>`${r} ${bi(o.usd)}`).join(" \xB7 "),n=Object.keys(t.byBox).length;return`${bi(t.totalUsd)}${e?` (${e})`:""}${n>1?` across ${n} boxes`:""}`}function pk(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(bi(e.totalUsd)),n.join(" \xB7 ")||"linked"}async function Hr(t,e){let n=await dn(e);if(!n)return;let r=await Xd(e,t).catch(()=>null);await n.api.upsertCard(n.cfg,e,{url:ss(t),title:`Bostrat Track: ${e.track.name}`,subtitle:pk(e,r)})}function cs(t){let e=new URL(Jd());return`${e.protocol}//${t}.${e.host}/`}var mk={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 Yd(t,e,{from:n,to:r,mode:o,phase:s=null,actor:a="user"}){e.state=r,await $e(),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 Zd(t,e,n){let r=await dn(e);if(!r)return;let o=r.cfg.ticketFlow?.[mk[n]]??"prompt";if(o==="off")return;let{current:s,states:a}=await r.api.issueStates(r.cfg,e),i=xi(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 Yd(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:Gd(),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 Qd(t){let e=await ee(t);if(!e)throw new Error("no ticket linked to this Track");let n=await dn(e);if(!n)throw new Error("no ticketing integration configured");let{current:r,states:o}=await n.api.issueStates(n.cfg,e),s=xi(r,o);return s?.name&&e.state!==s.name&&(e.state=s.name,await $e()),{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 vi(t,e,{mode:n="manual"}={}){if(!e)throw new Error("stateId required");let r=await ee(t);if(!r)throw new Error("no ticket linked to this Track");let o=await dn(r);if(!o)throw new Error("no ticketing integration configured");let{current:s,states:a}=await o.api.issueStates(o.cfg,r),i=xi(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 $e()),{key:r.key,from:l,to:c.name,moved:!1}):(await o.api.moveIssue(o.cfg,r,c.id),await Yd(t,r,{from:l,to:c.name,mode:n}),{key:r.key,from:l,to:c.name,moved:!0})}async function tf(t,e){let n=await ee(t);if(!n)throw new Error("no ticket linked to this Track");let r=await dn(n);if(!r)throw new Error("no ticketing integration configured");let o=String(e||"").trim();if(!o)throw new Error("comment body required");let s=await r.api.createComment(r.cfg,n,o);return await 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 ef(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 vi(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 Fd=50;async function nf(){let t=await cn();if(!t)throw new Error("no ticketing integration configured");return Vn(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>Fd)throw new Error(`too many issues (${n.length}); cap is ${Fd} per proposal`);return{epic:r,issues:n,team:e.team?String(e.team).trim():null,linkCurrentTrack:e.linkCurrentTrack===!0}}async function gk(t,e,n,r){let o=r??await Vn(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 ee(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 rf(t,e){let n=await cn();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 Vn(n),s=await gk(n,r,t,o),a=s?{id:s.id,key:s.key,name:s.name}:null,i=Gd();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 of(t,e,n,{teamId:r=null}={}){if(!e)throw new Error("proposalId required");let o=await F({id:t.id,cwd:t.cwd}),s=o.find(h=>h.type==="ticket.create.offered"&&h.payload?.proposalId===e);if(!s)throw new Error("unknown proposal");if(o.some(h=>h.type==="ticket.create.answered"&&h.payload?.proposalId===e))throw new Error("proposal already answered");if(!n)return await 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 cn();if(!a)throw new Error("no ticketing integration configured");let i=s.payload,c=i.team??null;if(!c&&r){let h=(await Vn(a)).find(m=>m.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=[],d=null;if(i.epic){let h=await fi(a,{teamId:c.id,name:i.epic.name,description:i.epic.description});d=h.id,l.push({kind:"epic",title:h.name,url:h.url})}for(let h of i.issues||[])try{let m=await pi(a,{teamId:c.id,title:h.title,description:h.description,projectId:d,priority:h.priority});l.push({kind:"issue",key:m.key,title:m.title,url:m.url})}catch(m){u.push({title:h.title,message:m?.message||String(m)})}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 f=l.filter(h=>h.kind==="issue"),p=null;return i.linkCurrentTrack&&f.length===1&&!await ee(t.id)&&(p=await Jn(t,f[0].key,{source:"created"}).catch(()=>null)),{proposalId:e,action:"created",created:l,failed:u,linked:p?p.key:null}}async function Fr(t,e){let n=await dn(t);n&&await n.api.createComment(n.cfg,t,e)}async function yk(t,e,n){let r=await dn(t);if(!r)return null;let o=await _e(e);if(!o)return null;let s=rn(o,n);if(!s)return null;let a=await Ur.readFile(jt(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 Ud=()=>Number(process.env.BOSTRAT_TICKET_COST_SYNC_MS??12e4),gi=new Map;function bk(t,e){if(Ud()<=0)return Hr(t,e);if(gi.has(t))return;let n=setTimeout(()=>{gi.delete(t),ee(t).then(r=>r?Hr(t,r):null).catch(()=>{})},Ud());n.unref?.(),gi.set(t,n)}var Hd=new Map;async function Wd(t,e,n){let r=Hd.get(t)??0;Date.now()-r<600*1e3||(Hd.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 kk(t,{resolveTrack:e=null}={}){let n=await ee(t.trackId),r=t.payload||{};if(!n){if(t.type!=="turn.user"||t.actor!=="user")return;let o=null;for(let a of await ki())if(o=a.api.findIssueUrl(r.text,a.cfg),o)break;if(!o)return;let s=e?await e(t.trackId):null;if(!s)return;try{await Jn(s,o,{source:"chat"})}catch(a){await Wd(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 $e())}try{switch(t.type){case"handoff.done":n.track.leadAgent=r.toAgent??n.track.leadAgent,await $e(),await Hr(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 $e(),await Hr(t.trackId,n),r.change==="opened"||r.change==="merged"){let o=r.change==="merged"?await Xd(n,t.trackId).catch(()=>null):null;await Fr(n,`**Bostrat** \u2014 PR ${r.change} for **${n.track.name}**${r.url?`: ${r.url}`:r.number?` (#${r.number})`:""}`+(o?`
|
|
39
|
-
|
|
40
|
-
Agent cost for this Track: ${
|
|
41
|
-
|
|
42
|
-
`)}else if(r.change==="picked"){let o=(r.siblings??[]).map(s=>`**${s.name}**`).join(", ");await
|
|
43
|
-
_(runs on the Track's branch; reachable on the operator's private network only)_`);break}case"cost.recorded":await bk(t.trackId,n);break;default:break}}catch(o){let s=e?await e(t.trackId).catch(()=>null):null;await Wd(t.trackId,s?.cwd??null,o)}}function sf({resolveTrack:t}){Dt.on("event",e=>{kk(e,{resolveTrack:t}).catch(()=>{})})}import ls from"node:fs/promises";import af from"node:path";import xk from"node:os";var cf=process.env.BOSTRAT_STATE_DIR||af.join(process.env.HOME||xk.tmpdir(),".bostrat"),Ti=af.join(cf,"pr-links.json"),st=(t,e)=>`${t}#${Number(e)}`,us=null,Ai=null,B=Promise.resolve();async function wt(){return us||(Ai??=ls.readFile(Ti,"utf8").then(t=>JSON.parse(t)?.links??{}).catch(()=>({})).then(t=>us=t).finally(()=>Ai=null),Ai)}async function X(){let t=JSON.stringify({links:us},null,2),e=`${Ti}.${process.pid}.tmp`;await ls.mkdir(cf,{recursive:!0}),await ls.writeFile(e,t),await ls.rename(e,Ti)}var Sk=new Set(["api","observed","reconciled"]);async function Mt({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 wt(),c=st(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:Sk.has(a),firstSeen:Date.now()},B=B.then(X,X),await B.catch(()=>{}),{link:i[c],created:!0})}async function Wr(t,e){if(!t||e==null)return null;let n=(await wt())[st(t,e)];return!n||n.announced?null:(n.announced=!0,B=B.then(X,X),await B.catch(()=>{}),n)}async function St(t,e){return!t||e==null?null:(await wt())[st(t,e)]??null}async function Oe(t,e,n){if(!t||e==null)return null;let r=(await wt())[st(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,B=B.then(X,X),await B.catch(()=>{}),r):null}async function lf(t,e,{headSha:n=null,trackId:r=null}={}){if(!t||e==null)return null;let o=(await wt())[st(t,e)];return o?(o.ciFix={delegatedAt:Date.now(),headSha:n,trackId:r},B=B.then(X,X),await B.catch(()=>{}),o):null}async function uf(t,e,{headSha:n=null,trackId:r=null}={}){if(!t||e==null)return null;let o=(await wt())[st(t,e)];return o?(o.reconcile={delegatedAt:Date.now(),headSha:n,trackId:r},B=B.then(X,X),await B.catch(()=>{}),o):null}async function ds(t,e,{headSha:n=null}={}){if(!t||e==null)return null;let r=(await wt())[st(t,e)];return!r||!r.ciFix?null:(r.ciFixAwait={since:Date.now(),trackId:r.ciFix.trackId??null,headSha:n},delete r.ciFix,B=B.then(X,X),await B.catch(()=>{}),r)}async function fs(t,e){if(!t||e==null)return null;let n=(await wt())[st(t,e)];return!n||!n.ciFixAwait?null:(delete n.ciFixAwait,B=B.then(X,X),await B.catch(()=>{}),n)}async function ps(t,e,{headSha:n=null}={}){if(!t||e==null)return null;let r=(await wt())[st(t,e)];return!r||!r.reconcile?null:(r.reconcileAwait={since:Date.now(),trackId:r.reconcile.trackId??null,headSha:n},delete r.reconcile,B=B.then(X,X),await B.catch(()=>{}),r)}async function ms(t,e){if(!t||e==null)return null;let n=(await wt())[st(t,e)];return!n||!n.reconcileAwait?null:(delete n.reconcileAwait,B=B.then(X,X),await B.catch(()=>{}),n)}async function df(t,e,{at:n=Date.now()}={}){if(!t||e==null)return null;let r=(await wt())[st(t,e)];return r?(r.baseFoldAt=n,B=B.then(X,X),await B.catch(()=>{}),r):null}async function ff(t,e){if(!t||e==null)return null;let n=(await wt())[st(t,e)];return!n||!n.baseFoldAt?null:(delete n.baseFoldAt,B=B.then(X,X),await B.catch(()=>{}),n)}async function pf(t,e,n){if(!t||e==null||!n)return null;let r=(await wt())[st(t,e)];return r?(r.lastHeadSha=n,B=B.then(X,X),await B.catch(()=>{}),r):null}async function qr(t,e,{merge:n=null}={}){if(!t||e==null)return null;let r=(await wt())[st(t,e)];return r?(r.armedAt=Date.now(),n&&(r.autoMerge={method:n.method||"merge",armedAt:r.armedAt}),B=B.then(X,X),await B.catch(()=>{}),r):null}async function zn(t,e,n=!0){if(!t||e==null)return null;let r=(await wt())[st(t,e)];return r?(n?r.archiveOnMerge=!0:delete r.archiveOnMerge,B=B.then(X,X),await B.catch(()=>{}),r):null}async function hs(t,e,n=!0){if(!t||e==null)return null;let r=(await wt())[st(t,e)];return r?(n?r.notifyOnMerge=!0:delete r.notifyOnMerge,B=B.then(X,X),await B.catch(()=>{}),r):null}async function mf(t,e,n){if(!t||e==null)return null;let r=(await wt())[st(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()],B=B.then(X,X),await B.catch(()=>{}),r}async function Gr(t,e){if(!t||e==null)return null;let n=(await wt())[st(t,e)];return!n||!n.armedAt&&!n.autoMerge?null:(delete n.armedAt,delete n.autoMerge,delete n.archiveOnMerge,delete n.notifyOnMerge,delete n.notifyPending,B=B.then(X,X),await B.catch(()=>{}),n)}var vk=new Set(["merged","closed","lost"]);async function hf(){return Object.values(await wt()).filter(t=>!vk.has(t.state))}async function Ce(){return Object.values(await wt()).sort((t,e)=>e.firstSeen-t.firstSeen)}var gf=t=>Math.round(t*100)/100;function Ak(t){return!t||t.kind==="setup"?[]:Rt(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():wf.basename(e.base)}))}function yf(t){let e=new Map;for(let n of t||[])for(let r of Ak(n)){let o=e.get(r.key);o||e.set(r.key,o={...r,tracks:[]}),o.tracks.push(n)}return e}function bf(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 _i=(t,e)=>`${t} ${e}${t===1?"":"s"}`;function Tk(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 Ei(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(_i(s,"PR")+" opened");let a=Object.keys(t?.subtasks||{}).length||r.filter(u=>u.type==="subtask-delegated").length;a&&n.push(_i(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(`${_i(i.length,"handoff")} ${u}${i.length>3?"\u2026":""}`)}let c=Te(e);c.totalUsd>=.01&&n.push(`$${c.totalUsd.toFixed(2)}`);let l=Number.isFinite(t?.archivedAt)&&Number.isFinite(t?.createdAt)?Tk(t.archivedAt-t.createdAt):null;return n.length&&l&&n.push(l),n.length?n.join(" \xB7 "):null}function _k(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 kf(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=bf(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:gf(l),firstActivityAt:c,lastActivityAt:i}}async function xf(t){let e=yf(t),n=await Ce().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,Te(c).totalUsd)}let s=[...e.values()].map(a=>kf(a,r,n)).sort((a,i)=>(i.lastActivityAt??0)-(a.lastActivityAt??0));return{generatedAt:Date.now(),arcs:s}}async function Sf(t,e){let r=yf(t).get(e);if(!r)return null;let[o,s,a,i,c]=await Promise.all([Ce().catch(()=>[]),Jo().catch(()=>[]),Qo().catch(()=>[]),ts().catch(()=>[]),is().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=Te(f);l.set(d.id,p.totalUsd);let h=c[d.id]||null;u.push({trackId:d.id,name:d.name,createdAt:d.createdAt??null,archivedAt:d.archivedAt??null,live:!d.archivedAt,beat:d.beat??null,agents:_k(d),handoffs:(d.events||[]).filter(m=>m.type==="lead-handoff").length,subtasks:Object.keys(d.subtasks||{}).length,repos:Rt(d.workspace).map(m=>m.repo||wf.basename(m.base||"")),prs:o.filter(m=>m.trackId===d.id&&(!r.slug||m.repo===r.slug)).map(m=>({repo:m.repo,number:m.number,state:m.state??null,title:m.title??null,url:m.url??null})),mocks:s.filter(m=>m.trackId===d.id).length,views:a.filter(m=>m.trackId===d.id).length,runbooks:i.filter(m=>m.trackId===d.id).map(m=>({name:m.name,status:m.status})),ticket:h?{provider:h.provider,key:h.key,url:h.url??null}:null,usd:gf(p.totalUsd),lastActivityAt:bf(d)})}return{...kf(r,l,o),generatedAt:Date.now(),segments:u}}import{execFile as Ek}from"node:child_process";import{promisify as Ik}from"node:util";import{randomUUID as Rk}from"node:crypto";var fn=Ik(Ek),ws=t=>new Promise(e=>setTimeout(e,t)),$k=Number(process.env.BOSTRAT_SWAP_EXIT_TIMEOUT_MS)||15e3,vf=Number(process.env.BOSTRAT_SWAP_SHELL_TIMEOUT_MS)||8e3,Ok=250,Ck=400,Af={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 _f(t){let e=await Yl(t);if(!e)return!1;try{return await fn("pgrep",["-P",String(e)]),!1}catch(n){return n?.code===1}}async function Ii(t,e){let n=Date.now()+e;for(;;){if(await _f(t))return!0;if(Date.now()>n)return!1;await ws(Ok)}}async function Mk(t,e){let n=Af[e]||Af.default;n.escape&&(await bt(t,["Escape"],{kind:"swap-exit"}),await ws(150)),n.wipe&&await Mo(t,400,{kind:"swap-exit"}),await Po(t,n.text,{perCharDelayMs:90,kind:"swap-exit"});let r=ce(e).submitDelayMs||0;await ws(Math.max(r,250)),await bt(t,["Enter"],{kind:"swap-exit"})}async function Pk(t,e){try{let{stdout:n}=await fn(P,N("show-environment","-t",t,e));return!String(n||"").trimStart().startsWith("-")}catch{return!1}}async function Tf(t){await fn(P,N("respawn-pane","-k","-t",t))}async function Ri(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||Mn(n),a=Number(e.exitTimeoutMs)||$k;await G({flow:"swap",phase:"start",sessionName:t,from:o,to:s,cwd:r.cwd});let i=!1;if(!await _f(t)&&(await Mk(t,o),!await Ii(t,a)&&(i=!0,await G({flow:"swap",phase:"respawn-forced",sessionName:t,from:o}),await Tf(t),!await Ii(t,vf))))throw new Error(`pane shell did not come back after respawn-pane in "${t}"`);let c=s==="claude"&&Cn(n),l=c?Ro():null,u=await Pk(t,"ANTHROPIC_API_KEY"),d=!1;try{l?(await fn(P,N("set-environment","-t",t,"ANTHROPIC_API_KEY",l)),d=!0):u&&(await fn(P,N("set-environment","-u","-t",t,"ANTHROPIC_API_KEY")),d=!0)}catch(b){throw l?xa(b,l):b}if(d&&!i&&(await Tf(t),!await Ii(t,vf)))throw new Error(`pane shell did not come back after env respawn in "${t}"`);let f=!!e.resumeSessionId,p=e.resumeSessionId||Rk(),h=f?n:Aa(n,p),m=f?e.pinned===!0:/(^|\s)--session-id(\s|=)/.test(h);return c&&await ke(r.cwd),s==="codex"&&await Rn(r.cwd),s==="cursor"&&await $n(r.cwd),await en(t),await fn(P,N("set-option","-t",t,"@bostrat-sid",p)),await fn(P,N("set-option","-t",t,"@bostrat-agent",s)),await ws(Ck),await kt(t,h,{kind:"swap-launch"}),await G({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:m,forced:i,envRespawned:d}}import Gk from"node:fs";import If from"node:fs/promises";import Kr from"node:path";import Kk from"node:os";import{fileURLToPath as Vk}from"node:url";import{execFile as Jk}from"node:child_process";import{promisify as zk}from"node:util";import Oi from"node:fs/promises";import Me from"node:path";import{execFile as Nk}from"node:child_process";import{promisify as Bk}from"node:util";var Dk=Bk(Nk),Ef=process.env.HOME||"/home/globo",jk=Me.join(process.env.BOSTRAT_STATE_DIR||Me.join(Ef,".bostrat"),"repos"),Lk=[...(process.env.BOSTRAT_REPOS_ROOT||Me.join(Ef,"Documents")).split(","),jk].map(t=>t.trim()).filter(Boolean),Fk=Number(process.env.BOSTRAT_REPOS_DEPTH)||3,Uk=new Set(["node_modules",".git",".cache","dist","build",".next","vendor","__pycache__","venv",".venv","target"]);async function Hk(t){try{return(await Oi.stat(Me.join(t,".git"))).isDirectory()?"repo":"worktree"}catch{return null}}async function Wk(t){let e=i=>Oi.readFile(Me.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 pe(){let t=new Map;async function e(n,r){if(t.has(n))return;let o=await Hk(n);if(o==="worktree")return;if(o==="repo"){t.set(n,{path:n,name:Me.basename(n),branch:await Wk(n)});return}if(r>=Fk)return;let s;try{s=await Oi.readdir(n,{withFileTypes:!0})}catch{return}for(let a of s)!a.isDirectory()||a.name.startsWith(".")||Uk.has(a.name)||await e(Me.join(n,a.name),r+1)}for(let n of Lk)await e(Me.resolve(n),0);return[...t.values()].sort((n,r)=>n.name.localeCompare(r.name))}function qk(t){let e=String(t||"").trim().match(/github\.com[:/]+([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/i);return e?`${e[1]}/${e[2]}`:null}var $i=new Map;async function Y(t){if(!t)return null;if($i.has(t))return $i.get(t);let e=null;try{let{stdout:n}=await Dk("git",["-C",t,"config","--get","remote.origin.url"],{timeout:5e3});e=qk(n)}catch{e=null}return $i.set(t,e),e}var Rf=zk(Jk),Xk=process.env.BOSTRAT_STATE_DIR||Kr.join(process.env.HOME||Kk.tmpdir(),".bostrat"),Yk=Kr.join(Xk,"repos"),$f=/^[^/\s]+\/[^/\s]+$/,Zk=5*6e4,Qk=6e4,tx=10*6e4,Of=fetch,Ci=new Map,pn=null;function Mi(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 gs(t=process.env){return!!(Mi(t)&&t.BOSTRAT_PLANE_TOKEN)}var Cf=t=>({authorization:`Bearer ${t.BOSTRAT_PLANE_TOKEN||""}`}),Xn=(t,e)=>Object.assign(new Error(t),{status:e});async function ys(t,{env:e=process.env}={}){let n=String(t||"").toLowerCase();if(!$f.test(n))throw Xn("repo must be owner/name",400);let r=Ci.get(n);if(r&&r.expiresAt-Date.now()>Zk)return r;if(!gs(e))throw Xn("node is not plane-linked \u2014 GitHub auth unavailable",503);let o;try{o=await Of(`${Mi(e)}/v1/github/token?repo=${encodeURIComponent(t)}`,{headers:Cf(e)})}catch(i){if(r&&r.expiresAt>Date.now())return r;throw Xn(`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 Xn(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 Ci.set(n,a),a}async function Vr({env:t=process.env,force:e=!1}={}){if(!gs(t))return{configured:!1,repos:[],username:null};if(!e&&pn&&Date.now()-pn.at<Qk)return pn.data;try{let n=await Of(`${Mi(t)}/v1/github/repos`,{headers:Cf(t)});if(!n.ok)throw new Error(`plane replied ${n.status}`);let r=await n.json();return pn={at:Date.now(),data:r},r}catch(n){return pn?pn.data:{configured:!0,repos:[],username:null,error:n?.message||String(n)}}}function Mf(){Ci.clear(),pn=null}async function ne({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 ys(r,{env:n});return o?{GH_TOKEN:o}:{}}catch{return{}}}async function Pi({env:t=process.env}={}){return(await Vr({env:t})).username??null}var ex=()=>Vk(new URL("../bin/bostrat-git-credential",import.meta.url)),nx=(t=process.env)=>`http://127.0.0.1:${Number(t.PORT)||3362}`;function rx(t){let[e,n]=String(t).split("/");return Kr.join(Yk,e,n)}async function Ni(t,{env:e=process.env}={}){let n=(...r)=>Rf("git",["-C",t,...r],{timeout:1e4});await n("config","--replace-all","credential.helper",""),await n("config","--add","credential.helper",`!"${ex()}" ${nx(e)}`),await n("config","credential.useHttpPath","true")}async function Bi(t,{env:e=process.env,baseUrl:n="https://github.com"}={}){if(!$f.test(String(t||"")))throw Xn(`bad repo slug "${t}"`,400);let r=rx(t);if(Gk.existsSync(Kr.join(r,".git")))return r;let{token:o}=await ys(t,{env:e});await If.mkdir(Kr.dirname(r),{recursive:!0});let s="!f() { echo username=x-access-token; echo password=$BOSTRAT_GIT_TOKEN; }; f";try{await Rf("git",["-c","credential.helper=","-c",`credential.helper=${s}`,"clone","--no-tags","-q",`${n}/${t}.git`,r],{timeout:tx,env:{...e,BOSTRAT_GIT_TOKEN:o,GIT_TERMINAL_PROMPT:"0"}})}catch(a){throw await If.rm(r,{recursive:!0,force:!0}).catch(()=>{}),Xn(`clone of ${t} failed: ${a?.stderr?.trim?.()||a?.message||a}`,502)}return await Ni(r,{env:e}),r}import Bx 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===`
|
|
44
|
-
`){e.push(n),n="";continue}n+=s}return e.push(n),e.map(o=>o.trim()).filter(Boolean)}function
|
|
45
|
-
`,n);i!==-1&&(e=e.slice(0,i))}let r=
|
|
46
|
-
`).map(n=>n.trim()).find(Boolean)||t;return e.length>88?`${e.slice(0,88).trimEnd()}\u2026`:e}function
|
|
47
|
-
`)){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(
|
|
48
|
-
`)){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
|
|
49
|
-
`).trim():"",
|
|
50
|
-
`);if(i!==-1)return o.slice(0,i);if(s+=a,o.length>=e)break}return o}finally{await n.close()}}async function
|
|
51
|
-
`).trim():"";async function
|
|
52
|
-
`)){let
|
|
53
|
-
`;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
|
|
54
|
-
`)){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
|
|
55
|
-
`);if(
|
|
56
|
-
`),
|
|
57
|
-
`),o=
|
|
58
|
-
`).
|
|
59
|
-
`),o
|
|
60
|
-
`),o
|
|
61
|
-
`),
|
|
62
|
-
`)
|
|
63
|
-
`),o
|
|
64
|
-
`),o=
|
|
65
|
-
`),o=
|
|
66
|
-
`)
|
|
67
|
-
`)
|
|
68
|
-
`),o=
|
|
69
|
-
`)
|
|
70
|
-
`)
|
|
71
|
-
`).
|
|
72
|
-
`).
|
|
73
|
-
`)
|
|
74
|
-
`)
|
|
75
|
-
`).map(s=>s.trim()).filter(Boolean)}async function yc(t,e,n,r){let{stdout:o}=await dt(t,hc(["diff","--binary",e,n],r));if(!o.trim())return{applied:!0,empty:!0,conflicts:[]};if((await lc(t,o,["--whitespace=nowarn"])).ok)return{applied:!0,empty:!1,conflicts:[]};let s=await lc(t,o,["--3way","--whitespace=nowarn"]),a=await x0(t);return a.length>0?{applied:!1,empty:!1,conflicts:a}:s.ok?{applied:!0,empty:!1,conflicts:[]}:(await lc(t,o,["--reject","--whitespace=nowarn"]),{applied:!1,empty:!1,conflicts:[`patch did not apply cleanly: ${s.err}`]})}async function lc(t,e,n){try{return await dt(t,["apply",...n,"-"],{input:e}),{ok:!0}}catch(r){return{ok:!1,err:r?.stderr?.trim()||r?.message||String(r)}}}async function x0(t){try{let{stdout:e}=await dt(t,["diff","--name-only","--diff-filter=U"]);return e.split(`
|
|
76
|
-
`).
|
|
77
|
-
`),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 Cp(t){let e=String(t||"").split(`
|
|
78
|
-
|
|
79
|
-
`);
|
|
80
|
-
`),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 jp(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&&!Np.has(o))throw new Error(`unknown codex effort "${o}" (low | medium | high | xhigh)`);return{model:r,effort:o||null}}if(t==="cursor"){let r=Bp.find(o=>o.alias===n);return{model:n,filter:r?r.label:n}}if(!_0.has(n))throw new Error(`unknown model "${n}"`);return{model:n}}async function Qr(t,e,{timeoutMs:n=A0}={}){let r=Date.now()+n;for(;;){let o=await ht(t,{lines:80}).catch(()=>""),s=e(o);if(s)return s;if(Date.now()>r)return null;await As(T0)}}async function R0(t,e){return await kt(t,`/model ${e.model}`,ce("claude")),{verified:!1,note:"confirmed when the next turn's transcript model flips"}}async function $0(t,e){await Co(t),await kt(t,"/model",{enter:!1}),await As(250),await bt(t,["Enter"],{kind:"model-switch"});let n=await Qr(t,Op);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=I0(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 Qr(t,i=>Cp(i)||!Op(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?Np.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 Qr(t,i=>!Cp(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 O0(){let t=$p.join(process.env.CURSOR_CONFIG_DIR||$p.join(v0.homedir(),".cursor"),"cli-config.json");try{return JSON.parse(await S0.readFile(t,"utf8"))?.model||null}catch{return null}}async function C0(t,e){await Co(t),await Po(t,`/model ${e.filter}`,{kind:"model-switch"});let n=await Qr(t,Mp),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 Qr(t,Mp,{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 As(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 As(800);let a=await O0();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 Lp(t,e,n){return t==="codex"?$0(e,n):t==="cursor"?C0(e,n):R0(e,n)}var Pt=t=>t||null;function _s(t){let e=t||{},n=M0(e);return n&&(n.payload={ts:e.at,...n.payload}),n}function M0(t){switch(t.type){case"said":return{type:"turn.user",actor:"user",source:"orchestrator",boxId:t.box,provider:Pt(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:Pt(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:Pt(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:Pt(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:Pt(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:Pt(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:Pt(t.agent),payload:{...ar(t)}};case"subtask-failed":return{type:"error",actor:"system",source:"orchestrator",boxId:t.box||t.to,provider:Pt(t.agent||t.toAgent),payload:{kind:"subtask-failed",...ar(t)}};case"subtask-resumed":return{type:"box.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Pt(t.agent),payload:{change:"subtask-resumed",...ar(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",...ar(t)}};case"archived":case"unarchived":case"track-deleted":return{type:"box.state.changed",actor:"user",source:"orchestrator",payload:{change:t.type,...ar(t)}};case"rehydrated":return{type:"box.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Pt(t.agent),payload:{change:"rehydrated"}};case"retagged":return{type:"box.state.changed",actor:"user",source:"orchestrator",payload:{change:"retagged",...ar(t)}};case"rehydrate-failed":return{type:"error",actor:"system",source:"orchestrator",boxId:t.box,provider:Pt(t.agent),payload:{kind:"rehydrate-failed",reason:t.reason}};default:return null}}var Ts=Promise.resolve(),kc=t=>(Ts=Ts.then(t).catch(()=>{}),Ts),Up=()=>Ts;function Hp(t,e){let n=_s(e);if(!n)return;let r=e?.type==="track-deleted"?{id:t.id}:t;kc(()=>C(r,n))}var Fp=new Set;async function Wp(t){if(!(!t?.id||Fp.has(t.id))){Fp.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 C(t,o)}}catch{}}}function ar(t){let{type:e,at:n,...r}=t;return r}var bc=new Map;function qp(t,e,n){!n?.promptId||bc.get(t.id)===n.promptId||(bc.set(t.id,n.promptId),kc(()=>C(t,{type:"prompt.raised",actor:"agent",source:"pane",boxId:e?.name,provider:Pt(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 Gp(t,e,n,r={}){bc.delete(t.id),kc(()=>C(t,{type:"prompt.answered",actor:"user",source:"orchestrator",boxId:e?.name,provider:Pt(n?.provider||e?.agent),payload:{promptId:n?.promptId,...r}}))}import{randomUUID as dS}from"node:crypto";var Vp=/(?<!\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,Jp=/(?<!\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,zp=/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,Xp=/(?<!\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,P0=new RegExp(`\\b(?:${Vp}|${Jp}|${zp}|${Xp})\\b`,"i"),N0=/\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,B0=/^(?:commit|push|open|create|raise|rebase|ready|ok(?:ay)?\s+to|good\s+to)\b/i,D0=90;function j0(t){return String(t).split(/(?<=[.?!])\s+|\n+/).map(e=>e.trim()).filter(Boolean)}function Yp(t){let e=String(t||"").trim();if(!e)return null;let n=e.slice(-600);for(let r of j0(n).reverse()){if(!P0.test(r))continue;let o=r.endsWith("?")&&r.length<=D0&&B0.test(r);if(N0.test(r)||o)return r.length>200?r.slice(0,197)+"\u2026":r}return null}var Zp=["commit","rebase","push","pr"],L0=new RegExp(`\\b(?:${Vp})\\b`,"i"),F0=new RegExp(`\\b(?:${Xp})\\b`,"i"),U0=new RegExp(`\\b(?:${Jp})\\b`,"i"),H0=new RegExp(`\\b(?:${zp})\\b`,"i"),W0=/\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 Qp(t){let e=String(t||""),n=[];L0.test(e)&&n.push("commit"),F0.test(e)&&n.push("rebase"),U0.test(e)&&n.push("push");let r=W0.test(e);!r&&H0.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 Es(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)`:"",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}${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${h}${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 q0=3e4;function tm(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<=q0&&n.push({box:r.box,from:r.from,to:e+1e12}),n}function em(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 Kp={commit:"commit",rebase:"rebase",push:"push",pr:"open a PR"};function nm(t){let e=(Array.isArray(t)?t:[]).filter(r=>Kp[r]).map(r=>Kp[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 z0}from"node:child_process";import{promisify as X0}from"node:util";import G0 from"node:fs";import xc from"node:fs/promises";import am from"node:path";import K0 from"node:os";var im=process.env.BOSTRAT_STATE_DIR||am.join(process.env.HOME||K0.tmpdir(),".bostrat"),Is=am.join(im,"github-metrics.json"),cm=60*6e4,rm=5*6e4,V0=6e4,ir=Date.now(),me=new Map,Be=null,Sn=0,om=0,Rs=t=>[...t.values()].reduce((e,n)=>e+n,0);try{let t=JSON.parse(G0.readFileSync(Is,"utf8"));t?.windowStart&&(Date.now()-t.windowStart<cm?(ir=t.windowStart,me=new Map(Object.entries(t.byCaller||{})),Be=t.lastHour||null):Rs(new Map(Object.entries(t.byCaller||{})))&&(Be={start:t.windowStart,total:Rs(new Map(Object.entries(t.byCaller||{}))),byCaller:t.byCaller||{}}))}catch{}var sm=Promise.resolve();function lm(t,e=!1){if(!e&&t-om<V0)return;om=t;let n=JSON.stringify({windowStart:ir,byCaller:Object.fromEntries(me),lastHour:Be});sm=sm.then(()=>xc.mkdir(im,{recursive:!0})).then(()=>xc.writeFile(`${Is}.tmp`,n)).then(()=>xc.rename(`${Is}.tmp`,Is)).catch(()=>{})}function um(t){t-ir<cm||(Be={start:ir,total:Rs(me),byCaller:Object.fromEntries(me)},Be.total&&console.log(`[github] ${Be.total} calls last hour`,Be.byCaller),ir=t,me=new Map,lm(t,!0))}function he(t="other",e=Date.now()){um(e),me.set(t,(me.get(t)||0)+1),lm(e)}function dm(t=Date.now()){return um(t),{windowStart:ir,total:Rs(me),byCaller:Object.fromEntries(me),lastHour:Be,backoffUntil:Sn>t?Sn:null}}var J0=/rate limit|too quickly|abuse detection|HTTP 429/i;function De(t){return J0.test(String(t?.message||t||""))}function je(t,e=Date.now()){return Sn<=e&&console.warn(`[github] rate limited \u2014 backing off ${rm/6e4} min:`,String(t?.message||t||"").slice(0,200)),Sn=Math.max(Sn,e+rm),Sn}function $s(t=Date.now()){return Math.max(0,Sn-t)}async function Sc(t,e){let n={working:!1,prompted:!1};if(!t)return n;let r=await ht(t,{lines:At}).catch(()=>"");if(!Bn(e,r))return hu(e,r)?{working:!0,prompted:!1}:n;if(rr(e,r)){let o=await Se(t).catch(()=>r);if(kn({name:t,agent:e},o))return{working:!1,prompted:!0}}return{working:!0,prompted:!1}}async function Os(t,e){return(await Sc(t,e)).working}var fm=X0(z0),pm=[4e3,15e3,45e3],Y0=2500,Z0=5*6e4;async function vc(t,e,{midTurn:n=Os,pollMs:r=Y0,capMs:o=Z0}={}){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 Q0=6e4,tS=9e4,eS=2e3,nS=/(?:^|[;&|]\s*|\)\s*)gh\s+(?:[^\s;&|"'`$]+\s+)*?pr\s+create\b/,rS=/(?:^|[;&|]\s*|\)\s*)gh\s+(?:[^\s;&|"'`$]+\s+)*?pr\s+merge\b/,oS=/\s--web\b/,mm=/(?:--repo|-R)[\s=]+([\w.-]+\/[\w.-]+)/;function Ac(t,e){return t.slice(e).replace(/^[\s;&|)]+/,"").replace(/"[^"]*"|'[^']*'/g,'""').split(/[;&|]/)[0]}function hm(t){let e=String(t||""),n=e.match(nS);if(!n)return null;let r=Ac(e,n.index);return oS.test(r)?null:{repo:r.match(mm)?.[1]??null}}var sS=new Set(["--subject","-t","--body","-b","--body-file","-F","--match-head-commit","--author-email","-A","--repo","-R"]);function wm(t){let e=String(t||""),n=e.match(rS);if(!n)return null;let r=Ac(e,n.index),o=Ac(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("-")){sS.has(c)&&i++;continue}s=c.replace(/^["']+|["']+$/g,"")||null;break}return{repo:r.match(mm)?.[1]??null,selector:s,auto:/\s--auto\b/.test(r)}}var aS="number,url,title,headRefName,createdAt";async function iS(t,e){let n=async s=>{he("observer");let{stdout:a}=await fm("gh",["pr","list","--repo",t,"--author","@me","--state","all","--limit","10","--json",aS],{timeout:3e4,maxBuffer:4*1024*1024,env:{...process.env,...s}}).catch(i=>{throw De(i)&&je(i),i});return JSON.parse(a||"[]")},r=await ne({slug:t}),o=await n(r);return!o.length&&r.GH_TOKEN&&(o=await n({}).catch(()=>[])),o.filter(s=>Date.parse(s.createdAt)>=e-Q0).sort((s,a)=>Date.parse(a.createdAt)-Date.parse(s.createdAt))}async function cS(t,e){if(e)return[e];let n=[];for(let r of gm(t)){let o=await Y(r).catch(()=>null);o&&!n.includes(o)&&n.push(o)}for(let r of await pe().catch(()=>[])){let o=await Y(r.path).catch(()=>null);o&&!n.includes(o)&&n.push(o)}return n}function gm(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 lS(t,e,n){let r=t.workspace?.branch,o=[];for(let s of await cS(t,e)){let a;try{a=await iS(s,n)}catch{continue}for(let i of a){let c=await St(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 Le=new Set;function ym(t,e,{delays:n=pm,turnEnd:r}={}){let o=hm(e.command);if(!o)return null;let s=!!wm(e.command),a=`${t.id}|${e.id??e.ts??e.command}`;if(Le.has(a))return null;Le.add(a);let i=e.ts??Date.now(),c=(async()=>{try{for(let l of n){await new Promise(m=>setTimeout(m,l));let u=await lS(t,o.repo,i).catch(()=>null);if(!u)continue;let{slug:d,pr:f}=u,{link:p,created:h}=await Mt({repo:d,number:f.number,trackId:t.id,url:f.url,headRef:f.headRefName,title:f.title,source:"observed"});return!h&&!(p?.trackId===t.id&&await Wr(d,f.number))?null:(await vc(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:f.number,url:f.url,title:f.title,branch:f.headRefName,repo:d,...s?{selfMerge:!0}:{}}}),f)}return null}finally{Le.delete(a)}})();return c.catch(()=>Le.delete(a)),c}var uS="number,url,title,headRefName,state";function bm(t,e,{delays:n=pm,turnEnd:r,openedWait:o}={}){let s=wm(e.command);if(!s)return null;let a=`merge|${t.id}|${e.id??e.ts??e.command}`;if(Le.has(a))return null;Le.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 h=gm(t);if(h.length>1&&/^\d+$/.test(String(s.selector??"")))return null;for(let m of h){let b=await Y(m).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(h,m)=>{let b=await St(c,h.number);return b&&b.trackId!==t.id?null:(b||await Mt({repo:c,number:h.number,trackId:t.id,url:h.url,headRef:h.headRefName,title:h.title,source:"merge-observed"}),await qr(c,h.number),m&&((await F(t).catch(()=>[])).some(E=>E.type==="pr.state.changed"&&(E.payload?.change==="auto-armed"||E.payload?.change==="merged")&&E.payload?.number===h.number)||(await vc(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)},p=null;for(let h of n){await new Promise(I=>setTimeout(I,h));let m=[];for(let I of d)try{he("observer");let{stdout:$}=await fm("gh",["pr","view",String(l),"--repo",I,"--json",uS],{timeout:3e4,maxBuffer:4*1024*1024,env:{...process.env,...await ne({slug:I})}}),_=JSON.parse($||"null");_?.number&&m.push({slug:I,pr:_})}catch($){De($)&&je($)}let b=m.find(I=>I.pr.state==="MERGED")??(m.length===1?m[0]:null);if(!b)continue;c=b.slug;let k=b.pr;if(!k?.number)continue;if(k.state==="CLOSED")return null;if(k.state!=="MERGED"){if(p=k,s.auto)return f(k,!0);continue}let v=await St(c,k.number);if(v&&v.trackId!==t.id)return null;v||await Mt({repo:c,number:k.number,trackId:t.id,url:k.url,headRef:k.headRefName,title:k.title,source:"merge-observed"}),await vc(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===k.number))return null;let M=I=>I.some($=>$.type==="pr.state.changed"&&$.payload?.change==="opened"&&$.payload?.number===k.number),lt=0;if(!M(E)){if(hm(e.command)&&await Wr(c,k.number)){let I=Date.now();lt=I+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:I,change:"opened",number:k.number,url:k.url,title:k.title,branch:k.headRefName,repo:c,selfMerge:!0}})}else if((await St(c,k.number))?.announced){let I=o?.capMs??tS,$=o?.pollMs??eS,_=Date.now()+I;for(;Date.now()<_&&(await new Promise(U=>setTimeout(U,$)),!M(await F(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(),lt),change:"merged",number:k.number,url:k.url,title:k.title,branch:k.headRefName,repo:c}}),await Ue(c,k.number).catch(()=>{}),await Fe(c,k.number).catch(()=>{}),await Oe(c,k.number,"merged"),k}return p?f(p,!1):null}finally{Le.delete(a)}})();return i.catch(()=>Le.delete(a)),i}function _c(t){return t.sourceId?`${t.role}|id:${t.sourceId}`:`${t.role}|${t.ts??"?"}|${String(t.text||"").slice(0,40)}`}function km(t){return t.id?`cmd|${t.box}|id:${t.id}`:`cmd|${t.box}|${t.ts??"?"}|${String(t.command||"").slice(0,40)}`}function xm(t){return`job|${t.box}|id:${t.id}`}var Tc=new Map,Cs=new Map;async function Ec(t){return Tc.has(t.id)?Tc.get(t.id):(Cs.has(t.id)||Cs.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(_c({role:o,sourceId:r.payload.sourceId})),n.keys.add(_c({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(km({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(xm({id:r.payload.id,box:r.boxId}));return Tc.set(t.id,n),n}).finally(()=>Cs.delete(t.id))),Cs.get(t.id))}async function Sm(t,e){try{let n=(await Ec(t)).keys;for(let r of e||[]){if(!r||r.ts==null||!String(r.text||"").trim()&&!r.images?.length)continue;let o=_c(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=Yp(r.text);if(s){let{offered:a,toPr:i}=Qp(s),c;if(t.workspace?.kind==="multi"){let l=await to(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:dS(),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 vm(t,e){try{let n=await Ec(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=km(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}}),ym(t,o),bm(t,o))}}catch{}}async function Am(t,e){try{let n=await Ec(t),r=[...e||[]].filter(o=>o&&o.id).sort((o,s)=>(o.ts||0)-(s.ts||0));for(let o of r){let s=xm(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 fS=/^Base directory for this skill:\s*(\S+)/,pS=/^Run the "([^"]+)" workflow\./,mS=/<command-name>\s*\/?([a-z0-9:_-]+)\s*<\/command-name>/i,hS=/^<local-command-(?:stdout|caveat)>/i,wS=/^\[Image: original \d+x\d+, displayed at \d+x\d+\.\s*Multiply coordinates by [\d.]+ to map to original image\.\]$/,gS=/^<task-notification>/i,yS=/<summary>([\s\S]*?)<\/summary>/i,bS=/<task-id>([\s\S]*?)<\/task-id>/i,kS=/<tool-use-id>([\s\S]*?)<\/tool-use-id>/i,xS=/<status>([\s\S]*?)<\/status>/i,SS=/<result>([\s\S]*?)(?:<\/result>|$)/i;var Ic=new Set(["model","clear","compact","login"]);function Tm(t){return Ic.has(String(t||"").replace(/^\//,"").toLowerCase())}var vS=/^\/([a-z0-9:_-]+)$/i;function Ms(t){let e=String(t||"").trim();if(!e)return null;let n=e.match(fS);if(n)return{kind:"skill",name:n[1].split("/").filter(Boolean).pop()||"skill"};let r=e.match(pS);if(r)return{kind:"skill",name:r[1]};let o=e.match(mS);if(o){let a=o[1].toLowerCase();return Ic.has(a)?{kind:"suppress"}:{kind:"command",command:`/${a}`}}let s=e.match(vS);if(s&&Ic.has(s[1].toLowerCase()))return{kind:"suppress"};if(hS.test(e))return{kind:"suppress"};if(wS.test(e))return{kind:"suppress"};if(gS.test(e)){let a=c=>AS(e.match(c)?.[1].trim()||"")||null,i=a(SS);return{kind:"task",summary:a(yS)||"",taskId:a(bS),toolUseId:a(kS),status:a(xS)?.toLowerCase()||null,resultHead:i?i.length>600?`${i.slice(0,600).trimEnd()}\u2026`:i:null}}return null}function AS(t){return t.replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/'/g,"'").replace(/&/g,"&")}function _m(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 Em(t){return t?.kind==="skill"?"skill-run":t?.kind==="task"?"task-done":"command-run"}var et=t=>t.payload?.ts??t.ts,TS=new Set(["opened","ci-fixed","reconciled","pushed"]),Im=t=>`${t.repo??""}#${t.number}`;function Ps(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,h=(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=xn(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=xn(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:et(g)};w.toolUseId&&i.set(w.toolUseId,y);let A=String(w.summary||"").match(u);A&&l.set(A[1],y)}let m=new Map,b=new Map,k=null,v=new Map,E=new Map,M=new Map,lt=new Map,I=new Map,$=new Map,_=new Map,U=new Map,V=(g,w)=>{let y=o.get(g);if(!y?.length)return!1;let A=xn(w),R=s.get(g);return y.some(D=>{if(!A.startsWith(D))return!1;let T=xn(A.slice(D.length));return!!T&&!!R?.has(T)})};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||Ms(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"&&Tm(y.command))break;y.kind!=="suppress"&&e.push({id:g.id,kind:"event",type:Em(y),name:y.name??null,command:y.command??null,summary:y.summary??null,ts:et(g),box:g.boxId,agent:g.provider});break}}if(r.has(`${g.boxId}|${et(g)}`)||g.source==="transcript"&&V(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:et(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:et(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":{_.clear();let y=(w.images||[]).filter(R=>!h(R,et(g)));if(!String(w.text||"").trim()&&!y.length)break;if(g.provider==="cursor"){let R=`${g.boxId}|${w.text}`;if(n.has(R))break;n.add(R)}let A=g.provider==="codex"?(w.tools||[]).filter(R=>!Vi.has(R)&&!Yr.has(R)):w.tools||[];e.push({id:g.id,kind:"message",role:"assistant",text:w.text,ts:et(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=et(g),A=i.get(w.id);if(!A&&(w.kind||"agent")==="agent"&&w.name){let O=l.get(w.name);O&&O.ts>=y&&(A=O)}let R={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},D=U.get(g.boxId);if(D&&D.jobs.some(O=>(O.finishedTs??1/0)>=y))D.jobs.push(R);else{let O={id:g.id,kind:"job-dock",dockId:w.id,ts:y,box:g.boxId,agent:g.provider,jobs:[R]};U.set(g.boxId,O),e.push(O)}break}case"handoff.done":e.push(He(g,"lead-handoff"));break;case"subtask.delegated":e.push({...He(g,"subtask-delegated"),to:g.boxId,toAgent:g.provider});break;case"subtask.merged":e.push(He(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=He(g,`pr-${w.change}`);if(TS.has(w.change)&&w.number!=null){let A=Im(w);if(m.set(A,{number:w.number,url:w.url??null,title:w.title??null,repo:w.repo??null,branch:w.branch??null}),m.size>=2){for(let D of b.values())for(let T of D)T.grouped=!0;y.groupPrs=[...m.values()];let R=[y];for(let D of m.keys())b.set(D,R)}else{let R=b.get(A)??[];R.push(y),b.set(A,R)}}else if(w.change==="merged"||w.change==="closed"||w.change==="lost"){let A=Im(w),R=k&&!k.complete||m.size>=2;if(m.has(A))m.delete(A),b.delete(A);else if(w.number!=null)for(let D of[...m.keys()])D.endsWith(`#${w.number}`)&&(m.delete(D),b.delete(D));if(R&&w.number!=null){(!k||k.complete)&&(k={id:g.id,kind:"event",type:"pr-group-settled",prs:[],remaining:[],complete:!1,at:et(g),ts:et(g),box:g.boxId,agent:g.provider},e.push(k)),k.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:et(g)}),k.remaining=[...m.values()],k.complete=m.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:et(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=He(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:et(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:et(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:et(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:et(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:et(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:et(g),box:g.boxId,agent:g.provider});break}case"ticket.move.offered":{if(!w.offerId)break;for(let A of M.values())A.key===w.key&&(A.status="stale",M.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:et(g),box:g.boxId,agent:g.provider};M.set(w.offerId,y),lt.set(w.offerId,y),e.push(y);break}case"ticket.move.answered":{let y=lt.get(w.offerId);y&&(y.status=w.action==="moved"?"moved":"dismiss",M.delete(w.offerId));break}case"ticket.state.changed":{for(let y of M.values())y.key===w.key&&y.to===w.to&&(y.status="stale",M.delete(y.offerId));if(w.mode==="offer")break;e.push(He(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:et(g),box:g.boxId,agent:g.provider});break}case"ticket.flow.changed":e.push(He(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:et(g),box:g.boxId,agent:g.provider};I.set(w.proposalId,y),$.set(w.proposalId,y),e.push(y);break}case"ticket.create.answered":{let y=$.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?$.get(w.proposalId):null;if(A){A.created=y;break}e.push({id:g.id,kind:"ticket-created",...y,ts:et(g),box:g.boxId,agent:g.provider});break}case"error":{if(!w.kind)break;e.push(He(g,w.kind));break}case"provider.connected":{e.push({id:g.id,kind:"event",type:"provider-connected",provider:w.provider||g.provider,ts:et(g),at:et(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=_.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:et(g)},_.set(y,A));let R=w.questionIndex??0,D={index:R,header:w.header||"",question:w.question,multiSelect:!!w.multiSelect,chosen:Array.isArray(w.chosen)?w.chosen:[]},T=A.questions.findIndex(W=>W.index===R);T>=0?A.questions[T]=D:A.questions.push(D),A.questions.sort((W,tt)=>W.index-tt.index),A.questionTotal=Math.max(A.questionTotal,w.questionTotal??1),new Set(A.questions.map(W=>W.index)).size>=A.questionTotal&&(e.push(A),_.delete(y));break}default:break}}return ES(e.sort((g,w)=>(g.ts??0)-(w.ts??0)))}var _S=2*6e4;function ES(t){let e=new Map,n=[];for(let r of t){if(r.kind!=="message"||!r.box){n.push(r);continue}let o=r.role==="assistant"&&!String(r.text||"").trim()&&r.images?.length,s=e.get(r.box);if(o&&s&&(r.ts??0)-s.lastTs<=_S){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 Rc(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:IS(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??Ps(t)).filter(o=>o.kind==="message");return Lf(n,r)}function IS(t){let e=String(t.summary||"").trim();return e&&e!==t.command&&e!==Qn(t.command)?e:Pe(t.command,t.description||"")}function He(t,e){let{ts:n,change:r,...o}=t.payload||{};return{id:t.id,kind:"event",type:e,...o,at:et(t),ts:et(t)}}function RS(t){let e=[],n=null;for(let r of String(t||"").split(`
|
|
81
|
-
`)){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 $c=new Map;async function $S(t,e){let n=`${t.id}:${e.name}`;if(!$c.has(n)){let r=await F(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);$c.set(n,o)}return $c.get(n)}async function Rm(t,e){try{if(e?.agent!=="codex"||!e.cwd)return;let n=await hn(e.cwd,e.pinned?e.sessionId:null);if(!n)return;let r=await $S(t,e),o=RS(await Tt(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:Jt(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 Ns from"node:fs";import Cc from"node:path";import OS from"node:os";var CS=process.env.BOSTRAT_STATE_DIR||Cc.join(process.env.HOME||OS.tmpdir(),".bostrat"),Om=(t=process.env)=>!!t.BOSTRAT_CURSOR_USAGE_COOKIE,MS="https://cursor.com/api/dashboard/get-filtered-usage-events",$m=100;async function PS({sinceMs:t,cookie:e,fetchImpl:n=fetch}){let r=[];for(let o=1;o<=20;o++){let s=await n(MS,{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:$m})});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<$m)break}return r}function NS(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 BS=5*6e4,We={at:0,rows:null,error:null},Bs=()=>Cc.join(CS,"cursor-usage.json");function Cm(){try{return JSON.parse(Ns.readFileSync(Bs(),"utf8"))}catch{return{sinceMs:0,seen:[]}}}function DS(t){try{Ns.mkdirSync(Cc.dirname(Bs()),{recursive:!0});let e=Bs()+".tmp";Ns.writeFileSync(e,JSON.stringify(t)),Ns.renameSync(e,Bs())}catch{}}var jS=5e3;async function LS({fetchImpl:t,now:e=Date.now()}={}){if(We.rows&&e-We.at<BS)return We.rows;let n=process.env.BOSTRAT_CURSOR_USAGE_COOKIE;if(!n)return null;let r=Cm(),o=Math.max(r.sinceMs-6e4,e-24*36e5);try{let s=await PS({sinceMs:o,cookie:n,fetchImpl:t});We={at:e,rows:s.map(NS).filter(Boolean),error:null}}catch(s){We={at:e,rows:null,error:String(s?.message||s)}}return We.rows}var Mm=()=>({enabled:Om(),lastPollAt:We.at||null,error:We.error}),Oc=new Map;async function FS(t,e){let n=`${t.id}:${e.name}`;if(!Oc.has(n)){let r=await F(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);Oc.set(n,o)}return Oc.get(n)}async function Pm(t,e,{fetchImpl:n}={}){try{if(e?.agent!=="cursor"||!e.cwd||!Om())return;let r=await LS({fetchImpl:n});if(!r||!r.length)return;let o=await Jf(e.cwd,e.pinned?e.sessionId:null);if(!o?.chatId)return;let s=await FS(t,e),a=Cm(),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",d=l.tokens||null,f=l.vendorCents!=null?l.vendorCents/100:0;!f&&d&&(f=Jt(u,d)),!f&&l.chargedCents!=null&&(f=l.chargedCents/100),await C(t,{type:"cost.recorded",actor:"agent",source:"vendor",boxId:e.name,provider:"cursor",payload:{usd:f,model:u,...d?{tokens:d}:{},...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}})}DS({sinceMs:c,seen:[...i].slice(-jS)})}catch{}}import{execFile as GS}from"node:child_process";import{promisify as KS}from"node:util";import Nm from"node:fs/promises";import Bm from"node:path";import US from"node:os";var Dm=process.env.BOSTRAT_STATE_DIR||Bm.join(process.env.HOME||US.tmpdir(),".bostrat"),HS=Bm.join(Dm,"github-latency.ndjson"),WS=100,Ds=[],Mc=new Map;function js({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};Ds.push(l),Ds.length>WS&&Ds.shift();let u=Mc.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)),Mc.set(t,u),qS(l),l}async function qS(t){try{await Nm.mkdir(Dm,{recursive:!0}),await Nm.appendFile(HS,JSON.stringify(t)+`
|
|
82
|
-
`)}catch{}}function jm(){let t={};for(let[e,n]of Mc)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:Ds.slice(-20)}}var Us=KS(GS),VS={timeout:3e4,maxBuffer:8*1024*1024};async function Ft(t,e,n={}){let{caller:r="prs",...o}=n,s=await ne({cwd:t});he(r);try{let{stdout:a}=await Us("gh",e,{cwd:t,...VS,...o,env:{...process.env,...s,...o.env||{}}});return String(a||"")}catch(a){throw De(a)&&je(a),a}}var Ls=null;async function Bc(){if(Ls&&Date.now()-Ls.at<6e4)return Ls.data;let t;try{he("auth");let e=await Us("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 Vr();e.configured&&(t={installed:!0,authenticated:!0,login:e.username,via:"broker"})}catch{}return Ls={at:Date.now(),data:t},t}async function JS(t){try{let{stdout:e}=await Us("git",["-C",t,"config","--get","remote.origin.url"],{timeout:5e3});return/github\.com/.test(e)}catch{return!1}}function Fm(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 Pc(t){let e=(t||[]).map(Fm),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 Dc="number,title,url,headRefName,headRefOid,baseRefName,isDraft,author,reviewDecision,mergeable,mergeStateStatus,statusCheckRollup,autoMergeRequest,additions,deletions,createdAt,updatedAt",Um=Dc.replace("statusCheckRollup,","");function jc(t){return/Resource not accessible by personal access token/.test(String(t?.stderr||t?.message||""))}async function Hm(t,e){if(!e)return null;let n=await Y(t);if(!n)return null;try{let r=await Ft(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 Wm(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 qm(t,e){let n=await Hm(t,e);return n===null?{...Pc([]),unavailable:!0}:Pc(Wm(n))}function Fs(t,e=Pc(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 zS=9e4,cr=new Map;function XS(t,e,n){let r=new Set;for(let o of e){let s=`${t}#${o.number}`;if(o.mergeable!=="UNKNOWN"&&o.mergeStateStatus!=="UNKNOWN"){cr.delete(s);continue}r.add(s),cr.has(s)||cr.set(s,n),o.mergeabilityPending=n-cr.get(s)<zS}for(let o of cr.keys())o.startsWith(`${t}#`)&&!r.has(o)&&cr.delete(o);return e}async function lr(t,e){let n=await we(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");try{let o=await Ft(n.path,["pr","view",String(r),"--json",Dc]);return Fs(JSON.parse(o))}catch(o){if(!jc(o))throw o;let s=await Ft(n.path,["pr","view",String(r),"--json",Um]),a=JSON.parse(s);return Fs(a,await qm(n.path,a.headRefOid))}}var Lm=(t,e)=>t.number-e.number;async function no(t,{caller:e="prs"}={}){try{let n=await Ft(t,["pr","list","--json",Dc,"--limit","50"],{caller:e});return JSON.parse(n||"[]").map(r=>Fs(r)).sort(Lm)}catch(n){if(!jc(n))throw n;let r=await Ft(t,["pr","list","--json",Um,"--limit","50"],{caller:e});return(await Promise.all(JSON.parse(r||"[]").map(async o=>Fs(o,await qm(t,o.headRefOid))))).sort(Lm)}}var YS=3e4,ZS=1500,eo=new Map;function oe(){eo.clear()}async function QS(t,e){if(!t)return e;for(let n of e){let r=await St(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 ds(t,n.number,{headSha:n.headSha}).catch(()=>{}):n.ciFix={delegatedAt:r.ciFix.delegatedAt,trackId:r.ciFix.trackId??null}),r.reconcile&&(o(r.reconcile)?await ps(t,n.number,{headSha:n.headSha}).catch(()=>{}):n.reconcile={delegatedAt:r.reconcile.delegatedAt,trackId:r.reconcile.trackId??null})}return e}async function Gm(){let t=await Bc();if(!t.authenticated)return{auth:t,repos:[]};let e=[];for(let a of await pe())await JS(a.path)&&e.push(a);let n=Date.now(),r=$s(n)>0,o=await Promise.all(e.map(async a=>{let i=eo.get(a.path),c=i?.pending?ZS:YS;if(i&&(r||n-i.at<c))return i.entry;try{let l=await Y(a.path).catch(()=>null),u=XS(a.path,await no(a.path,{caller:"tab"}),n);if(await QS(l,u),i?.entry?.prs)for(let p of u)i.entry.prs.find(m=>m.number===p.number)?.checks?.pending>0&&p.checks.total>0&&p.checks.pending===0&&js({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 eo.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:re(l)}}})),s=new Set(e.map(a=>a.path));for(let a of eo.keys())s.has(a)||eo.delete(a);return{auth:t,repos:o}}async function we(t){let e=(await pe()).find(n=>n.path===t);if(!e)throw new Error(`unknown repo: ${t}`);return e}async function vn(t){if(!t)return null;for(let e of await pe())if(await Y(e.path).catch(()=>null)===t)return e.path;return null}var ro=new Set(["squash","merge","rebase"]);async function ur(t,e,{method:n="squash"}={}){let r=await we(t);if(!ro.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 ne({cwd:r.path})).GH_TOKEN){let a=await Pi().catch(()=>null);a&&s.push("--body",`Merged by bostrat on behalf of @${a}`)}try{await Ft(r.path,s)}catch(a){throw new Error(re(a))}finally{oe()}return{ok:!0,number:o}}async function oo(t,e){let n=await we(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");try{await Ft(n.path,["api","-X","PUT",`repos/{owner}/{repo}/pulls/${r}/update-branch`])}catch(s){throw new Error(re(s))}finally{oe()}let o=await Y(n.path).catch(()=>null);return o&&await df(o,r).catch(()=>{}),{ok:!0,number:r}}async function Lc(t,e){let n=await we(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");try{await Ft(n.path,["pr","merge",String(r),"--disable-auto"])}catch(o){throw new Error(re(o))}finally{oe()}return{ok:!0,number:r}}var tv=2500,ev=9e4,nv=t=>new Promise(e=>setTimeout(e,t));function Fc(t){return t.green&&!t.isDraft}function rv(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 Km(t,{update:e=!0}={}){return t.isDraft?{action:"skip",reason:"draft"}:Fc(t)?{action:"merge"}:e&&t.mergeStateStatus==="BEHIND"&&t.mergeable!=="CONFLICTING"&&t.checks.failed===0?{action:"update-arm"}:{action:"arm"}}function Vm(t){return[...t.filter(e=>e.plan.action!=="merge"),...t.filter(e=>e.plan.action==="merge")]}var Nc=new Map;function Jm(){let t={};for(let[e,n]of Nc)t[e]={...n};return t}async function zm(t,{method:e="merge",onMerged:n=null}={}){let r=await we(t);if(!ro.has(e))throw new Error(`invalid merge method: ${e}`);if(Nc.get(r.path)?.running)throw new Error("a merge-all is already running for this repo");let o=(await no(r.path)).filter(Fc).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 Nc.set(r.path,s),sv(s,o,{listPrs:()=>no(r.path),merge:a=>ur(r.path,a,{method:e}),sleep:nv,onMerged:n}).catch(()=>{}),{ok:!0,repo:r.path,total:s.total,queue:o.map(a=>a.number)}}async function ov(t,e){let n=Date.now()+(e.settleMaxMs??ev),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(tv)}}async function sv(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 ov(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: ${re(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(!Fc(a)){t.skipped.push({number:a.number,title:a.title,reason:rv(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:re(i)})}}}finally{t.current=null,t.running=!1,t.finishedAt=Date.now(),oe()}}async function Uc(t,{title:e,body:n="",base:r=null}={}){if(typeof e!="string"||!e.trim())throw new Error("title required");try{await Us("git",["-C",t,"push","-u","origin","HEAD"],{timeout:12e4,maxBuffer:4*1024*1024})}catch(l){throw new Error(`push failed: ${re(l)}`)}let o=n||"";if((await ne({cwd:t})).GH_TOKEN){let l=await Pi().catch(()=>null);l&&(o=`${o?`${o}
|
|
83
|
-
|
|
84
|
-
`:""}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 Ft(t,s)}catch(l){throw new Error(re(l))}finally{oe()}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 Xm(t,e){let n=await we(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");let o;try{let i;try{i=await Ft(n.path,["pr","checks",String(r),"--json","name,state,link,bucket"])}catch(c){if(i=String(c?.stdout||""),!i.trim())throw new Error(re(c))}o=JSON.parse(i||"[]").filter(c=>c.bucket==="fail")}catch(i){if(!jc(i))throw i;let c=await Ft(n.path,["pr","view",String(r),"--json","headRefOid"]),l=await Hm(n.path,JSON.parse(c).headRefOid);if(l===null)throw new Error(re(i));o=Wm(l).map(Fm).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=av(i.link),l=null;c&&!s.has(c)&&(s.add(c),l=await Ft(n.path,["run","view",c,"--log-failed"]).then(u=>iv(u)).catch(()=>null)),a.push({name:i.name,link:i.link||null,log:l})}return a}function av(t){return String(t||"").match(/\/actions\/runs\/(\d+)/)?.[1]??null}function iv(t,{maxLines:e=400,maxBytes:n=64*1024}={}){let r=String(t||""),o=r.split(`
|
|
45
|
+
}`,{contentType:n,filename:e,size:r.length}),s=o.fileUpload?.uploadFile;if(!o.fileUpload?.success||!s?.uploadUrl)throw new Error("linear: fileUpload did not succeed");let a={"content-type":n};for(let c of s.headers||[])a[c.key]=c.value;let i=await fetch(s.uploadUrl,{method:"PUT",headers:a,body:r});if(!i.ok)throw new Error(`linear: asset PUT failed (HTTP ${i.status})`);return s.assetUrl}async function Wx(t,e){let n=await re(t,"query($id: String!) { issue(id: $id) { state { id name type } team { states { nodes { id name type position } } } } }",{id:e.key});if(!n.issue)throw new Error(`linear: issue "${e.key}" not found`);return{current:n.issue.state??null,states:n.issue.team?.states?.nodes??[]}}async function qx(t,e,n){if(!(await re(t,"mutation($id: String!, $input: IssueUpdateInput!) { issueUpdate(id: $id, input: $input) { success } }",{id:e.issueId,input:{stateId:n}})).issueUpdate?.success)throw new Error("linear: issueUpdate did not succeed")}async function Xn(t){return((await re(t,"query { teams(first: 100) { nodes { id key name } } }")).teams?.nodes??[]).map(n=>({id:n.id,key:n.key,name:n.name}))}var $f=255;async function ji(t,{teamId:e,name:n,description:r}){let o=r||null,s=!!o&&o.length>$f,a=s?`${o.slice(0,$f-1).trimEnd()}\u2026`:o,i=await re(t,"mutation($input: ProjectCreateInput!) { projectCreate(input: $input) { success project { id url name } } }",{input:{teamIds:[e],name:n,...a?{description:a}:{},...s?{content:o}:{}}}),c=i.projectCreate?.project;if(!i.projectCreate?.success||!c)throw new Error("linear: projectCreate did not succeed");return{id:c.id,url:c.url,name:c.name}}async function Li(t,{teamId:e,title:n,description:r,projectId:o,priority:s}){let a=await re(t,"mutation($input: IssueCreateInput!) { issueCreate(input: $input) { success issue { id identifier url title } } }",{input:{teamId:e,title:n,...r?{description:r}:{},...o?{projectId:o}:{},...Number.isInteger(s)?{priority:s}:{}}}),i=a.issueCreate?.issue;if(!a.issueCreate?.success||!i)throw new Error("linear: issueCreate did not succeed");return{id:i.id,key:i.identifier,url:i.url,title:i.title}}var jf={linear:{api:Fi,config:un},jira:{api:Bi,config:As}};async function Ki(){let t=[];for(let[e,{api:n,config:r}]of Object.entries(jf)){let o=await r();o&&t.push({name:e,api:n,cfg:o})}return t}async function pn(t){let e=jf[t?.provider];if(!e)return null;let n=await e.config();return n?{name:t.provider,api:e.api,cfg:n}:null}function Vi(t,e){if(!t||t.name)return t;let n=e.find(r=>r.id===t.id);return n?{...t,name:n.name}:t}var Lf=()=>process.env.BOSTRAT_STATE_DIR||Bf.join(process.env.HOME||Gx.tmpdir(),".bostrat"),qi=()=>Bf.join(Lf(),"tickets.json"),Ff=()=>(process.env.BOSTRAT_PUBLIC_URL||"http://bostrat.globopi").replace(/\/$/,""),Es=t=>`${Ff()}/track/${t}`,_s=null,Ui=null,Hi=Promise.resolve();async function Is(){return _s||(Ui??=Jr.readFile(qi(),"utf8").then(t=>JSON.parse(t)?.tickets??{}).catch(()=>({})).then(t=>_s=t).finally(()=>Ui=null),Ui)}async function Of(){let t=JSON.stringify({tickets:_s},null,2),e=`${qi()}.${process.pid}.tmp`;await Jr.mkdir(Lf(),{recursive:!0}),await Jr.writeFile(e,t),await Jr.rename(e,qi())}async function Ne(){Hi=Hi.then(Of,Of),await Hi.catch(()=>{})}async function oe(t){return(await Is())[t]??null}async function Rs(){return{...await Is()}}async function Uf(){return(await Ki()).length>0}async function Yn(t,e,{source:n="api"}={}){let r=await Ki();if(!r.length)throw new Error("no ticketing integration configured \u2014 add a linear or jira entry to ~/.bostrat/integrations.json");let o=null,s=null,a=null;for(let u of r){let f=u.api.parseIssueRef(e);if(f)try{s=await u.api.resolveIssue(u.cfg,f),o=u;break}catch(d){a=d}}if(!o)throw a||new Error(`"${e}" is not a Linear or Jira issue URL or key (ABC-123)`);let i=(t.boxes||[]).find(u=>u.name===t.leadBox),c={provider:o.name,key:s.key,issueId:s.issueId,url:s.url,title:s.title,state:s.state,source:n,linkedAt:Date.now(),track:{name:t.name,branch:t.workspace?.branch??null,leadAgent:i?.agent??null,cwd:t.cwd??null,...t.workspace?.kind==="multi"?{repos:(t.workspace.repos??[]).map(u=>u?.name).filter(Boolean)}:{}},pr:null};(await Is())[t.id]=c,await Ne(),await O({id:t.id,cwd:t.cwd},{type:"ticket.linked",actor:"user",source:"orchestrator",payload:{ts:Date.now(),provider:c.provider,key:c.key,url:c.url,title:c.title,linkSource:n}}).catch(()=>{}),await Xr(t.id,c);let l=[];return c.track.branch&&l.push(`\`${c.track.branch}\``),c.track.repos?.length&&l.push(`repos ${c.track.repos.join(" + ")}`),c.track.leadAgent&&l.push(`lead ${c.track.leadAgent}`),await zr(c,`**Bostrat** \u2014 Track **${c.track.name}** linked${l.length?` (${l.join(", ")})`:""}.`),await qf(t.id,c,"linked"),c}async function zi(t){let e=await Is();return e[t]?(delete e[t],await Ne(),!0):!1}var Gi=t=>`$${t>=100?Math.round(t):t.toFixed(2)}`;async function Hf(t,e){if(!t.track?.cwd)return null;let n=Yt(await F({id:e,cwd:t.track.cwd}));return!n.totalUsd||n.totalUsd<.01?null:n}function Kx(t){let e=Object.entries(t.byProvider).sort((r,o)=>o[1].usd-r[1].usd).map(([r,o])=>`${r} ${Gi(o.usd)}`).join(" \xB7 "),n=Object.keys(t.byBox).length;return`${Gi(t.totalUsd)}${e?` (${e})`:""}${n>1?` across ${n} boxes`:""}`}function Vx(t,e){let n=[];return t.track.leadAgent&&n.push(`lead ${t.track.leadAgent}`),t.track.branch&&n.push(t.track.branch),t.track.repos?.length&&n.push(t.track.repos.join(" + ")),t.pr?.number&&n.push(`PR #${t.pr.number} ${t.pr.change==="merged"?"merged":"open"}`),e&&n.push(Gi(e.totalUsd)),n.join(" \xB7 ")||"linked"}async function Xr(t,e){let n=await pn(e);if(!n)return;let r=await Hf(e,t).catch(()=>null);await n.api.upsertCard(n.cfg,e,{url:Es(t),title:`Bostrat Track: ${e.track.name}`,subtitle:Vx(e,r)})}function $s(t){let e=new URL(Ff());return`${e.protocol}//${t}.${e.host}/`}var zx={linked:"linked",opened:"prOpened",merged:"prMerged"};function Jx(t,e,n){let r=o=>e.filter(s=>s.type===o).sort((s,a)=>s.position-a.position);if(n==="linked"){if(["triage","backlog","unstarted"].includes(t?.type))return r("started")[0]??null}else if(n==="opened"){if(t?.type!=="completed")return r("started").find(o=>/review/i.test(o.name))??null}else if(n==="merged"&&t?.type!=="completed")return r("completed")[0]??null;return null}async function Wf(t,e,{from:n,to:r,mode:o,phase:s=null,actor:a="user"}){e.state=r,await Ne(),await O({id:t,cwd:e.track?.cwd},{type:"ticket.state.changed",actor:a,source:"orchestrator",payload:{ts:Date.now(),key:e.key,url:e.url,from:n??null,to:r,mode:o,...s?{phase:s}:{}}}).catch(()=>{})}async function qf(t,e,n){let r=await pn(e);if(!r)return;let o=r.cfg.ticketFlow?.[zx[n]]??"prompt";if(o==="off")return;let{current:s,states:a}=await r.api.issueStates(r.cfg,e),i=Vi(s,a),c=Jx(i,a,n);if(!c||c.id===i?.id)return;let l=i?.name??e.state??null;if(o==="auto"){await r.api.moveIssue(r.cfg,e,c.id),await Wf(t,e,{from:l,to:c.name,mode:"auto",phase:n,actor:"system"});return}await O({id:t,cwd:e.track?.cwd},{type:"ticket.move.offered",actor:"system",source:"orchestrator",payload:{ts:Date.now(),offerId:Df(),provider:e.provider,key:e.key,url:e.url,phase:n,from:l,to:{id:c.id,name:c.name,type:c.type},...e.pr?.number?{prNumber:e.pr.number}:{}}}).catch(()=>{})}async function Gf(t){let e=await oe(t);if(!e)throw new Error("no ticket linked to this Track");let n=await pn(e);if(!n)throw new Error("no ticketing integration configured");let{current:r,states:o}=await n.api.issueStates(n.cfg,e),s=Vi(r,o);return s?.name&&e.state!==s.name&&(e.state=s.name,await Ne()),{key:e.key,url:e.url,current:s?{id:s.id,name:s.name??null,type:s.type??null}:null,states:[...o].sort((a,i)=>a.position-i.position).map(({id:a,name:i,type:c,position:l})=>({id:a,name:i,type:c,position:l}))}}async function Ji(t,e,{mode:n="manual"}={}){if(!e)throw new Error("stateId required");let r=await oe(t);if(!r)throw new Error("no ticket linked to this Track");let o=await pn(r);if(!o)throw new Error("no ticketing integration configured");let{current:s,states:a}=await o.api.issueStates(o.cfg,r),i=Vi(s,a),c=a.find(u=>u.id===e);if(!c)throw new Error("unknown state for this ticket's team");let l=i?.name??r.state??null;return i?.id===c.id?(r.state!==c.name&&(r.state=c.name,await Ne()),{key:r.key,from:l,to:c.name,moved:!1}):(await o.api.moveIssue(o.cfg,r,c.id),await Wf(t,r,{from:l,to:c.name,mode:n}),{key:r.key,from:l,to:c.name,moved:!0})}async function Kf(t,e){let n=await oe(t);if(!n)throw new Error("no ticket linked to this Track");let r=await pn(n);if(!r)throw new Error("no ticketing integration configured");let o=String(e||"").trim();if(!o)throw new Error("comment body required");let s=await r.api.createComment(r.cfg,n,o);return await O({id:t,cwd:n.track?.cwd},{type:"ticket.commented",actor:"user",source:"orchestrator",payload:{ts:Date.now(),key:n.key,url:s??n.url,excerpt:o.length>280?`${o.slice(0,279)}\u2026`:o}}).catch(()=>{}),{key:n.key,url:s??n.url}}async function Vf(t,e,n){if(!e)throw new Error("offerId required");let r=await F({id:t.id,cwd:t.cwd}),o=r.find(a=>a.type==="ticket.move.offered"&&a.payload?.offerId===e);if(!o)throw new Error("unknown offer");if(r.some(a=>a.type==="ticket.move.answered"&&a.payload?.offerId===e))throw new Error("offer already answered");let s=null;return n&&(s=await Ji(t.id,o.payload.to?.id,{mode:"offer"})),await O({id:t.id,cwd:t.cwd},{type:"ticket.move.answered",actor:"user",source:"orchestrator",payload:{ts:Date.now(),offerId:e,action:n?"moved":"dismiss",...s?{from:s.from,to:s.to}:{}}}),{offerId:e,action:n?"moved":"dismiss",...s??{}}}var Cf=50;async function zf(){let t=await un();if(!t)throw new Error("no ticketing integration configured");return Xn(t)}function Xx(t){let e=t&&typeof t=="object"?t:{},n=(Array.isArray(e.issues)?e.issues:[]).map(o=>o&&typeof o=="object"?o:{title:String(o||"")}).map(o=>({title:String(o.title||"").trim(),description:o.description?String(o.description):null,priority:Number.isInteger(o.priority)&&o.priority>=0&&o.priority<=4?o.priority:null})).filter(o=>o.title),r=e.epic&&String(e.epic.name||"").trim()?{name:String(e.epic.name).trim(),description:e.epic.description?String(e.epic.description):null}:null;if(!n.length&&!r)throw new Error("a plan needs an epic or at least one issue");if(n.length>Cf)throw new Error(`too many issues (${n.length}); cap is ${Cf} per proposal`);return{epic:r,issues:n,team:e.team?String(e.team).trim():null,linkCurrentTrack:e.linkCurrentTrack===!0}}async function Yx(t,e,n,r){let o=r??await Xn(t),s=i=>o.find(c=>c.id===i||c.key.toLowerCase()===String(i).toLowerCase()||c.name.toLowerCase()===String(i).toLowerCase())??null;if(e.team){let i=s(e.team);if(i)return i}let a=await oe(n.id);if(a?.key){let i=a.key.split("-")[0],c=o.find(l=>l.key===i);if(c)return c}if(t.defaultTeam){let i=s(t.defaultTeam);if(i)return i}return o.length===1?o[0]:null}async function Jf(t,e){let n=await un();if(!n)throw new Error("no ticketing integration configured \u2014 add a linear apiKey to ~/.bostrat/integrations.json");if(n.ticketFlow?.create==="off")throw new Error("ticket creation offers are off (bostrat-ticket flow set create=prompt to re-enable)");let r=Xx(e),o=await Xn(n),s=await Yx(n,r,t,o),a=s?{id:s.id,key:s.key,name:s.name}:null,i=Df();return await O({id:t.id,cwd:t.cwd},{type:"ticket.create.offered",actor:"system",source:"orchestrator",payload:{ts:Date.now(),proposalId:i,provider:"linear",epic:r.epic,issues:r.issues,team:a,needsTeam:!s,linkCurrentTrack:r.linkCurrentTrack}}),{proposalId:i,epic:r.epic,issues:r.issues,team:a,needsTeam:!s}}async function Xf(t,e,n,{teamId:r=null}={}){if(!e)throw new Error("proposalId required");let o=await F({id:t.id,cwd:t.cwd}),s=o.find(h=>h.type==="ticket.create.offered"&&h.payload?.proposalId===e);if(!s)throw new Error("unknown proposal");if(o.some(h=>h.type==="ticket.create.answered"&&h.payload?.proposalId===e))throw new Error("proposal already answered");if(!n)return await O({id:t.id,cwd:t.cwd},{type:"ticket.create.answered",actor:"user",source:"orchestrator",payload:{ts:Date.now(),proposalId:e,action:"dismiss"}}),{proposalId:e,action:"dismiss"};let a=await un();if(!a)throw new Error("no ticketing integration configured");let i=s.payload,c=i.team??null;if(!c&&r){let h=(await Xn(a)).find(w=>w.id===r);if(!h)throw new Error("unknown team");c={id:h.id,key:h.key,name:h.name}}if(!c)throw new Error("a team is required to create \u2014 pick one");let l=[],u=[],f=null;if(i.epic){let h=await ji(a,{teamId:c.id,name:i.epic.name,description:i.epic.description});f=h.id,l.push({kind:"epic",title:h.name,url:h.url})}for(let h of i.issues||[])try{let w=await Li(a,{teamId:c.id,title:h.title,description:h.description,projectId:f,priority:h.priority});l.push({kind:"issue",key:w.key,title:w.title,url:w.url})}catch(w){u.push({title:h.title,message:w?.message||String(w)})}await O({id:t.id,cwd:t.cwd},{type:"ticket.created",actor:"user",source:"orchestrator",payload:{ts:Date.now(),proposalId:e,team:{key:c.key,name:c.name},items:l,failed:u}}),await O({id:t.id,cwd:t.cwd},{type:"ticket.create.answered",actor:"user",source:"orchestrator",payload:{ts:Date.now(),proposalId:e,action:"created",createdCount:l.length,failedCount:u.length}});let d=l.filter(h=>h.kind==="issue"),m=null;return i.linkCurrentTrack&&d.length===1&&!await oe(t.id)&&(m=await Yn(t,d[0].key,{source:"created"}).catch(()=>null)),{proposalId:e,action:"created",created:l,failed:u,linked:m?m.key:null}}async function zr(t,e){let n=await pn(t);n&&await n.api.createComment(n.cfg,t,e)}async function Zx(t,e,n){let r=await pn(t);if(!r)return null;let o=await $e(e);if(!o)return null;let s=an(o,n);if(!s)return null;let a=await Jr.readFile(Ft(o,s.v).png).catch(()=>null);return a?r.api.uploadImage(r.cfg,{issue:t.key,filename:`${o.name.replace(/[^\w.-]+/g,"-")}.v${s.v}.png`,contentType:"image/png",buffer:a}):null}var Mf=()=>Number(process.env.BOSTRAT_TICKET_COST_SYNC_MS??12e4),Wi=new Map;function Qx(t,e){if(Mf()<=0)return Xr(t,e);if(Wi.has(t))return;let n=setTimeout(()=>{Wi.delete(t),oe(t).then(r=>r?Xr(t,r):null).catch(()=>{})},Mf());n.unref?.(),Wi.set(t,n)}var Nf=new Map;async function Pf(t,e,n){let r=Nf.get(t)??0;Date.now()-r<600*1e3||(Nf.set(t,Date.now()),await O({id:t,cwd:e},{type:"ticket.sync.failed",actor:"system",source:"orchestrator",payload:{ts:Date.now(),message:n?.message||String(n)}}).catch(()=>{}))}async function t0(t,{resolveTrack:e=null}={}){let n=await oe(t.trackId),r=t.payload||{};if(!n){if(t.type!=="turn.user"||t.actor!=="user")return;let o=null;for(let a of await Ki())if(o=a.api.findIssueUrl(r.text,a.cfg),o)break;if(!o)return;let s=e?await e(t.trackId):null;if(!s)return;try{await Yn(s,o,{source:"chat"})}catch(a){await Pf(t.trackId,s.cwd,a)}return}if(!n.track.cwd&&e){let o=await e(t.trackId).catch(()=>null);o?.cwd&&(n.track.cwd=o.cwd,await Ne())}try{switch(t.type){case"handoff.done":n.track.leadAgent=r.toAgent??n.track.leadAgent,await Ne(),await Xr(t.trackId,n);break;case"pr.state.changed":{if(!r.change)break;if(r.number&&(n.pr={number:r.number,url:r.url??n.pr?.url??null,change:r.change}),await Ne(),await Xr(t.trackId,n),r.change==="opened"||r.change==="merged"){let o=r.change==="merged"?await Hf(n,t.trackId).catch(()=>null):null;await zr(n,`**Bostrat** \u2014 PR ${r.change} for **${n.track.name}**${r.url?`: ${r.url}`:r.number?` (#${r.number})`:""}`+(o?`
|
|
46
|
+
|
|
47
|
+
Agent cost for this Track: ${Kx(o)}.`:"")),await qf(t.trackId,n,r.change)}break}case"mock.state.changed":{if(r.change==="ready"&&r.mockId){let o=await Zx(n,r.mockId,r.version??null);if(!o)break;let s=(r.version??1)>1?` revised (v${r.version})`:"";await zr(n,`**Bostrat** \u2014 mock **${r.name}**${s}${r.group?` (group \`${r.group}\`)`:""}:
|
|
48
|
+
|
|
49
|
+
`)}else if(r.change==="picked"){let o=(r.siblings??[]).map(s=>`**${s.name}**`).join(", ");await zr(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 zr(n,`**Bostrat** \u2014 live preview **${r.name}**: ${$s(r.slug)}
|
|
50
|
+
_(runs on the Track's branch; reachable on the operator's private network only)_`);break}case"cost.recorded":await Qx(t.trackId,n);break;default:break}}catch(o){let s=e?await e(t.trackId).catch(()=>null):null;await Pf(t.trackId,s?.cwd??null,o)}}function Yf({resolveTrack:t}){Lt.on("event",e=>{t0(e,{resolveTrack:t}).catch(()=>{})})}import Os from"node:fs/promises";import Zf from"node:path";import e0 from"node:os";var Qf=process.env.BOSTRAT_STATE_DIR||Zf.join(process.env.HOME||e0.tmpdir(),".bostrat"),Yi=Zf.join(Qf,"pr-links.json"),ut=(t,e)=>`${t}#${Number(e)}`,Cs=null,Xi=null,D=Promise.resolve();async function gt(){return Cs||(Xi??=Os.readFile(Yi,"utf8").then(t=>JSON.parse(t)?.links??{}).catch(()=>({})).then(t=>Cs=t).finally(()=>Xi=null),Xi)}async function Y(){let t=JSON.stringify({links:Cs},null,2),e=`${Yi}.${process.pid}.tmp`;await Os.mkdir(Qf,{recursive:!0}),await Os.writeFile(e,t),await Os.rename(e,Yi)}var n0=new Set(["api","observed","reconciled"]);async function Bt({repo:t,number:e,trackId:n,url:r=null,headRef:o=null,title:s=null,source:a="matched"}){if(!t||!Number.isInteger(Number(e))||!n)return{link:null,created:!1};let i=await gt(),c=ut(t,e);return i[c]?{link:i[c],created:!1}:(i[c]={repo:t,number:Number(e),trackId:n,url:r,headRef:o,title:s,source:a,announced:n0.has(a),firstSeen:Date.now()},D=D.then(Y,Y),await D.catch(()=>{}),{link:i[c],created:!0})}async function Yr(t,e){if(!t||e==null)return null;let n=(await gt())[ut(t,e)];return!n||n.announced?null:(n.announced=!0,D=D.then(Y,Y),await D.catch(()=>{}),n)}async function Tt(t,e){return!t||e==null?null:(await gt())[ut(t,e)]??null}async function Pe(t,e,n){if(!t||e==null)return null;let r=(await gt())[ut(t,e)];return r?(r.state=n,r.resolvedAt=Date.now(),delete r.armedAt,delete r.autoMerge,delete r.archiveOnMerge,delete r.notifyOnMerge,delete r.notifyPending,D=D.then(Y,Y),await D.catch(()=>{}),r):null}async function tp(t,e,{headSha:n=null,trackId:r=null}={}){if(!t||e==null)return null;let o=(await gt())[ut(t,e)];return o?(o.ciFix={delegatedAt:Date.now(),headSha:n,trackId:r},D=D.then(Y,Y),await D.catch(()=>{}),o):null}async function ep(t,e,{headSha:n=null,trackId:r=null}={}){if(!t||e==null)return null;let o=(await gt())[ut(t,e)];return o?(o.reconcile={delegatedAt:Date.now(),headSha:n,trackId:r},D=D.then(Y,Y),await D.catch(()=>{}),o):null}async function Ms(t,e,{headSha:n=null}={}){if(!t||e==null)return null;let r=(await gt())[ut(t,e)];return!r||!r.ciFix?null:(r.ciFixAwait={since:Date.now(),trackId:r.ciFix.trackId??null,headSha:n},delete r.ciFix,D=D.then(Y,Y),await D.catch(()=>{}),r)}async function Ns(t,e){if(!t||e==null)return null;let n=(await gt())[ut(t,e)];return!n||!n.ciFixAwait?null:(delete n.ciFixAwait,D=D.then(Y,Y),await D.catch(()=>{}),n)}async function Ps(t,e,{headSha:n=null}={}){if(!t||e==null)return null;let r=(await gt())[ut(t,e)];return!r||!r.reconcile?null:(r.reconcileAwait={since:Date.now(),trackId:r.reconcile.trackId??null,headSha:n},delete r.reconcile,D=D.then(Y,Y),await D.catch(()=>{}),r)}async function Bs(t,e){if(!t||e==null)return null;let n=(await gt())[ut(t,e)];return!n||!n.reconcileAwait?null:(delete n.reconcileAwait,D=D.then(Y,Y),await D.catch(()=>{}),n)}async function np(t,e,{at:n=Date.now()}={}){if(!t||e==null)return null;let r=(await gt())[ut(t,e)];return r?(r.baseFoldAt=n,D=D.then(Y,Y),await D.catch(()=>{}),r):null}async function rp(t,e){if(!t||e==null)return null;let n=(await gt())[ut(t,e)];return!n||!n.baseFoldAt?null:(delete n.baseFoldAt,D=D.then(Y,Y),await D.catch(()=>{}),n)}async function op(t,e,n){if(!t||e==null||!n)return null;let r=(await gt())[ut(t,e)];return r?(r.lastHeadSha=n,D=D.then(Y,Y),await D.catch(()=>{}),r):null}async function Zr(t,e,{merge:n=null}={}){if(!t||e==null)return null;let r=(await gt())[ut(t,e)];return r?(r.armedAt=Date.now(),n&&(r.autoMerge={method:n.method||"merge",armedAt:r.armedAt}),D=D.then(Y,Y),await D.catch(()=>{}),r):null}async function Zn(t,e,n=!0){if(!t||e==null)return null;let r=(await gt())[ut(t,e)];return r?(n?r.archiveOnMerge=!0:delete r.archiveOnMerge,D=D.then(Y,Y),await D.catch(()=>{}),r):null}async function Ds(t,e,n=!0){if(!t||e==null)return null;let r=(await gt())[ut(t,e)];return r?(n?r.notifyOnMerge=!0:delete r.notifyOnMerge,D=D.then(Y,Y),await D.catch(()=>{}),r):null}async function sp(t,e,n){if(!t||e==null)return null;let r=(await gt())[ut(t,e)];if(!r)return null;r.notifyOnMerge=!0;let o=new Map((r.notifyPending||[]).map(s=>[`${s.repo}#${s.number}`,s]));for(let s of n||[])s&&s.number!=null&&o.set(`${s.repo}#${s.number}`,s);return r.notifyPending=[...o.values()],D=D.then(Y,Y),await D.catch(()=>{}),r}async function Qr(t,e){if(!t||e==null)return null;let n=(await gt())[ut(t,e)];return!n||!n.armedAt&&!n.autoMerge?null:(delete n.armedAt,delete n.autoMerge,delete n.archiveOnMerge,delete n.notifyOnMerge,delete n.notifyPending,D=D.then(Y,Y),await D.catch(()=>{}),n)}var r0=new Set(["merged","closed","lost"]);async function ap(){return Object.values(await gt()).filter(t=>!r0.has(t.state))}async function se(){return Object.values(await gt()).sort((t,e)=>e.firstSeen-t.firstSeen)}var cp=t=>Math.round(t*100)/100;function o0(t){return!t||t.kind==="setup"?[]:Ot(t.workspace).filter(e=>e&&(e.repo||e.base)).map(e=>({key:e.repo||e.base,slug:e.repo??null,base:e.base??null,name:e.repo?e.repo.split("/").pop():ip.basename(e.base)}))}function lp(t){let e=new Map;for(let n of t||[])for(let r of o0(n)){let o=e.get(r.key);o||e.set(r.key,o={...r,tracks:[]}),o.tracks.push(n)}return e}function js(t){let e=t.events?.length?t.events[t.events.length-1].at:0;return Math.max(t.createdAt||0,t.archivedAt||0,t.lastResponseAt||0,e||0)||null}var Zi=(t,e)=>`${t} ${e}${t===1?"":"s"}`;function s0(t){if(!Number.isFinite(t)||t<=0)return null;let e=t/864e5;if(e>=1)return`${Math.round(e)}d`;let n=t/36e5;return n>=1?`${Math.round(n)}h`:`${Math.max(1,Math.round(t/6e4))}m`}function Qi(t,e=[]){let n=[],r=t?.events||[],o=new Map,s=0;for(let u of e){if(u.type!=="pr.state.changed")continue;let f=u.payload||{};!f.repo||f.number==null||(f.change==="merged"?o.set(`${f.repo}#${f.number}`,f):f.change==="opened"&&s++)}if(o.size){let u=[...o.keys()].map(f=>{let[d,m]=f.split("#");return`${d.split("/").pop()}#${m}`});n.push(`Merged ${u.slice(0,4).join(", ")}${u.length>4?` +${u.length-4}`:""}`)}else s&&n.push(Zi(s,"PR")+" opened");let a=Object.keys(t?.subtasks||{}).length||r.filter(u=>u.type==="subtask-delegated").length;a&&n.push(Zi(a,"subtask"));let i=r.filter(u=>u.type==="lead-handoff"&&u.fromAgent&&u.toAgent);if(i.length){let u=i.slice(0,3).map(f=>`${f.fromAgent}\u2192${f.toAgent}`).join(", ");n.push(`${Zi(i.length,"handoff")} ${u}${i.length>3?"\u2026":""}`)}let c=Yt(e);c.totalUsd>=.01&&n.push(`$${c.totalUsd.toFixed(2)}`);let l=Number.isFinite(t?.archivedAt)&&Number.isFinite(t?.createdAt)?s0(t.archivedAt-t.createdAt):null;return n.length&&l&&n.push(l),n.length?n.join(" \xB7 "):null}function a0(t){let e=[],n=[...t?.boxes||[]].sort((o,s)=>(o.createdAt||0)-(s.createdAt||0));for(let o of n){if(o.role!=="lead"&&o.role!=="handed-off")continue;let s=Array.isArray(o.epochs)&&o.epochs.length?o.epochs.map(a=>a.agent):[o.agent];for(let a of s)a&&e[e.length-1]!==a&&e.push(a)}let r=new Set(n.filter(o=>o.role==="subtask"&&o.agent&&!e.includes(o.agent)).map(o=>o.agent));return{lead:e,subtask:[...r]}}function up(t,e,n){let r=new Set(t.tracks.map(d=>d.id)),o=0,s=0,a=0,i=null,c=null,l=0;for(let d of t.tracks){d.archivedAt?s++:o++,d.beat?.text&&a++;let m=js(d);m&&(!i||m>i)&&(i=m),d.createdAt&&(!c||d.createdAt<c)&&(c=d.createdAt),l+=e.get(d.id)||0}let u=0,f=0;for(let d of n)r.has(d.trackId)&&(t.slug&&d.repo!==t.slug||t.slug&&(d.state==="merged"?f++:d.state!=="closed"&&u++));return{key:t.key,slug:t.slug,base:t.base,name:t.name,tracks:{total:t.tracks.length,live:o,archived:s,withBeat:a},prs:{open:u,merged:f},usd:cp(l),firstActivityAt:c,lastActivityAt:i}}async function dp(t){let e=lp(t),n=await se().catch(()=>[]),r=new Map,o=new Set;for(let a of e.values())for(let i of a.tracks){if(o.has(i.id))continue;o.add(i.id);let c=await F(i).catch(()=>[]);r.set(i.id,Yt(c).totalUsd)}let s=[...e.values()].map(a=>up(a,r,n)).sort((a,i)=>(i.lastActivityAt??0)-(a.lastActivityAt??0));return{generatedAt:Date.now(),arcs:s}}async function fp(t,e){let r=lp(t).get(e);if(!r)return null;let[o,s,a,i,c]=await Promise.all([se().catch(()=>[]),hs().catch(()=>[]),ks().catch(()=>[]),xs().catch(()=>[]),Rs().catch(()=>({}))]),l=new Map,u=[],f=(d,m)=>(js(m)||m.createdAt||0)-(js(d)||d.createdAt||0);for(let d of[...r.tracks].sort(f)){let m=await F(d).catch(()=>[]),h=Yt(m);l.set(d.id,h.totalUsd);let w=c[d.id]||null;u.push({trackId:d.id,name:d.name,createdAt:d.createdAt??null,archivedAt:d.archivedAt??null,live:!d.archivedAt,beat:d.beat??null,agents:a0(d),handoffs:(d.events||[]).filter(y=>y.type==="lead-handoff").length,subtasks:Object.keys(d.subtasks||{}).length,repos:Ot(d.workspace).map(y=>y.repo||ip.basename(y.base||"")),prs:o.filter(y=>y.trackId===d.id&&(!r.slug||y.repo===r.slug)).map(y=>({repo:y.repo,number:y.number,state:y.state??null,title:y.title??null,url:y.url??null})),mocks:s.filter(y=>y.trackId===d.id).length,views:a.filter(y=>y.trackId===d.id).length,runbooks:i.filter(y=>y.trackId===d.id).map(y=>({name:y.name,status:y.status})),ticket:w?{provider:w.provider,key:w.key,url:w.url??null}:null,usd:cp(h.totalUsd),lastActivityAt:js(d)})}return{...up(r,l,o),generatedAt:Date.now(),segments:u}}import{execFile as i0}from"node:child_process";import{promisify as c0}from"node:util";import{randomUUID as l0}from"node:crypto";var mn=c0(i0),Ls=t=>new Promise(e=>setTimeout(e,t)),u0=Number(process.env.BOSTRAT_SWAP_EXIT_TIMEOUT_MS)||15e3,pp=Number(process.env.BOSTRAT_SWAP_SHELL_TIMEOUT_MS)||8e3,d0=250,f0=400,mp={claude:{escape:!0,wipe:!0,text:"/exit"},codex:{escape:!0,wipe:!0,text:"/quit"},cursor:{escape:!0,wipe:!0,text:"/quit"},default:{escape:!1,wipe:!1,text:"exit"}};async function wp(t){let e=await Cu(t);if(!e)return!1;try{return await mn("pgrep",["-P",String(e)]),!1}catch(n){return n?.code===1}}async function tc(t,e){let n=Date.now()+e;for(;;){if(await wp(t))return!0;if(Date.now()>n)return!1;await Ls(d0)}}async function p0(t,e){let n=mp[e]||mp.default;n.escape&&(await kt(t,["Escape"],{kind:"swap-exit"}),await Ls(150)),n.wipe&&await Zo(t,400,{kind:"swap-exit"}),await Qo(t,n.text,{perCharDelayMs:90,kind:"swap-exit"});let r=fe(e).submitDelayMs||0;await Ls(Math.max(r,250)),await kt(t,["Enter"],{kind:"swap-exit"})}async function m0(t,e){try{let{stdout:n}=await mn(N,B("show-environment","-t",t,e));return!String(n||"").trimStart().startsWith("-")}catch{return!1}}async function hp(t){await mn(N,B("respawn-pane","-k","-t",t))}async function ec(t,e={}){if(!mt(t))throw new Error("invalid session name");let n=String(e.agentCmd||"").trim();if(!n)throw new Error("agentCmd is required");let r=await X(t);if(!r)throw new Error(`session "${t}" not found`);if(!r.cwd)throw new Error(`can't resolve cwd for "${t}"`);let o=r.agent||"agent",s=e.agent||Bn(n),a=Number(e.exitTimeoutMs)||u0;await V({flow:"swap",phase:"start",sessionName:t,from:o,to:s,cwd:r.cwd});let i=!1;if(!await wp(t)&&(await p0(t,o),!await tc(t,a)&&(i=!0,await V({flow:"swap",phase:"respawn-forced",sessionName:t,from:o}),await hp(t),!await tc(t,pp))))throw new Error(`pane shell did not come back after respawn-pane in "${t}"`);let c=s==="claude"&&Pn(n),l=c?Ko():null,u=await m0(t,"ANTHROPIC_API_KEY"),f=!1;try{l?(await mn(N,B("set-environment","-t",t,"ANTHROPIC_API_KEY",l)),f=!0):u&&(await mn(N,B("set-environment","-u","-t",t,"ANTHROPIC_API_KEY")),f=!0)}catch(y){throw l?Jo(y,l):y}if(f&&!i&&(await hp(t),!await tc(t,pp)))throw new Error(`pane shell did not come back after env respawn in "${t}"`);let d=!!e.resumeSessionId,m=e.resumeSessionId||l0(),h=d?n:Ja(n,m),w=d?e.pinned===!0:/(^|\s)--session-id(\s|=)/.test(h);return c&&await Te(r.cwd),s==="codex"&&await Cn(r.cwd),s==="cursor"&&await Mn(r.cwd),await on(t),await mn(N,B("set-option","-t",t,"@bostrat-sid",m)),await mn(N,B("set-option","-t",t,"@bostrat-agent",s)),await Ls(f0),await xt(t,h,{kind:"swap-launch"}),await V({flow:"swap",phase:"done",sessionName:t,from:o,to:s,sid:m,forced:i,envRespawned:f}),{name:t,cwd:r.cwd,sessionId:m,agent:s,pinned:w,forced:i,envRespawned:f}}import T0 from"node:fs";import yp from"node:fs/promises";import eo from"node:path";import _0 from"node:os";import{fileURLToPath as E0}from"node:url";import{execFile as I0}from"node:child_process";import{promisify as R0}from"node:util";import rc from"node:fs/promises";import Be from"node:path";import{execFile as h0}from"node:child_process";import{promisify as w0}from"node:util";var g0=w0(h0),gp=process.env.HOME||"/home/globo",y0=Be.join(process.env.BOSTRAT_STATE_DIR||Be.join(gp,".bostrat"),"repos"),b0=[...(process.env.BOSTRAT_REPOS_ROOT||Be.join(gp,"Documents")).split(","),y0].map(t=>t.trim()).filter(Boolean),k0=Number(process.env.BOSTRAT_REPOS_DEPTH)||3,x0=new Set(["node_modules",".git",".cache","dist","build",".next","vendor","__pycache__","venv",".venv","target"]);async function S0(t){try{return(await rc.stat(Be.join(t,".git"))).isDirectory()?"repo":"worktree"}catch{return null}}async function v0(t){let e=i=>rc.readFile(Be.join(t,".git",i),"utf8").then(c=>c.trim()).catch(()=>null),r=(await e("refs/remotes/origin/HEAD"))?.match(/^ref:\s*refs\/remotes\/origin\/(.+)/);if(r)return r[1].trim();let o;for(let i of["main","master"])if(await e(`refs/heads/${i}`)||(o??=await e("packed-refs")||"",new RegExp(`^[0-9a-f]{40} refs/heads/${i}$`,"m").test(o)))return i;let a=(await e("HEAD"))?.match(/^ref:\s*refs\/heads\/(.+)/);return a?a[1].trim():null}async function ye(){let t=new Map;async function e(o,s){if(t.has(o))return;let a=await S0(o);if(a==="worktree")return;if(a==="repo"){t.set(o,{path:o,name:Be.basename(o),branch:await v0(o)});return}if(s>=k0)return;let i;try{i=await rc.readdir(o,{withFileTypes:!0})}catch{return}for(let c of i)!c.isDirectory()||c.name.startsWith(".")||x0.has(c.name)||await e(Be.join(o,c.name),s+1)}for(let o of b0)await e(Be.resolve(o),0);let n=new Set,r=[];for(let o of t.values()){let s=(await Z(o.path))?.toLowerCase();if(s){if(n.has(s))continue;n.add(s)}r.push(o)}return r.sort((o,s)=>o.name.localeCompare(s.name))}function A0(t){let e=String(t||"").trim().match(/github\.com[:/]+([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/i);return e?`${e[1]}/${e[2]}`:null}var nc=new Map;async function Z(t){if(!t)return null;if(nc.has(t))return nc.get(t);let e=null;try{let{stdout:n}=await g0("git",["-C",t,"config","--get","remote.origin.url"],{timeout:5e3});e=A0(n)}catch{e=null}return nc.set(t,e),e}async function to(t){let e=String(t||"").toLowerCase();if(!e)return null;for(let n of await ye())if((await Z(n.path))?.toLowerCase()===e)return n.path;return null}var bp=R0(I0),$0=process.env.BOSTRAT_STATE_DIR||eo.join(process.env.HOME||_0.tmpdir(),".bostrat"),O0=eo.join($0,"repos"),kp=/^[^/\s]+\/[^/\s]+$/,C0=5*6e4,M0=6e4,N0=10*6e4,xp=fetch,oc=new Map,hn=null;function tr(t=process.env){let e=t.BOSTRAT_PLANE_URL||"";if(!e)return null;try{let n=new URL(e);return n.protocol=n.protocol==="ws:"?"http:":"https:",n.origin}catch{return null}}function Fs(t=process.env){return!!(tr(t)&&t.BOSTRAT_PLANE_TOKEN)}var Sp=t=>({authorization:`Bearer ${t.BOSTRAT_PLANE_TOKEN||""}`}),Qn=(t,e)=>Object.assign(new Error(t),{status:e});async function Us(t,{env:e=process.env}={}){let n=String(t||"").toLowerCase();if(!kp.test(n))throw Qn("repo must be owner/name",400);let r=oc.get(n);if(r&&r.expiresAt-Date.now()>C0)return r;if(!Fs(e))throw Qn("node is not plane-linked \u2014 GitHub auth unavailable",503);let o;try{o=await xp(`${tr(e)}/v1/github/token?repo=${encodeURIComponent(t)}`,{headers:Sp(e)})}catch(i){if(r&&r.expiresAt>Date.now())return r;throw Qn(`plane unreachable \u2014 GitHub auth unavailable (${i?.message||i})`,503)}let s=await o.json().catch(()=>({}));if(!o.ok){if(o.status>=500&&r&&r.expiresAt>Date.now())return r;throw Qn(s?.error||`plane replied ${o.status}`,o.status)}let a={token:s.token,expiresAt:Number(s.expiresAt)||Date.now()+55*6e4,username:s.username??null,installationId:s.installationId??null};return oc.set(n,a),a}async function no({env:t=process.env,force:e=!1}={}){if(!Fs(t))return{configured:!1,repos:[],username:null};if(!e&&hn&&Date.now()-hn.at<M0)return hn.data;try{let n=await xp(`${tr(t)}/v1/github/repos`,{headers:Sp(t)});if(!n.ok)throw new Error(`plane replied ${n.status}`);let r=await n.json();return hn={at:Date.now(),data:r},r}catch(n){return hn?hn.data:{configured:!0,repos:[],username:null,error:n?.message||String(n)}}}function vp(){oc.clear(),hn=null}async function ae({cwd:t=null,slug:e=null,env:n=process.env}={}){try{let r=e||(t?await Z(t):null);if(!r)return{};let{token:o}=await Us(r,{env:n});return o?{GH_TOKEN:o}:{}}catch{return{}}}async function sc({env:t=process.env}={}){return(await no({env:t})).username??null}var P0=()=>E0(new URL("../bin/bostrat-git-credential",import.meta.url)),B0=(t=process.env)=>`http://127.0.0.1:${Number(t.PORT)||3362}`;function D0(t){let[e,n]=String(t).split("/");return eo.join(O0,e,n)}async function ac(t,{env:e=process.env}={}){let n=(...r)=>bp("git",["-C",t,...r],{timeout:1e4});await n("config","--replace-all","credential.helper",""),await n("config","--add","credential.helper",`!"${P0()}" ${B0(e)}`),await n("config","credential.useHttpPath","true")}async function ic(t,{env:e=process.env,baseUrl:n="https://github.com"}={}){if(!kp.test(String(t||"")))throw Qn(`bad repo slug "${t}"`,400);let r=D0(t);if(T0.existsSync(eo.join(r,".git")))return r;let{token:o}=await Us(t,{env:e});await yp.mkdir(eo.dirname(r),{recursive:!0});let s="!f() { echo username=x-access-token; echo password=$BOSTRAT_GIT_TOKEN; }; f";try{await bp("git",["-c","credential.helper=","-c",`credential.helper=${s}`,"clone","--no-tags","-q",`${n}/${t}.git`,r],{timeout:N0,env:{...e,BOSTRAT_GIT_TOKEN:o,GIT_TERMINAL_PROMPT:"0"}})}catch(a){throw await yp.rm(r,{recursive:!0,force:!0}).catch(()=>{}),Qn(`clone of ${t} failed: ${a?.stderr?.trim?.()||a?.message||a}`,502)}return await ac(r,{env:e}),r}import wS from"node:fs/promises";function j0(t){let e=[],n="",r=null;for(let o=0;o<t.length;o++){let s=t[o];if(r){n+=s,s===r&&t[o-1]!=="\\"&&(r=null);continue}if(s==="'"||s==='"'||s==="`"){r=s,n+=s;continue}if(s==="&"&&t[o+1]==="&"){e.push(n),n="",o++;continue}if(s==="|"&&t[o+1]==="|"){e.push(n),n="",o++;continue}if(s==="|"||s===";"||s===`
|
|
51
|
+
`){e.push(n),n="";continue}n+=s}return e.push(n),e.map(o=>o.trim()).filter(Boolean)}function ro(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 oo(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 nr=(t,e)=>t.length>e?`${t.slice(0,e).trimEnd()}\u2026`:t,vt=t=>t&&!/[$`]/.test(t)?t:null,ft=t=>t.replace(/\/+$/,"").split("/").pop()||t,er=t=>{let e=t.replace(/\/+$/,"").split("/").filter(Boolean);return e.length>3?e.slice(-2).join("/"):t},_p=t=>t.startsWith("-"),Ap=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(_p(o)){e.has(o)&&r++;continue}n.push(o)}return n}var L0={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 ${ft(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"},F0={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 Ep(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 U0(t){let e=Ep(t),n=vt(rt(t).find(r=>!/^\d+(,\d+)?p$/.test(r)));return e&&n?e.from===e.to?`Read line ${e.from} of ${ft(n)}`:`Read lines ${e.from}\u2013${e.to} of ${ft(n)}`:t.includes("-i")&&n?`Edit ${ft(n)}`:null}function cc(t){if(t.includes("--files")){let s=vt(rt(t,Ap)[0]);return s&&s!=="."?`List files in ${er(s)}`:"List files"}let e=rt(t,Ap),n=e[0];if(!n)return null;let r=vt(e.slice(1).find(s=>s!==".")),o=nr(n,40);return r?`Search for \u201C${o}\u201D in ${er(r)}`:`Search for \u201C${o}\u201D`}function lc(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 uc(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 ${ft(n)}`:null}function H0(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 Tp(t){return t.find(e=>/^https?:\/\//.test(e))||null}var W0={git:t=>{let e=rt(t,new Set(["-C","-c"])),n=L0[e[0]];return n?n(e,t):null},gh:t=>{let e=rt(t),n=F0[e[0]];return n?n(e):null},sed:U0,rg:cc,grep:cc,egrep:cc,nl:t=>{let e=vt(rt(t)[0]);return e?`Read ${ft(e)}`:null},cat:t=>{let e=vt(rt(t)[0]);return e?`Read ${ft(e)}`:null},head:t=>{let e=vt(rt(t,new Set(["-n","-c"])).find(n=>!/^\d+$/.test(n)));return e?`Read the start of ${ft(e)}`:null},tail:t=>{let e=vt(rt(t,new Set(["-n","-c"])).find(n=>!/^\d+$/.test(n)));return e?`Read the end of ${ft(e)}`:null},wc:t=>{let e=vt(rt(t)[0]);return e?`Count lines in ${ft(e)}`:"Count input lines"},ls:t=>{let e=vt(rt(t)[0]);return e&&e!=="."?`List files in ${er(e)}`:"List files"},find:t=>vt(t[1])&&!_p(t[1])&&t[1]!=="."?`Find files in ${er(t[1])}`:"Find files",tree:()=>"List the directory tree",mkdir:t=>{let e=vt(rt(t)[0]);return e?`Create directory ${er(e)}`:null},touch:t=>{let e=vt(rt(t)[0]);return e?`Create ${ft(e)}`:null},rm:t=>{let e=vt(rt(t)[0]);return e?`Delete ${er(e)}`:null},mv:t=>{let e=vt(rt(t)[0]);return e?`Move ${ft(e)}`:null},cp:t=>{let e=vt(rt(t)[0]);return e?`Copy ${ft(e)}`:null},chmod:t=>{let e=vt(rt(t)[1]);return e?`Change permissions on ${ft(e)}`:null},diff:t=>{let e=rt(t);return e.length>=2?`Compare ${ft(e[0])} and ${ft(e[1])}`:null},curl:t=>{let e=Tp(t);return e?`Fetch ${nr(e,56)}`:"Fetch a URL"},wget:t=>{let e=Tp(t);return e?`Fetch ${nr(e,56)}`:"Fetch a URL"},npm:lc,pnpm:lc,yarn:lc,npx:t=>{let e=rt(t)[0];return e?`Run ${e}`:null},node:t=>uc(t,"Node"),python:t=>uc(t,"Python"),python3:t=>uc(t,"Python"),make:t=>{let e=rt(t)[0];return e?`Run make ${e}`:"Run make"},systemctl:H0,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}},q0=new Set(["pwd","echo","cd","sleep","true","false","test","[","set","export","exit","read"]);function G0(t){let e=oo(ro(t));if(!e.length)return null;if(/^(ba|z)?sh$/.test(ft(e[0]))&&(e[1]==="-lc"||e[1]==="-c")&&e[2])return dc(e[2]);let n=ft(e[0]);if(q0.has(n))return null;if(e.includes("--version"))return`Check ${n} version`;if(n.endsWith(".sh"))return`Run ${n}`;let r=W0[n];return r?r(e.map((o,s)=>s===0?n:o)):null}function dc(t){let e=String(t||"").trim();if(!e)return null;let n=e.indexOf("<<");if(n!==-1){let i=e.indexOf(`
|
|
52
|
+
`,n);i!==-1&&(e=e.slice(0,i))}let r=j0(e);for(let i=0;i<r.length-1;i++){let c=oo(ro(r[i])),l=oo(ro(r[i+1]));if(!(!c.length||!l.length)&&["nl","cat"].includes(ft(c[0]))&&ft(l[0])==="sed"){let u=Ep(l),f=rt(c)[0];if(u&&f)return nr(u.from===u.to?`Read line ${u.from} of ${ft(f)}`:`Read lines ${u.from}\u2013${u.to} of ${ft(f)}`,88)}}let o=[];for(let i of r){let c=G0(i);c&&o.push({d:c,head:ft(oo(ro(i))[0]||""),sub:rt(oo(ro(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":nr(s?s.d:o[0].d,88)}var pc=new Set(["Bash"]);function rr(t){let e=t.split(`
|
|
53
|
+
`).map(n=>n.trim()).find(Boolean)||t;return e.length>88?`${e.slice(0,88).trimEnd()}\u2026`:e}function De(t,e=""){let n=e.trim();return n||dc(t)||rr(t)}function fc(t,e){if(t){if(Array.isArray(t)){for(let n of t)fc(n,e);return}if(typeof t=="object"){if(t.type==="tool_use"&&pc.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))fc(n,e)}}}async function Ip(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(`
|
|
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(fc(c.content,l),!l.length)continue;let u=i.timestamp?Date.parse(i.timestamp):null;for(let f of l)o.push({...f,summary:De(f.command,f.description),ts:u})}return o.slice(-n)}function Rp(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 K0=/^Async (?:agent|task) launched/i,V0=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 $p(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(`
|
|
55
|
+
`)){let c=i.trim();if(!c||c[0]!=="{")continue;let l;try{l=JSON.parse(c)}catch{continue}if(l.isSidechain)continue;let u=l.message?.content;if(!Array.isArray(u))continue;let f=l.timestamp?Date.parse(l.timestamp):null;for(let d of u)if(!(!d||typeof d!="object"))if(d.type==="tool_use"&&d.id){if(d.name==="Agent"){let m=d.input||{};o.set(d.id,{id:d.id,kind:"agent",name:String(m.description||"").trim()||null,detail:String(m.subagent_type||"").trim()||null,ts:f,proven:!1})}else if(d.name==="Bash"&&d.input?.run_in_background===!0){let m=String(d.input.command||"").trim();o.set(d.id,{id:d.id,kind:"command",name:String(d.input.description||"").trim()||(m?rr(m):null),detail:m?rr(m):null,ts:f,proven:!0})}}else d.type==="tool_result"&&d.tool_use_id&&K0.test(V0(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 so from"node:fs";import J0 from"node:fs/promises";import wn from"node:path";import X0 from"node:os";import Y0 from"node:crypto";import Z0 from"node:zlib";var Hs=new Set(["Shell","Read","Grep","Glob","StrReplace","Write","ReadLints","Delete","ApplyPatch","Await"]),Gt=(t,e=88)=>t.length>e?`${t.slice(0,e).trimEnd()}\u2026`:t,or=t=>String(t).replace(/\/+$/,"").split("/").pop()||String(t);function je(t,e){if(!t)return null;let n=String(t);if(e){let o=e.endsWith("/")?e:`${e}/`;if(n===e)return".";if(n.startsWith(o))return n.slice(o.length)||"."}let r=n.split("/").filter(Boolean);return n.startsWith("/")&&r.length>2?r.slice(-2).join("/"):n}var mc=t=>`"${String(t).replace(/"/g,'\\"')}"`;function z0(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 Op(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:Gt(`read ${je(r,n)}${o?` (lines ${o}\u2013${s??"end"})`:""}`,140),description:o?`Read lines ${o}\u2013${s??"end"} of ${or(r)}`:`Read ${or(r)}`}}case"Grep":{let r=e?.pattern;if(!r)return null;let o=e?.path?je(e.path,n):null,s=e?.glob?` --glob ${mc(e.glob)}`:"",a=Gt(String(r),40);return{command:Gt(`grep ${mc(r)}${o&&o!=="."?` ${o}`:""}${s}`,140),description:o&&o!=="."?`Search for \u201C${a}\u201D in ${o}`:`Search for \u201C${a}\u201D`}}case"Glob":{let r=e?.glob_pattern;if(!r)return null;let o=e?.target_directory?je(e.target_directory,n):null;return{command:Gt(`glob ${mc(r)}${o&&o!=="."?` ${o}`:""}`,140),description:`Find files matching \u201C${Gt(String(r),40)}\u201D`}}case"StrReplace":return e?.path?{command:Gt(`edit ${je(e.path,n)}`,140),description:`Edit ${or(e.path)}`}:null;case"Write":return e?.path?{command:Gt(`write ${je(e.path,n)}`,140),description:`Write ${or(e.path)}`}:null;case"Delete":return e?.path?{command:Gt(`delete ${je(e.path,n)}`,140),description:`Delete ${or(e.path)}`}:null;case"ReadLints":{let o=(Array.isArray(e?.paths)?e.paths:[]).map(s=>je(s,n)).filter(Boolean);return{command:Gt(o.length?`lints ${o.join(" ")}`:"lints",140),description:"Check lints"}}case"ApplyPatch":{let r=z0(e);if(!r.length)return null;let o=r.length>1?` (+${r.length-1} more)`:"";return{command:Gt(`apply_patch ${je(r[0],n)}${o}`,140),description:r.length>1?`Patch ${r.length} files`:`Patch ${or(r[0])}`}}case"Await":{let r=e?.shell_id??e?.task_id;return{command:Gt(`await ${r??"background output"}`,140),description:"Wait for a background command"}}default:return null}}var Q0=process.env.BOSTRAT_CURSOR_CHATS||wn.join(X0.homedir(),".cursor","chats");function Np(t){let e=Y0.createHash("md5").update(String(t)).digest("hex");return wn.join(Q0,e)}function Pp(t){try{let e=JSON.parse(so.readFileSync(wn.join(t,"meta.json"),"utf8"));return{createdAtMs:Number(e.createdAtMs)||null,updatedAtMs:Number(e.updatedAtMs)||Number(e.createdAtMs)||null,hasConversation:e.hasConversation===!0}}catch{return null}}function tS(t){let e=Np(t),n;try{n=so.readdirSync(e,{withFileTypes:!0}).filter(o=>o.isDirectory())}catch{return null}let r=null;for(let o of n){let s=wn.join(e,o.name),a=wn.join(s,"store.db");if(!so.existsSync(a))continue;let i=Pp(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 Ws(t,e){if(!t)return null;if(e){let n=wn.join(Np(t),e),r=wn.join(n,"store.db");if(so.existsSync(r))return{id:e,dir:n,store:r,...Pp(n)||{createdAtMs:null,updatedAtMs:null}}}return tS(t)}function Cp(t,e){let n=t.prepare("SELECT data FROM blobs WHERE id = ?").get(e);return n?Buffer.from(n.data):null}function eS(t){if(!t)return null;let e=t;if(e.length>1&&e[0]===31&&e[1]===139)try{e=Z0.gunzipSync(e)}catch{}try{return JSON.parse(e.toString("utf8"))}catch{return null}}function nS(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 rS(t,e){if(!Array.isArray(t))return[];let n=[];for(let r of t){if(!r||r.type!=="tool-call"||!Hs.has(r.toolName))continue;let o=Op(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 oS=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(`
|
|
56
|
+
`).trim():"",sS=t=>Array.isArray(t)?t.filter(e=>e&&e.type==="tool-call"&&e.toolName).map(e=>e.toolName):[],Mp=process.getBuiltinModule?.("node:sqlite"),sr={turns:[],trailingCommands:[],model:null,requestIds:[]};async function hc(t,e){if(!Mp)return sr;let{DatabaseSync:n}=Mp,r;try{r=new n(t,{readOnly:!0})}catch{return sr}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 sr;let s;try{s=JSON.parse(Buffer.from(String(o.value),"hex").toString("utf8"))}catch{return sr}let a=s.lastUsedModel||null,i=s.latestRootBlobId?Cp(r,s.latestRootBlobId):null;if(!i)return{...sr,model:a};let c=[],l=[],u=[],f=null,d=[];for(let m of nS(i)){let h=eS(Cp(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 A of h.content){let _=A?.providerOptions?.cursor?.modelName;_&&(f=_)}l.push(...rS(h.content,e).map((A,_)=>({...A,id:A.id||`${m}:${_}`}))),u.push(...sS(h.content));let y=oS(h.content);if(!y)continue;let b=[...new Set(u)];u=[],c.push({id:m,text:y,tools:b.length?b:void 0,commands:l,model:f}),l=[]}return{turns:c,trailingCommands:l,model:a,requestIds:d}}catch{return sr}finally{try{r.close()}catch{}}}function Bp(t,e){let n=t.updatedAtMs;if(!n)try{n=Math.round(so.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 Dp(t,e,{limit:n=80}={}){let r=Ws(t,e);if(!r)return[];let{turns:o,model:s}=await hc(r.store,t);if(!o.length)return[];let a=o.slice(-n),{tsAt:i}=Bp(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 jp(t,e){let n=Ws(t,e);if(!n)return null;let{turns:r,requestIds:o,model:s}=await hc(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 Lp(t,e,{limit:n=80}={}){let r=Ws(t,e);if(!r)return[];let{turns:o,trailingCommands:s}=await hc(r.store,t);if(!o.length&&!s.length)return[];let a=o.slice(-n),{end:i,tsAt:c}=Bp(r,a.length||1),l=[];a.forEach((u,f)=>{for(let d of u.commands)l.push({...d,summary:De(d.command,d.description),ts:c(f)})});for(let u of s)l.push({...u,summary:De(u.command,u.description),ts:i+1});return l}async function Fp(t,e){let n=Ws(t,e);if(!n)return null;if(n.updatedAtMs)return n.updatedAtMs;try{return Math.round((await J0.stat(n.store)).mtimeMs)}catch{return null}}import Up from"node:fs";import gc from"node:fs/promises";import wc from"node:path";import aS from"node:os";var iS=process.env.BOSTRAT_CODEX_SESSIONS||wc.join(process.env.CODEX_HOME||wc.join(aS.homedir(),".codex"),"sessions");async function Hp(t){try{return await gc.realpath(t)}catch{return t}}function cS(t=iS){let e=[],n=r=>{let o;try{o=Up.readdirSync(r,{withFileTypes:!0})}catch{return}for(let s of o){let a=wc.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:Up.statSync(a).mtimeMs})}catch{}}}};return n(t),e.sort((r,o)=>o.mtimeMs-r.mtimeMs)}async function lS(t,e=1048576){let n;try{n=await gc.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(`
|
|
57
|
+
`);if(i!==-1)return o.slice(0,i);if(s+=a,o.length>=e)break}return o}finally{await n.close()}}async function uS(t){let e=await lS(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 gn(t,e){if(!t&&!e)return null;let n=cS();if(e){let o=n.find(s=>s.id===e);return o||null}let r=await Hp(t);for(let o of n){let s=await uS(o.file);if(s&&(s===t||await Hp(s)===r))return o}return null}var dS=new Set(["output_text","text"]),fS=t=>typeof t=="string"?t.trim():Array.isArray(t)?t.filter(e=>e&&typeof e.text=="string"&&dS.has(e.type)).map(e=>e.text).join(`
|
|
58
|
+
`).trim():"";async function Wp(t,e,{limit:n=80,maxBytes:r=524288}={}){let o=await gn(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(`
|
|
59
|
+
`)){let m=d.trim();if(!m||m[0]!=="{")continue;let h;try{h=JSON.parse(m)}catch{continue}if((h.type==="turn_context"&&h.payload?.model||h.type==="session_meta"&&h.payload?.model)&&(i=h.payload.model),h.type==="event_msg"){let A=h.payload?.type;A==="task_started"?(l=!0,u=!1,f=h.payload?.turn_id||null):A==="task_complete"&&l&&(l=!1,!u&&h.payload?.last_agent_message==null&&a.push({role:"assistant",failed:!0,text:bc,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,yc.has(w.name)||c.push(w.name);continue}if(w.type!=="message"||w.role!=="assistant")continue;u=!0;let y=fS(w.content),b=c.length?c.slice():null;c=[],!(!y&&!b)&&a.push({role:"assistant",text:y,tools:b||void 0,ts:h.timestamp?Date.parse(h.timestamp):null,model:i})}if(i)for(let d of a)d.model||(d.model=i);return a.slice(-n)}var ao=new Set(["exec_command","shell","exec"]),yc=new Set(["wait","write_stdin"]),bc="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 pS(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 mS(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+=`
|
|
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 hS(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=mS(i.slice(c.index+c[0].length).trimStart());l&&l.trim()&&o.push(l.trim())}),o}async function qp(t,e,{limit:n=400,maxBytes:r=524288}={}){let o=await gn(t,e);if(!o)return[];let s;try{s=await It(o.file,r)}catch{return[]}let a=[];for(let i of s.split(`
|
|
61
|
+
`)){let c=i.trim();if(!c||c[0]!=="{")continue;let l;try{l=JSON.parse(c)}catch{continue}if(l.type!=="response_item")continue;let u=l.payload;if(!u)continue;let f=[];if(u.type==="function_call"&&ao.has(u.name)){let m;try{m=JSON.parse(u.arguments||"{}")}catch{continue}let h=pS(m);h&&(f=[h])}else u.type==="custom_tool_call"&&u.name==="exec"&&(f=hS(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:De(m),ts:l.timestamp?Date.parse(l.timestamp):null})})}return a.slice(-n)}async function Gp(t,e){let n=await gn(t,e);if(!n)return null;try{return Math.round((await gc.stat(n.file)).mtimeMs)}catch{return n.mtimeMs||null}}var ar=class{constructor(e,n=new Set){this.agent=e,this.cliTools=n}async readMessages(){return[]}async readCommands(){return[]}async readJobs(){return[]}async updatedAt(){return null}},kc=class extends ar{transcript(e){return!e?.cwd||!e?.sessionId?null:de(e.cwd,e.sessionId)}async readMessages(e,n){let r=this.transcript(e);return r?oi(r,n):[]}async readCommands(e,n){let r=this.transcript(e);return r?Ip(r,n):[]}async readJobs(e,n){let r=this.transcript(e);return r?$p(r,n):[]}async updatedAt(e){let n=this.transcript(e);if(!n)return null;try{return Math.round((await wS.stat(n)).mtimeMs)}catch{return null}}},xc=class extends ar{async readMessages(e,n){return Dp(e.cwd,e.sessionId,n)}async readCommands(e,n){return Lp(e.cwd,e.sessionId,n)}async updatedAt(e){return Fp(e.cwd,e.sessionId)}},Sc=class extends ar{sessionId(e){return e?.pinned?e.sessionId:null}async readMessages(e,n){return Wp(e.cwd,this.sessionId(e),n)}async readCommands(e,n){return qp(e.cwd,this.sessionId(e),n)}async updatedAt(e){return Gp(e.cwd,this.sessionId(e))}},gS=new ar("unknown"),Kp=new Map;function vc(t){return Kp.set(t.agent,t),t}function io(t){return Kp.get(t)||gS}vc(new kc("claude",pc));vc(new xc("cursor",Hs));vc(new Sc("codex",ao));var Ac=t=>e=>({...e,box:t.name,agent:t.agent,boxRole:t.role});async function Tc(t,e={}){return(await io(t.agent).readMessages(t,e)).map(Ac(t))}async function Vp(t,e={}){return(await io(t.agent).readCommands(t,e)).map(Ac(t))}async function zp(t,e={}){return(await io(t.agent).readJobs(t,e)).map(Ac(t))}var Yp=/to navigate\b.*\bEsc to cancel|Enter to select\b/,Ic=/Chat about this/i,yn=/^\s*(❯)?\s*(\d+)\.\s+(?:\[([ xX✔])\]\s+)?(.+?)\s*$/,Jp=/^(type something|chat about this|submit)\b/i,bn=/^[─—-]{6,}\s*$/,Rc=/[\u2500-\u257F\u2550-\u256F┌┐└┘├┤│┬┴┼─✂]/;function kn(t){let e=t.search(Rc);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 bS(t){let e=String(t).trim();return!!(!e||bn.test(t)||Rc.test(e)||/hidden|\d+\s+lines?/i.test(e)||/notespress|press n to add/i.test(e)||/^\s*notes?\s*$/i.test(e))}function kS(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=xS(n,e);return n.forEach((a,i)=>{a.current=i===s}),{tabs:n,questionIndex:s,questionTotal:n.length,currentTabLabel:n[s].label}}function xS(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 Xp(t){return t.replace(/[←→✔☐☒●▶]/g," ").replace(/\bSubmit\b/g," ").replace(/\s+/g," ").trim()}var Zp=/^\s*Switch model\?\s*$/m;function SS(t,e=""){let n=String(t);if(!Zp.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
62
|
+
`),o=-1;for(let u=r.length-1;u>=0;u--)if(/^\s*Switch model\?\s*$/.test(r[u])){o=u;break}if(o<0)return null;let s=[],a=[];for(let u=o+1;u<r.length;u++){let f=r[u].match(yn);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&&!bn.test(r[u])&&a.push(d)}}if(s.length<2)return null;let i=s.map(u=>({label:kn(u.label),description:""})),c=Math.max(0,s.findIndex(u=>u.cursor)),l=["Switch model?",...a].join(" ");return{provider:"claude",box:e,multiSelect:!1,cursorIndex:c,promptId:`claude:model-confirm:${i.map(u=>u.label).join("|")}`.slice(0,240),questions:[{question:l,header:"Model",multiSelect:!1,options:i}]}}var Qp=/^\s*Enter to confirm\s*·\s*Esc to cancel\s*$/,vS=/^\s*[─—▔-]{6,}\s*$/;function AS(t,e=""){let n=String(t).replace(/\r/g,"").split(`
|
|
63
|
+
`),r=n.length-1;for(;r>=0&&n[r].trim()==="";)r--;if(r<0||!Qp.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(yn);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&&!(vS.test(n[c])||bn.test(n[c]));c--){let l=n[c].trim();l&&a.unshift(l)}let i=s.map(c=>({label:kn(c.label),description:""}));return{provider:"claude",box:e,multiSelect:!1,cursorIndex:Math.max(0,s.findIndex(c=>c.cursor)),promptId:`claude:confirm-gate:${i.map(c=>c.label).join("|")}`.slice(0,240),questions:[{question:a.join(" ")||"Claude Code is asking you to confirm",header:"Confirm",multiSelect:!1,options:i}]}}function TS(t,e=""){let n=String(t);if(!/ready to submit your answers/i.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
64
|
+
`),o=[];for(let c=0;c<r.length;c++){let l=r[c].match(yn);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 _c=/Resume from summary/i,_S=/^\s*This session is\b/;function ES(t,e=""){let n=String(t);if(!_c.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
65
|
+
`),o=-1;for(let u=r.length-1;u>=0;u--)if(_S.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(yn);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&&!bn.test(r[u])&&a.push(d)}}if(s.length<2||!s.some(u=>_c.test(u.label)))return null;let i=s.map(u=>({label:kn(u.label),description:""})),c=Math.max(0,s.findIndex(u=>u.cursor)),l=a.join(" ");return{provider:"claude",box:e,multiSelect:!1,cursorIndex:c,promptId:`claude:resume-confirm:${i.map(u=>u.label).join("|")}`.slice(0,240),questions:[{question:l,header:"Resume",multiSelect:!1,options:i}]}}var xn=/shift\+tab to cycle|⏵⏵ bypass permissions on|⏸ manual mode on/;function $c(t){let e=[];for(let r=0;r<t.length;r++){let o=t[r].match(yn);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 tm=/Choose the text style that looks best with your terminal/,em=/Let's get started\./;function IS(t,e=""){let n=String(t);if(!tm.test(n)||!em.test(n)||xn.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
66
|
+
`),o=$c(r);return o?{provider:"claude",box:e,multiSelect:!1,cursorIndex:Math.max(0,o.findIndex(s=>s.cursor)),setup:"theme",promptId:`claude:setup:theme:${o.map(s=>s.label).join("|")}`.slice(0,200),questions:[{question:"Claude Code asks: choose the text style that looks best with your terminal. You can change it later with /theme.",header:"Theme",multiSelect:!1,options:o.map(s=>({label:kn(s.label),description:""}))}]}:null}var nm=/Select login method:/;function RS(t,e=""){let n=String(t);if(!nm.test(n)||xn.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
67
|
+
`),o=$c(r);if(!o)return null;let s=o.map(a=>{let[i,...c]=a.label.split("\xB7");return{label:kn(i),description:c.join("\xB7").trim()}});return{provider:"claude",box:e,multiSelect:!1,cursorIndex:Math.max(0,o.findIndex(a=>a.cursor)),setup:"login-method",promptId:`claude:setup:login-method:${s.map(a=>a.label).join("|")}`.slice(0,200),questions:[{question:"How do you want to sign in to Claude? A Claude subscription (Pro/Max/Team) uses your existing plan; a Console account bills API usage. Your login stays on this box \u2014 bostrat never sees it.",header:"Log in",multiSelect:!1,options:s}]}}var Oc=/Paste code here if prompted/,$S=/^[A-Za-z0-9%&=+._~#?/:\-]+$/;function OS(t,e){let n=t[e].trim();for(let r=e+1;r<t.length;r++){let o=t[r].trim();if(!o||!$S.test(o)||Oc.test(t[r]))break;n+=o}return n}function CS(t,e=""){let n=String(t);if(!Oc.test(n)||xn.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
68
|
+
`),o="";for(let s=0;s<r.length;s++)if(/^\s*https:\/\//.test(r[s])){o=OS(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 Ec=/OAuth error:/,MS=/Press Enter to retry\./;function NS(t,e=""){let n=String(t);if(!Ec.test(n)||!MS.test(n)||xn.test(n))return null;let s=([...n.replace(/\r/g,"").split(`
|
|
69
|
+
`)].reverse().find(a=>Ec.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 rm=/Quick safety check: Is this a project you created or one you trust/;function PS(t,e=""){let n=String(t);if(!rm.test(n)||xn.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
70
|
+
`),o=$c(r);return o?{provider:"claude",box:e,multiSelect:!1,cursorIndex:Math.max(0,o.findIndex(s=>s.cursor)),setup:"trust",promptId:`claude:setup:trust:${o.map(s=>s.label).join("|")}`.slice(0,200),questions:[{question:"Claude Code asks whether it can read, edit, and run files in this workspace. This box's workspace is bostrat-managed scratch space, so trusting it is safe.",header:"Folder trust",multiSelect:!1,options:o.map(s=>({label:kn(s.label),description:""}))}]}:null}var om=/^(.{0,60}?)Press\s+Enter\s+to\s+continue\s*[.…]{0,3}\s*$/;function BS(t,e=""){let n=String(t);if(xn.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
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(om);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(bn.test(r[l])||Rc.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 DS(t){let e=-1;for(let r=t.length-1;r>=0;r--)if(Ic.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 jS(t,e=""){let n=String(t);if(!Yp.test(n)||!Ic.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
72
|
+
`),o=DS(r);if(!o)return null;let{headerLine:s,chatLine:a,optStart:i,optEnd:c}=o,l=[],u=-1;for(let v=i;v<c;v++){let $=r[v].match(yn);$&&(u<0&&(u=v),l.push({line:v,cursor:!!$[1],checked:$[3]!=null&&$[3]!==" ",hasCheckbox:$[3]!=null,label:$[4].trim(),desc:""}))}if(!l.length)return null;for(let v of l)for(let $=v.line+1;$<Math.min(r.length,v.line+4)&&!(yn.test(r[$])||bn.test(r[$]));$++){let W=r[$].trim();if(!(!W||bS(r[$])||Jp.test(W))){v.desc=yS(W);break}}let f=l.filter(v=>!Jp.test(v.label));if(!f.length)return null;let d=f.some(v=>v.hasCheckbox),m=Math.max(0,f.findIndex(v=>v.cursor)),h=s>=0?s+1:0,w=[];for(let v=h;v<u;v++){let $=r[v].trim();$&&!bn.test(r[v])&&!/[☐☒]/.test(r[v])&&w.push($)}let y=w.join(" ")||(s>=0?Xp(r[s]):"")||"The agent is asking a question",b=s>=0?kS(r[s],y):null,A=b?.currentTabLabel||(s>=0?Xp(r[s]):""),_=f.map(v=>({label:kn(v.label),description:v.desc})),C=_.map(v=>v.label).join("|"),I=`claude:${b?`q${b.questionIndex+1}/${b.questionTotal}`:"q1/1"}:${A}:${y}:${C}`.slice(0,240),L=b?`claude:ask:${b.tabs.map(v=>v.label).join("|")}`.slice(0,240):I;return{provider:"claude",box:e,multiSelect:d,cursorIndex:m,promptId:I,groupId:L,...b?{questionIndex:b.questionIndex,questionTotal:b.questionTotal,currentTabLabel:b.currentTabLabel,tabs:b.tabs}:{},questions:[{question:y,header:A,multiSelect:d,options:_}]}}function ir(t,e){let n=String(e||"");return n?t==="claude"?Yp.test(n)&&Ic.test(n)||Zp.test(n)||_c.test(n)||Qp.test(n.trimEnd().split(`
|
|
73
|
+
`).pop()||"")?!0:xn.test(n)?!1:tm.test(n)&&em.test(n)||nm.test(n)||Oc.test(n)||Ec.test(n)||rm.test(n)||om.test(n.trimEnd().split(`
|
|
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 Sn(t,e){return!t||!e?null:t.agent==="claude"?TS(e,t.name)||jS(e,t.name)||SS(e,t.name)||ES(e,t.name)||NS(e,t.name)||CS(e,t.name)||RS(e,t.name)||IS(e,t.name)||PS(e,t.name)||BS(e,t.name)||AS(e,t.name):t.agent==="cursor"||t.agent==="codex"?Zu(t.agent,e,t.name):null}async function Cc(t){if(!t)return null;let e=await Ee(t.name).catch(()=>"");return Sn(t,e)}function sm(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 am(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 cr(t){return typeof t=="string"?t.trim():""}var im=/^\s*─{8,}\s*$/,LS=/^❯[ ]?(.*)$/;function qs(t,e){if(t!=="claude")return null;let n=String(e).replace(/\r/g,"").split(`
|
|
75
|
+
`).map(c=>c.replace(/\s+$/,"")),r=-1;for(let c=n.length-1;c>=0;c--)if(im.test(n[c])){r=c;break}if(r<=0)return null;let o=-1;for(let c=r-1;c>=0;c--)if(im.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(LS);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 Kt(t){return String(t||"").replace(/\s+/g," ").trim()}function cm(t,e,{slackMs:n=2e4}={}){let r=Kt(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&&Kt(o.text).includes(r))}function lm(t,e){let n=Kt(t),r=Kt(e).replace(/^…\s*/,"").replace(/\s*…$/,"");return!n||!r||r.length<Math.min(n.length,12)?!1:n===r||n.includes(r)}import{execFile as FS}from"node:child_process";import{promisify as US}from"node:util";var Nc=US(FS),um=64*1024*1024;async function pt(t,e,{input:n}={}){let r=Nc("git",["-C",t,...e],{maxBuffer:um});return n!==void 0&&r.child.stdin&&(r.child.stdin.on("error",()=>{}),r.child.stdin.end(n)),r}async function Gs(t){if(!t)return!1;try{let{stdout:e}=await pt(t,["rev-parse","--is-inside-work-tree"]);return e.trim()==="true"}catch{return!1}}async function dm(t){let{stdout:e}=await pt(t,["rev-parse","--show-toplevel"]);return e.trim()}async function HS(t){try{return await pt(t,["rev-parse","--verify","HEAD"]),!0}catch{return!1}}async function Pc(t,e="bostrat: subtask snapshot"){let{stdout:n}=await pt(t,["rev-parse","--absolute-git-dir"]),r=`${n.trim()}/bostrat-snapshot-index`,o={...process.env,GIT_INDEX_FILE:r},s=(a,i)=>Nc("git",["-C",t,...a],{maxBuffer:um,env:o,...i});try{let a=await HS(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 Nc("rm",["-f",r])}catch{}}}async function Bc(t,e){let{stdout:n}=await pt(t,["status","--porcelain"]);return n.trim()?(await pt(t,["add","-A"]),await pt(t,["-c","user.email=agent@bostrat.local","-c","user.name=bostrat","commit","-m",e]),!0):!1}async function Dc(t,{ignorePlumbing:e=!1}={}){let{stdout:n}=await pt(t,["status","--porcelain"]),r=n.split(`
|
|
76
|
+
`).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 pt(t,["rev-list","--count",`${e}..HEAD`]);return Number(n.trim())||0}catch{return 0}}async function lr(t,e,n,r){return await pt(t,["worktree","add","--no-track","-b",n,e,r]),e}async function fm(t,e){try{return await pt(t,["rev-parse","--verify","--quiet",`refs/heads/${e}`]),!0}catch{return!1}}async function pm(t,e,n){return await pt(t,["worktree","add",e,n]),e}async function Ks(t){if(process.env.BOSTRAT_TRACK_BASE_REF)return process.env.BOSTRAT_TRACK_BASE_REF;try{let{stdout:e}=await pt(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 pt(t,["rev-parse","--verify","--quiet",e]),n}catch{}return"HEAD"}async function mm(t){await pt(t,["init","-q"]),await pt(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 pt(t,["worktree","remove","--force",e])}catch{try{await pt(t,["worktree","prune"])}catch{}}}function WS(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 Lc(t,e){return!e||!e.length?t:[...t,"--",".",...e.map(n=>`:(exclude)${n}`)]}async function Fc(t,e,n,r){let{stdout:o}=await pt(t,Lc(["diff","--shortstat",e,n],r));return WS(o)}async function Uc(t,e,n,r){let{stdout:o}=await pt(t,Lc(["diff","--name-only",e,n],r));return o.split(`
|
|
77
|
+
`).map(s=>s.trim()).filter(Boolean)}async function Hc(t,e,n,r){let{stdout:o}=await pt(t,Lc(["diff","--binary",e,n],r));if(!o.trim())return{applied:!0,empty:!0,conflicts:[]};if((await Mc(t,o,["--whitespace=nowarn"])).ok)return{applied:!0,empty:!1,conflicts:[]};let s=await Mc(t,o,["--3way","--whitespace=nowarn"]),a=await qS(t);return a.length>0?{applied:!1,empty:!1,conflicts:a}:s.ok?{applied:!0,empty:!1,conflicts:[]}:(await Mc(t,o,["--reject","--whitespace=nowarn"]),{applied:!1,empty:!1,conflicts:[`patch did not apply cleanly: ${s.err}`]})}async function Mc(t,e,n){try{return await pt(t,["apply",...n,"-"],{input:e}),{ok:!0}}catch(r){return{ok:!1,err:r?.stderr?.trim()||r?.message||String(r)}}}async function qS(t){try{let{stdout:e}=await pt(t,["diff","--name-only","--diff-filter=U"]);return e.split(`
|
|
78
|
+
`).map(n=>n.trim()).filter(Boolean)}catch{return[]}}import GS from"node:fs/promises";import KS from"node:os";import hm from"node:path";var Vs=t=>new Promise(e=>setTimeout(e,t)),VS=Number(process.env.BOSTRAT_MODEL_PICKER_TIMEOUT_MS)||8e3,zS=300,bm=[{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"}],JS=new Set([...bm.map(t=>t.alias),"best","opus[1m]","sonnet[1m]","claude-opus-4-8","claude-sonnet-4-6","claude-haiku-4-5","claude-fable-5"]),XS=[{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"}],km=new Map([["low","Low"],["medium","Medium"],["high","High"],["xhigh","Extra high"]]),xm=[{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 Sm(t){return t==="codex"?XS:t==="cursor"?xm:bm}function wm(t){let e=String(t||"").split(`
|
|
79
|
+
`),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 gm(t){let e=String(t||"").split(`
|
|
80
|
+
`),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 YS(t){let e=String(t||"").split(`
|
|
81
|
+
`);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 ym(t){let e=String(t||"").split(`
|
|
82
|
+
`),n=e.findIndex(s=>/Available models|Models matching/i.test(s));if(n<0)return null;let r=/Models matching/i.test(e[n]),o=[];for(let s=n+1;s<e.length;s++){let a=e[s];if(/^\s*Filter:/i.test(a)||!a.trim())continue;if(/to filter|Enter to select|^\s*\d+-\d+ of \d+/i.test(a))break;let i=a.match(/^\s*(→\s+)?(.+?)\s*$/);if(!i)continue;let c=i[2].replace(/\s*\(Tab to modify\)\s*$/i,""),l=null,u=c.split(/\s{2,}/);u.length>1&&(c=u[0],l=u.slice(1).join(" ").trim()||null),c&&o.push({label:c.trim(),params:l,active:!!i[1]})}return o.length?{filtered:r,rows:o}:null}function vm(t,e){if(typeof e!="string"||!e.trim())throw new Error("model (string) required");let n=e.trim();if(t==="codex"){let[r,o]=n.split(/[\s:]+/);if(o&&!km.has(o))throw new Error(`unknown codex effort "${o}" (low | medium | high | xhigh)`);return{model:r,effort:o||null}}if(t==="cursor"){let r=xm.find(o=>o.alias===n);return{model:n,filter:r?r.label:n}}if(!JS.has(n))throw new Error(`unknown model "${n}"`);return{model:n}}async function co(t,e,{timeoutMs:n=VS}={}){let r=Date.now()+n;for(;;){let o=await wt(t,{lines:80}).catch(()=>""),s=e(o);if(s)return s;if(Date.now()>r)return null;await Vs(zS)}}async function ZS(t,e){return await xt(t,`/model ${e.model}`,fe("claude")),{verified:!1,note:"confirmed when the next turn's transcript model flips"}}async function QS(t,e){await Yo(t),await xt(t,"/model",{enter:!1}),await Vs(250),await kt(t,["Enter"],{kind:"model-switch"});let n=await co(t,wm);if(!n)throw new Error("codex model picker didn't open");let r=n.find(i=>i.id===e.model);if(!r)throw await kt(t,["Escape"],{kind:"model-switch"}).catch(()=>{}),new Error(`unknown model "${e.model}" \u2014 codex offers: ${n.map(i=>i.id).join(", ")}`);let o=i=>{let c=YS(i);return!c||c.model!==e.model||e.effort&&c.effort&&c.effort.toLowerCase()!==e.effort.toLowerCase()?null:c};await xt(t,String(r.n),{enter:!1,kind:"model-switch"});let s=await co(t,i=>gm(i)||!wm(i)&&o(i)&&{done:o(i)});if(!s)throw await kt(t,["Escape"],{kind:"model-switch"}).catch(()=>{}),new Error("codex reasoning-level picker didn't open");if(!s.done){let i=e.effort?km.get(e.effort):null,c=i?s.find(l=>l.label.toLowerCase()===i.toLowerCase()):s.find(l=>l.isDefault)||s[0];if(!c)throw await kt(t,["Escape","Escape"],{kind:"model-switch"}).catch(()=>{}),new Error(`codex doesn't offer effort "${e.effort}" for ${e.model}`);await xt(t,String(c.n),{enter:!1,kind:"model-switch"})}let a=s.done||await co(t,i=>!gm(i)&&o(i));if(!a)throw new Error("codex never confirmed the model change");return{verified:!0,model:a.model,effort:a.effort}}async function tv(){let t=hm.join(process.env.CURSOR_CONFIG_DIR||hm.join(KS.homedir(),".cursor"),"cli-config.json");try{return JSON.parse(await GS.readFile(t,"utf8"))?.model||null}catch{return null}}async function ev(t,e){await Yo(t),await Qo(t,`/model ${e.filter}`,{kind:"model-switch"});let n=await co(t,ym),r=c=>String(c||"").toLowerCase(),o=c=>r(c.label).startsWith(r(e.filter)),s=n?.rows.find(c=>c.active)||null;if(n&&!(s&&o(s)))for(let c=0;c<12;c++){let l=await co(t,ym,{timeoutMs:1500});if(s=l?.rows.find(u=>u.active)||null,s&&o(s))break;if(!l||!l.rows.some(o)){s=null;break}await kt(t,["Down"],{kind:"model-switch"}),await Vs(250)}if(!n||!s||!o(s)){await kt(t,["Escape"],{kind:"model-switch"}).catch(()=>{});let c=Array(`/model ${e.filter}`.length).fill("BSpace");throw await kt(t,c,{settleMs:15,kind:"model-switch"}).catch(()=>{}),new Error(n?`cursor picker has no model matching "${e.filter}"`:"cursor model picker didn't open")}await kt(t,["Enter"],{kind:"model-switch"}),await Vs(800);let a=await tv();return{verified:!!a&&(a.modelId===e.model||a.displayModelId===e.model||r(a.displayName).startsWith(r(e.filter))),model:a?.modelId||null,note:"cursor applies this as the global default for new sessions too"}}async function Am(t,e,n){return t==="codex"?QS(e,n):t==="cursor"?ev(e,n):ZS(e,n)}var Dt=t=>t||null;function Js(t){let e=t||{},n=nv(e);return n&&(n.payload={ts:e.at,...n.payload}),n}function nv(t){switch(t.type){case"said":return{type:"turn.user",actor:"user",source:"orchestrator",boxId:t.box,provider:Dt(t.agent),payload:{text:t.text,boxRole:t.boxRole,...t.kind?{kind:t.kind}:{},...t.attachments?.length?{attachments:t.attachments}:{}}};case"unsaid":return{type:"turn.retracted",actor:"user",source:"orchestrator",boxId:t.box,provider:Dt(t.agent),payload:{saidTs:t.saidTs,text:t.text,boxRole:t.boxRole}};case"orchestration-prompt":return{type:"turn.user",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Dt(t.agent),payload:{text:t.prompt,kind:t.kind,boxRole:t.boxRole}};case"lead-handoff":return{type:"handoff.done",actor:"orchestrator",source:"orchestrator",boxId:t.to,provider:Dt(t.toAgent),payload:{from:t.from,fromAgent:t.fromAgent,to:t.to,toAgent:t.toAgent,reason:t.reason,handoffStartedAt:t.handoffStartedAt,fromSessionId:t.fromSessionId,toSessionId:t.toSessionId,epoch:t.epoch,singleBox:t.singleBox,forced:t.forced,recovered:t.recovered}};case"lead-handoff-failed":return{type:"handoff.failed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Dt(t.toAgent),payload:{box:t.box,fromAgent:t.fromAgent,toAgent:t.toAgent,reason:t.reason,error:t.error,rolledBack:t.rolledBack,handoffStartedAt:t.handoffStartedAt}};case"subtask-delegated":return{type:"subtask.delegated",actor:"orchestrator",source:"orchestrator",boxId:t.to,provider:Dt(t.toAgent),payload:{task:t.task,subtaskId:t.subtaskId,reason:t.reason}};case"subtask-merged":return{type:"subtask.merged",actor:"orchestrator",source:"orchestrator",boxId:t.box||t.from,provider:Dt(t.agent),payload:{...ur(t)}};case"subtask-failed":return{type:"error",actor:"system",source:"orchestrator",boxId:t.box||t.to,provider:Dt(t.agent||t.toAgent),payload:{kind:"subtask-failed",...ur(t)}};case"subtask-resumed":return{type:"box.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Dt(t.agent),payload:{change:"subtask-resumed",...ur(t)}};case"model-change":return{type:"box.state.changed",actor:"user",source:"orchestrator",boxId:t.box,payload:{change:"model",model:t.to,from:t.from??null,verified:t.verified??null}};case"codex-update":return{type:"box.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:"codex",payload:{change:"cli-update",from:t.from??null,to:t.to??null,status:t.status,...t.error?{error:t.error}:{}}};case"opened":return{type:"box.state.changed",actor:"user",source:"orchestrator",boxId:t.box,payload:{change:"opened",...ur(t)}};case"archived":case"unarchived":case"track-deleted":return{type:"box.state.changed",actor:"user",source:"orchestrator",payload:{change:t.type,...ur(t)}};case"rehydrated":return{type:"box.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Dt(t.agent),payload:{change:"rehydrated"}};case"retagged":return{type:"box.state.changed",actor:"user",source:"orchestrator",payload:{change:"retagged",...ur(t)}};case"rehydrate-failed":return{type:"error",actor:"system",source:"orchestrator",boxId:t.box,provider:Dt(t.agent),payload:{kind:"rehydrate-failed",reason:t.reason}};default:return null}}var zs=Promise.resolve(),qc=t=>(zs=zs.then(t).catch(()=>{}),zs),_m=()=>zs;function Em(t,e){let n=Js(e);if(!n)return;let r=e?.type==="track-deleted"?{id:t.id}:t;qc(()=>O(r,n))}var Tm=new Set;async function Im(t){if(!(!t?.id||Tm.has(t.id))){Tm.add(t.id);try{let e=await F(t),n=new Set(e.filter(r=>r.source==="orchestrator").map(r=>`${r.type}|${r.payload?.ts??"?"}`));for(let r of t.events||[]){let o=Js(r);!o||n.has(`${o.type}|${o.payload?.ts??"?"}`)||await O(t,o)}}catch{}}}function ur(t){let{type:e,at:n,...r}=t;return r}var Wc=new Map;function Rm(t,e,n){!n?.promptId||Wc.get(t.id)===n.promptId||(Wc.set(t.id,n.promptId),qc(()=>O(t,{type:"prompt.raised",actor:"agent",source:"pane",boxId:e?.name,provider:Dt(n.provider||e?.agent),payload:{promptId:n.promptId,question:n.question||n.title||null,options:(n.options||[]).map(r=>r?.label??r).slice(0,12)}})))}function $m(t,e,n,r={}){Wc.delete(t.id),qc(()=>O(t,{type:"prompt.answered",actor:"user",source:"orchestrator",boxId:e?.name,provider:Dt(n?.provider||e?.agent),payload:{promptId:n?.promptId,...r}}))}import{randomUUID as Nv}from"node:crypto";var Cm=/(?<!\b(?:a|an|the|this|that|another|one|each|any|no|separate|single|new|initial|final|first|second|third|last|later|next|subsequent|extra|additional|amend(?:ed)?|squash|merge|follow[\s-]?up|clean[\s-]?up)\s)commit(?!s\b)(?!\s+(?:history|log|message|hash|sha)s?\b)/.source,Mm=/(?<!\b(?:a|an|the|this|that|another|one|each|any|no|separate|single|new|initial|final|first|second|third|last|later|next|subsequent|extra|additional)\s)push(?!\s+notifications?\b)(?:\s+(?:it|this|that|them|everything|the\s+\w+))?\s*(?:up)?/.source,Nm=/open(?:\s+up)?\s+(?:a|the|that)\s+(?:draft\s+)?(?:pr|pull\s+request)|creat(?:e|ing)\s+(?:a|the)\s+(?:draft\s+)?(?:pr|pull\s+request)|rais(?:e|ing)\s+(?:a|the)\s+(?:pr|pull\s+request)|put\s+up\s+(?:a|the)\s+(?:pr|pull\s+request)|send\s+(?:up\s+)?(?:a|the)\s+(?:pr|pull\s+request)/.source,Pm=/(?<!\b(?:a|an|the|this|that|another|one|each|any|no|clean|interactive)\s)rebase(?!s\b)(?:\s+(?:it|this|that|them|(?:this|that|the)\s+branch))?(?:\s+(?:on(?:to)?|against|atop|over)\s+\S+)?/.source,rv=new RegExp(`\\b(?:${Cm}|${Mm}|${Nm}|${Pm})\\b`,"i"),ov=/\b(?:want\s+me\s+to|would\s+you\s+like(?:\s+me)?(?:\s+to)?|should\s+i|shall\s+i|do\s+you\s+want(?:\s+me)?(?:\s+to)?|happy\s+to|ready\s+for\s+me\s+to|let\s+me\s+know\s+(?:if|when|whether|and)|say\s+the\s+word|give\s+me\s+the\s+(?:word|go[\s-]?ahead)|if\s+you(?:'d|\s+would)?\s+(?:like|want|prefer)|just\s+say\s+(?:so|yes|go))\b/i,sv=/^(?:commit|push|open|create|raise|rebase|ready|ok(?:ay)?\s+to|good\s+to)\b/i,av=90;function iv(t){return String(t).split(/(?<=[.?!])\s+|\n+/).map(e=>e.trim()).filter(Boolean)}function Bm(t){let e=String(t||"").trim();if(!e)return null;let n=e.slice(-600);for(let r of iv(n).reverse()){if(!rv.test(r))continue;let o=r.endsWith("?")&&r.length<=av&&sv.test(r);if(ov.test(r)||o)return r.length>200?r.slice(0,197)+"\u2026":r}return null}var Dm=["commit","rebase","push","pr"],cv=new RegExp(`\\b(?:${Cm})\\b`,"i"),lv=new RegExp(`\\b(?:${Pm})\\b`,"i"),uv=new RegExp(`\\b(?:${Mm})\\b`,"i"),dv=new RegExp(`\\b(?:${Nm})\\b`,"i"),fv=/\b(?:to|into|onto|on|updat(?:e|es|ing))\s+(?:the|that|this)\s+(?:open\s+|existing\s+|current\s+|same\s+)?(?:draft\s+)?(?:pr|pull\s+request)\b/i;function jm(t){let e=String(t||""),n=[];cv.test(e)&&n.push("commit"),lv.test(e)&&n.push("rebase"),uv.test(e)&&n.push("push");let r=fv.test(e);!r&&dv.test(e)&&n.push("pr");let o=null;if(r){let s=e.match(/(?:pr|pull\s+request)\s*\(?\s*#(\d+)\)?/i)||e.match(/#(\d+)/);o={number:s?Number(s[1]):null}}return{offered:n.length?n:null,toPr:o}}function Xs(t,{branch:e=null,base:n=null,toPr:r=null,repos:o=null,offeredRebase:s=!1}={}){Array.isArray(t)||(t=t==="push"?["commit","push"]:["commit","push","pr"]);let a=new Set(t),i=Array.isArray(o)&&o.length>0,c=i?o.map(m=>`${m.name}/`).join(", "):null,l=e?` on this branch (${e})`:"",u=[];if(a.has("commit")&&u.push(i?`commit the work in each changed checkout (${c})${l} with a clear commit message per repo`:`commit the work${l} with a clear commit message`),a.has("rebase")){let m=a.has("commit")?"the branch":`this branch${e?` (${e})`:""}`;u.push(i?`rebase ${m} in each changed checkout (${c}) onto its own base branch (fetch origin first), resolving any conflicts so both sides' intent is preserved`:`rebase ${m} onto ${n?`the latest ${n}`:"its base branch"} (fetch origin first), resolving any conflicts so both sides' intent is preserved`)}if(a.has("push")||a.has("pr")){let m=r&&!a.has("pr")?` (the open PR${r.number?` #${r.number}`:""} picks it up)`:"",h=a.has("rebase")?" with `--force-with-lease` (the rebase rewrote history)":"";i?u.push(a.has("commit")||a.has("rebase")?`push each one to its remote${h}${m}`:`push this branch${e?` (${e})`:""} from each changed checkout (${c}) to its remote${m}`):u.push(a.has("commit")||a.has("rebase")?`push it to the remote${h}${m}`:`push this branch${l} to the remote${m}`)}a.has("pr")&&u.push(i?"open a pull request in each changed repo with a concise title and a description of what changed and why (run `gh pr create` inside each checkout)":`open a pull request${n?` against ${n}`:""} with a concise title and a description of what changed and why (use \`gh pr create\`)`);let f=[];a.has("commit")||f.push("don't create any new commits \u2014 ship what's already committed"),s&&!a.has("rebase")&&f.push("don't rebase \u2014 leave the branch's history as it is"),!a.has("push")&&!a.has("pr")&&f.push("don't push"),a.has("pr")||f.push("don't open a pull request");let d=`Yes \u2014 go ahead: ${u.join(", then ")}.`;return f.length&&(d+=` ${f.join(", and ").replace(/^./,m=>m.toUpperCase())}.`),a.has("pr")&&(d+=i?" If a repo's PR for this branch is already open, just push that repo's update instead.":" If a PR for this branch is already open, just push the update instead."),d}var pv=3e4;function Lm(t,e=Date.now()){let n=[],r=null;for(let o of t||[]){let s=o.payload||{},a=s.ts??o.ts;if(o.type==="pr.offer.answered"&&s.action==="pr"){r={box:o.boxId,from:a,lastActivity:a};continue}!r||a==null||a<r.from||(o.type==="pr.offer.answered"?r=null:o.type==="pr.state.changed"&&s.change==="opened"?(n.push({box:r.box,from:r.from,to:a,settled:!0}),r=null):o.type==="turn.user"&&o.source!=="hook"&&!s.kind&&String(s.text||"").trim()?r=null:o.boxId===r.box&&/^(?:turn|tool)\./.test(o.type)&&(r.lastActivity=Math.max(r.lastActivity,a)))}return r&&e-r.lastActivity<=pv&&n.push({box:r.box,from:r.from,to:e+1e12}),n}function Fm(t,e){let n=new Set;for(let r of e||[]){if(!r.settled)continue;let o=null;for(let s of t||[])s.kind!=="message"||s.role!=="assistant"||s.box!==r.box||s.ts==null||s.ts<r.from||s.ts>r.to||(!o||s.ts>=o.ts)&&(o=s);o&&n.add(o)}return n}var Om={commit:"commit",rebase:"rebase",push:"push",pr:"open a PR"};function Um(t){let e=(Array.isArray(t)?t:[]).filter(r=>Om[r]).map(r=>Om[r]);return`The ship you were given the go-ahead for${e.length?` (${e.join(", then ")})`:""} should now be complete. This is an automated ship notification the operator armed on the offer: carry straight on with this Track's follow-up work (dependent tasks, cleanup, next steps) \u2014 don't wait for review or merge. If a ship step failed or is unfinished, finish the ship first. If nothing needs doing, say so briefly.`}import{execFile as yv}from"node:child_process";import{promisify as bv}from"node:util";import mv from"node:fs";import Gc from"node:fs/promises";import Gm from"node:path";import hv from"node:os";var Km=process.env.BOSTRAT_STATE_DIR||Gm.join(process.env.HOME||hv.tmpdir(),".bostrat"),Ys=Gm.join(Km,"github-metrics.json"),Vm=60*6e4,Hm=5*6e4,wv=6e4,dr=Date.now(),be=new Map,Le=null,vn=0,Wm=0,Zs=t=>[...t.values()].reduce((e,n)=>e+n,0);try{let t=JSON.parse(mv.readFileSync(Ys,"utf8"));t?.windowStart&&(Date.now()-t.windowStart<Vm?(dr=t.windowStart,be=new Map(Object.entries(t.byCaller||{})),Le=t.lastHour||null):Zs(new Map(Object.entries(t.byCaller||{})))&&(Le={start:t.windowStart,total:Zs(new Map(Object.entries(t.byCaller||{}))),byCaller:t.byCaller||{}}))}catch{}var qm=Promise.resolve();function zm(t,e=!1){if(!e&&t-Wm<wv)return;Wm=t;let n=JSON.stringify({windowStart:dr,byCaller:Object.fromEntries(be),lastHour:Le});qm=qm.then(()=>Gc.mkdir(Km,{recursive:!0})).then(()=>Gc.writeFile(`${Ys}.tmp`,n)).then(()=>Gc.rename(`${Ys}.tmp`,Ys)).catch(()=>{})}function Jm(t){t-dr<Vm||(Le={start:dr,total:Zs(be),byCaller:Object.fromEntries(be)},Le.total&&console.log(`[github] ${Le.total} calls last hour`,Le.byCaller),dr=t,be=new Map,zm(t,!0))}function ke(t="other",e=Date.now()){Jm(e),be.set(t,(be.get(t)||0)+1),zm(e)}function Xm(t=Date.now()){return Jm(t),{windowStart:dr,total:Zs(be),byCaller:Object.fromEntries(be),lastHour:Le,backoffUntil:vn>t?vn:null}}var gv=/rate limit|too quickly|abuse detection|HTTP 429/i;function Fe(t){return gv.test(String(t?.message||t||""))}function Ue(t,e=Date.now()){return vn<=e&&console.warn(`[github] rate limited \u2014 backing off ${Hm/6e4} min:`,String(t?.message||t||"").slice(0,200)),vn=Math.max(vn,e+Hm),vn}function Qs(t=Date.now()){return Math.max(0,vn-t)}async function Kc(t,e){let n={working:!1,prompted:!1};if(!t)return n;let r=await wt(t,{lines:At}).catch(()=>"");if(!Ln(e,r))return nd(e,r)?{working:!0,prompted:!1}:n;if(ir(e,r)){let o=await Ee(t).catch(()=>r);if(Sn({name:t,agent:e},o))return{working:!1,prompted:!0}}return{working:!0,prompted:!1}}async function ta(t,e){return(await Kc(t,e)).working}var Ym=bv(yv),Zm=[4e3,15e3,45e3],kv=2500,xv=5*6e4;async function Vc(t,e,{midTurn:n=ta,pollMs:r=kv,capMs:o=xv}={}){let s=Date.now()+o;for(;await n(t,e).catch(()=>!1);){if(Date.now()>=s)return!1;await new Promise(a=>setTimeout(a,r))}return!0}var Sv=6e4,vv=9e4,Av=2e3,Tv=/(?:^|[;&|]\s*|\)\s*)gh\s+(?:[^\s;&|"'`$]+\s+)*?pr\s+create\b/,_v=/(?:^|[;&|]\s*|\)\s*)gh\s+(?:[^\s;&|"'`$]+\s+)*?pr\s+merge\b/,Ev=/\s--web\b/,Qm=/(?:--repo|-R)[\s=]+([\w.-]+\/[\w.-]+)/;function zc(t,e){return t.slice(e).replace(/^[\s;&|)]+/,"").replace(/"[^"]*"|'[^']*'/g,'""').split(/[;&|]/)[0]}function th(t){let e=String(t||""),n=e.match(Tv);if(!n)return null;let r=zc(e,n.index);return Ev.test(r)?null:{repo:r.match(Qm)?.[1]??null}}var Iv=new Set(["--subject","-t","--body","-b","--body-file","-F","--match-head-commit","--author-email","-A","--repo","-R"]);function eh(t){let e=String(t||""),n=e.match(_v);if(!n)return null;let r=zc(e,n.index),o=zc(e,n.index+n[0].length),s=null,a=o.trim().split(/\s+/).filter(Boolean);for(let i=0;i<a.length;i++){let c=a[i];if(c.startsWith("-")){Iv.has(c)&&i++;continue}s=c.replace(/^["']+|["']+$/g,"")||null;break}return{repo:r.match(Qm)?.[1]??null,selector:s,auto:/\s--auto\b/.test(r)}}var Rv="number,url,title,headRefName,createdAt";async function $v(t,e){let n=async s=>{ke("observer");let{stdout:a}=await Ym("gh",["pr","list","--repo",t,"--author","@me","--state","all","--limit","10","--json",Rv],{timeout:3e4,maxBuffer:4*1024*1024,env:{...process.env,...s}}).catch(i=>{throw Fe(i)&&Ue(i),i});return JSON.parse(a||"[]")},r=await ae({slug:t}),o=await n(r);return!o.length&&r.GH_TOKEN&&(o=await n({}).catch(()=>[])),o.filter(s=>Date.parse(s.createdAt)>=e-Sv).sort((s,a)=>Date.parse(a.createdAt)-Date.parse(s.createdAt))}async function Ov(t,e){if(e)return[e];let n=[];for(let r of nh(t)){let o=await Z(r).catch(()=>null);o&&!n.includes(o)&&n.push(o)}for(let r of await ye().catch(()=>[])){let o=await Z(r.path).catch(()=>null);o&&!n.includes(o)&&n.push(o)}return n}function nh(t){let e=t.workspace;return e?.kind==="multi"?(e.repos??[]).map(n=>n?.base).filter(Boolean):[e?.base||t.cwd].filter(Boolean)}async function Cv(t,e,n){let r=t.workspace?.branch,o=[];for(let s of await Ov(t,e)){let a;try{a=await $v(s,n)}catch{continue}for(let i of a){let c=await Tt(s,i.number);if(!(c&&(c.trackId!==t.id||c.announced))){if(r&&i.headRefName===r)return{slug:s,pr:i};o.push({slug:s,pr:i})}}}return o.length?(o.sort((s,a)=>Date.parse(a.pr.createdAt)-Date.parse(s.pr.createdAt)),o[0]):null}var He=new Set;function rh(t,e,{delays:n=Zm,turnEnd:r}={}){let o=th(e.command);if(!o)return null;let s=!!eh(e.command),a=`${t.id}|${e.id??e.ts??e.command}`;if(He.has(a))return null;He.add(a);let i=e.ts??Date.now(),c=(async()=>{try{for(let l of n){await new Promise(w=>setTimeout(w,l));let u=await Cv(t,o.repo,i).catch(()=>null);if(!u)continue;let{slug:f,pr:d}=u,{link:m,created:h}=await Bt({repo:f,number:d.number,trackId:t.id,url:d.url,headRef:d.headRefName,title:d.title,source:"observed"});return!h&&!(m?.trackId===t.id&&await Yr(f,d.number))?null:(await Vc(e.box,e.agent,r),await O({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:Date.now(),change:"opened",number:d.number,url:d.url,title:d.title,branch:d.headRefName,repo:f,...s?{selfMerge:!0}:{}}}),d)}return null}finally{He.delete(a)}})();return c.catch(()=>He.delete(a)),c}var Mv="number,url,title,headRefName,state";function oh(t,e,{delays:n=Zm,turnEnd:r,openedWait:o}={}){let s=eh(e.command);if(!s)return null;let a=`merge|${t.id}|${e.id??e.ts??e.command}`;if(He.has(a))return null;He.add(a);let i=(async()=>{try{let c=s.repo,l=s.selector,u=l?.match(/github\.com\/([\w.-]+\/[\w.-]+)\/pull\/(\d+)/);u&&([,c,l]=u);let f=[];if(c)f=[c];else{let h=nh(t);if(h.length>1&&/^\d+$/.test(String(s.selector??"")))return null;for(let w of h){let y=await Z(w).catch(()=>null);y&&!f.includes(y)&&f.push(y)}}if(l||=t.workspace?.branch,!f.length||!l)return null;c=f[0];let d=async(h,w)=>{let y=await Tt(c,h.number);return y&&y.trackId!==t.id?null:(y||await Bt({repo:c,number:h.number,trackId:t.id,url:h.url,headRef:h.headRefName,title:h.title,source:"merge-observed"}),await Zr(c,h.number),w&&((await F(t).catch(()=>[])).some(_=>_.type==="pr.state.changed"&&(_.payload?.change==="auto-armed"||_.payload?.change==="merged")&&_.payload?.number===h.number)||(await Vc(e.box,e.agent,r),await O({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:Date.now(),change:"auto-armed",number:h.number,url:h.url,title:h.title,branch:h.headRefName,repo:c}}))),h)},m=null;for(let h of n){await new Promise(I=>setTimeout(I,h));let w=[];for(let I of f)try{ke("observer");let{stdout:L}=await Ym("gh",["pr","view",String(l),"--repo",I,"--json",Mv],{timeout:3e4,maxBuffer:4*1024*1024,env:{...process.env,...await ae({slug:I})}}),v=JSON.parse(L||"null");v?.number&&w.push({slug:I,pr:v})}catch(L){Fe(L)&&Ue(L)}let y=w.find(I=>I.pr.state==="MERGED")??(w.length===1?w[0]:null);if(!y)continue;c=y.slug;let b=y.pr;if(!b?.number)continue;if(b.state==="CLOSED")return null;if(b.state!=="MERGED"){if(m=b,s.auto)return d(b,!0);continue}let A=await Tt(c,b.number);if(A&&A.trackId!==t.id)return null;A||await Bt({repo:c,number:b.number,trackId:t.id,url:b.url,headRef:b.headRefName,title:b.title,source:"merge-observed"}),await Vc(e.box,e.agent,r);let _=await F(t).catch(()=>[]);if(_.some(I=>I.type==="pr.state.changed"&&I.payload?.change==="merged"&&I.payload?.number===b.number))return null;let C=I=>I.some(L=>L.type==="pr.state.changed"&&L.payload?.change==="opened"&&L.payload?.number===b.number),K=0;if(!C(_)){if(th(e.command)&&await Yr(c,b.number)){let I=Date.now();K=I+1,await O({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:I,change:"opened",number:b.number,url:b.url,title:b.title,branch:b.headRefName,repo:c,selfMerge:!0}})}else if((await Tt(c,b.number))?.announced){let I=o?.capMs??vv,L=o?.pollMs??Av,v=Date.now()+I;for(;Date.now()<v&&(await new Promise($=>setTimeout($,L)),!C(await F(t).catch(()=>[]))););}}return await O({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:Math.max(Date.now(),K),change:"merged",number:b.number,url:b.url,title:b.title,branch:b.headRefName,repo:c}}),await qe(c,b.number).catch(()=>{}),await We(c,b.number).catch(()=>{}),await Pe(c,b.number,"merged"),b}return m?d(m,!1):null}finally{He.delete(a)}})();return i.catch(()=>He.delete(a)),i}function Xc(t){return t.sourceId?`${t.role}|id:${t.sourceId}`:`${t.role}|${t.ts??"?"}|${String(t.text||"").slice(0,40)}`}function sh(t){return t.id?`cmd|${t.box}|id:${t.id}`:`cmd|${t.box}|${t.ts??"?"}|${String(t.command||"").slice(0,40)}`}function ah(t){return`job|${t.box}|id:${t.id}`}var Jc=new Map,ea=new Map;async function Yc(t){return Jc.has(t.id)?Jc.get(t.id):(ea.has(t.id)||ea.set(t.id,F(t).then(e=>{let n={keys:new Set,cmdN:0};for(let r of e)if((r.type==="turn.user"||r.type==="turn.agent")&&r.payload?.text!=null){let o=r.type==="turn.user"?"user":"assistant";r.payload.sourceId&&n.keys.add(Xc({role:o,sourceId:r.payload.sourceId})),n.keys.add(Xc({role:o,ts:r.payload.ts,text:r.payload.text}))}else if(r.type==="tool.called"&&r.source==="transcript"&&r.payload?.command){let o=r.payload;n.keys.add(sh({id:o.id,box:r.boxId,ts:o.ts,command:o.command})),Number(o.n)>n.cmdN&&(n.cmdN=Number(o.n))}else r.type==="job.started"&&r.payload?.id&&n.keys.add(ah({id:r.payload.id,box:r.boxId}));return Jc.set(t.id,n),n}).finally(()=>ea.delete(t.id))),ea.get(t.id))}async function ih(t,e){try{let n=(await Yc(t)).keys;for(let r of e||[]){if(!r||r.ts==null||!String(r.text||"").trim()&&!r.images?.length)continue;let o=Xc(r);if(!n.has(o)&&(n.add(o),await O(t,{type:r.role==="user"?"turn.user":"turn.agent",actor:r.role==="user"?"user":"agent",source:"transcript",boxId:r.box,provider:r.agent||null,payload:{text:r.text,ts:r.ts,model:r.model||null,tools:r.tools||void 0,boxRole:r.boxRole,sourceId:r.sourceId||void 0,note:r.note||void 0,images:r.images?.length?r.images:void 0}}),r.role==="assistant"&&r.box===t.leadBox&&t.workspace?.branch)){let s=Bm(r.text);if(s){let{offered:a,toPr:i}=jm(s),c;if(t.workspace?.kind==="multi"){let l=await lo(t).catch(()=>[]);c=l.length?l.map(u=>({name:u.name,slug:u.repo??null})):void 0}await O(t,{type:"pr.offered",actor:"agent",source:"transcript",boxId:r.box,provider:r.agent||null,payload:{offerId:Nv(),ts:r.ts,excerpt:s,branch:t.workspace.branch,base:t.workspace.baseRef?.replace(/^origin\//,"")||null,boxRole:r.boxRole,offered:a||void 0,toPr:i||void 0,repos:c}})}}}}catch{}}async function ch(t,e){try{let n=await Yc(t),r=[...e||[]].filter(o=>o&&String(o.command||"").trim()).sort((o,s)=>(o.ts||0)-(s.ts||0));for(let o of r){let s=sh(o);n.keys.has(s)||(n.keys.add(s),n.cmdN+=1,await O(t,{type:"tool.called",actor:"agent",source:"transcript",boxId:o.box,provider:o.agent||null,payload:{id:o.id??null,tool:o.tool||null,command:o.command,description:o.description||"",summary:o.summary||o.command,ts:o.ts??null,boxRole:o.boxRole,n:n.cmdN}}),rh(t,o),oh(t,o))}}catch{}}async function lh(t,e){try{let n=await Yc(t),r=[...e||[]].filter(o=>o&&o.id).sort((o,s)=>(o.ts||0)-(s.ts||0));for(let o of r){let s=ah(o);n.keys.has(s)||(n.keys.add(s),await O(t,{type:"job.started",actor:"agent",source:"transcript",boxId:o.box,provider:o.agent||null,payload:{id:o.id,kind:o.kind||"agent",name:o.name||null,detail:o.detail||null,ts:o.ts??null,boxRole:o.boxRole}}))}}catch{}}var Pv=/^Base directory for this skill:\s*(\S+)/,Bv=/^Run the "([^"]+)" workflow\./,Dv=/<command-name>\s*\/?([a-z0-9:_-]+)\s*<\/command-name>/i,jv=/^<local-command-(?:stdout|caveat)>/i,Lv=/^\[Image: original \d+x\d+, displayed at \d+x\d+\.\s*Multiply coordinates by [\d.]+ to map to original image\.\]$/,Fv=/^Continue from where you left off\.$/,Uv=/^<task-notification>/i,Hv=/<summary>([\s\S]*?)<\/summary>/i,Wv=/<task-id>([\s\S]*?)<\/task-id>/i,qv=/<tool-use-id>([\s\S]*?)<\/tool-use-id>/i,Gv=/<status>([\s\S]*?)<\/status>/i,Kv=/<result>([\s\S]*?)(?:<\/result>|$)/i;var Zc=new Set(["model","clear","compact","login","exit","quit"]);function uh(t){return Zc.has(String(t||"").replace(/^\//,"").toLowerCase())}var Vv=/^\/([a-z0-9:_-]+)$/i;function na(t){let e=String(t||"").trim();if(!e)return null;let n=e.match(Pv);if(n)return{kind:"skill",name:n[1].split("/").filter(Boolean).pop()||"skill"};let r=e.match(Bv);if(r)return{kind:"skill",name:r[1]};let o=e.match(Dv);if(o){let a=o[1].toLowerCase();return Zc.has(a)?{kind:"suppress"}:{kind:"command",command:`/${a}`}}let s=e.match(Vv);if(s&&Zc.has(s[1].toLowerCase()))return{kind:"suppress"};if(jv.test(e))return{kind:"suppress"};if(Fv.test(e))return{kind:"suppress"};if(Lv.test(e))return{kind:"suppress"};if(Uv.test(e)){let a=c=>zv(e.match(c)?.[1].trim()||"")||null,i=a(Kv);return{kind:"task",summary:a(Hv)||"",taskId:a(Wv),toolUseId:a(qv),status:a(Gv)?.toLowerCase()||null,resultHead:i?i.length>600?`${i.slice(0,600).trimEnd()}\u2026`:i:null}}return null}function zv(t){return t.replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/'/g,"'").replace(/&/g,"&")}function dh(t){return t?.kind==="skill"?`Ran the ${t.name} skill`:t?.kind==="command"?`Ran ${t.command}`:t?.kind==="task"?t.summary||"A background task finished":""}function fh(t){return t?.kind==="skill"?"skill-run":t?.kind==="task"?"task-done":"command-run"}var ot=t=>t.payload?.ts??t.ts,Jv=new Set(["opened","ci-fixed","reconciled","pushed"]),ph=t=>`${t.repo??""}#${t.number}`;function Xv(t,e,n){let r=ph(n),o=t.has(r)?r:null;if(!o&&n.number!=null)for(let[i,c]of t){if(Number(c.number)!==Number(n.number))continue;let l=!!(n.url&&c.url&&n.url===c.url),u=!c.repo&&!!n.repo||!!c.repo&&!n.repo;if(l||u&&(!c.url||!n.url||c.url===n.url)){o=i;break}}let s=n.repo||!o?r:o,a=o?t.get(o):t.get(s);if(o&&o!==s){t.delete(o);let i=e.get(o);if(e.delete(o),i?.length){let c=e.get(s)??[];e.set(s,c.concat(i))}}return t.set(s,{number:n.number,url:n.url??a?.url??null,title:n.title??a?.title??null,repo:n.repo??a?.repo??null,branch:n.branch??a?.branch??null}),s}function ra(t){let e=[],n=new Set,r=new Set,o=new Map,s=new Map,a=new Set,i=new Map,c=new Set,l=new Map,u=/^Agent "([\s\S]+)" finished$/,f=new Map;for(let g of t||[]){if(g.type!=="tool.called"||g.source!=="transcript")continue;let p=String(g.payload?.command||"");if(/\bbostrat-mock\s+(?:add|update)\b/.test(p))for(let S of p.matchAll(/[\w./~-]+\.(?:png|jpe?g|gif|webp|bmp|svg)\b/gi)){let T=S[0].split("/").pop().toLowerCase();f.has(T)||f.set(T,[]),f.get(T).push(g.payload?.ts??g.ts)}}let d=3*6e4,m=20*6e4,h=(g,p)=>!!(g?.name&&f.get(String(g.name).toLowerCase()))?.some(T=>T>=p-d&&T<=p+m);for(let g of t||[])if(g.type==="turn.retracted"){r.add(`${g.boxId}|${g.payload?.saidTs}`);let p=Kt(g.payload?.text);p&&(o.has(g.boxId)||o.set(g.boxId,[]),o.get(g.boxId).push(p))}else if(g.type==="turn.user"&&g.source==="orchestrator"){let p=Kt(g.payload?.text);p&&(s.has(g.boxId)||s.set(g.boxId,new Set),s.get(g.boxId).add(p))}else if(g.type==="job.started"&&g.payload?.id)a.add(g.payload.id),(g.payload.kind||"agent")==="agent"&&g.payload.name&&c.add(g.payload.name);else if(g.type==="turn.user"&&g.payload?.note?.kind==="task"){let p=g.payload.note,S={status:p.status||null,summary:p.summary||null,resultHead:p.resultHead??null,ts:ot(g)};p.toolUseId&&i.set(p.toolUseId,S);let T=String(p.summary||"").match(u);T&&l.set(T[1],S)}let w=new Map,y=new Map,b=null,A=new Map,_=new Map,C=new Map,K=new Map,I=new Map,L=new Map,v=new Map,$=6e4,W=new Map,nt=(g,p)=>{let S=o.get(g);if(!S?.length)return!1;let T=Kt(p),J=s.get(g);return S.some(R=>{if(!T.startsWith(R))return!1;let M=Kt(T.slice(R.length));return!!M&&!!J?.has(M)})};for(let g of t||[]){let p=g.payload||{};switch(g.type){case"turn.user":{if(g.source==="hook"||!String(p.text||"").trim()&&!p.images?.length)break;{let S=p.note||na(p.text);if(S){if(S.kind==="task"){if(S.toolUseId&&a.has(S.toolUseId))break;let T=String(S.summary||"").match(u);if(!S.toolUseId&&T&&c.has(T[1]))break}if(S.kind==="command"&&uh(S.command))break;S.kind!=="suppress"&&e.push({id:g.id,kind:"event",type:fh(S),name:S.name??null,command:S.command??null,summary:S.summary??null,ts:ot(g),box:g.boxId,agent:g.provider});break}}if(r.has(`${g.boxId}|${ot(g)}`)||g.source==="transcript"&&nt(g.boxId,p.text))break;if(p.kind==="merge-notify"||p.kind==="ship-notify"){e.push({id:g.id,kind:"event",type:p.kind==="ship-notify"?"ship-notified":"merge-notified",text:p.text,ts:ot(g),box:g.boxId,agent:g.provider});break}if(p.kind==="mock-picked"||p.kind==="ci-fix"||p.kind==="reconcile"||p.kind==="pr-go"||p.kind==="push-go"||p.kind==="spin-up-view")break;for(let S of A.values())S.status="stale";A.clear(),e.push({id:g.id,kind:"message",role:"user",text:p.text,ts:ot(g),box:g.boxId,agent:g.provider,boxRole:p.boxRole,...g.actor==="orchestrator"?{orchestration:p.kind||!0}:{},...p.sourceId?{sourceId:p.sourceId}:{},...p.attachments?.length?{attachments:p.attachments}:{},...p.images?.length?{images:p.images}:{}});break}case"turn.agent":{if(p.model==="<synthetic>"&&/^No response requested\.$/.test(String(p.text||"").trim()))break;v.clear();let S=(p.images||[]).filter(J=>!h(J,ot(g)));if(!String(p.text||"").trim()&&!S.length)break;if(g.provider==="cursor"){let J=`${g.boxId}|${p.text}`;if(n.has(J))break;n.add(J)}let T=g.provider==="codex"?(p.tools||[]).filter(J=>!yc.has(J)&&!ao.has(J)):p.tools||[];e.push({id:g.id,kind:"message",role:"assistant",text:p.text,ts:ot(g),box:g.boxId,agent:g.provider,boxRole:p.boxRole,...T.length?{tools:T}:{},...p.model?{model:p.model}:{},...p.sourceId?{sourceId:p.sourceId}:{},...S.length?{images:S}:{}});break}case"job.started":{if(!p.id)break;let S=ot(g),T=i.get(p.id);if(!T&&(p.kind||"agent")==="agent"&&p.name){let P=l.get(p.name);P&&P.ts>=S&&(T=P)}let J={jobId:p.id,jobKind:p.kind||"agent",name:p.name||null,detail:p.detail||null,status:T?T.status&&T.status!=="completed"?"failed":"done":"running",startedTs:S,finishedTs:T?.ts??null,summary:T?.summary??null,resultHead:T?.resultHead??null},R=W.get(g.boxId),M=R?.jobs[R.jobs.length-1]?.startedTs;if(R&&M!=null&&S-M<=$)R.jobs.push(J);else{let P={id:g.id,kind:"job-dock",dockId:p.id,ts:S,box:g.boxId,agent:g.provider,jobs:[J]};W.set(g.boxId,P),e.push(P)}break}case"handoff.done":e.push(Ge(g,"lead-handoff"));break;case"subtask.delegated":e.push({...Ge(g,"subtask-delegated"),to:g.boxId,toAgent:g.provider});break;case"subtask.merged":e.push(Ge(g,"subtask-merged"));break;case"pr.state.changed":{if(!p.change)break;if(p.change==="opened"){for(let T of A.values())T.status="stale";A.clear()}let S=Ge(g,`pr-${p.change}`);if(Jv.has(p.change)&&p.number!=null){let T=Xv(w,y,p);if(w.size>=2){for(let R of y.values())for(let M of R)M.grouped=!0;S.groupPrs=[...w.values()];let J=[S];for(let R of w.keys())y.set(R,J)}else{let J=y.get(T)??[];J.push(S),y.set(T,J)}}else if(p.change==="merged"||p.change==="closed"||p.change==="lost"){let T=ph(p),J=b&&!b.complete||w.size>=2;if(w.has(T))w.delete(T),y.delete(T);else if(p.number!=null)for(let R of[...w.keys()])R.endsWith(`#${p.number}`)&&(w.delete(R),y.delete(R));if(J&&p.number!=null){(!b||b.complete)&&(b={id:g.id,kind:"event",type:"pr-group-settled",prs:[],remaining:[],complete:!1,at:ot(g),ts:ot(g),box:g.boxId,agent:g.provider},e.push(b)),b.prs.push({number:p.number,url:p.url??null,title:p.title??null,repo:p.repo??null,branch:p.branch??null,change:p.change,ts:ot(g)}),b.remaining=[...w.values()],b.complete=w.size===0;break}}e.push(S);break}case"pr.offered":{if(!p.offerId)break;for(let T of A.values())T.status="stale";A.clear();let S={id:g.id,kind:"pr-offer",offerId:p.offerId,excerpt:p.excerpt||null,branch:p.branch||null,base:p.base||null,offered:Array.isArray(p.offered)&&p.offered.length?p.offered:null,toPr:p.toPr&&typeof p.toPr=="object"?{number:p.toPr.number??null}:null,repos:Array.isArray(p.repos)&&p.repos.length?p.repos:null,status:"open",ts:ot(g),box:g.boxId,agent:g.provider};A.set(p.offerId,S),_.set(p.offerId,S),e.push(S);break}case"pr.offer.answered":{let S=_.get(p.offerId);S&&(S.status=p.action||"dismiss",S.actions=Array.isArray(p.actions)?p.actions:null,S.notify=p.notify===!0,A.delete(p.offerId));break}case"box.state.changed":{let S=p.change==="model"?"model-change":p.change;if(!S)break;let T=Ge(g,S);S==="model-change"&&(T.to=p.model),e.push(T);break}case"view.state.changed":{if(p.change!=="created"||p.restarted)break;e.push({id:g.id,kind:"view-card",viewId:p.viewId,name:p.name,slug:p.slug,port:p.port,origin:p.origin,ts:ot(g),box:g.boxId,agent:g.provider});break}case"view.spinup.failed":{e.push({id:g.id,kind:"view-spinup-failed",name:p.name||null,reason:p.reason||"no-view",ts:ot(g),box:g.boxId,agent:g.provider});break}case"mock.state.changed":{if(p.change==="picked"){e.push({id:g.id,kind:"mock-picked",mockId:p.mockId,name:p.name,group:p.group??null,version:p.version??null,siblings:p.siblings??[],ts:ot(g),box:g.boxId,agent:g.provider});break}if(p.change!=="created")break;e.push({id:g.id,kind:"mock-card",mockId:p.mockId,name:p.name,group:p.group??null,ts:ot(g),box:g.boxId,agent:g.provider});break}case"runbook.state.changed":{if(p.change!=="created")break;e.push({id:g.id,kind:"runbook-card",runbookId:p.runbookId,name:p.name,ts:ot(g),box:g.boxId,agent:g.provider});break}case"ticket.linked":{e.push({id:g.id,kind:"ticket-card",provider:p.provider||"linear",key:p.key,url:p.url,title:p.title??null,linkSource:p.linkSource??null,ts:ot(g),box:g.boxId,agent:g.provider});break}case"ticket.move.offered":{if(!p.offerId)break;for(let T of C.values())T.key===p.key&&(T.status="stale",C.delete(T.offerId));let S={id:g.id,kind:"ticket-offer",offerId:p.offerId,provider:p.provider||"linear",key:p.key,url:p.url??null,phase:p.phase??null,from:p.from??null,to:p.to?.name??null,prNumber:p.prNumber??null,status:"open",ts:ot(g),box:g.boxId,agent:g.provider};C.set(p.offerId,S),K.set(p.offerId,S),e.push(S);break}case"ticket.move.answered":{let S=K.get(p.offerId);S&&(S.status=p.action==="moved"?"moved":"dismiss",C.delete(p.offerId));break}case"ticket.state.changed":{for(let S of C.values())S.key===p.key&&S.to===p.to&&(S.status="stale",C.delete(S.offerId));if(p.mode==="offer")break;e.push(Ge(g,"ticket-state"));break}case"ticket.commented":{e.push({id:g.id,kind:"ticket-comment",key:p.key,url:p.url??null,excerpt:p.excerpt??null,ts:ot(g),box:g.boxId,agent:g.provider});break}case"ticket.flow.changed":e.push(Ge(g,"ticket-flow"));break;case"ticket.create.offered":{if(!p.proposalId)break;for(let T of I.values())T.status="stale";I.clear();let S={id:g.id,kind:"ticket-create-offer",proposalId:p.proposalId,provider:p.provider||"linear",epic:p.epic??null,issues:Array.isArray(p.issues)?p.issues:[],team:p.team??null,needsTeam:!!p.needsTeam,linkCurrentTrack:!!p.linkCurrentTrack,status:"open",ts:ot(g),box:g.boxId,agent:g.provider};I.set(p.proposalId,S),L.set(p.proposalId,S),e.push(S);break}case"ticket.create.answered":{let S=L.get(p.proposalId);S&&(S.status=p.action==="created"?"created":"dismiss",S.createdCount=p.createdCount??null,S.failedCount=p.failedCount??null,I.delete(p.proposalId));break}case"ticket.created":{let S={items:Array.isArray(p.items)?p.items:[],failed:Array.isArray(p.failed)?p.failed:[],team:p.team??null},T=p.proposalId?L.get(p.proposalId):null;if(T){T.created=S;break}e.push({id:g.id,kind:"ticket-created",...S,ts:ot(g),box:g.boxId,agent:g.provider});break}case"error":{if(!p.kind)break;e.push(Ge(g,p.kind));break}case"provider.connected":{e.push({id:g.id,kind:"event",type:"provider-connected",provider:p.provider||g.provider,ts:ot(g),at:ot(g),box:g.boxId,agent:g.provider});break}case"prompt.answered":{if(p.control||!p.question)break;let S=p.groupId||p.promptId;if(!S)break;let T=v.get(S);T||(T={id:g.id,kind:"prompt",groupId:S,provider:g.provider,box:g.boxId,agent:g.provider,questionTotal:p.questionTotal??1,questions:[],ts:ot(g)},v.set(S,T));let J=p.questionIndex??0,R={index:J,header:p.header||"",question:p.question,multiSelect:!!p.multiSelect,chosen:Array.isArray(p.chosen)?p.chosen:[]},M=T.questions.findIndex(P=>P.index===J);M>=0?T.questions[M]=R:T.questions.push(R),T.questions.sort((P,et)=>P.index-et.index),T.questionTotal=Math.max(T.questionTotal,p.questionTotal??1),new Set(T.questions.map(P=>P.index)).size>=T.questionTotal&&(e.push(T),v.delete(S));break}default:break}}return Zv(e.sort((g,p)=>(g.ts??0)-(p.ts??0)))}var Yv=2*6e4;function Zv(t){let e=new Map,n=[];for(let r of t){if(r.kind!=="message"||!r.box){n.push(r);continue}let o=r.role==="assistant"&&!String(r.text||"").trim()&&r.images?.length,s=e.get(r.box);if(o&&s&&(r.ts??0)-s.lastTs<=Yv){s.item.images.push(...r.images),s.lastTs=r.ts??s.lastTs;continue}if(o){let a={...r,images:[...r.images]};e.set(r.box,{item:a,lastTs:r.ts??0}),n.push(a)}else e.delete(r.box),n.push(r)}return n}function Qc(t,e=null){let n=[];for(let o of t||[]){if(o.type!=="tool.called"||o.source!=="transcript")continue;let s=o.payload||{};String(s.command||"").trim()&&n.push({id:s.id??null,tool:s.tool||null,command:s.command,description:s.description||"",summary:Qv(s),ts:s.ts??o.ts,n:s.n,box:o.boxId,agent:o.provider,boxRole:s.boxRole})}if(!n.length)return[];let r=(e??ra(t)).filter(o=>o.kind==="message");return Rp(n,r)}function Qv(t){let e=String(t.summary||"").trim();return e&&e!==t.command&&e!==rr(t.command)?e:De(t.command,t.description||"")}function Ge(t,e){let{ts:n,change:r,...o}=t.payload||{};return{id:t.id,kind:"event",type:e,...o,at:ot(t),ts:ot(t)}}function tA(t){let e=[],n=null;for(let r of String(t||"").split(`
|
|
83
|
+
`)){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 tl=new Map;async function eA(t,e){let n=`${t.id}:${e.name}`;if(!tl.has(n)){let r=await F(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);tl.set(n,o)}return tl.get(n)}async function mh(t,e){try{if(e?.agent!=="codex"||!e.cwd)return;let n=await gn(e.cwd,e.pinned?e.sessionId:null);if(!n)return;let r=await eA(t,e),o=tA(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 O(t,{type:"cost.recorded",actor:"agent",source:"transcript",boxId:e.name,provider:"codex",payload:{usd:Xt(c,i),model:c,tokens:i,reasoningOutput:a.last.reasoningOutput||void 0,codexTs:a.ts,ts:a.ts}}),a===s&&a.contextWindow&&await O(t,{type:"context.usage",actor:"agent",source:"transcript",boxId:e.name,provider:"codex",payload:{occupancy:a.occupancy,window:a.contextWindow,pct:a.occupancy!=null?a.occupancy/a.contextWindow:null,model:a.model||null,ts:a.ts}})}}catch{}}import oa from"node:fs";import nl from"node:path";import nA from"node:os";var rA=process.env.BOSTRAT_STATE_DIR||nl.join(process.env.HOME||nA.tmpdir(),".bostrat"),wh=(t=process.env)=>!!t.BOSTRAT_CURSOR_USAGE_COOKIE,oA="https://cursor.com/api/dashboard/get-filtered-usage-events",hh=100;async function sA({sinceMs:t,cookie:e,fetchImpl:n=fetch}){let r=[];for(let o=1;o<=20;o++){let s=await n(oA,{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:hh})});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<hh)break}return r}function aA(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 iA=5*6e4,Ke={at:0,rows:null,error:null},sa=()=>nl.join(rA,"cursor-usage.json");function gh(){try{return JSON.parse(oa.readFileSync(sa(),"utf8"))}catch{return{sinceMs:0,seen:[]}}}function cA(t){try{oa.mkdirSync(nl.dirname(sa()),{recursive:!0});let e=sa()+".tmp";oa.writeFileSync(e,JSON.stringify(t)),oa.renameSync(e,sa())}catch{}}var lA=5e3;async function uA({fetchImpl:t,now:e=Date.now()}={}){if(Ke.rows&&e-Ke.at<iA)return Ke.rows;let n=process.env.BOSTRAT_CURSOR_USAGE_COOKIE;if(!n)return null;let r=gh(),o=Math.max(r.sinceMs-6e4,e-24*36e5);try{let s=await sA({sinceMs:o,cookie:n,fetchImpl:t});Ke={at:e,rows:s.map(aA).filter(Boolean),error:null}}catch(s){Ke={at:e,rows:null,error:String(s?.message||s)}}return Ke.rows}var rl=()=>({enabled:wh(),lastPollAt:Ke.at||null,error:Ke.error}),el=new Map;async function dA(t,e){let n=`${t.id}:${e.name}`;if(!el.has(n)){let r=await F(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);el.set(n,o)}return el.get(n)}async function yh(t,e,{fetchImpl:n}={}){try{if(e?.agent!=="cursor"||!e.cwd||!wh())return;let r=await uA({fetchImpl:n});if(!r||!r.length)return;let o=await jp(e.cwd,e.pinned?e.sessionId:null);if(!o?.chatId)return;let s=await dA(t,e),a=gh(),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=Xt(u,f)),!d&&l.chargedCents!=null&&(d=l.chargedCents/100),await O(t,{type:"cost.recorded",actor:"agent",source:"vendor",boxId:e.name,provider:"cursor",payload:{usd:d,model:u,...f?{tokens:f}:{},...l.vendorCents!=null?{vendorCents:l.vendorCents}:{},...l.chargedCents!=null?{chargedCents:l.chargedCents}:{},...l.kind?{kind:l.kind}:{},conversationId:l.conversationId,cursorEventId:l.id,ts:l.ts??void 0}})}cA({sinceMs:c,seen:[...i].slice(-lA)})}catch{}}import{execFile as wA}from"node:child_process";import{promisify as gA}from"node:util";import bh from"node:fs/promises";import kh from"node:path";import fA from"node:os";var xh=process.env.BOSTRAT_STATE_DIR||kh.join(process.env.HOME||fA.tmpdir(),".bostrat"),pA=kh.join(xh,"github-latency.ndjson"),mA=100,aa=[],ol=new Map;function ia({change:t,repo:e=null,number:n=null,githubAt:r=null,observedAt:o=Date.now(),lane:s=null,intervalMs:a=null,source:i=null}){let c=r?o-r:null,l={change:t,repo:e,number:n,githubAt:r,observedAt:o,latencyMs:c,lane:s,intervalMs:a,source:i};aa.push(l),aa.length>mA&&aa.shift();let u=ol.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)),ol.set(t,u),hA(l),l}async function hA(t){try{await bh.mkdir(xh,{recursive:!0}),await bh.appendFile(pA,JSON.stringify(t)+`
|
|
84
|
+
`)}catch{}}function Sh(){let t={};for(let[e,n]of ol)t[e]={count:n.count,measured:n.measured,avgMs:n.measured?Math.round(n.totalMs/n.measured):null,maxMs:n.measured?n.maxMs:null};return{byChange:t,recent:aa.slice(-20)}}var ua=gA(wA),yA={timeout:3e4,maxBuffer:8*1024*1024};async function Ht(t,e,n={}){let{caller:r="prs",...o}=n,s=await ae({cwd:t});ke(r);try{let{stdout:a}=await ua("gh",e,{cwd:t,...yA,...o,env:{...process.env,...s,...o.env||{}}});return String(a||"")}catch(a){throw Fe(a)&&Ue(a),a}}var ca=null;async function il(){if(ca&&Date.now()-ca.at<6e4)return ca.data;let t;try{ke("auth");let e=await ua("gh",["auth","status"],{timeout:1e4});t={installed:!0,authenticated:!0,login:`${e.stdout||""}${e.stderr||""}`.match(/account\s+(\S+)/)?.[1]??null}}catch(e){t=e?.code==="ENOENT"?{installed:!1,authenticated:!1,login:null}:{installed:!0,authenticated:!1,login:null}}if(t.installed&&!t.authenticated)try{let e=await no();e.configured&&(t={installed:!0,authenticated:!0,login:e.username,via:"broker"})}catch{}return ca={at:Date.now(),data:t},t}async function bA(t){try{let{stdout:e}=await ua("git",["-C",t,"config","--get","remote.origin.url"],{timeout:5e3});return/github\.com/.test(e)}catch{return!1}}function Ah(t){let e=t.name||t.context||"check",n=t.detailsUrl||t.targetUrl||null,r=t.completedAt||t.createdAt||null,o;return t.__typename==="StatusContext"?o=t.state==="SUCCESS"?"pass":t.state==="PENDING"||t.state==="EXPECTED"?"pending":"fail":t.status&&t.status!=="COMPLETED"?o="pending":["SUCCESS","NEUTRAL","SKIPPED"].includes(t.conclusion)?o="pass":o="fail",{name:e,state:o,url:n,completedAt:r}}function sl(t){let e=(t||[]).map(Ah),n=e.filter(s=>s.state==="pending").length,r=e.map(s=>Date.parse(s.completedAt||"")).filter(s=>s>0),o=e.length&&!n&&r.length?Math.max(...r):null;return{total:e.length,passed:e.filter(s=>s.state==="pass").length,failed:e.filter(s=>s.state==="fail").length,pending:n,failing:e.filter(s=>s.state==="fail").map(({name:s,url:a})=>({name:s,url:a})),settledAt:o}}var cl="number,title,url,headRefName,headRefOid,baseRefName,isDraft,author,reviewDecision,mergeable,mergeStateStatus,statusCheckRollup,autoMergeRequest,additions,deletions,createdAt,updatedAt",Th=cl.replace("statusCheckRollup,","");function ll(t){return/Resource not accessible by personal access token/.test(String(t?.stderr||t?.message||""))}async function _h(t,e){if(!e)return null;let n=await Z(t);if(!n)return null;try{let r=await Ht(t,["api",`repos/${n}/actions/runs?head_sha=${encodeURIComponent(e)}&per_page=100`,"--jq",".workflow_runs"]),o=new Map;for(let s of JSON.parse(r||"[]"))o.has(s.name)||o.set(s.name,s);return[...o.values()]}catch{return null}}function Eh(t){return(t||[]).map(e=>({name:e.name,detailsUrl:e.html_url||null,status:String(e.status||"").toUpperCase(),conclusion:e.conclusion?String(e.conclusion).toUpperCase():null,completedAt:e.status==="completed"&&e.updated_at||null}))}async function Ih(t,e){let n=await _h(t,e);return n===null?{...sl([]),unavailable:!0}:sl(Eh(n))}function la(t,e=sl(t.statusCheckRollup)){return{number:t.number,title:t.title,url:t.url,author:t.author?.login??null,headRefName:t.headRefName,headSha:t.headRefOid??null,baseRefName:t.baseRefName,isDraft:!!t.isDraft,reviewDecision:t.reviewDecision||null,mergeable:t.mergeable||"UNKNOWN",mergeStateStatus:t.mergeStateStatus||"UNKNOWN",additions:t.additions??0,deletions:t.deletions??0,createdAt:t.createdAt,updatedAt:t.updatedAt,checks:e,green:e.failed===0&&e.pending===0&&t.mergeStateStatus==="CLEAN",autoMergeArmed:!!t.autoMergeRequest,mergeabilityPending:!1}}var kA=9e4,fr=new Map;function xA(t,e,n){let r=new Set;for(let o of e){let s=`${t}#${o.number}`;if(o.mergeable!=="UNKNOWN"&&o.mergeStateStatus!=="UNKNOWN"){fr.delete(s);continue}r.add(s),fr.has(s)||fr.set(s,n),o.mergeabilityPending=n-fr.get(s)<kA}for(let o of fr.keys())o.startsWith(`${t}#`)&&!r.has(o)&&fr.delete(o);return e}async function pr(t,e){let n=await xe(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");try{let o=await Ht(n.path,["pr","view",String(r),"--json",cl]);return la(JSON.parse(o))}catch(o){if(!ll(o))throw o;let s=await Ht(n.path,["pr","view",String(r),"--json",Th]),a=JSON.parse(s);return la(a,await Ih(n.path,a.headRefOid))}}var vh=(t,e)=>t.number-e.number;async function fo(t,{caller:e="prs"}={}){try{let n=await Ht(t,["pr","list","--json",cl,"--limit","50"],{caller:e});return JSON.parse(n||"[]").map(r=>la(r)).sort(vh)}catch(n){if(!ll(n))throw n;let r=await Ht(t,["pr","list","--json",Th,"--limit","50"],{caller:e});return(await Promise.all(JSON.parse(r||"[]").map(async o=>la(o,await Ih(t,o.headRefOid))))).sort(vh)}}var SA=3e4,vA=1500,uo=new Map;function ce(){uo.clear()}async function AA(t,e){if(!t)return e;for(let n of e){let r=await Tt(t,n.number).catch(()=>null);if(!r)continue;r.trackId&&(n.trackId=r.trackId),r.autoMerge&&(n.autoMergeArmed=!0),r.archiveOnMerge&&(n.archiveOnMerge=!0),r.notifyOnMerge&&(n.notifyOnMerge=!0);let o=s=>n.headSha&&s.headSha&&n.headSha!==s.headSha;r.ciFix&&(o(r.ciFix)?await Ms(t,n.number,{headSha:n.headSha}).catch(()=>{}):n.ciFix={delegatedAt:r.ciFix.delegatedAt,trackId:r.ciFix.trackId??null}),r.reconcile&&(o(r.reconcile)?await Ps(t,n.number,{headSha:n.headSha}).catch(()=>{}):n.reconcile={delegatedAt:r.reconcile.delegatedAt,trackId:r.reconcile.trackId??null})}return e}async function Rh(){let t=await il();if(!t.authenticated)return{auth:t,repos:[]};let e=[];for(let a of await ye())await bA(a.path)&&e.push(a);let n=Date.now(),r=Qs(n)>0,o=await Promise.all(e.map(async a=>{let i=uo.get(a.path),c=i?.pending?vA:SA;if(i&&(r||n-i.at<c))return i.entry;try{let l=await Z(a.path).catch(()=>null),u=xA(a.path,await fo(a.path,{caller:"tab"}),n);if(await AA(l,u),i?.entry?.prs)for(let m of u)i.entry.prs.find(w=>w.number===m.number)?.checks?.pending>0&&m.checks.total>0&&m.checks.pending===0&&ia({change:m.checks.failed>0?"checks-failed":"checks-green",repo:l||a.name,number:m.number,githubAt:m.checks.settledAt,source:"tab"});let f={name:a.name,path:a.path,prs:u},d=u.some(m=>m.mergeabilityPending||m.checks.pending>0||m.autoMergeArmed);return uo.set(a.path,{at:Date.now(),entry:f,pending:d}),f}catch(l){return i?i.entry:{name:a.name,path:a.path,prs:[],error:ie(l)}}})),s=new Set(e.map(a=>a.path));for(let a of uo.keys())s.has(a)||uo.delete(a);return{auth:t,repos:o}}async function xe(t){let e=(await ye()).find(n=>n.path===t);if(!e)throw new Error(`unknown repo: ${t}`);return e}async function An(t){return to(t)}var po=new Set(["squash","merge","rebase"]);async function mr(t,e,{method:n="squash"}={}){let r=await xe(t);if(!po.has(n))throw new Error(`invalid merge method: ${n}`);let o=Number(e);if(!Number.isInteger(o)||o<=0)throw new Error("invalid PR number");let s=["pr","merge",String(o),`--${n}`];if(n!=="rebase"&&(await ae({cwd:r.path})).GH_TOKEN){let a=await sc().catch(()=>null);a&&s.push("--body",`Merged by bostrat on behalf of @${a}`)}try{await Ht(r.path,s)}catch(a){throw new Error(ie(a))}finally{ce()}return{ok:!0,number:o}}async function mo(t,e){let n=await xe(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");try{await Ht(n.path,["api","-X","PUT",`repos/{owner}/{repo}/pulls/${r}/update-branch`])}catch(s){throw new Error(ie(s))}finally{ce()}let o=await Z(n.path).catch(()=>null);return o&&await np(o,r).catch(()=>{}),{ok:!0,number:r}}async function ul(t,e){let n=await xe(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");try{await Ht(n.path,["pr","merge",String(r),"--disable-auto"])}catch(o){throw new Error(ie(o))}finally{ce()}return{ok:!0,number:r}}var TA=2500,_A=9e4,EA=t=>new Promise(e=>setTimeout(e,t));function dl(t){return t.green&&!t.isDraft}function IA(t){return t.isDraft?"draft":t.mergeable==="CONFLICTING"?`conflicts with ${t.baseRefName}`:t.checks.failed>0?"checks failing":t.checks.pending>0?"checks running":t.mergeable==="UNKNOWN"||t.mergeStateStatus==="UNKNOWN"?"mergeability unresolved":t.mergeStateStatus==="BEHIND"?`behind ${t.baseRefName}`:t.mergeStateStatus==="BLOCKED"?"blocked by branch protection":`not clean (${t.mergeStateStatus})`}function $h(t,{update:e=!0}={}){return t.isDraft?{action:"skip",reason:"draft"}:dl(t)?{action:"merge"}:e&&t.mergeStateStatus==="BEHIND"&&t.mergeable!=="CONFLICTING"&&t.checks.failed===0?{action:"update-arm"}:{action:"arm"}}function Oh(t){return[...t.filter(e=>e.plan.action!=="merge"),...t.filter(e=>e.plan.action==="merge")]}var al=new Map;function Ch(){let t={};for(let[e,n]of al)t[e]={...n};return t}async function Mh(t,{method:e="merge",onMerged:n=null}={}){let r=await xe(t);if(!po.has(e))throw new Error(`invalid merge method: ${e}`);if(al.get(r.path)?.running)throw new Error("a merge-all is already running for this repo");let o=(await fo(r.path)).filter(dl).sort((a,i)=>Date.parse(a.createdAt)-Date.parse(i.createdAt));if(!o.length)throw new Error("no PRs ready to merge");let s={running:!0,startedAt:Date.now(),finishedAt:null,method:e,total:o.length,current:o[0].number,queue:o.map(a=>({number:a.number,title:a.title})),merged:[],skipped:[],failed:[]};return al.set(r.path,s),$A(s,o,{listPrs:()=>fo(r.path),merge:a=>mr(r.path,a,{method:e}),sleep:EA,onMerged:n}).catch(()=>{}),{ok:!0,repo:r.path,total:s.total,queue:o.map(a=>a.number)}}async function RA(t,e){let n=Date.now()+(e.settleMaxMs??_A),r=null;for(;;){let o=null;try{if(o=await e.listPrs(),r=null,!o.some(a=>t.includes(a.number)&&(a.mergeable==="UNKNOWN"||a.mergeStateStatus==="UNKNOWN")))return o}catch(s){r=s}if(Date.now()>=n){if(o)return o;if(r)throw r;return[]}await e.sleep(TA)}}async function $A(t,e,n){try{let r=e;for(let o=0;o<e.length;o++){let s=e[o];if(t.current=s.number,o>0)try{r=await RA(e.slice(o).map(i=>i.number),n)}catch(i){for(let c of e.slice(o))t.failed.push({number:c.number,title:c.title,reason:`couldn't re-check PR state: ${ie(i)}`});break}let a=r.find(i=>i.number===s.number);if(!a){t.skipped.push({number:s.number,title:s.title,reason:"no longer open"});continue}if(!dl(a)){t.skipped.push({number:a.number,title:a.title,reason:IA(a)});continue}try{await n.merge(a.number),t.merged.push({number:a.number,title:a.title});try{await n.onMerged?.(a)}catch{}}catch(i){t.failed.push({number:a.number,title:a.title,reason:ie(i)})}}}finally{t.current=null,t.running=!1,t.finishedAt=Date.now(),ce()}}async function fl(t,{title:e,body:n="",base:r=null}={}){if(typeof e!="string"||!e.trim())throw new Error("title required");try{await ua("git",["-C",t,"push","-u","origin","HEAD"],{timeout:12e4,maxBuffer:4*1024*1024})}catch(l){throw new Error(`push failed: ${ie(l)}`)}let o=n||"";if((await ae({cwd:t})).GH_TOKEN){let l=await sc().catch(()=>null);l&&(o=`${o?`${o}
|
|
85
|
+
|
|
86
|
+
`:""}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(ie(l))}finally{ce()}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 Nh(t,e){let n=await xe(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(ie(c))}o=JSON.parse(i||"[]").filter(c=>c.bucket==="fail")}catch(i){if(!ll(i))throw i;let c=await Ht(n.path,["pr","view",String(r),"--json","headRefOid"]),l=await _h(n.path,JSON.parse(c).headRefOid);if(l===null)throw new Error(ie(i));o=Eh(l).map(Ah).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=OA(i.link),l=null;c&&!s.has(c)&&(s.add(c),l=await Ht(n.path,["run","view",c,"--log-failed"]).then(u=>CA(u)).catch(()=>null)),a.push({name:i.name,link:i.link||null,log:l})}return a}function OA(t){return String(t||"").match(/\/actions\/runs\/(\d+)/)?.[1]??null}function CA(t,{maxLines:e=400,maxBytes:n=64*1024}={}){let r=String(t||""),o=r.split(`
|
|
85
87
|
`);return o.length>e&&(r=`\u2026 (${o.length-e} earlier lines truncated)
|
|
86
88
|
`+o.slice(-e).join(`
|
|
87
89
|
`)),Buffer.byteLength(r)>n&&(r=`\u2026 (truncated)
|
|
88
|
-
`+r.slice(-n)),r}function
|
|
89
|
-
`).find(n=>n.trim())||"gh failed"}var
|
|
90
|
+
`+r.slice(-n)),r}function ie(t){return String(t?.stderr||t?.message||t||"").trim().split(`
|
|
91
|
+
`).find(n=>n.trim())||"gh failed"}var MA=6e3,da=new Map;function pl(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 Ph(t){let e=t?.workspace?.branch;if(!e)return;let n;try{n=await F(t)}catch{return}if(pl(n)==null)return;let r=(t.boxes||[]).find(c=>c.name===t.leadBox);if(await ta(t.leadBox,r?.agent).catch(()=>!1))return;let o=Date.now();if(o-(da.get(t.id)||0)<MA)return;da.set(t.id,o);let s;try{s=await fo(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 Bt({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 Yr(i,a.number))){da.delete(t.id);return}}await O(t,{type:"pr.state.changed",actor:"agent",source:"orchestrator",boxId:t.leadBox,payload:{ts:Date.now(),change:"opened",number:a.number,url:a.url,title:a.title,branch:e,...i?{repo:i}:{}}}).catch(()=>{}),da.delete(t.id)}import ml from"node:fs/promises";import ho from"node:path";import{randomUUID as NA}from"node:crypto";var Dh=".bostrat/uploads",hl=t=>ho.join(t,Dh),Bh=Number(process.env.BOSTRAT_MAX_UPLOAD_BYTES)||24*1024*1024,PA=new Set(["png","jpg","jpeg","gif","webp","svg","bmp","tif","tiff","heic","heif","avif","ico"]),BA=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 wl(t){let e=String(t||"").toLowerCase().replace(/^\./,"");return PA.has(e)?"image":BA.has(e)?"doc":null}function DA(t){let r=ho.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 jh(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>Bh)throw new Error(`file too large \u2014 max ${Math.floor(Bh/(1024*1024))} MB`);let r=DA(e),o=ho.extname(r).slice(1),s=wl(o);if(!s)throw new Error(`unsupported file type "${o||"unknown"}" \u2014 attach an image or a document`);let a=NA().slice(0,8),i=hl(t);await ml.mkdir(i,{recursive:!0});let c=`${a}-${r}`;return await ml.writeFile(ho.join(i,c),n,{mode:384}),await ml.chmod(ho.join(i,c),384).catch(()=>{}),{id:a,name:r,rel:`${Dh}/${c}`,kind:s,size:n.length}}function Lh(t){let e=t.map(r=>`\`${r}\``).join(", ");return`[Attached \u2014 please read ${t.length===1?"this file":`these ${t.length} files`}: ${e}]`}var jA=/\[Attached — please read (?:this file|these \d+ files): [^\]]*\]$/;function gl(t){return t.role==="user"&&jA.test((t.text||"").trim())}function hr(t,e,n=Date.now()){return e?t.idleSince==null?(t.idleSince=n,!1):n-t.idleSince>=6500:(t.idleSince=null,!1)}var UA=FA(LA),Ve=process.env.BOSTRAT_STATE_DIR||H.join(process.env.HOME||"/tmp",".bostrat"),Vh=H.join(Ve,"tracks.json"),HA=H.join(Ve,"events.jsonl"),vl=H.join(Ve,"workspaces"),WA=process.env.BOSTRAT_DEFAULT_REPO||null,ma={claude:{envVar:"BOSTRAT_AGENT_CMD",base:"claude",skipFlag:"--dangerously-skip-permissions"},cursor:{envVar:"BOSTRAT_CURSOR_CMD",base:"cursor-agent",skipFlag:"--force"},codex:{envVar:"BOSTRAT_CODEX_CMD",base:"codex",skipFlag:"--dangerously-bypass-approvals-and-sandbox"}},ha=Object.keys(ma);function qA(t,{skipPermissions:e=!0}={}){let n=ma[t];if(!n)throw new Error(`unknown agent "${t}"`);let r=process.env[n.envVar];return r||(e&&n.skipFlag?`${n.base} ${n.skipFlag}`:n.base)}function zh(t,e,{skipPermissions:n=!0}={}){let r=ma[t];if(!r)throw new Error(`unknown agent "${t}"`);let o=process.env[r.envVar];if(o)return o;let s=n&&r.skipFlag?` ${r.skipFlag}`:"";switch(t){case"claude":return`${r.base} --resume ${e}${s}`;case"cursor":return`${r.base} --continue${s}`;case"codex":return`${r.base} resume --last${s}`;default:return`${r.base}${s}`}}function vo(t){let e=ma[t];if(e)return process.env[e.envVar]||e.base}var Tn={at:0,value:null};async function Tl({maxAgeMs:t=6e4}={}){if(Tn.value&&Date.now()-Tn.at<t)return Tn.value;let e=await Promise.all(ha.map(async n=>[n,await Xo(vo(n))]));return Tn.value=Object.fromEntries(e),Tn.at=Date.now(),Tn.value}async function wa(t,e=!0){let n=qA(t,{skipPermissions:e});if(!await Xo(n)){Tn.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.":Ya[r]?`Install it on this node: ${Ya[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 ga=".bostrat/handoff/latest.md";function GA(t,e=ga){return`${t.trim()}
|
|
90
92
|
|
|
91
|
-
(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
|
|
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 KA=/\(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 Fh(t){return t.role==="user"&&KA.test((t.text||"").trim())}var fa=".bostrat/handoff/subtask-result.md",go=".bostrat/handoff/lead-context.md",VA=(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 ${go} \u2014 read that first for context. Your scoped task:
|
|
92
94
|
|
|
93
95
|
${t}
|
|
94
96
|
|
|
95
|
-
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 ${
|
|
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 ${fa} (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 ${go} \u2014 read that first for context. Your scoped task:
|
|
96
98
|
|
|
97
99
|
${t}
|
|
98
100
|
|
|
99
|
-
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 ${
|
|
100
|
-
`;return Vc=Vc.then(()=>j.mkdir(qe,{recursive:!0})).then(()=>j.appendFile(gv,n)).catch(()=>{}),Vc}var ot=(t,e,n={})=>{let r={at:Date.now(),type:e,...n};return t.events.push(r),Av(t.id,r),Hp(t,r),r};du(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 J().catch(()=>{}))});var Jc=(t,e,n)=>e!=null&&n.some(r=>t===r.box&&e>=r.from&&e<=r.to),Tv=new Set(["stuck","wrong-approach","too-slow","want-different-model","context-refresh","cost","parallelize","other"]);function fh(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=>Tv.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 ph(t,e,n,{kind:r}={}){let o=await z(e);if(!o)throw new Error(`box "${e}" is not running`);let s=Vs(o.agent);await Ae({cwd:o.cwd,sessionId:o.sessionId,sessionName:e,agent:o.agent,agentCmd:s}),await kt(e,n,{...ce(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 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 J()}var An=new Set(["merged","conflict","canceled","failed","handed-off"]),oh=new Map;async function _v(t){let e=L.join(qo(t),`${t.id}.ndjson`),n;try{n=await j.stat(e)}catch{return null}let r=oh.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 oh.set(t.id,{mtimeMs:n.mtimeMs,at:o}),o}async function Ev(t,e){let n=e.find(r=>r.name===t.leadBox);return n?.state!=="live"?{working:!1,prompted:!1}:Sc(n.name,n.agent)}async function Ge(t){let e=new Map((await Pn()).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([_v(t),Ev(t,n)]);return{...t,boxes:n,lastResponseAt:r,working:o,prompted:s}}var Iv=5e3;function Rv(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-Iv,f=r[u+1]?.createdAt??1/0,p=o.find(h=>!s.has(h.id)&&h.birthMs>=d&&h.birthMs<f);p&&(s.add(p.id),c.push({box:l.name,sessionId:p.id}))}return c}async function mh(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=su(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=Rv(u,a,f=>i.has(f));for(let f of d){let p=u.find(h=>h.name===f.box);p&&(p.sessionId=f.sessionId),await zl(f.box,f.sessionId),n=!0}}return n}async function ft(){let t=await H();return await mh(t)&&await J(),Promise.all(t.tracks.map(Ge))}async function hh(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 nt(t){let e=await H(),n=Z(e,t);return n?(await mh(e)&&await J(),Ge(n)):null}var $v={claude:[/^\[Request interrupted by user(?: for tool use)?\]$/i],codex:[/^\[?Request interrupted/i,/^Conversation interrupted/i],cursor:[/^\[?Request interrupted/i]};function sh(t){if(t.role!=="user")return!1;let e=(t.text||"").trim();return($v[t.agent]||[]).some(n=>n.test(e))}var Ov=[/^\/model(?:\s|$)/i,/<command-(?:name|message|args)>[^<]*\bmodel\b/i,/^Model changed to\b/i];function ah(t){let e=(t.text||"").trim();return e?Ov.some(n=>n.test(e)):!1}var Cv=10*6e4;async function tl(t,e={}){let n=await nt(t);if(!n)return null;let r=new Set([...n.events.filter(x=>x.type==="orchestration-prompt"&&x.prompt).map(x=>String(x.prompt).trim()),...n.events.filter(x=>x.type==="said"&&x.text).map(x=>String(x.text).trim())]),o=po(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=Qa(n.id),c=!!(i&&!i.finishedAt),l=c&&i.startedAt&&i.box?[{box:i.box,from:i.startedAt,to:Date.now()+1e12}]:[],u=[...fd(n),...l],f={windows:[..._u(n),...Eu(n,Ho),...a,...u]},h=(await Promise.all(n.boxes.map(x=>rp(x,e)))).flat(),m=h.map(x=>{let q={kind:"message",...x};if(q.role==="assistant"&&(q.text||"").trim()&&q.tools?.length){let En=Zr(q.agent).cliTools,Io=q.tools.filter(Ze=>!En.has(Ze));return{...q,tools:Io.length?Io:void 0}}return q}).map(x=>{if(x.role!=="user")return x;let q=Ms(x.text);return q?q.kind==="suppress"?null:{...x,note:q,text:_m(q)}:x}).filter(Boolean).filter(x=>!(x.role==="user"&&r.has((x.text||"").trim()))).filter(x=>!sh(x)).filter(x=>!ah(x)).filter(x=>!rh(x)).filter(x=>!Gc(x)).filter(x=>x.role!=="assistant"||!!(x.text||"").trim()||!!x.images?.length).filter(x=>!Iu(x,f)),b=m.filter(x=>x.failed&&x.text===Ji);if(b.length){let x=new Map;for(let q of b){if(!x.has(q.box)){let Ze=[...(await ht(q.box,{lines:At}).catch(()=>"")).matchAll(/^\s*■\s+(.+?)\s*$/gm)].map(ga=>ga[1]);x.set(q.box,Ze.length?Ze[Ze.length-1]:null)}let En=x.get(q.box);En&&(q.text=`\u25A0 ${En}`)}}let k=await Promise.all(n.boxes.map(x=>sp(x,e)));await Am(n,k.flat()),await Sm(n,m),await Wp(n),await yd(n,m).catch(()=>{});for(let x of n.boxes)x.agent==="codex"&&x.state==="live"&&await Rm(n,x),x.agent==="cursor"&&x.state==="live"&&await Pm(n,x);let v=await Promise.all(n.boxes.map(x=>op(x,e)));await vm(n,v.flat().filter(x=>!Jc(x.box,x.ts,u)));let E=await F(n),M=c&&i.viewId?i.viewId:null,lt=tm(E),I=Ps(E).filter(x=>!(x.kind==="message"&&(sh(x)||ah(x)||rh(x)||Gc(x)))&&!(x.kind==="view-card"&&M&&x.viewId===M)),$=em(I,lt),_=I.filter(x=>!(x.kind==="message"&&x.role==="assistant"&&!$.has(x)&&Jc(x.box,x.ts,lt))),U=Rc(E,_).map(x=>({kind:"command-batch",id:x.id,ts:x.pending?x.ts:(x.anchorTs??x.ts)-1,anchorTs:x.anchorTs,anchorBox:x.anchorBox,agent:x.agent,boxRole:x.boxRole,commands:x.commands,pending:x.pending})).filter(x=>!Jc(x.anchorBox,x.anchorTs??x.ts,u)),V=[..._,...U].filter(x=>x.ts!=null).sort((x,q)=>x.ts-q.ts),g=Mv(n),w=g.size?V.filter(x=>x.kind==="event"||!g.has(x.box??x.anchorBox)):V,y=n.boxes.find(x=>x.name===n.leadBox)||null,A=y?await $r(y.cwd,y.sessionId,{fallbackNewest:!1}):null;if(!A&&y){let x=[...E].reverse().find(q=>q.type==="context.usage"&&q.boxId===y.name);x?.payload?.window&&(A={pct:x.payload.pct??(x.payload.occupancy!=null?x.payload.occupancy/x.payload.window:null),occupancy:x.payload.occupancy??null,window:x.payload.window,windowKnown:!0,model:x.payload.model||null})}let R=[...h].reverse().find(x=>x.role==="assistant"&&x.model),D=R?.model||null,T=[...n.events||[]].reverse().find(x=>x.type==="model-change"),O=A?.model||D;T&&(!R?.ts||T.at>=R.ts)&&(O=T.to);let W=y?await ht(y.name,{lines:At}).catch(()=>""):"",tt=y&&rr(y.agent,W)?await Se(y.name).catch(()=>W):W,it=y?kn(y,tt):null;it&&qp(n,y,it);let yt=!!y&&!it&&Bn(y.agent,W);y&&!y.readyAt&&(yt||it||W&&jo(y.agent,W))&&(y.readyAt=Date.now(),await J());let Bt=!!y&&!y.readyAt&&Date.now()-(n.createdAt||0)<Cv;if(n.kind==="setup"&&n.setup?.provider&&!n.setup.connectedAt&&y&&jo(y.agent,W)&&!it){let x=await H(),q=Z(x,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:y.name,provider:y.agent,payload:{provider:y.agent,ts:q.setup.connectedAt}}).catch(()=>{}),await J())}let Ye=s?{running:!0,step:o.step,toAgent:o.toAgent||null}:null,Ut=null;if(c){let x=Date.now()-i.startedAt;if(i.viewId&&!yt){let q=Yo(n.id);q&&(ot(n,"view-spinup",{box:i.box,startedAt:i.startedAt,endedAt:q.finishedAt}),await J())}else if(it||x>ud&&!yt&&!i.viewId||x>Za){let q=it?"needs-input":x>Za?"timeout":"no-view";Yo(n.id,{error:q})&&await C(n,{type:"view.spinup.failed",actor:"orchestrator",source:"orchestrator",boxId:i.box,provider:y?.agent||null,payload:{ts:i.startedAt,name:i.name,reason:q}}).catch(()=>{})}else Ut={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:O,options:Dp(y?.agent)},pendingPrompt:it,working:yt,leadStarting:Bt,handoff:Ye,view:Ut,queued:Nv(n.id).filter(x=>x.kind!=="ship-notify")}}function Mv(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 wh(t,e){let n=await nt(t);if(!n)return null;let r=gr(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=Ps(s).filter(d=>d.box===o),i=Rc(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 ht(o,{lines:At}).catch(()=>"");u=Bn(l.agent||r.agent,d)}return{items:c,working:u,box:o,agent:r.agent,task:r.task}}async function el(t,{promptId:e,answers:n,text:r}={}){let o=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 ic(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=xp(i,n||[]);await bt(o.leadBox,f,{kind:"answer-prompt"}),c={mode:"keys",value:f.join(" "),count:f.length}}else{let f=up(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 G({flow:"prompt",phase:"answered",sessionName:o.leadBox,agent:s.agent,provider:i.provider,promptId:i.promptId,...c}).catch(()=>{}),Gp(o,s,i,c),{ok:!0,promptId:i.promptId,sent:c.count}}async function gh(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 ic(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=Sp(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 Pv=1500,io=new Map;function nl(t){let e=io.get(t);return e||(e={items:[],timer:null,flushing:!1,idleSince:null},io.set(t,e)),e}function Nv(t){return(io.get(t)?.items??[]).map(e=>({...e}))}function Xs(t){let e=io.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 yh(t){let e=nl(t);e.timer||(e.timer=setInterval(()=>Bv(t).catch(()=>{}),Pv))}async function Bv(t){let e=io.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){Xs(t);return}let r=po(t);if(r&&!r.finishedAt&&r.step!=="done"&&!r.error){dr(e,!1);return}let o=await z(n.leadBox);if(!o){dr(e,!1);return}let s=n.boxes.find(c=>c.name===n.leadBox),a=await ht(n.leadBox,{lines:At}).catch(()=>"");if(Do(s?.agent,a)){dr(e,!1);return}if(rr(s?.agent,a)){let c=await Se(n.leadBox).catch(()=>a);if(kn(s,c)){dr(e,!1);return}}if(!dr(e,!0))return;let i=e.items.shift();e.idleSince=null,await bh(n,s,o,i.text,i.kind??null,i.attachments??null),await J()}finally{e.flushing=!1}}}async function bh(t,e,n,r,o=null,s=null){let a=r,i=!1;t.pendingHandoffHydrate&&(a=kv(r,t.pendingHandoffHydrate.artifact||zs),delete t.pendingHandoffHydrate,i=!0);let c=Array.isArray(s)?s.filter(u=>u&&u.rel):[];if(c.length){let u=nh(c.map(d=>d.rel));a=a.trim()?`${a.trim()}
|
|
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 ${fa} 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.`,zA=(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."),le=null,yl=null;async function q(){return le||(yl||=(async()=>{try{le=JSON.parse(await U.readFile(Vh,"utf8"))}catch{le={tracks:[]}}finally{yl=null}return le})(),yl)}async function G(){await Zt(Vh,le)}var tt=(t,e)=>t.tracks.find(n=>n.id===e)||null,bl=Promise.resolve();function JA(t,e){let n=JSON.stringify({trackId:t,...e})+`
|
|
102
|
+
`;return bl=bl.then(()=>U.mkdir(Ve,{recursive:!0})).then(()=>U.appendFile(HA,n)).catch(()=>{}),bl}var st=(t,e,n={})=>{let r={at:Date.now(),type:e,...n};return t.events.push(r),JA(t.id,r),Em(t,r),r};Ku(async({sessionName:t,from:e,to:n,version:r,status:o,error:s})=>{let i=(await q()).tracks.find(c=>c.leadBox===t||(c.boxes||[]).some(l=>l.name===t));i&&(st(i,"codex-update",{box:t,from:e,to:r||n,status:o,...s?{error:s}:{}}),await G().catch(()=>{}))});var kl=(t,e,n)=>e!=null&&n.some(r=>t===r.box&&e>=r.from&&e<=r.to),XA=new Set(["stuck","wrong-approach","too-slow","want-different-model","context-refresh","cost","parallelize","other"]);function Jh(t){if(!t)return null;let e=typeof t=="string"?[t]:Array.isArray(t?.codes)&&t.codes.length?t.codes:t?.code?[t.code]:[],n=[...new Set(e.filter(s=>XA.has(s)))];if(!n.length)return null;let r=typeof t=="object"?t?.note:null,o=r&&String(r).slice(0,500).trim()||null;return{code:n[0],...n.length>1?{codes:n}:{},note:o}}async function Xh(t,e,n,{kind:r}={}){let o=await X(e);if(!o)throw new Error(`box "${e}" is not running`);let s=vo(o.agent);await Re({cwd:o.cwd,sessionId:o.sessionId,sessionName:e,agent:o.agent,agentCmd:s}),await xt(e,n,{...fe(o.agent),kind:r||"orchestration"}),await V({flow:"orchestration",phase:"sent",trackId:t,sessionName:e,agent:o.agent,kind:r||"orchestration",promptHead:n.slice(0,160)});let a=await q(),i=tt(a,t);if(!i)return;let c=i.boxes.find(l=>l.name===e);st(i,"orchestration-prompt",{box:e,agent:c?.agent||o.agent,boxRole:c?.role,prompt:n,kind:r||"orchestration"}),await G()}var _n=new Set(["merged","conflict","canceled","failed","handed-off"]),Uh=new Map;async function YA(t){let e=H.join(us(t),`${t.id}.ndjson`),n;try{n=await U.stat(e)}catch{return null}let r=Uh.get(t.id);if(r&&r.mtimeMs===n.mtimeMs)return r.at;let o=null;for(let s of await F(t)){if(s.type!=="turn.agent")continue;let a=s.payload?.ts??s.ts;a&&(!o||a>o)&&(o=a)}return Uh.set(t.id,{mtimeMs:n.mtimeMs,at:o}),o}async function ZA(t,e){let n=e.find(r=>r.name===t.leadBox);return n?.state!=="live"?{working:!1,prompted:!1}:Kc(n.name,n.agent)}async function ze(t,e=null){let n=e??new Map((await nn()).map(i=>[i.name,i])),r=t.boxes.map(i=>{let c=n.get(i.name),l=_n.has(i.state)?i.state:c?i.role==="lead"?"live":i.state:"stopped";return{...i,state:l,cwd:c?.cwd||i.cwd,agent:c?.agent||i.agent,sessionId:c?.sessionId||i.sessionId}}),[o,{working:s,prompted:a}]=await Promise.all([YA(t),ZA(t,r)]);return{...t,boxes:r,lastResponseAt:o,working:s,prompted:a}}var QA=5e3;function tT(t,e,n){let r=[...t].sort((l,u)=>l.createdAt-u.createdAt),o=[...e].sort((l,u)=>l.birthMs-u.birthMs),s=new Set,a=[];for(let l of r)l.sessionId&&n(l.sessionId)&&!s.has(l.sessionId)?s.add(l.sessionId):a.push(l);let i=new Map(r.map((l,u)=>[l,u])),c=[];for(let l of a){if(l.sessionId&&n(l.sessionId)){c.push({box:l.name,sessionId:null});continue}if(!l.sessionId||l.pinned)continue;let u=i.get(l),f=l.createdAt-QA,d=r[u+1]?.createdAt??1/0,m=o.find(h=>!s.has(h.id)&&h.birthMs>=f&&h.birthMs<d);m&&(s.add(m.id),c.push({box:l.name,sessionId:m.id}))}return c}async function Yh(t){let e=new Map;for(let r of t.tracks)for(let o of r.boxes)o.agent==="claude"&&o.cwd&&(e.has(o.cwd)||e.set(o.cwd,[]),e.get(o.cwd).push({track:r,box:o}));let n=!1;for(let[r,o]of e){let s=o.map(d=>d.box.sessionId),a=Fu(r),i=new Set(a.map(d=>d.id)),c=s.every(d=>d&&i.has(d)),l=new Set(s).size===s.length;if(c&&l)continue;let u=o.map(d=>d.box),f=tT(u,a,d=>i.has(d));for(let d of f){let m=u.find(h=>h.name===d.box);m&&(m.sessionId=d.sessionId),await $u(d.box,d.sessionId),n=!0}}return n}async function at(){let t=await q();await Yh(t)&&await G();let e=new Map((await nn()).map(n=>[n.name,n]));return Promise.all(t.tracks.map(n=>ze(n,e)))}async function Zh(t,e){let n=await q();if(t)for(let r of n.tracks||[]){let o=r.boxes.find(s=>s.sessionId&&s.sessionId===t);if(o)return{track:r,box:o}}if(e)for(let r of n.tracks||[]){let o=r.boxes.find(s=>s.cwd===e);if(o)return{track:r,box:o}}return null}async function Qh(t){let e=Or(t);if(!e)return null;let n=await q();for(let r of n.tracks||[])for(let o of r.boxes||[])if(o.hookTokenHash&&xu(o.hookTokenHash,e))return{track:r,box:o};return null}async function Q(t){let e=await q(),n=tt(e,t);return n?(await Yh(e)&&await G(),ze(n)):null}var eT={claude:[/^\[Request interrupted by user(?: for tool use)?\]$/i],codex:[/^\[?Request interrupted/i,/^Conversation interrupted/i],cursor:[/^\[?Request interrupted/i]};function Hh(t){if(t.role!=="user")return!1;let e=(t.text||"").trim();return(eT[t.agent]||[]).some(n=>n.test(e))}var nT=[/^\/model(?:\s|$)/i,/<command-(?:name|message|args)>[^<]*\bmodel\b/i,/^Model changed to\b/i];function Wh(t){let e=(t.text||"").trim();return e?nT.some(n=>n.test(e)):!1}var rT=10*6e4;async function _l(t,e={}){let n=await Q(t);if(!n)return null;let r=new Set([...n.events.filter(k=>k.type==="orchestration-prompt"&&k.prompt).map(k=>String(k.prompt).trim()),...n.events.filter(k=>k.type==="said"&&k.text).map(k=>String(k.text).trim())]),o=Ao(n.id),s=!!(o&&!o.finishedAt&&o.step!=="done"&&!o.error),a=s&&o?.startedAt?[...o.fromBox?[{box:o.fromBox,from:o.startedAt,to:Date.now()+1e12}]:[],...o.toBox?[{box:o.toBox,from:o.startedAt,to:Date.now()+1e12}]:[]]:[],i=Ti(n.id),c=!!(i&&!i.finishedAt),l=c&&i.startedAt&&i.box?[{box:i.box,from:i.startedAt,to:Date.now()+1e12}]:[],u=[...nf(n),...l],d={windows:[...wd(n),...gd(n,cs),...a,...u]},h=(await Promise.all(n.boxes.map(k=>Tc(k,e)))).flat(),w=h.map(k=>{let j={kind:"message",...k};if(j.role==="assistant"&&(j.text||"").trim()&&j.tools?.length){let Rn=io(j.agent).cliTools,Ho=j.tools.filter($n=>!Rn.has($n));return{...j,tools:Ho.length?Ho:void 0}}return j}).map(k=>{if(k.role!=="user")return k;let j=na(k.text);return j?j.kind==="suppress"?null:{...k,note:j,text:dh(j)}:k}).filter(Boolean).filter(k=>!(k.role==="user"&&r.has((k.text||"").trim()))).filter(k=>!Hh(k)).filter(k=>!Wh(k)).filter(k=>!Fh(k)).filter(k=>!gl(k)).filter(k=>k.role!=="assistant"||!!(k.text||"").trim()||!!k.images?.length).filter(k=>!yd(k,d)),y=w.filter(k=>k.failed&&k.text===bc);if(y.length){let k=new Map;for(let j of y){if(!k.has(j.box)){let $n=[...(await wt(j.box,{lines:At}).catch(()=>"")).matchAll(/^\s*■\s+(.+?)\s*$/gm)].map(yu=>yu[1]);k.set(j.box,$n.length?$n[$n.length-1]:null)}let Rn=k.get(j.box);Rn&&(j.text=`\u25A0 ${Rn}`)}}let b=await Promise.all(n.boxes.map(k=>zp(k,e)));await lh(n,b.flat()),await ih(n,w),await Im(n),await uf(n,w).catch(()=>{});for(let k of n.boxes)k.agent==="codex"&&k.state==="live"&&await mh(n,k),k.agent==="cursor"&&k.state==="live"&&await yh(n,k);let A=await Promise.all(n.boxes.map(k=>Vp(k,e)));await ch(n,A.flat().filter(k=>!kl(k.box,k.ts,u)));let _=await F(n),C=c&&i.viewId?i.viewId:null,K=Lm(_),I=ra(_).filter(k=>!(k.kind==="message"&&(Hh(k)||Wh(k)||Fh(k)||gl(k)))&&!(k.kind==="view-card"&&C&&k.viewId===C)),L=Fm(I,K),v=I.filter(k=>!(k.kind==="message"&&k.role==="assistant"&&!L.has(k)&&kl(k.box,k.ts,K))),$=Qc(_,v).map(k=>({kind:"command-batch",id:k.id,ts:k.pending?k.ts:(k.anchorTs??k.ts)-1,anchorTs:k.anchorTs,anchorBox:k.anchorBox,agent:k.agent,boxRole:k.boxRole,commands:k.commands,pending:k.pending})).filter(k=>!kl(k.anchorBox,k.anchorTs??k.ts,u)),W=[...v,...$].filter(k=>k.ts!=null).sort((k,j)=>k.ts-j.ts),nt=oT(n),g=nt.size?W.filter(k=>k.kind==="event"||!nt.has(k.box??k.anchorBox)):W,p=n.boxes.find(k=>k.name===n.leadBox)||null,S=p?await Dr(p.cwd,p.sessionId,{fallbackNewest:!1}):null;if(!S&&p){let k=[..._].reverse().find(j=>j.type==="context.usage"&&j.boxId===p.name);k?.payload?.window&&(S={pct:k.payload.pct??(k.payload.occupancy!=null?k.payload.occupancy/k.payload.window:null),occupancy:k.payload.occupancy??null,window:k.payload.window,windowKnown:!0,model:k.payload.model||null})}let T=[...h].reverse().find(k=>k.role==="assistant"&&k.model),J=T?.model||null,R=[...n.events||[]].reverse().find(k=>k.type==="model-change"),M=S?.model||J;R&&(!T?.ts||R.at>=T.ts)&&(M=R.to);let E=p?await wt(p.name,{lines:At}).catch(()=>""):"",P=p&&ir(p.agent,E)?await Ee(p.name).catch(()=>E):E,et=p?Sn(p,P):null;et&&Rm(n,p,et);let lt=!!p&&!et&&Ln(p.agent,E);p&&!p.readyAt&&(lt||et||E&&os(p.agent,E))&&(p.readyAt=Date.now(),await G());let bt=!!p&&!p.readyAt&&Date.now()-(n.createdAt||0)<rT;if(n.kind==="setup"&&n.setup?.provider&&!n.setup.connectedAt&&p&&os(p.agent,E)&&!et){let k=await q(),j=tt(k,n.id);j?.setup&&!j.setup.connectedAt&&(j.setup.connectedAt=Date.now(),n.setup.connectedAt=j.setup.connectedAt,await O(j,{type:"provider.connected",actor:"system",source:"pane",boxId:p.name,provider:p.agent,payload:{provider:p.agent,ts:j.setup.connectedAt}}).catch(()=>{}),await G())}let Et=s?{running:!0,step:o.step,toAgent:o.toAgent||null}:null,Nt=null;if(c){let k=Date.now()-i.startedAt;if(i.viewId&&!lt){let j=ys(n.id);j&&(st(n,"view-spinup",{box:i.box,startedAt:i.startedAt,endedAt:j.finishedAt}),await G())}else if(et||k>tf&&!lt&&!i.viewId||k>Ai){let j=et?"needs-input":k>Ai?"timeout":"no-view";ys(n.id,{error:j})&&await O(n,{type:"view.spinup.failed",actor:"orchestrator",source:"orchestrator",boxId:i.box,provider:p?.agent||null,payload:{ts:i.startedAt,name:i.name,reason:j}}).catch(()=>{})}else Nt={running:!0,step:i.step,name:i.name}}return{trackId:n.id,leadBox:n.leadBox,items:g,context:S?{pct:S.pct,occupancy:S.occupancy,window:S.window,windowKnown:S.windowKnown,model:S.model}:null,models:{current:M,options:Sm(p?.agent)},pendingPrompt:et,working:lt,leadStarting:bt,handoff:Et,view:Nt,queued:aT(n.id).filter(k=>k.kind!=="ship-notify")}}function oT(t){let e=new Set;for(let n of t.boxes||[])n.role==="subtask"&&n.name&&e.add(n.name);for(let n of Object.values(t.subtasks||{}))n?.box&&e.add(n.box);return e}async function tw(t,e){let n=await Q(t);if(!n)return null;let r=xr(t).find(f=>f.id===e)||null;if(!r)return null;let o=r.box||null;if(!o)return{items:[],working:!1,box:null,agent:r.agent,task:r.task};let s=await F(n),a=ra(s).filter(f=>f.box===o),i=Qc(s.filter(f=>f.boxId===o),a.filter(f=>f.kind==="message")).map(f=>({kind:"command-batch",id:f.id,ts:f.pending?f.ts:(f.anchorTs??f.ts)-1,anchorTs:f.anchorTs,anchorBox:f.anchorBox,agent:f.agent,boxRole:f.boxRole,commands:f.commands,pending:f.pending})),c=[...a,...i].filter(f=>f.ts!=null).sort((f,d)=>f.ts-d.ts),l=n.boxes.find(f=>f.name===o)||null,u=!1;if(l&&l.state==="live"){let f=await wt(o,{lines:At}).catch(()=>"");u=Ln(l.agent||r.agent,f)}return{items:c,working:u,box:o,agent:r.agent,task:r.task}}async function El(t,{promptId:e,answers:n,text:r}={}){let o=tt(await q(),t);if(!o)throw new Error(`track "${t}" not found`);let s=o.boxes.find(d=>d.name===o.leadBox);if(!s)throw new Error(`track "${t}" has no lead box`);if(!await X(o.leadBox))throw new Error(`lead box "${o.leadBox}" is not running`);let i=await Cc(s);if(!i)throw new Error("no pending prompt to answer");if(e&&e!==i.promptId)throw new Error("prompt changed since it was shown \u2014 reload and answer the current prompt");let c;if(i.answerMode==="text"){let d=typeof r=="string"?r.trim():"";if(!d)throw new Error("this prompt needs a code \u2014 paste it into the card");if(/\s/.test(d)||d.length>2048)throw new Error("that doesn't look like a sign-in code \u2014 paste the code exactly as shown");await xt(o.leadBox,d,{enter:!0,kind:"answer-prompt"}),c={mode:"text-input",value:"\u2039code\u203A",count:1,redacted:!0}}else if(i.answerMode==="esc")await kt(o.leadBox,["Escape"],{kind:"answer-prompt"}),c={mode:"keys",value:"Escape",count:1};else if(i.provider==="claude"||i.answerMode==="keys"){let d=sm(i,n||[]);await kt(o.leadBox,d,{kind:"answer-prompt"}),c={mode:"keys",value:d.join(" "),count:d.length}}else{let d=Qu(i,n||[]);await xt(o.leadBox,d,{enter:!0,kind:"answer-prompt"}),c={mode:"text",value:d,count:1}}let l=i.questions?.[0]||{},u=(Array.isArray(n?.[0])?n[0]:[]).map(d=>l.options?.[d]).filter(Boolean).map(d=>({label:d.label,description:d.description||""})),f=i.review?"review":/^claude:model-confirm:/.test(i.promptId||"")?"model-confirm":/^claude:resume-confirm:/.test(i.promptId||"")?"resume-confirm":void 0;return c={...c,groupId:i.groupId||i.promptId,questionIndex:i.questionIndex??0,questionTotal:i.questionTotal??1,header:l.header||"",question:l.question||"",multiSelect:!!l.multiSelect,chosen:u,...f?{control:f}:{}},await V({flow:"prompt",phase:"answered",sessionName:o.leadBox,agent:s.agent,provider:i.provider,promptId:i.promptId,...c}).catch(()=>{}),$m(o,s,i,c),{ok:!0,promptId:i.promptId,sent:c.count}}async function ew(t,{promptId:e,questionIndex:n}={}){let r=tt(await q(),t);if(!r)throw new Error(`track "${t}" not found`);let o=r.boxes.find(l=>l.name===r.leadBox);if(!o)throw new Error(`track "${t}" has no lead box`);if(!await X(r.leadBox))throw new Error(`lead box "${r.leadBox}" is not running`);let a=await Cc(o);if(!a)throw new Error("no pending prompt to navigate");if(a.provider!=="claude")throw new Error("prompt navigation not supported for this provider");if(a.review)throw new Error("cannot navigate on the review screen \u2014 answer or cancel");let i=a.questionTotal??1;if(i<2)throw new Error("not a multi-question prompt");if(!Number.isInteger(n)||n<0||n>=i)throw new Error("questionIndex out of range");if(e&&e!==a.promptId)throw new Error("prompt changed since it was shown \u2014 reload and try again");let c=am(a,n);return c.length?(await kt(r.leadBox,c,{kind:"navigate-prompt"}),await V({flow:"prompt",phase:"navigated",sessionName:r.leadBox,agent:o.agent,provider:a.provider,promptId:a.promptId,fromIndex:a.questionIndex??0,toIndex:n,mode:"keys",value:c.join(" "),count:c.length}).catch(()=>{}),{ok:!0,promptId:a.promptId,sent:c.length}):{ok:!0,promptId:a.promptId,sent:0}}var sT=1500,yo=new Map;function Il(t){let e=yo.get(t);return e||(e={items:[],timer:null,flushing:!1,idleSince:null},yo.set(t,e)),e}function aT(t){return(yo.get(t)?.items??[]).map(e=>({...e}))}function ya(t){let e=yo.get(t);if(!e)return[];let n=e.items.splice(0);return e.idleSince=null,e.timer&&(clearInterval(e.timer),e.timer=null),n}function nw(t){let e=Il(t);e.timer||(e.timer=setInterval(()=>iT(t).catch(()=>{}),sT))}async function iT(t){let e=yo.get(t);if(!(!e||e.flushing)){if(!e.items.length){e.timer&&(clearInterval(e.timer),e.timer=null);return}e.flushing=!0;try{let n=tt(await q(),t);if(!n||n.archivedAt){ya(t);return}let r=Ao(t);if(r&&!r.finishedAt&&r.step!=="done"&&!r.error){hr(e,!1);return}let o=await X(n.leadBox);if(!o){hr(e,!1);return}let s=n.boxes.find(c=>c.name===n.leadBox),a=await wt(n.leadBox,{lines:At}).catch(()=>"");if(rs(s?.agent,a)){hr(e,!1);return}if(ir(s?.agent,a)){let c=await Ee(n.leadBox).catch(()=>a);if(Sn(s,c)){hr(e,!1);return}}if(!hr(e,!0))return;let i=e.items.shift();e.idleSince=null,await rw(n,s,o,i.text,i.kind??null,i.attachments??null),await G()}finally{e.flushing=!1}}}async function rw(t,e,n,r,o=null,s=null){let a=r,i=!1;t.pendingHandoffHydrate&&(a=GA(r,t.pendingHandoffHydrate.artifact||ga),delete t.pendingHandoffHydrate,i=!0);let c=Array.isArray(s)?s.filter(u=>u&&u.rel):[];if(c.length){let u=Lh(c.map(f=>f.rel));a=a.trim()?`${a.trim()}
|
|
101
103
|
|
|
102
|
-
${u}`:u}let l=
|
|
104
|
+
${u}`:u}let l=st(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 Re({cwd:n.cwd,sessionId:n.sessionId,sessionName:t.leadBox,agent:n.agent,agentCmd:vo(n.agent)}),await xt(t.leadBox,a,{...fe(e?.agent),kind:i?"lead-handoff-first-prompt":"say"})}catch(u){throw st(t,"unsaid",{box:t.leadBox,agent:e?.agent,boxRole:e?.role,saidTs:l.at,text:r}),u}i&&await V({flow:"lead-handoff",phase:"first-prompt",trackId:t.id,sessionName:t.leadBox,agent:e?.agent,userTextLen:r.length})}function ow(t,{actions:e}={}){Il(t).items.push({id:So(),text:Um(e),ts:Date.now(),kind:"ship-notify"}),nw(t)}async function Se(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"||!cr(e))&&!o.length)throw new Error("text (string) or an attachment required");let s=tt(await q(),t);if(!s)throw new Error(`track "${t}" not found`);if(s.archivedAt)throw new Error(`track "${t}" is archived \u2014 restore it before driving it`);let a=await X(s.leadBox);if(!a&&await Cl(s)&&(await G(),a=await X(s.leadBox)),!a)throw new Error(`lead box "${s.leadBox}" is not running`);let i=s.boxes.find(d=>d.name===s.leadBox),c=cr(e),l=Il(t),u=await wt(s.leadBox,{lines:At}).catch(()=>""),f=l.items.length>0||rs(i?.agent,u);if(!f&&ir(i?.agent,u)){let d=await Ee(s.leadBox).catch(()=>u);f=!!Sn(i,d)}if(f){let d={id:So(),text:c,ts:Date.now(),...n?{kind:n}:{},...o.length?{attachments:o}:{}};return l.items.push(d),nw(t),delete s.draft,await G(),{ok:!0,leadBox:s.leadBox,queued:!0,id:d.id,position:l.items.length}}return await rw(s,i,a,c,n,o),await G(),{ok:!0,leadBox:s.leadBox}}var qh="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 sw(t,{hint:e}={}){let n=tt(await q(),t);if(!n)throw new Error(`track "${t}" not found`);if(n.archivedAt)throw new Error(`track "${t}" is archived \u2014 restore it first`);let r=Ti(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";ef(n.id,{name:s,box:n.leadBox});let a=String(e||"").trim()?`${qh}
|
|
103
105
|
|
|
104
|
-
What to preview: ${String(e).trim()}`:
|
|
106
|
+
What to preview: ${String(e).trim()}`:qh;try{await Se(t,a,{kind:"spin-up-view"})}catch(i){throw ys(n.id,{error:"drive-failed"}),i}return{ok:!0,running:!0}}async function Rl(t){let e=tt(await q(),t);if(!e)throw new Error(`track "${t}" not found`);if(e.archivedAt)throw new Error(`track "${t}" is archived`);let n=e.boxes.find(f=>f.name===e.leadBox),r=ya(t),o=await X(e.leadBox),s=o?await uT(e.leadBox,n?.agent):{stopped:!1,sends:0},a=o&&s.stopped?await pT(e,n):null,i=!a&&o&&s.stopped?await wT(e,n):null,c=o?await dT(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(`
|
|
107
|
+
|
|
108
|
+
`),e.draft={text:u,updatedAt:Date.now()},await G()),await V({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 cT=[300,350,400],lT=3;async function uT(t,e){let n=0;for(let r=0;r<lT;r++){await kt(t,["Escape"],{kind:"interrupt"}),n++;for(let o of cT){await To(o);let s=await wt(t,{lines:At}).catch(()=>"");if(!Ln(e,s))return{stopped:!0,sends:n}}}return{stopped:!1,sends:n}}async function dT(t){let e=pl(await F(t));return e?.offerId?(await O(t,{type:"pr.offer.answered",actor:"user",source:"orchestrator",boxId:e.box,payload:{ts:Date.now(),offerId:e.offerId,action:"dismiss",actions:null,reason:"interrupt"}}),e.offerId):null}var fT=[250,400,500,600];async function pT(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 fT){await To(r);let o=await wt(t.leadBox,{lines:At}).catch(()=>""),s=qs(e.agent,o);if(s&&lm(n.text,s))return await gT(t.leadBox,e.agent,n.text.length+8),t.draft={text:n.text,updatedAt:Date.now()},st(t,"unsaid",{box:t.leadBox,agent:e.agent,boxRole:e.role,saidTs:n.at,text:n.text}),await G(),n.text}return null}var mT=3e4,hT=[350,650,900];async function wT(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>mT||n.some(s=>s.type==="unsaid"&&s.saidTs===r.at)||!Kt(r.text))return null;for(let s of hT){await To(s);let a;try{a=await Tc(e,{})}catch{return null}if(cm(a,r))return null;let i=await wt(t.leadBox,{lines:At}).catch(()=>"");if(qs(e.agent,i))return null}return t.draft={text:r.text,updatedAt:Date.now()},st(t,"unsaid",{box:t.leadBox,agent:e.agent,boxRole:e.role,saidTs:r.at,text:r.text,reason:"lost"}),await G(),r.text}async function gT(t,e,n){for(let r=0;r<3;r++){await Zo(t,n,{kind:"unsend-wipe"}).catch(()=>{}),await To(200);let o=await wt(t,{lines:At}).catch(()=>""),s=qs(e,o);if(!s)return!0;n=s.length+8}return!1}async function aw(t,e){if(typeof e!="string")throw new Error("text (string) required");let n=tt(await q(),t);if(!n)throw new Error(`track "${t}" not found`);let r=cr(e);return r?n.draft={text:r,updatedAt:Date.now()}:delete n.draft,await G(),{ok:!0,draft:n.draft||null}}async function iw(t,e){if(typeof e!="string"||!e.trim())throw new Error("model (string) required");let n=e.trim(),r=tt(await q(),t);if(!r)throw new Error(`track "${t}" not found`);let o=await X(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 Dr(o.cwd,o.sessionId,{fallbackNewest:!1}).catch(()=>null))?.model??null),await Re({cwd:o.cwd,sessionId:o.sessionId,sessionName:r.leadBox,agent:o.agent,agentCmd:vo(o.agent)});let u=await Am(a,r.leadBox,i);return st(r,"model-change",{to:n,from:l,box:r.leadBox,agent:a,verified:!!u.verified}),await G(),{ok:!0,requested:n,leadBox:r.leadBox,...u}}async function cw(t){for(let e of t){if(!e||!await Gs(e))continue;let n=await dm(e).catch(()=>null);if(!n)continue;if((await U.stat(H.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:H.basename(e.base),dir:e.dir??null,base:e.base,baseRef:e.baseRef??null,repo:e.repo??null}]:[]:[]}async function lo(t){let e=[];for(let n of Ot(t?.workspace)){if(!n?.dir)continue;let r=await Dc(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 lw(t){let e=t;typeof e=="string"&&!H.isAbsolute(e)&&/^[^/\s]+\/[^/\s]+$/.test(e)&&(e=await to(e)??await ic(e));let n=await cw([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 yT(t,e,n,r){let o=`track/${Pl(e)}-${t}`,s=[];try{await U.mkdir(n,{recursive:!0});let a=new Set([".bostrat"]);for(let i of r){let c=await lw(i);if(s.some(m=>m.base===c))throw new Error(`repo tagged twice: ${i}`);let l=await Z(c).catch(()=>null),u=H.basename(c);a.has(u)&&l&&(u=l.replace("/","-"));for(let m=2;a.has(u);m++)u=`${H.basename(c)}-${m}`;a.add(u);let f=H.join(n,u),d=await Ks(c);await lr(c,f,o,d),s.push({name:u,dir:f,base:c,baseRef:d,repo:l})}return await ba(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 U.rm(n,{recursive:!0,force:!0}).catch(()=>{}),a}}var uw=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 dw(t,e){let n=await q(),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 lw(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=H.basename(s);i.has(c)&&a&&(c=a.replace("/","-"));for(let d=2;i.has(c);d++)c=`${H.basename(s)}-${d}`;let l=H.join(o.dir,c),u=await Ks(s);await fm(s,o.branch)?await pm(s,l,o.branch):await lr(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 ba(o.dir,o.repos,o.branch),st(r,"retagged",{action:"add",repo:a??s,name:c,workspace:uw(o)}),await G(),f}async function fw(t,e){let n=await q(),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 Vt.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 Dc(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 U.rm(s.dir,{recursive:!0,force:!0}).catch(()=>{}),o.repos=(o.repos??[]).filter(c=>c&&c!==s),await ba(o.dir,o.repos,o.branch),st(r,"retagged",{action:"remove",repo:s.repo??s.base,name:s.name,workspace:uw(o)}),await G(),{removed:s.name}}async function bT(t,e,{repo:n,repos:r,cwd:o}={}){await U.mkdir(vl,{recursive:!0});let s=H.join(vl,t),a=Array.isArray(r)?r.map(c=>typeof c=="string"?c.trim():"").filter(Boolean):[];if(a.length>=2)return yT(t,e,s,a);a.length===1&&!n&&(n=a[0]),typeof n=="string"&&!H.isAbsolute(n)&&/^[^/\s]+\/[^/\s]+$/.test(n)&&(n=await to(n)??await ic(n));let i=await cw([n,o,WA]);if(i){let c=`track/${Pl(e)}-${t}`;try{let l=await Ks(i);await lr(i,s,c,l),await Al(s);let u=await Z(i).catch(()=>null);return{dir:s,base:i,branch:c,kind:"worktree",baseRef:l,repo:u}}catch{await U.rm(s,{recursive:!0,force:!0}).catch(()=>{})}}return await U.mkdir(s,{recursive:!0}),await mm(s),await Al(s),{dir:s,base:null,branch:null,kind:"repo"}}var bo="# bostrat workspace",wo=`${bo}
|
|
105
109
|
|
|
106
110
|
When asked to run, preview, or demo this app so the operator can review it: do NOT
|
|
107
111
|
start an ad-hoc dev server and do NOT share a localhost URL (the operator is on
|
|
@@ -213,6 +217,23 @@ add a near-duplicate. When every step is confirmed, run
|
|
|
213
217
|
than creating another. Runbooks are read-only for the operator: they act and
|
|
214
218
|
report back in chat; you are the only writer.
|
|
215
219
|
|
|
220
|
+
When something happens that the operator would want to know about RIGHT NOW and
|
|
221
|
+
they are plausibly away from the screen, you can reach their phone:
|
|
222
|
+
|
|
223
|
+
bostrat-notify "<message>" [--title "<title>"] [--urgent]
|
|
224
|
+
|
|
225
|
+
Use this SPARINGLY and only for: long work finishing, a real blocker that needs
|
|
226
|
+
their decision, or a failure that wastes their time if it goes unnoticed. NEVER
|
|
227
|
+
for progress updates, never to announce that you are starting, and never more
|
|
228
|
+
than once per task unless the situation genuinely changed. If they are plausibly
|
|
229
|
+
watching the chat, just say it in chat \u2014 the chat is ALWAYS the default and a
|
|
230
|
+
notification is the exception. Write the message so it stands alone on a lock
|
|
231
|
+
screen (they cannot see the chat from there): say what happened and what it
|
|
232
|
+
means, not "done" or "see chat". Never put a secret value in one \u2014 notifications
|
|
233
|
+
travel off-box, and the server rejects token-shaped content. Anything worth
|
|
234
|
+
notifying about belongs in your chat message too; the notification is a nudge
|
|
235
|
+
toward the chat, never the only record.
|
|
236
|
+
|
|
216
237
|
When you FINISH a piece of implementation work (feature built, bug fixed, checks
|
|
217
238
|
green) and the operator has not already told you to ship it: do NOT commit, push,
|
|
218
239
|
or open a PR on your own. Instead END your final message with a short, explicit
|
|
@@ -223,9 +244,9 @@ which steps to run (and which to skip). An offer buried mid-message, phrased as
|
|
|
223
244
|
statement, or omitted entirely renders no controls \u2014 so close EVERY completed
|
|
224
245
|
implementation turn this way, every time. When the operator has already asked you
|
|
225
246
|
to commit / push / open the PR, just do it; do not re-offer.
|
|
226
|
-
`;async function
|
|
227
|
-
`)),
|
|
228
|
-
`);return`${
|
|
247
|
+
`;async function Al(t){try{await U.writeFile(H.join(t,"AGENTS.md"),wo,{flag:"wx"})}catch{return}await ST(t,["/AGENTS.md"])}var xl=t=>typeof t=="string"&&(t===bo||t.startsWith(`${bo}
|
|
248
|
+
`)),kT="# bostrat multi-repo workspace";function xT(t,e){let n=t.map(r=>`- \`${r.name}/\`${r.repo?` \u2014 github.com/${r.repo}`:""}${r.baseRef?` (forked from ${r.baseRef})`:""}`).join(`
|
|
249
|
+
`);return`${kT}
|
|
229
250
|
|
|
230
251
|
This directory is NOT a git repo. It is a bostrat Track workspace holding one
|
|
231
252
|
git worktree per tagged repo, ALL on the same branch \`${e}\`:
|
|
@@ -237,25 +258,25 @@ push / open PRs) in each repo separately, always on that shared branch. Work
|
|
|
237
258
|
only needs to land in the repos it actually touches \u2014 an untouched repo's
|
|
238
259
|
branch simply stays at its fork point and never gets pushed. When asked to run
|
|
239
260
|
or preview an app, run it from that app's own repo directory.
|
|
240
|
-
`}async function
|
|
241
|
-
`,`${
|
|
261
|
+
`}async function ba(t,e,n){let r=xT(e,n);for(let o of["CLAUDE.md","AGENTS.md"])await U.writeFile(H.join(t,o),r).catch(()=>{})}var Sl=wo.replace(`${bo}
|
|
262
|
+
`,`${bo}
|
|
242
263
|
|
|
243
264
|
(Global copy managed by bostrat \u2014 rewritten on service start; edits will be lost.)
|
|
244
265
|
SCOPE: these instructions apply ONLY when your working directory is under
|
|
245
266
|
~/.bostrat/workspaces/ or ~/.bostrat/worktrees/ \u2014 a bostrat-orchestrated box.
|
|
246
267
|
When working anywhere else, ignore everything below.
|
|
247
|
-
`);async function
|
|
248
|
-
`).includes(s));o.length&&await
|
|
268
|
+
`);async function pw(t){let{stdout:e}=await UA("git",["-C",t,"rev-parse","--git-path","info/exclude"]);return H.resolve(t,e.trim())}async function ST(t,e){try{let n=await pw(t);await U.mkdir(H.dirname(n),{recursive:!0});let r=await U.readFile(n,"utf8").catch(()=>""),o=e.filter(s=>!r.split(`
|
|
269
|
+
`).includes(s));o.length&&await U.appendFile(n,(r.endsWith(`
|
|
249
270
|
`)||!r?"":`
|
|
250
271
|
`)+o.join(`
|
|
251
272
|
`)+`
|
|
252
|
-
`)}catch{}}async function
|
|
273
|
+
`)}catch{}}async function vT(t,e){try{let n=await pw(t),r=await U.readFile(n,"utf8"),o=r.split(`
|
|
253
274
|
`).filter(s=>s!==e);o.length!==r.split(`
|
|
254
|
-
`).length&&await
|
|
255
|
-
`))}catch{}}async function
|
|
275
|
+
`).length&&await U.writeFile(n,o.join(`
|
|
276
|
+
`))}catch{}}async function mw(){let t={refreshed:0,removed:0,codexGlobal:null};try{await U.mkdir(Ve,{recursive:!0});for(let r of["CLAUDE.md","AGENTS.md"])await U.writeFile(H.join(Ve,r),wo).catch(()=>{});let e=process.env.CODEX_HOME||H.join(process.env.HOME||"/tmp",".codex");if((await U.stat(e).catch(()=>null))?.isDirectory()){let r=H.join(e,"AGENTS.md"),o=await U.readFile(r,"utf8").catch(()=>null);o===null?(await U.writeFile(r,Sl,{flag:"wx"}).catch(()=>{}),t.codexGlobal="created"):xl(o)&&o!==Sl&&(await U.writeFile(r,Sl).catch(()=>{}),t.codexGlobal="refreshed")}for(let r of[vl,yr]){let o=await U.readdir(r,{withFileTypes:!0}).catch(()=>[]);for(let s of o){if(!s.isDirectory())continue;let a=H.join(r,s.name),i=H.join(a,"AGENTS.md"),c=await U.readFile(i,"utf8").catch(()=>null);c!==null&&xl(c)&&c!==wo&&(await U.writeFile(i,wo).catch(()=>{}),t.refreshed++);let l=H.join(a,"CLAUDE.md"),u=await U.readFile(l,"utf8").catch(()=>null);u!==null&&xl(u)&&(await U.rm(l,{force:!0}).catch(()=>{}),await vT(a,"/CLAUDE.md"),t.removed++)}}}catch{}return t}async function AT(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 U.rm(e.dir,{recursive:!0,force:!0})}else e.kind==="worktree"&&e.base?await Ut(e.base,e.dir):await U.rm(e.dir,{recursive:!0,force:!0})}catch{}}async function $l({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 wa(o,s),i=await q(),c=So().slice(0,8),l=await bT(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(Za(f))&&(f=`${t.trim().slice(0,50).trim()} ${c} lead`);let d=await rn(f,l.dir,{agentCmd:a,agent:o});ss({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:[Aw(d,"lead","live")],events:[],createdAt:Date.now()};return st(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:y,repo:b})=>({name:h,base:w,baseRef:y,repo:b}))}:{}}}),i.tracks.push(m),await G(),ze(m)}var TT=new Set(["claude","codex","cursor"]);async function hw({provider:t="claude",fresh:e=!1}={}){if(!TT.has(t))throw new Error(`unknown setup provider "${t}" (claude | codex | cursor)`);let r=(await q()).tracks.find(u=>u.kind==="setup"&&u.setup?.provider===t&&!u.archivedAt);if(r){if(!e){let u=await ze(r);if(u.boxes.find(d=>d.name===u.leadBox)?.state==="live")return u}await kr(r.id).catch(()=>{})}let o=(await wa(t,!1)).replace(/\s+--(?:dangerously-skip-permissions|dangerously-bypass-approvals-and-sandbox)\b/g,""),s=So().slice(0,8),a=H.join(Ve,"setup",s);await U.mkdir(a,{recursive:!0});let i=await rn(`Connect ${t} ${s}`,a,{agentCmd:o,agent:t,firstRun:!0});t==="codex"&&ss({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:[Aw(i,"lead","live")],events:[],createdAt:Date.now()};return st(c,"opened",{agent:t,box:i.name,skipPermissions:!1,trackKind:"setup",name:c.name,cwd:c.cwd,workspace:{base:null,branch:null,kind:"scratch"}}),(await q()).tracks.push(c),await G(),ze(c)}async function ww(t,e,n){await lf(t).catch(()=>{});let r=ko.get(t);r&&!r.finishedAt&&(r.error=n,r.finishedAt=Date.now()),ko.delete(t),e.handoff&&!e.handoff.finishedAt&&(e.handoff.step="error",e.handoff.error=n,e.handoff.finishedAt=Date.now());for(let a of xr(t))a.finishedAt||(a.canceled=!0,a.finishedAt=Date.now()),await Dl(a).catch(()=>{}),Vt.delete(a.id);for(let a of Object.values(e.subtasks||{}))a.finishedAt||(a.step="error",a.error=n,a.canceled=!0,a.finishedAt=Date.now());let o=await q(),s=new Set((o.tracks||[]).filter(a=>a.id!==t&&!a.archivedAt).flatMap(a=>(a.boxes||[]).map(i=>i.name)));for(let a of e.boxes)s.has(a.name)||await Jt(a.name).catch(()=>{})}async function gw(t,{cascadeMocks:e=!0,cascadeRunbooks:n=!1}={}){let r=await q(),o=r.tracks.findIndex(a=>a.id===t);if(o===-1)throw new Error(`track "${t}" not found`);let s=r.tracks[o];return e||await qd(s),await ww(t,s,"track deleted"),n?await xf(t).catch(()=>{}):await kf(t).catch(()=>{}),await _m(),await AT(s),e&&await Gd(t).catch(()=>{}),st(s,"track-deleted",{name:s.name,boxes:s.boxes.length,ageMs:s.createdAt?Date.now()-s.createdAt:null}),r.tracks.splice(o,1),await G(),{ok:!0,id:t,name:s.name,boxes:s.boxes.length}}async function kr(t){let e=await q(),n=tt(e,t);if(!n)throw new Error(`track "${t}" not found`);if(n.archivedAt)throw new Error(`track "${t}" is already archived`);return n.archivedAt=Date.now(),st(n,"archived",{boxes:n.boxes.length}),await G(),n.kind==="setup"?await yw(t):ET(t).catch(()=>{}),ze(n)}var _T=9e4;async function ET(t){try{await IT(t)}catch{}await yw(t).catch(()=>{})}async function yw(t){let e=await q(),n=tt(e,t);if(!(!n||!n.archivedAt)){await ww(t,n,"track archived");for(let r of n.boxes)_n.has(r.state)||(r.state="stopped");await G()}}async function IT(t){let e=await q(),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 X(n.leadBox).catch(()=>null):null;if(i?.cwd&&a)try{({text:r,items:o}=await hd(n.leadBox,i.cwd,{timeoutMs:_T})),s="lead-cli"}catch{}if(r||(r=Qi(n,await F(n).catch(()=>[]))),!r)return;let c={text:r,source:s,at:Date.now()};o.length&&(c.items=o),n.beat=c,await O(n,{type:"track.summary.recorded",actor:"orchestrator",source:"orchestrator",boxId:n.leadBox||null,provider:i?.agent||null,payload:o.length?{text:r,items:o,source:s}:{text:r,source:s}}).catch(()=>{}),await G()}async function bw(){let t=await q(),e=0,n=0,r=0;for(let o of t.tracks){if(!o.archivedAt||o.kind==="setup"||o.beat?.text){n++;continue}let s=Qi(o,await F(o).catch(()=>[]));if(!s){r++;continue}o.beat={text:s,source:"derived",at:Date.now()},await O(o,{type:"track.summary.recorded",actor:"orchestrator",source:"orchestrator",payload:{text:s,source:"derived",backfill:!0}}).catch(()=>{}),e++}return await G(),{recorded:e,skipped:n,empty:r}}async function We(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 Zn(o.repo,o.number,!0).catch(()=>{});return!1}try{return await kr(n.trackId),!0}catch(o){if(/already archived/i.test(String(o?.message||o)))return!1;throw o}}async function kw(t){return(await se().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 qe(t,e){return Ol(t,e,"merged")}async function Ol(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 sp(c.repo,c.number,i).catch(()=>{});return!1}let a=[...r.notifyPending||[],o];try{return await Se(r.trackId,RT(a),{kind:"merge-notify"}),!0}catch{return!1}}var Gh={merged:"merged",closed:"closed on GitHub WITHOUT merging",lost:"no longer found on GitHub (deleted, or its repo left the grant)"};function RT(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 ${Gh[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 ${Gh[i.outcome]??i.outcome}`).join(`
|
|
256
277
|
`),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}
|
|
257
278
|
${s}
|
|
258
|
-
${a}`}async function al(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=dh(e.agent,e.sessionId);if(!await _a(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 tn(e.name,e.cwd,{agentCmd:n,agent:e.agent,resumeSessionId:e.sessionId,pinned:e.pinned===!0}),Lo({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 Dh(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 al(n),await J(),Ge(n)}async function jh(){let t=await H(),e=new Set((await Pn()).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 Vt(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 J(),r}async function Lh(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 ht(n.leadBox,{lines:At}).catch(()=>"");if(Do(r.agent,a))throw new Error("lead is mid-turn \u2014 retry when it goes idle");await Vt(n.leadBox),r.state="stopped"}let s=await al(n);if(await J(),!s)throw new Error("rehydrate failed \u2014 see the track's rehydrate-failed event");return Ge(n)}var Fh=(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 Uh(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 lo=new Map,po=t=>lo.get(t)||(se?Z(se,t)?.handoff:null)||null;async function Gs(t,e){let n=await H(),r=Z(n,t);r&&(r.handoff={...r.handoff||{},...e},await J())}function nA(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 Hh(){let t=await H(),e=!1;for(let n of t.tracks||[]){let r=n.handoff,o=nA(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=Uh(l),f=d[d.length-1],p=f.agent,h=f.sessionId??null;f.endedAt=u,r.reason&&(f.endReason=r.reason);let m=c.agent==="claude"&&!!c.sessionId;d.push({agent:r.toAgent,sessionId:c.sessionId||null,pinned:m,startedAt:u,endedAt:null}),l.agent=r.toAgent,l.sessionId=c.sessionId||null,l.pinned=m,l.state="live",n.pendingHandoffHydrate={artifact:zs,at:u},ot(n,"lead-handoff",{from:r.fromBox,to:r.fromBox,fromAgent:p,toAgent:r.toAgent,fromSessionId:h,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 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 Vt(i).catch(()=>{}),a&&await Vt(a).catch(()=>{})}return e&&await J(),e}async function il(t,e,n=!0,r=null){let o=lo.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 Js(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:fh(r),toBox:s.leadBox,startedAt:Date.now(),finishedAt:null,error:null};return lo.set(t,c),await Gs(t,{step:c.step,fromBox:c.fromBox,toAgent:e,toBox:c.toBox,skipPermissions:n,startedAt:c.startedAt,finishedAt:null,error:null}),rA(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 Gs(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 rA(t,e,n,r,o){let s=async(E,M={})=>{t.step=E,console.log(`[lead-handoff ${t.trackId}] step \u2192 ${E}`),await Gs(t.trackId,{step:E,toBox:t.toBox}),await G({flow:"lead-handoff",phase:E,trackId:t.trackId,fromBox:t.fromBox,toBox:t.toBox,toAgent:o,sessionName:M.sessionName,...M})};console.log(`[lead-handoff ${t.trackId}] ${t.fromBox}: ${n.agent} \u2192 ${o} IN PLACE`);let a=await 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 Ou(t.fromBox),await s("swap",{cwd:n.cwd,agentCmd:r});let d=await Ri(t.fromBox,{agentCmd:r,agent:o});await s("hydrate",{sessionName:t.fromBox}),await Ae({cwd:n.cwd,sessionId:d.sessionId,sessionName:t.fromBox,agent:o,agentCmd:Vs(o)});let f=await ht(t.fromBox,{lines:At}).catch(()=>"");if(!jo(o,f)){await s("rollback",{sessionName:t.fromBox,fromAgent:c});let E=dh(c,l,{skipPermissions:t.skipPermissions});await Ri(t.fromBox,{agentCmd:E,agent:c,resumeSessionId:l,pinned:u});let M=await H(),lt=Z(M,t.trackId);if(lt){let I=lt.boxes.find($=>$.name===t.fromBox);I&&(I.state="live"),ot(lt,"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 J()}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(),h=Z(p,t.trackId);if(!h)throw new Error(`track "${t.trackId}" not found during handoff commit \u2014 was it deleted or archived mid-flight?`);let m=h.boxes.find(E=>E.name===t.fromBox);if(!m)throw new Error(`lead box row "${t.fromBox}" missing during handoff commit`);let b=Date.now(),k=Uh(m),v=k[k.length-1];v.endedAt=b,t.reason&&(v.endReason=t.reason),k.push({agent:o,sessionId:d.sessionId,pinned:d.pinned,startedAt:b,endedAt:null}),m.agent=o,m.sessionId=d.sessionId,m.pinned=d.pinned,m.state="live",m.role="lead",h.pendingHandoffHydrate={artifact:zs,at:b},ot(h,"lead-handoff",{from:t.fromBox,to:t.fromBox,fromAgent:c,toAgent:o,fromSessionId:l,toSessionId:d.sessionId,epoch:k.length-1,singleBox:!0,forced:d.forced||void 0,skipPermissions:t.skipPermissions,reason:t.reason,handoffStartedAt:t.startedAt}),await J(),await s("done",{sessionName:t.fromBox}),await Gs(t.trackId,{finishedAt:Date.now(),error:null}),t.finishedAt=Date.now()}var oA=Number(process.env.BOSTRAT_SUBTASK_TIMEOUT_MS)||18e5,sA=Number(process.env.BOSTRAT_SUBTASK_POLL_MS)||3e3,mr=L.join(qe,"worktrees"),fr=[ao,qs],Gt=new Map;var gr=t=>{let e=new Map([...Gt.values()].filter(r=>r.trackId===t).map(r=>[r.id,r])),n=se?Z(se,t):null;if(n?.subtasks)for(let[r,o]of Object.entries(n.subtasks))e.has(r)||e.set(r,o);return[...e.values()].sort((r,o)=>r.startedAt-o.startedAt)},cl=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 aA(t,e,n){let r=await H(),o=Z(r,t);o&&(o.subtasks=o.subtasks||{},o.subtasks[e]={...o.subtasks[e]||{},...n},await J())}var hr=t=>aA(t.trackId,t.id,cl(t));async function pr(t,e){t.step=e,await hr(t)}function iA(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 cA(t){return!!t&&!t.finishedAt&&!t.canceled&&t.step==="working"&&!!t.box&&!!t.dir&&!!t.base&&!!t.branch}async function lA(t,e,n){if(!cA(n)||Gt.has(e)||!await z(n.box).catch(()=>null))return!1;try{await j.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 Gt.set(e,s),Vh(s,o).catch(a=>Gh(s,a)),!0}async function Wh(){let t=await H(),e=!1;for(let n of t.tracks||[]){for(let[r,o]of Object.entries(n.subtasks||{})){if(Gt.has(r))continue;if(await lA(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=iA(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 G({flow:"subtask",phase:"reconciled",trackId:n.id,subtaskId:r,outcome:a.step,recovered:a.recovered,box:o.box,branch:o.branch}).catch(()=>{}),await lh(n,o.box,o.dir)}for(let r of n.boxes){if(r.role!=="subtask"||An.has(r.state)||Gt.has(r.subtaskId)||n.subtasks?.[r.subtaskId])continue;e=!0,r.state="canceled";let o=r.subtaskId?L.join(mr,`${n.id}-${r.subtaskId}`):null;await G({flow:"subtask",phase:"reaped-legacy-orphan",trackId:n.id,subtaskId:r.subtaskId,box:r.name}).catch(()=>{}),await lh(n,r.name,o)}}return e=await uA(t)||e,e&&await J(),e}async function lh(t,e,n){if(e&&await Vt(e).catch(()=>{}),!n)return;if(t.workspace?.kind==="multi"){for(let o of Rt(t.workspace)){let s=L.join(n,o.name);await Lt(o.base,s).then(()=>!0,()=>!1)||await Lt(o.dir,s).catch(()=>{})}await j.rm(n,{recursive:!0,force:!0}).catch(()=>{});return}let r=await z(t.leadBox).catch(()=>null);r?.cwd&&await Lt(r.cwd,n).catch(()=>{})}async function uA(t){let e;try{e=await j.readdir(mr)}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 Gt.values())o.dir&&n.add(L.basename(o.dir));let r=!1;for(let o of e){if(n.has(o))continue;let s=L.join(mr,o),a=(t.tracks||[]).find(i=>o.startsWith(`${i.id}-`));if(a?.workspace?.kind==="multi"){for(let i of Rt(a.workspace))await Lt(i.base,L.join(s,i.name)).catch(()=>{});await j.rm(s,{recursive:!0,force:!0}).catch(()=>{})}else{let i=a?await z(a.leadBox).catch(()=>null):null;i?.cwd?await Lt(i.cwd,s).catch(()=>{}):await j.rm(s,{recursive:!0,force:!0}).catch(()=>{})}await G({flow:"subtask",phase:"swept-orphan-worktree",dir:s}).catch(()=>{}),r=!0}return r}var ll=t=>t.trim().replace(/\s+/g,"-").replace(/[^A-Za-z0-9._-]/g,"").toLowerCase()||"track",uo=class extends Error{};async function qh(t,{agent:e,task:n,skipPermissions:r=!0,reason:o=null}={}){if(typeof n!="string"||!or(n))throw new Error("task (string) required");let s=await Js(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=Rt(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 Ss(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 Ss(i.cwd))throw new Error(`subtask delegation needs the lead's workspace to be a git repo (got ${i.cwd})`);let u=fo().slice(0,8),d={id:u,trackId:t,trackName:a.name,agent:e,task:or(n),skipPermissions:r,reason:fh(o),step:"snapshot",leadBox:a.leadBox,box:null,branch:`bostrat/${ll(a.name)}-${u}`,base:null,repos:l,dir:L.join(mr,`${t}-${u}`),stat:null,conflicts:[],applied:null,startedAt:Date.now(),finishedAt:null,error:null,canceled:!1,forceMerge:!1};return Gt.set(u,d),await hr(d),dA(d,i,s).catch(f=>Gh(d,f)),d}async function Gh(t,e){t.failedStep=t.step,t.error=e instanceof uo?"canceled":e?.message||String(e),t.step=e instanceof uo?"canceled":"error",t.finishedAt=Date.now(),await dl(t).catch(()=>{}),await hr(t).catch(()=>{}),await hA(t).catch(()=>{})}function ul(t){let e=Gt.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 Kh(t){let e=Gt.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 dA(t,e,n){if(await pr(t,"snapshot"),t.repos){for(let s of t.repos)s.base=await dc(s.leadDir,`bostrat: snapshot for subtask ${t.id}`);await hr(t)}else t.base=await dc(e.cwd,`bostrat: snapshot for subtask ${t.id}`);if(await pr(t,"spawn"),await j.mkdir(mr,{recursive:!0}),t.repos){await j.mkdir(t.dir,{recursive:!0});for(let a of t.repos)await sr(a.leadDir,L.join(t.dir,a.name),t.branch,a.base);let s=Rt((Z(await H(),t.trackId)||{}).workspace);await Ys(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 sr(e.cwd,t.dir,t.branch,t.base),await Zc(t.dir);await fA(t,e);let r=t.trackName.slice(0,38).trim(),o=await tn(`${r} ${t.agent} sub ${t.id}`,t.dir,{agentCmd:n,agent:t.agent});t.box=o.name,await pA(t,o),await ph(t.trackId,o.name,Sv(t.task,!!t.repos),{kind:"subtask"}),await Vh(t,e)}async function Vh(t,e){await pr(t,"working");let n=L.join(t.dir,qs);if(await gA(n,t),t.canceled)throw new uo;if(await pr(t,"merge"),t.repos){let o={files:0,insertions:0,deletions:0},s=[],a=[],i=!0;for(let c of t.repos){let l=L.join(t.dir,c.name);await fc(l,`bostrat: subtask ${t.id} result (auto-committed)`),c.stat=await wc(c.leadDir,c.base,t.branch,fr),c.files=await gc(c.leadDir,c.base,t.branch,fr);let u=await yc(c.leadDir,c.base,t.branch,fr);c.applied=u.applied,c.conflicts=u.conflicts??[],o.files+=c.stat.files,o.insertions+=c.stat.insertions,o.deletions+=c.stat.deletions,s.push(...c.files.map(d=>L.join(c.name,d))),a.push(...c.conflicts.map(d=>d.startsWith("patch did not apply cleanly")?`${c.name}: ${d}`:L.join(c.name,d))),u.applied||(i=!1)}t.stat=o,t.files=s,t.conflicts=a,t.applied=i}else{await fc(t.dir,`bostrat: subtask ${t.id} result (auto-committed)`),t.stat=await wc(e.cwd,t.base,t.branch,fr),t.files=await gc(e.cwd,t.base,t.branch,fr);let o=await yc(e.cwd,t.base,t.branch,fr);t.applied=o.applied,t.conflicts=o.conflicts}await hr(t);let r=await wA(t,e,n);await pr(t,"resume"),await ph(t.trackId,t.leadBox,vv(t.task,t.stat,t.conflicts,r),{kind:"subtask-resume"}),await pr(t,"reap"),await dl(t),t.step="done",t.finishedAt=Date.now(),await mA(t),await hr(t)}async function fA(t,e){let n=L.join(t.dir,L.dirname(ao));await j.mkdir(n,{recursive:!0});let r="";try{r=await iu(e.cwd,e.sessionId)}catch{}if(!r)try{r=await j.readFile(L.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}
|
|
279
|
+
${a}`}async function Cl(t){if(t.archivedAt)return!1;let e=t.boxes.find(r=>r.name===t.leadBox);if(!e||_n.has(e.state)||await X(e.name))return!1;let n=zh(e.agent,e.sessionId);if(!await Xo(n))return st(t,"rehydrate-failed",{box:e.name,agent:e.agent,reason:`agent "${e.agent}" not available on this box`}),!1;try{let r=await rn(e.name,e.cwd,{agentCmd:n,agent:e.agent,resumeSessionId:e.sessionId,pinned:e.pinned===!0});return e.hookTokenHash=Or(r.hookToken),ss({cwd:e.cwd,sessionName:e.name,agent:e.agent,agentCmd:n}),e.state="live",st(t,"rehydrated",{box:e.name,agent:e.agent}),!0}catch(r){return st(t,"rehydrate-failed",{box:e.name,agent:e.agent,reason:String(r?.message||r)}),!1}}async function xw(t){let e=await q(),n=tt(e,t);if(!n)throw new Error(`track "${t}" not found`);if(!n.archivedAt)throw new Error(`track "${t}" is not archived`);return delete n.archivedAt,st(n,"unarchived",{}),await Cl(n),await G(),ze(n)}async function Sw(){let t=await q(),e=new Set((await nn()).map(s=>s.name)),n=new Set((t.tracks||[]).filter(s=>!s.archivedAt).flatMap(s=>(s.boxes||[]).map(a=>a.name))),r=0,o=!1;for(let s of t.tracks||[])if(s.archivedAt)for(let a of s.boxes)e.has(a.name)&&!n.has(a.name)&&(await Jt(a.name).catch(()=>{}),st(s,"orphan-reaped",{box:a.name,agent:a.agent}),r++,o=!0),!_n.has(a.state)&&a.state!=="stopped"&&(a.state="stopped",o=!0);return o&&await G(),r}async function vw(t){let e=await q(),n=tt(e,t);if(!n)throw new Error(`track "${t}" not found`);if(n.archivedAt)throw new Error(`track "${t}" is archived \u2014 restore it before restarting the lead`);let r=n.boxes.find(a=>a.name===n.leadBox);if(!r)throw new Error(`track "${t}" has no lead box`);if(_n.has(r.state))throw new Error("lead box is terminal \u2014 nothing to restart");if(await X(n.leadBox)){let a=await wt(n.leadBox,{lines:At}).catch(()=>"");if(rs(r.agent,a))throw new Error("lead is mid-turn \u2014 retry when it goes idle");await Jt(n.leadBox),r.state="stopped"}let s=await Cl(n);if(await G(),!s)throw new Error("rehydrate failed \u2014 see the track's rehydrate-failed event");return ze(n)}var Aw=(t,e,n)=>({name:t.name,agent:t.agent,role:e,state:n,cwd:t.cwd,sessionId:t.sessionId,pinned:t.pinned===!0,hookTokenHash:Or(t.hookToken),createdAt:Date.now()});function Tw(t){return(!Array.isArray(t.epochs)||t.epochs.length===0)&&(t.epochs=[{agent:t.agent??null,sessionId:t.sessionId??null,pinned:t.pinned===!0,startedAt:t.createdAt??null,endedAt:null}]),t.epochs}var ko=new Map,Ao=t=>ko.get(t)||(le?tt(le,t)?.handoff:null)||null;async function pa(t,e){let n=await q(),r=tt(n,t);r&&(r.handoff={...r.handoff||{},...e},await G())}function $T(t,e=Date.now()){let n=t?.handoff;if(!n||n.finishedAt)return null;let r=n.step,o=!!n.toBox&&n.toBox===n.fromBox,s=t.boxes?.find?.(i=>i.name===t.leadBox)||null;return(o?t.leadBox===n.fromBox&&!!n.startedAt&&Array.isArray(s?.epochs)&&s.epochs.some(i=>i.startedAt!=null&&i.startedAt>=n.startedAt):!!n.toBox&&t.leadBox===n.toBox)?{...n,step:"done",finishedAt:e,recovered:`committed before restart (was "${r}")`}:{...n,step:"error",finishedAt:e,error:`interrupted by an agent restart during "${r}"; lead "${t.leadBox}" retained \u2014 retry the handoff`,recovered:"rolled-back"}}async function _w(){let t=await q(),e=!1;for(let n of t.tracks||[]){let r=n.handoff,o=$T(n);if(!o)continue;e=!0;let s=!!r.toBox&&r.toBox===r.fromBox;if(s&&o.step==="error"){let c=await X(r.fromBox).catch(()=>null),l=n.boxes.find(u=>u.name===r.fromBox);if(c&&l&&c.agent===r.toAgent&&r.toAgent!==l.agent){let u=Date.now(),f=Tw(l),d=f[f.length-1],m=d.agent,h=d.sessionId??null;d.endedAt=u,r.reason&&(d.endReason=r.reason);let w=c.agent==="claude"&&!!c.sessionId;f.push({agent:r.toAgent,sessionId:c.sessionId||null,pinned:w,startedAt:u,endedAt:null}),l.agent=r.toAgent,l.sessionId=c.sessionId||null,l.pinned=w,l.state="live",n.pendingHandoffHydrate={artifact:ga,at:u},st(n,"lead-handoff",{from:r.fromBox,to:r.fromBox,fromAgent:m,toAgent:r.toAgent,fromSessionId:h,toSessionId:c.sessionId||null,epoch:f.length-1,singleBox:!0,skipPermissions:r.skipPermissions,reason:r.reason??null,handoffStartedAt:r.startedAt,recovered:!0}),n.handoff={...r,step:"done",finishedAt:u,recovered:"swap adopted from live tmux tags after restart"},await V({flow:"lead-handoff",phase:"reconciled",trackId:n.id,outcome:"done",recovered:n.handoff.recovered,fromBox:r.fromBox,toBox:r.toBox}).catch(()=>{});continue}}let a=!s&&o.recovered==="rolled-back"&&r.toBox&&r.toBox!==n.leadBox?r.toBox:null,i=!s&&o.step==="done"?r.fromBox:null;n.handoff=o,await V({flow:"lead-handoff",phase:"reconciled",trackId:n.id,outcome:o.step,recovered:o.recovered,fromBox:o.fromBox,toBox:o.toBox}).catch(()=>{}),i&&await Jt(i).catch(()=>{}),a&&await Jt(a).catch(()=>{})}return e&&await G(),e}async function Ml(t,e,n=!0,r=null){let o=ko.get(t);if(o&&!o.finishedAt)throw new Error("a lead handoff is already running for this Track");let s=tt(await q(),t);if(!s)throw new Error(`track "${t}" not found`);let a=await wa(e,n),i=await X(s.leadBox);if(!i)throw new Error(`lead box "${s.leadBox}" is not running`);let c={trackId:t,step:"checkpoint",fromBox:s.leadBox,toAgent:e,skipPermissions:n,reason:Jh(r),toBox:s.leadBox,startedAt:Date.now(),finishedAt:null,error:null};return ko.set(t,c),await pa(t,{step:c.step,fromBox:c.fromBox,toAgent:e,toBox:c.toBox,skipPermissions:n,startedAt:c.startedAt,finishedAt:null,error:null}),OT(c,s,i,a,e).catch(async l=>{c.error=l?.message||String(l),c.finishedAt=Date.now(),console.error(`[lead-handoff ${t}] FAILED at step "${c.step}": ${c.error}`),await pa(t,{step:c.step,error:c.error,finishedAt:c.finishedAt}).catch(()=>{}),V({flow:"lead-handoff",phase:"failed",trackId:t,step:c.step,error:c.error,fromBox:c.fromBox,toAgent:c.toAgent,toBox:c.toBox}).catch(()=>{})}),V({flow:"lead-handoff",phase:"started",trackId:t,fromBox:c.fromBox,toAgent:e,cwd:i.cwd}).catch(()=>{}),c}async function OT(t,e,n,r,o){let s=async(_,C={})=>{t.step=_,console.log(`[lead-handoff ${t.trackId}] step \u2192 ${_}`),await pa(t.trackId,{step:_,toBox:t.toBox}),await V({flow:"lead-handoff",phase:_,trackId:t.trackId,fromBox:t.fromBox,toBox:t.toBox,toAgent:o,sessionName:C.sessionName,...C})};console.log(`[lead-handoff ${t.trackId}] ${t.fromBox}: ${n.agent} \u2192 ${o} IN PLACE`);let a=await q(),i=tt(a,t.trackId)?.boxes.find(_=>_.name===t.fromBox)||null,c=i?.agent||n.agent,l=i?.sessionId??n.sessionId??null,u=i?.pinned===!0;await s("checkpoint",{sessionName:t.fromBox}),await xd(t.fromBox),await s("swap",{cwd:n.cwd,agentCmd:r});let f=await ec(t.fromBox,{agentCmd:r,agent:o});await s("hydrate",{sessionName:t.fromBox}),await Re({cwd:n.cwd,sessionId:f.sessionId,sessionName:t.fromBox,agent:o,agentCmd:vo(o)});let d=await wt(t.fromBox,{lines:At}).catch(()=>"");if(!os(o,d)){await s("rollback",{sessionName:t.fromBox,fromAgent:c});let _=zh(c,l,{skipPermissions:t.skipPermissions});await ec(t.fromBox,{agentCmd:_,agent:c,resumeSessionId:l,pinned:u});let C=await q(),K=tt(C,t.trackId);if(K){let I=K.boxes.find(L=>L.name===t.fromBox);I&&(I.state="live"),st(K,"lead-handoff-failed",{box:t.fromBox,fromAgent:c,toAgent:o,reason:t.reason,error:`incoming ${o} CLI never drew its composer`,rolledBack:!0,handoffStartedAt:t.startedAt}),await G()}throw new Error(`incoming ${o} CLI never drew its composer \u2014 rolled back to ${c} (--resume, context intact)`)}await s("commit");let m=await q(),h=tt(m,t.trackId);if(!h)throw new Error(`track "${t.trackId}" not found during handoff commit \u2014 was it deleted or archived mid-flight?`);let w=h.boxes.find(_=>_.name===t.fromBox);if(!w)throw new Error(`lead box row "${t.fromBox}" missing during handoff commit`);let y=Date.now(),b=Tw(w),A=b[b.length-1];A.endedAt=y,t.reason&&(A.endReason=t.reason),b.push({agent:o,sessionId:f.sessionId,pinned:f.pinned,startedAt:y,endedAt:null}),w.agent=o,w.sessionId=f.sessionId,w.pinned=f.pinned,w.state="live",w.role="lead",h.pendingHandoffHydrate={artifact:ga,at:y},st(h,"lead-handoff",{from:t.fromBox,to:t.fromBox,fromAgent:c,toAgent:o,fromSessionId:l,toSessionId:f.sessionId,epoch:b.length-1,singleBox:!0,forced:f.forced||void 0,skipPermissions:t.skipPermissions,reason:t.reason,handoffStartedAt:t.startedAt}),await G(),await s("done",{sessionName:t.fromBox}),await pa(t.trackId,{finishedAt:Date.now(),error:null}),t.finishedAt=Date.now()}var CT=Number(process.env.BOSTRAT_SUBTASK_TIMEOUT_MS)||18e5,MT=Number(process.env.BOSTRAT_SUBTASK_POLL_MS)||3e3,yr=H.join(Ve,"worktrees"),wr=[go,fa],Vt=new Map;var xr=t=>{let e=new Map([...Vt.values()].filter(r=>r.trackId===t).map(r=>[r.id,r])),n=le?tt(le,t):null;if(n?.subtasks)for(let[r,o]of Object.entries(n.subtasks))e.has(r)||e.set(r,o);return[...e.values()].sort((r,o)=>r.startedAt-o.startedAt)},Nl=t=>({id:t.id,trackId:t.trackId,trackName:t.trackName,agent:t.agent,task:t.task,skipPermissions:t.skipPermissions,reason:t.reason||null,step:t.step,failedStep:t.failedStep||null,box:t.box,branch:t.branch,base:t.base,repos:t.repos||null,dir:t.dir,stat:t.stat,files:t.files||null,conflicts:t.conflicts||[],applied:t.applied,error:t.error,canceled:t.canceled,startedAt:t.startedAt,finishedAt:t.finishedAt});async function NT(t,e,n){let r=await q(),o=tt(r,t);o&&(o.subtasks=o.subtasks||{},o.subtasks[e]={...o.subtasks[e]||{},...n},await G())}var br=t=>NT(t.trackId,t.id,Nl(t));async function gr(t,e){t.step=e,await br(t)}function PT(t,e=Date.now()){if(!t||t.finishedAt)return null;let n=t.step;return["snapshot","spawn","working"].includes(n)?{...t,step:"error",finishedAt:e,error:`interrupted by an agent restart during "${n}"; no changes were applied to the lead \u2014 retry the delegation`,recovered:"rolled-back"}:{...t,step:"error",finishedAt:e,error:`interrupted by an agent restart during "${n}"; the lead's working tree may hold a PARTIAL merge \u2014 review git status. The subtask's work is preserved on branch ${t.branch}`,recovered:"partial-merge"}}function BT(t){return!!t&&!t.finishedAt&&!t.canceled&&t.step==="working"&&!!t.box&&!!t.dir&&!!t.base&&!!t.branch}async function DT(t,e,n){if(!BT(n)||Vt.has(e)||!await X(n.box).catch(()=>null))return!1;try{await U.access(n.dir)}catch{return!1}let o=await X(t.leadBox).catch(()=>null);if(!o?.cwd)return!1;let s={...n,id:e,leadBox:t.leadBox,step:"working",canceled:!1,forceMerge:!1};return Vt.set(e,s),Ow(s,o).catch(a=>Rw(s,a)),!0}async function Ew(){let t=await q(),e=!1;for(let n of t.tracks||[]){for(let[r,o]of Object.entries(n.subtasks||{})){if(Vt.has(r))continue;if(await DT(n,r,o)){e=!0,st(n,"subtask-resumed",{box:o.box,agent:o.agent,task:o.task,subtaskId:r,resumedStep:o.step}),await V({flow:"subtask",phase:"resumed",trackId:n.id,subtaskId:r,box:o.box,branch:o.branch}).catch(()=>{});continue}let s=o.step,a=PT(o);if(!a)continue;e=!0,n.subtasks[r]=a;let i=n.boxes.find(c=>c.subtaskId===r);i&&!_n.has(i.state)&&(i.state="canceled"),st(n,"subtask-failed",{from:o.box,fromAgent:o.agent,task:o.task,subtaskId:r,failedStep:s,error:a.error,recovered:a.recovered,durationMs:o.startedAt?a.finishedAt-o.startedAt:null}),await V({flow:"subtask",phase:"reconciled",trackId:n.id,subtaskId:r,outcome:a.step,recovered:a.recovered,box:o.box,branch:o.branch}).catch(()=>{}),await Kh(n,o.box,o.dir)}for(let r of n.boxes){if(r.role!=="subtask"||_n.has(r.state)||Vt.has(r.subtaskId)||n.subtasks?.[r.subtaskId])continue;e=!0,r.state="canceled";let o=r.subtaskId?H.join(yr,`${n.id}-${r.subtaskId}`):null;await V({flow:"subtask",phase:"reaped-legacy-orphan",trackId:n.id,subtaskId:r.subtaskId,box:r.name}).catch(()=>{}),await Kh(n,r.name,o)}}return e=await jT(t)||e,e&&await G(),e}async function Kh(t,e,n){if(e&&await Jt(e).catch(()=>{}),!n)return;if(t.workspace?.kind==="multi"){for(let o of Ot(t.workspace)){let s=H.join(n,o.name);await Ut(o.base,s).then(()=>!0,()=>!1)||await Ut(o.dir,s).catch(()=>{})}await U.rm(n,{recursive:!0,force:!0}).catch(()=>{});return}let r=await X(t.leadBox).catch(()=>null);r?.cwd&&await Ut(r.cwd,n).catch(()=>{})}async function jT(t){let e;try{e=await U.readdir(yr)}catch{return!1}let n=new Set;for(let o of t.tracks||[]){for(let s of Object.keys(o.subtasks||{}))n.add(`${o.id}-${s}`);for(let s of o.boxes||[])s.subtaskId&&n.add(`${o.id}-${s.subtaskId}`)}for(let o of Vt.values())o.dir&&n.add(H.basename(o.dir));let r=!1;for(let o of e){if(n.has(o))continue;let s=H.join(yr,o),a=(t.tracks||[]).find(i=>o.startsWith(`${i.id}-`));if(a?.workspace?.kind==="multi"){for(let i of Ot(a.workspace))await Ut(i.base,H.join(s,i.name)).catch(()=>{});await U.rm(s,{recursive:!0,force:!0}).catch(()=>{})}else{let i=a?await X(a.leadBox).catch(()=>null):null;i?.cwd?await Ut(i.cwd,s).catch(()=>{}):await U.rm(s,{recursive:!0,force:!0}).catch(()=>{})}await V({flow:"subtask",phase:"swept-orphan-worktree",dir:s}).catch(()=>{}),r=!0}return r}var Pl=t=>t.trim().replace(/\s+/g,"-").replace(/[^A-Za-z0-9._-]/g,"").toLowerCase()||"track",xo=class extends Error{};async function Iw(t,{agent:e,task:n,skipPermissions:r=!0,reason:o=null}={}){if(typeof n!="string"||!cr(n))throw new Error("task (string) required");let s=await wa(e,r),a=tt(await q(),t);if(!a)throw new Error(`track "${t}" not found`);let i=await X(a.leadBox);if(!i)throw new Error(`lead box "${a.leadBox}" is not running`);if(!i.cwd)throw new Error("can't resolve the lead's working directory");let c=a.workspace?.kind==="multi",l=null;if(c){let d=Ot(a.workspace);if(!d.length)throw new Error("this multi-repo Track has no tagged repos to delegate into");for(let m of d)if(!m?.dir||!await Gs(m.dir))throw new Error(`tagged checkout "${m?.name??"?"}" is missing from the lead workspace (${m?.dir??"no dir"})`);l=d.map(m=>({name:m.name,leadDir:m.dir,base:null}))}else if(!await Gs(i.cwd))throw new Error(`subtask delegation needs the lead's workspace to be a git repo (got ${i.cwd})`);let u=So().slice(0,8),f={id:u,trackId:t,trackName:a.name,agent:e,task:cr(n),skipPermissions:r,reason:Jh(o),step:"snapshot",leadBox:a.leadBox,box:null,branch:`bostrat/${Pl(a.name)}-${u}`,base:null,repos:l,dir:H.join(yr,`${t}-${u}`),stat:null,conflicts:[],applied:null,startedAt:Date.now(),finishedAt:null,error:null,canceled:!1,forceMerge:!1};return Vt.set(u,f),await br(f),LT(f,i,s).catch(d=>Rw(f,d)),f}async function Rw(t,e){t.failedStep=t.step,t.error=e instanceof xo?"canceled":e?.message||String(e),t.step=e instanceof xo?"canceled":"error",t.finishedAt=Date.now(),await Dl(t).catch(()=>{}),await br(t).catch(()=>{}),await WT(t).catch(()=>{})}function Bl(t){let e=Vt.get(t);if(!e)throw new Error(`subtask "${t}" not found`);if(e.finishedAt)throw new Error("subtask already finished");return e.canceled=!0,e}function $w(t){let e=Vt.get(t);if(!e)throw new Error(`subtask "${t}" not found`);if(e.finishedAt)throw new Error("subtask already finished");if(e.step!=="working")throw new Error(`subtask is not waiting to merge (step: ${e.step})`);return e.forceMerge=!0,e}async function LT(t,e,n){if(await gr(t,"snapshot"),t.repos){for(let s of t.repos)s.base=await Pc(s.leadDir,`bostrat: snapshot for subtask ${t.id}`);await br(t)}else t.base=await Pc(e.cwd,`bostrat: snapshot for subtask ${t.id}`);if(await gr(t,"spawn"),await U.mkdir(yr,{recursive:!0}),t.repos){await U.mkdir(t.dir,{recursive:!0});for(let a of t.repos)await lr(a.leadDir,H.join(t.dir,a.name),t.branch,a.base);let s=Ot((tt(await q(),t.trackId)||{}).workspace);await ba(t.dir,t.repos.map(a=>{let i=s.find(c=>c?.name===a.name);return{name:a.name,repo:i?.repo??null,baseRef:null}}),t.branch)}else await lr(e.cwd,t.dir,t.branch,t.base),await Al(t.dir);await FT(t,e);let r=t.trackName.slice(0,38).trim(),o=await rn(`${r} ${t.agent} sub ${t.id}`,t.dir,{agentCmd:n,agent:t.agent});t.box=o.name,await UT(t,o),await Xh(t.trackId,o.name,VA(t.task,!!t.repos),{kind:"subtask"}),await Ow(t,e)}async function Ow(t,e){await gr(t,"working");let n=H.join(t.dir,fa);if(await GT(n,t),t.canceled)throw new xo;if(await gr(t,"merge"),t.repos){let o={files:0,insertions:0,deletions:0},s=[],a=[],i=!0;for(let c of t.repos){let l=H.join(t.dir,c.name);await Bc(l,`bostrat: subtask ${t.id} result (auto-committed)`),c.stat=await Fc(c.leadDir,c.base,t.branch,wr),c.files=await Uc(c.leadDir,c.base,t.branch,wr);let u=await Hc(c.leadDir,c.base,t.branch,wr);c.applied=u.applied,c.conflicts=u.conflicts??[],o.files+=c.stat.files,o.insertions+=c.stat.insertions,o.deletions+=c.stat.deletions,s.push(...c.files.map(f=>H.join(c.name,f))),a.push(...c.conflicts.map(f=>f.startsWith("patch did not apply cleanly")?`${c.name}: ${f}`:H.join(c.name,f))),u.applied||(i=!1)}t.stat=o,t.files=s,t.conflicts=a,t.applied=i}else{await Bc(t.dir,`bostrat: subtask ${t.id} result (auto-committed)`),t.stat=await Fc(e.cwd,t.base,t.branch,wr),t.files=await Uc(e.cwd,t.base,t.branch,wr);let o=await Hc(e.cwd,t.base,t.branch,wr);t.applied=o.applied,t.conflicts=o.conflicts}await br(t);let r=await qT(t,e,n);await gr(t,"resume"),await Xh(t.trackId,t.leadBox,zA(t.task,t.stat,t.conflicts,r),{kind:"subtask-resume"}),await gr(t,"reap"),await Dl(t),t.step="done",t.finishedAt=Date.now(),await HT(t),await br(t)}async function FT(t,e){let n=H.join(t.dir,H.dirname(go));await U.mkdir(n,{recursive:!0});let r="";try{r=await Hu(e.cwd,e.sessionId)}catch{}if(!r)try{r=await U.readFile(H.join(e.cwd,".bostrat/handoff/latest.md"),"utf8")}catch{r="_(no prior conversation digest available \u2014 rely on the working tree.)_"}let o=`# Lead context for subtask ${t.id}
|
|
259
280
|
|
|
260
281
|
**Delegated task:** ${t.task}
|
|
261
282
|
|
|
@@ -266,42 +287,47 @@ ${a}`}async function al(t){if(t.archivedAt)return!1;let e=t.boxes.find(r=>r.name
|
|
|
266
287
|
## Conversation so far (lead \u2192 you)
|
|
267
288
|
|
|
268
289
|
${r}
|
|
269
|
-
`;await
|
|
270
|
-
${n}`}},{type:"section",text:{type:"mrkdwn",text:r}},{type:"context",elements:[{type:"mrkdwn",text:`<${
|
|
290
|
+
`;await U.writeFile(H.join(t.dir,go),o)}async function UT(t,e){let n=await q(),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:Or(e.hookToken),createdAt:Date.now(),subtaskId:t.id,branch:t.branch,task:t.task}),st(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]||{},...Nl(t)},await G())}async function HT(t){let e=await q(),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),st(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 G()}async function WT(t){let e=await q(),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"),st(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]||{},...Nl(t)},await G()}async function qT(t,e,n){let r=`.bostrat/handoff/subtask-${t.id}.md`;try{let o=H.join(e.cwd,r);await U.mkdir(H.dirname(o),{recursive:!0}),await U.copyFile(n,o)}catch{}return r}async function Dl(t){if(t.box&&await Jt(t.box).catch(()=>{}),t.repos&&t.dir){for(let e of t.repos)await Ut(e.leadDir,H.join(t.dir,e.name)).catch(()=>{});await U.rm(t.dir,{recursive:!0,force:!0}).catch(()=>{})}else if(t.dir){let n=(await X(t.leadBox).catch(()=>null))?.cwd;n&&await Ut(n,t.dir).catch(()=>{})}}async function GT(t,e){let n=(e.startedAt||Date.now())+CT;for(;;){if(e.canceled||e.forceMerge)return;try{await U.access(t);return}catch{}if(Date.now()>n)throw new Error("subtask timed out without signaling completion (no result digest written)");await To(MT)}}function To(t){return new Promise(e=>setTimeout(e,t))}import _o from"node:fs/promises";import Sa from"node:path";import XT from"node:os";import KT from"ws";var Cw=()=>process.env.BOSTRAT_SLACK_API_BASE||"https://slack.com/api";async function ka(t,e,n={}){let r=await fetch(`${Cw()}/${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 jl=(t,e)=>ka("chat.postMessage",t,e),Sr=(t,e)=>ka("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 VT(t,e,n={}){let r=await fetch(`${Cw()}/${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 Mw(t,{filename:e,buffer:n,title:r,channelId:o,threadTs:s,initialComment:a}){let{upload_url:i,file_id:c}=await VT("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 ka("files.completeUploadExternal",t,{files:[{id:c,title:r}],channel_id:o,thread_ts:s,...a?{initial_comment:a}:{}})}var zT=2200;function Nw({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 ka("apps.connections.open",u))}catch(u){return n(`connections.open failed: ${u?.message||u}`),c()}o=new KT(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()},zT);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 Dw=()=>process.env.BOSTRAT_STATE_DIR||Sa.join(process.env.HOME||XT.tmpdir(),".bostrat"),Wl=()=>Sa.join(Dw(),"slack.json"),xa=null,Ll=null,Fl=Promise.resolve();async function Gl(){return xa||(Ll??=_o.readFile(Wl(),"utf8").then(t=>JSON.parse(t)?.threads??{}).catch(()=>({})).then(t=>xa=t).finally(()=>Ll=null),Ll)}async function Pw(){let t=JSON.stringify({threads:xa},null,2),e=`${Wl()}.${process.pid}.tmp`;await _o.mkdir(Dw(),{recursive:!0}),await _o.writeFile(e,t),await _o.rename(e,Wl())}async function ve(){Fl=Fl.then(Pw,Pw),await Fl.catch(()=>{})}async function Kl(t){return(await Gl())[t]??null}async function YT(t,e){let n=await Gl();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 Eo(t,e){let n=String(t??"").trim();return n.length<=e?n:`${n.slice(0,e-1).trimEnd()}\u2026`}function ZT(t,e=500){let n=String(t??"").trim().split(/\n\s*\n/)[0]??"";return Eo(n.replace(/\n+/g," "),e)}function jw(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(Eo(t.kickoff,300))}`:"_no kickoff yet \u2014 reply in this thread to steer the lead_";return[{type:"section",text:{type:"mrkdwn",text:`${e}
|
|
291
|
+
${n}`}},{type:"section",text:{type:"mrkdwn",text:r}},{type:"context",elements:[{type:"mrkdwn",text:`<${Es(t.trackId)}|open the Track> \xB7 reachable on the operator's private network only`}]}]}async function ql(t,e){await Sr(t.botToken,{channel:e.channel,ts:e.ts,text:`Track: ${e.name}`,blocks:jw(e)})}async function In(t,e,n,r=void 0){return jl(t.botToken,{channel:e.channel,thread_ts:e.ts,text:n,...r?{blocks:r}:{},unfurl_links:!1})}var Bw=new Set;function En(t){Bw.add(t),t.catch(()=>{}).finally(()=>Bw.delete(t))}var dt=t=>({response_type:"ephemeral",text:t}),QT="*/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._",t_=["claude","cursor","codex"];function Ul(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 Hl=(t,e)=>dt(`No live Track matches \u201C${z(e)}\u201D. Live: ${t.map(n=>`\`${z(n.name)}\``).join(" \xB7 ")||"none"}`);async function Lw(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,{...dt(`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,{...dt("No designated channel configured \u2014 set `slack.channel` in integrations.json."),replace_original:!0});return}let i=await s.createTrack({name:n,...r?{repo:r}:{}}),c=i.workspace||{},l={trackId:i.id,channel:e.channel,name:i.name,branch:c.branch||null,repoLabel:c.repo||(c.base?Sa.basename(c.base):"fresh workspace"),lead:"claude"},u=await jl(e.botToken,{channel:e.channel,text:`Track started: ${l.name}`,blocks:jw(l),unfurl_links:!1});l.ts=u.ts,(await Gl())[i.id]=l,await ve(),await Ct(o,{...dt(`\u2713 *${z(l.name)}* started \u2014 root message posted.`),replace_original:!0})}catch(a){await Ct(o,{...dt(`Start failed: ${a?.message||a}`),replace_original:!0})}}async function e_(t,e,{track:n,responseUrl:r}){let o=t.deps;try{await o.archiveTrack(n.id);let s=await Kl(n.id);s&&(s.archivedAt=Date.now(),await ve(),await ql(e,s).catch(()=>{})),await Ct(r,{...dt(`\u2713 *${z(n.name)}* archived.`),replace_original:!0})}catch(s){await Ct(r,{...dt(`Archive failed: ${s?.message||s}`),replace_original:!0})}}async function n_(t,e,{track:n,agent:r,responseUrl:o}){try{await t.deps.startLeadHandoff(n.id,r),await Ct(o,{...dt(`\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,{...dt(`Handoff failed: ${s?.message||s}`),replace_original:!0})}}async function r_(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,{...dt(`\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,{...dt(`Stop failed: ${s?.message||s}`),replace_original:!0})}}function o_(t,e){let n=[...e.slice(0,99).map(r=>({text:{type:"plain_text",text:Eo(`${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 s_(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?Sa.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(`
|
|
271
292
|
`)+(o?`
|
|
272
|
-
_${o} archived_`:"")}async function
|
|
273
|
-
\u2713 *${
|
|
274
|
-
\u2713 _answered_`}}]})}async function
|
|
275
|
-
_runs on the Track's branch; reachable on the operator's private network only_`}}]);return}case"mock.state.changed":return
|
|
276
|
-
`)){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 zA(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 XA(t,e){return t?.transcript_path?t.transcript_path:e?.cwd&&e?.sessionId?ie(e.cwd,e.sessionId):e?.cwd?Oa(e.cwd):null}async function rw(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 YA(r,o,t,i);break;default:break}return a}async function YA(t,e,n,r){let o=[],s=XA(n,e);if(s)try{o=JA(await VA.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=zA(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 h of Object.keys(p.tokens))p.tokens[h]+=d.tokens[h];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:Jt(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 tT from"node:fs";import xl from"node:fs/promises";import eT from"node:http";import br from"node:path";import ra from"node:os";import{createRequire as nT}from"node:module";import Sl from"ws";import ZA from"node:path";import QA from"node:os";var _M=process.env.BOSTRAT_STATE_DIR||ZA.join(process.env.HOME||QA.tmpdir(),".bostrat");function ow(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 sw(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 na=(()=>{try{return nT(import.meta.url)("../package.json").version||null}catch{return null}})(),rT=process.env.BOSTRAT_STATE_DIR||br.join(process.env.HOME||ra.tmpdir(),".bostrat");function oT(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 sT({home:t=process.env.HOME||ra.homedir(),env:e=process.env}={}){let n=r=>{try{return tT.statSync(r),!0}catch{return!1}};return{claude:n(br.join(t,".claude",".credentials.json")),codex:n(br.join(e.CODEX_HOME||br.join(t,".codex"),"auth.json"))}}var vt=1,aw=1e3,aT=3e4,iw=100,iT=2e3,cT=25e3;function cw({url:t,token:e,nodeId:n=ra.hostname(),name:r=ra.hostname(),listTracks:o,selfUrl:s=`http://127.0.0.1:${process.env.PORT||3362}`,log:a=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=sw(t);let i=s.replace(/^http/,"ws"),c=new Map,l=new Map,u=br.join(rT,"plane-link","cursor.json"),d={},f=new Map,p=null,h=!1,m=!1,b=aw,k=null,v=null,E=null,M=!1;async function lt(){try{d=JSON.parse(await xl.readFile(u,"utf8"))}catch{d={}}}function I(){M=!0,!v&&(v=setTimeout(async()=>{if(v=null,!!M){M=!1;try{await xl.mkdir(br.dirname(u),{recursive:!0}),await xl.writeFile(u,JSON.stringify(d))}catch(R){a.error("[plane-link] cursor flush failed:",R?.message||R)}}},iT),v.unref?.())}function $(R){return!p||p.readyState!==Sl.OPEN?!1:(p.send(JSON.stringify(R)),!0)}function _(R){for(let{trackId:D,event:T}of R)f.set(T.id,{trackId:D,seq:T.seq});return $({v:vt,ch:"events",type:"append",events:R})}async function U(){let R;try{R=await o()}catch(D){a.error("[plane-link] backfill listTracks failed:",D?.message||D);return}$({v:vt,ch:"ctl",type:"tracks",ids:R.map(D=>D.id).filter(D=>typeof D=="string")});for(let D of R){if(!h)return;let T;try{T=await F(D)}catch{continue}let O=d[D.id]??0,W=T.filter(tt=>tt.seq>O&&typeof tt.id=="string");for(let tt=0;tt<W.length;tt+=iw){let it=W.slice(tt,tt+iw).map(yt=>({trackId:D.id,event:yt}));if(!_(it))return}}}function V(R){!h||!R?.trackId||typeof R.id!="string"||_([{trackId:R.trackId,event:R}])}let g=new Map;async function w(R){let{reqId:D,method:T,path:O,headers:W={},body:tt}=R,it=Object.fromEntries(Object.entries(W).filter(([,Bt])=>Bt!=null)),yt=new AbortController;g.set(D,yt);try{if(R.host){await y(R,it,yt);return}let Bt=await fetch(s+O,{method:T,headers:it,body:tt?Buffer.from(tt,"base64"):void 0,signal:yt.signal});if($({v:vt,ch:"control",type:"http_head",reqId:D,status:Bt.status,headers:{"content-type":Bt.headers.get("content-type")||"application/octet-stream"}}),Bt.body){for await(let Ye of Bt.body)if(!$({v:vt,ch:"control",type:"http_data",reqId:D,data:Buffer.from(Ye).toString("base64")}))break}$({v:vt,ch:"control",type:"http_end",reqId:D,error:null})}finally{g.delete(D)}}function y(R,D,T){let{reqId:O,method:W,path:tt,body:it}=R,yt=new URL(s);return new Promise(Bt=>{let Ye=eT.request({host:yt.hostname,port:yt.port,method:W,path:tt,headers:{...D,host:R.host},signal:T.signal},Ut=>{let{connection:x,"keep-alive":q,"transfer-encoding":En,upgrade:Io,...Ze}=Ut.headers;$({v:vt,ch:"control",type:"http_head",reqId:O,status:Ut.statusCode||502,headers:Ze}),Ut.on("data",ga=>{$({v:vt,ch:"control",type:"http_data",reqId:O,data:Buffer.from(ga).toString("base64")})||Ut.destroy()}),Ut.on("end",()=>{$({v:vt,ch:"control",type:"http_end",reqId:O,error:null}),Bt()}),Ut.on("error",()=>{$({v:vt,ch:"control",type:"http_end",reqId:O,error:"view upstream error"}),Bt()})});Ye.on("error",Ut=>{$({v:vt,ch:"control",type:"http_end",reqId:O,error:Ut?.message||"view request failed"}),Bt()}),it&&Ye.write(Buffer.from(it,"base64")),Ye.end()})}function A(){if(m)return;p=new Sl(t,{headers:{authorization:`Bearer ${e}`}}),p.on("open",()=>{$({v:vt,ch:"ctl",type:"hello",nodeId:n,name:r,platform:process.platform,version:na,providers:sT()})}),p.on("message",D=>{let T;try{T=JSON.parse(D)}catch{return}if(T.ch==="ctl"&&T.type==="hello_ok"){h=!0,b=aw,a.log(`[plane-link] connected to plane as ${n}`),T.minNodeVersion&&na&&oT(na,T.minNodeVersion)&&a.error(`[plane-link] this node is bostrat-node ${na}; the plane asks for \u2265${T.minNodeVersion} \u2014 update with: npm install -g bostrat-node`),E&&clearInterval(E),E=setInterval(()=>$({v:vt,ch:"ctl",type:"ping"}),cT),E.unref?.(),U().catch(O=>a.error("[plane-link] backfill failed:",O?.message||O));return}if(T.ch==="events"&&T.type==="ack"&&Array.isArray(T.ids)){for(let O of T.ids){let W=f.get(O);W&&(f.delete(O),(d[W.trackId]??0)<W.seq&&(d[W.trackId]=W.seq))}I();return}if(T.ch==="control"&&T.type==="http_open"){w(T).catch(O=>$({v:vt,ch:"control",type:"http_end",reqId:T.reqId,error:O?.message||"tunnel error"}));return}if(T.ch==="control"&&T.type==="http_cancel"){g.get(T.reqId)?.abort();return}if(T.ch==="attach"&&T.type==="open"){let O=new Sl(i+T.path,Array.isArray(T.protocols)?T.protocols:[],T.host?{headers:{host:T.host}}:void 0);c.set(T.streamId,O),l.set(T.streamId,[]),O.on("open",()=>{let tt=l.get(T.streamId)||[];l.delete(T.streamId);for(let{buf:it,bin:yt}of tt)O.send(it,{binary:yt})}),O.on("message",(tt,it)=>{let yt=it?tt:Buffer.from(String(tt));$({v:vt,ch:"attach",type:"data",streamId:T.streamId,data:yt.toString("base64"),...it?{bin:!0}:{}})});let W=()=>{l.delete(T.streamId),c.delete(T.streamId)&&$({v:vt,ch:"attach",type:"close",streamId:T.streamId})};O.on("close",W),O.on("error",W);return}if(T.ch==="attach"&&T.type==="data"){let O=c.get(T.streamId);if(!O)return;let W=Buffer.from(T.data,"base64"),tt=T.bin===!0,it=l.get(T.streamId);it?it.push({buf:W,bin:tt}):O.send(W,{binary:tt});return}if(T.ch==="attach"&&T.type==="close"){let O=c.get(T.streamId);c.delete(T.streamId),l.delete(T.streamId),O?.close();return}});let R=()=>{h&&a.log("[plane-link] disconnected from plane"),h=!1,E&&(clearInterval(E),E=null),f.clear();for(let D of c.values())D.close();c.clear(),l.clear();for(let D of g.values())D.abort();g.clear(),!(m||k)&&(k=setTimeout(()=>{k=null,A()},b),k.unref?.(),b=Math.min(b*2,aT))};p.on("close",R),p.on("error",R)}return Dt.on("event",V),lt().then(A),{stop:()=>{m=!0,Dt.off("event",V),k&&clearTimeout(k),v&&clearTimeout(v),E&&clearInterval(E),p?.close()},status:()=>({connected:h,nodeId:n,cursor:{...d},inflight:f.size})}}function lT(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 lw=()=>({input:0,output:0,cacheRead:0,cacheCreation:0}),uw=(t,e)=>{for(let n of Object.keys(t))t[n]+=e?.[n]||0},vl=t=>Math.round(t*1e4)/1e4;async function dw(t){let e=[],n={},r={},o=0,s=0,a=0,i=0,c=new Set,l=lw(),u=0,d=[];for(let f of t||[]){let p=await F(f),h=Te(p,{boxLabels:lT(f)});if(h.turns===0){f.archivedAt?u++:d.push(f.name||f.id);continue}o+=h.totalUsd,s+=h.turns,a+=h.repricedTurns||0,i+=h.unpricedTurns||0;for(let m of h.unpricedModels||[])c.add(m);uw(l,h.totalTokens);for(let[m,b]of Object.entries(h.byModel))n[m]||={usd:0,events:0,tokens:lw()},n[m].usd=vl(n[m].usd+b.usd),n[m].events+=b.events,uw(n[m].tokens,b.tokens);for(let[m,b]of Object.entries(h.byProvider))r[m]||={usd:0,events:0},r[m].usd=vl(r[m].usd+b.usd),r[m].events+=b.events;e.push({id:f.id,name:f.name,usd:h.totalUsd,rollup:h})}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:vl(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 wo=t=>`$${t.toFixed(4)}`,kr=(t,e)=>String(t).padEnd(e);function fw(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 ${wo(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(` ${kr(a,26)} ${kr(wo(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(` ${kr(a.name||a.id,30)} ${wo(a.usd)}`);let i=Object.values(a.rollup.byBox).sort((c,l)=>l.usd-c.usd);for(let c of i)if(r.push(` ${kr(c.label,40)} ${kr(wo(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))}${kr(wo(u.usd),10)} ${u.models.join(",")||"?"}`)}return r.join(`
|
|
277
|
-
`)}import yT from"node:fs/promises";import bT from"node:path";var uT=["bug-fix","orchestration","design","ops-infra","docs-writing","research","feature-build"],dT={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 oa(t){let e=String(t||"").toLowerCase();if(!e.trim())return null;let n=null;for(let r of uT){let o=0;for(let s of dT[r])s.test(e)&&o++;o>0&&(!n||o>n.score)&&(n={category:r,score:o})}return n}var fT=new Set(["Read","Grep","Glob","LS","WebSearch","WebFetch","NotebookRead"]),pT=new Set(["Edit","Write","MultiEdit","NotebookEdit"]);function pw(t){let e=0,n=0;for(let[r,o]of Object.entries(t||{}))fT.has(r)&&(e+=o||0),pT.has(r)&&(n+=o||0);return e>=5&&n===0?{category:"research",score:1}:n>=3?{category:"feature-build",score:1}:null}function Al(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 sa=t=>t.payload?.ts??t.ts,mT=12e4,Ke=t=>Math.round(t*1e4)/1e4,hT=()=>({usd:0,activeMs:0,turns:0,toolCalls:0,models:{},providers:{},byTrack:new Map});function wT(t,{since:e}){let{track:n,events:r,brief:o}=t,s=v=>sa(v)>=e,a=oa([Al(n?.name),Al(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=oa(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)=>sa(v)-sa(E)),f=0,p=0,h=0,m={turns:0};for(let v of d){let E=l(v.boxId),M=E.segments[E.segments.length-1],lt=sa(v);switch(E.lastTs!=null&<>E.lastTs&&s(v)&&(M.activeMs+=Math.min(lt-E.lastTs,mT)),E.lastTs=lt,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 $=u(oa(I));E.segments.push($),s(v)&&($.turns+=1);break}case"tool.called":{if(v.source==="transcript")break;let I=v.payload?.tool;I&&(M.tools[I]=(M.tools[I]||0)+1),s(v)&&(M.toolCalls+=1);break}case"cost.recorded":{if(!s(v))break;let I=v.payload||{},$={input:I.tokens?.input||0,output:I.tokens?.output||0,cacheRead:I.tokens?.cacheRead||0,cacheWrite:I.tokens?.cacheWrite??I.tokens?.cacheCreation??0},_=Number(I.usd)||0;!_&&I.tokens&&(_=Jt(I.model,$)),!_&&I.tokens&&!Go(I.model)&&(m.turns+=1),M.usd+=_,M.tokens.input+=$.input,M.tokens.output+=$.output,M.tokens.cacheRead+=$.cacheRead,M.tokens.cacheWrite+=$.cacheWrite;let U=I.model||"unknown";M.models[U]=(M.models[U]||0)+_,v.provider&&(M.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"&&(h+=1);break;default:break}}let b=new Map,k=!1;for(let[v,E]of c)for(let M of E.segments){if(!(M.usd>0||M.activeMs>0||M.turns>0||M.toolCalls>0))continue;let I=M.textCat?.category??i.get(v)?.category??pw(M.tools)?.category??a?.category??"uncategorized";b.has(I)||b.set(I,{usd:0,activeMs:0,turns:0,toolCalls:0,models:{},providers:{}});let $=b.get(I);$.usd+=M.usd,$.activeMs+=M.activeMs,$.turns+=M.turns,$.toolCalls+=M.toolCalls;for(let[U,V]of Object.entries(M.models))$.models[U]=($.models[U]||0)+V;let _=M.provider||E.provider;_&&($.providers[_]=($.providers[_]||0)+M.usd),_==="cursor"&&M.usd===0&&(k=!0)}return{perCategory:b,handoffs:f,merged:p,failed:h,sawCursorActivity:k,unpricedTurns:m.turns}}function hw(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 m of t||[]){let{track:b,events:k}=m;if(!k?.length){a+=1;continue}let{perCategory:v,handoffs:E,merged:M,failed:lt,sawCursorActivity:I,unpricedTurns:$}=wT(m,{since:r});I&&(i=!0),c+=$;let _={trackId:b?.id,name:b?.name||b?.id,archived:!!b?.archivedAt,usd:0,activeMs:0,turns:0,categories:{},models:{},providers:{},handoffs:E,subtasksMerged:M,subtasksFailed:lt};for(let[U,V]of v){_.usd+=V.usd,_.activeMs+=V.activeMs,_.turns+=V.turns,_.categories[U]={usd:Ke(V.usd),activeMs:V.activeMs};for(let[y,A]of Object.entries(V.models))_.models[y]=Ke((_.models[y]||0)+A);for(let[y,A]of Object.entries(V.providers))_.providers[y]=Ke((_.providers[y]||0)+A);o.has(U)||o.set(U,hT());let g=o.get(U);g.usd+=V.usd,g.activeMs+=V.activeMs,g.turns+=V.turns,g.toolCalls+=V.toolCalls;for(let[y,A]of Object.entries(V.models))g.models[y]=(g.models[y]||0)+A;for(let[y,A]of Object.entries(V.providers))g.providers[y]=(g.providers[y]||0)+A;let w=g.byTrack.get(b?.id)||{usd:0,activeMs:0};w.usd+=V.usd,w.activeMs+=V.activeMs,g.byTrack.set(b?.id,w)}(_.usd>0||_.activeMs>0||_.turns>0)&&(_.usd=Ke(_.usd),_.dominantCategory=gT(_.categories),s.push(_))}let l=[...o.values()].reduce((m,b)=>m+b.usd,0),u=[...o.values()].reduce((m,b)=>m+b.activeMs,0),d=l>0?"usd":"time",f=d==="usd"?l:u,p=new Map((t||[]).map(m=>[m.track?.id,m.track?.name||m.track?.id])),h=[...o.entries()].map(([m,b])=>({category:m,sharePct:f>0?Math.round((d==="usd"?b.usd:b.activeMs)/f*100):0,shareBasis:d,usd:Ke(b.usd),activeMs:b.activeMs,turns:b.turns,toolCalls:b.toolCalls,models:mw(b.models),providers:mw(b.providers),topWorkstreams:[...b.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:p.get(k),usd:Ke(v.usd),activeMs:v.activeMs}))})).sort((m,b)=>b.usd-m.usd||b.activeMs-m.activeMs);return s.sort((m,b)=>b.usd-m.usd||b.activeMs-m.activeMs),{since:r,capturedAt:n,totalUsd:Ke(l),activeMs:u,turns:[...o.values()].reduce((m,b)=>m+b.turns,0),bullets:h,workstreams:s,gaps:{tracksWithoutEvents:a,cursorCostUnknown:i,unpricedTurns:c}}}var gT=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},mw=t=>Object.fromEntries(Object.entries(t).map(([e,n])=>[e,Ke(n)]));var Tl={"24h":24*36e5,"7d":168*36e5,"30d":720*36e5,all:null};function kT(t){let e=String(t||"7d");return e in Tl?{key:e,ms:Tl[e]}:{key:"7d",ms:Tl["7d"]}}function xT(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 ST(t){if(!t?.cwd)return null;try{let n=(await yT.readFile(bT.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 ww(t,{window:e="7d",now:n=Date.now()}={}){let{key:r,ms:o}=kT(e),s=[];for(let a of t||[]){let i=await F(a);s.push({track:a,events:xT(a,i),brief:await ST(a)})}return{window:r,...hw(s,{windowMs:o,now:n})}}import xr from"node:fs/promises";import vT from"node:fs";import mt from"node:path";import AT from"node:os";import{execFile as TT}from"node:child_process";import{promisify as _T}from"node:util";var ET=_T(TT),gw=process.env.BOSTRAT_STATE_DIR||mt.join(process.env.HOME||AT.tmpdir(),".bostrat"),_l=!1;async function go(t,e){let n=await xr.stat(t).catch(()=>null);if(!n?.isFile())return!1;let r=await xr.stat(e).catch(()=>null);return r&&r.size===n.size&&r.mtimeMs>=n.mtimeMs?!1:(await xr.mkdir(mt.dirname(e),{recursive:!0}),await xr.copyFile(t,e),!0)}var IT=new Set([".claude",".codex",".credentials.json","auth.json",".claude.json","integrations.json"]);async function yo(t,e){let n=0,r=await xr.readdir(t,{withFileTypes:!0}).catch(()=>[]);for(let o of r){if(IT.has(o.name))continue;let s=mt.join(t,o.name);o.isDirectory()?n+=await yo(s,mt.join(e,o.name)):o.isFile()&&(n+=await go(s,mt.join(e,o.name))?1:0)}return n}async function yw({stateDir:t=gw,dest:e=process.env.BOSTRAT_SYNC_DIR||`${gw}-sync`,remote:n=process.env.BOSTRAT_STATE_REMOTE||null,tracks:r=null}={}){try{await xr.mkdir(e,{recursive:!0});let o=0;o+=await go(mt.join(t,"tracks.json"),mt.join(e,"tracks.json"))?1:0,o+=await go(mt.join(t,"mocks.json"),mt.join(e,"mocks.json"))?1:0,o+=await go(mt.join(t,"runbooks.json"),mt.join(e,"runbooks.json"))?1:0,o+=await yo(mt.join(t,"runbooks"),mt.join(e,"runbooks")),o+=await yo(mt.join(t,"mock-archive"),mt.join(e,"mock-archive")),o+=await go(mt.join(t,"events.jsonl"),mt.join(e,"events.jsonl"))?1:0,o+=await yo(mt.join(t,"events"),mt.join(e,"events"));for(let l of r||[])!l?.cwd||!l?.id||(o+=await yo(mt.join(l.cwd,".bostrat"),mt.join(e,"workspaces",l.id,".bostrat")));let s=(...l)=>ET("git",["-C",e,...l]);vT.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 Ni(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||_l))try{await s("push","-q",n,"HEAD:refs/heads/main"),c=!0,_l=!1}catch{_l=!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 RT}from"node:child_process";import{promisify as $T}from"node:util";import{randomUUID as OT}from"node:crypto";import CT from"node:fs";import MT from"node:os";import PT from"node:path";var Je=$T(RT),aa=t=>new Promise(e=>setTimeout(e,t)),ze=va,El=process.env.BOSTRAT_USAGE_PROBE_CWD||PT.join(MT.homedir(),".bostrat","usage-probe"),NT=Number(process.env.BOSTRAT_USAGE_IDLE_REAP_MS)||5*6e4,BT=Number(process.env.BOSTRAT_USAGE_PANEL_TIMEOUT_MS)||15e3,DT=Number(process.env.BOSTRAT_USAGE_READY_TIMEOUT_MS)||45e3,bw=Number(process.env.BOSTRAT_USAGE_CACHE_MS)||2e4,Ve=null,bo=null,ko=null;async function jT(){try{return await Je(P,N("has-session","-t",ze)),!0}catch{return!1}}function LT(){Ve&&clearTimeout(Ve),Ve=setTimeout(()=>FT().catch(()=>{}),NT),Ve.unref&&Ve.unref()}async function FT(){Ve&&(clearTimeout(Ve),Ve=null);try{await Je(P,N("kill-session","-t",ze))}catch{}}async function UT(){let t=Date.now()+DT;for(;;){let e=await Il().catch(()=>"");if(/for agents|for shortcuts|bypass permissions on/i.test(e)){await aa(500);return}if(Date.now()>t)throw new Error("usage probe didn't reach the prompt (trust/onboarding may be blocking)");await aa(500)}}async function HT(){if(await jT())return;if(!await xe())throw new Error("host tmux server not running \u2014 start globo-tmux.service");CT.mkdirSync(El,{recursive:!0}),await ke(El);let t=OT();await Je(P,N("new-session","-d","-s",ze,"-c",El,"-x","200","-y","60"));let e=`${Sa} --session-id ${t}`;await Je(P,N("send-keys","-t",ze,e,"Enter")),await UT()}async function Il(){let{stdout:t}=await Je(P,N("capture-pane","-t",ze,"-p"));return t}async function WT(){await HT(),await Je(P,N("send-keys","-t",ze,"-l","/usage")),await Je(P,N("send-keys","-t",ze,"Enter"));let t=Date.now()+BT,e="";for(;await aa(400),e=await Il(),!(/Current\s+(session|week)/i.test(e)||/\d+%\s*used/i.test(e)||Date.now()>t););return await aa(900),e=await Il(),await Je(P,N("send-keys","-t",ze,"Escape")).catch(()=>{}),LT(),e}function qT(t){let e=String(t||"").split(`
|
|
278
|
-
`).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
|
|
279
|
-
`).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
|
|
280
|
-
`),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:
|
|
293
|
+
_${o} archived_`:"")}async function a_(t,e){let n=await dn();if(!n)return dt("Slack integration not configured.");let r=t.deps,o=(e.text||"").trim().split(/\s+/).filter(Boolean),s=(o[0]||"help").toLowerCase(),a=o.slice(1);switch(s){case"start":{if(!a.length)return dt("Usage: `/bostrat start <name> [repo]`");let i=await r.listRepos(),c=a[a.length-1]?.toLowerCase(),l=a.length>1?i.find(f=>f.name.toLowerCase()===c):null,u=(l?a.slice(0,-1):a).join(" ");return l?(En(Lw(t,n,{name:u,repo:l.path,responseUrl:e.response_url})),dt(`Starting *${z(u)}* on ${z(l.name)}\u2026`)):{response_type:"ephemeral",text:`Pick a repo for \u201C${u}\u201D`,blocks:o_(u,i)}}case"archive":{if(!a.length)return dt("Usage: `/bostrat archive <track>`");let{match:i,live:c}=Ul(await r.listTracks(),a.join(" "));return i?(En(e_(t,n,{track:i,responseUrl:e.response_url})),dt(`Archiving *${z(i.name)}*\u2026`)):Hl(c,a.join(" "))}case"handoff":{let i=r.knownAgents??t_,c=a.at(-1)?.toLowerCase();if(a.length<2||!i.includes(c))return dt(`Usage: \`/bostrat handoff <track> <${i.join("|")}>\``);let l=a.slice(0,-1).join(" "),{match:u,live:f}=Ul(await r.listTracks(),l);return u?(En(n_(t,n,{track:u,agent:c,responseUrl:e.response_url})),dt(`Handing *${z(u.name)}* to ${z(c)}\u2026`)):Hl(f,l)}case"stop":{if(!a.length)return dt("Usage: `/bostrat stop <track>`");let{match:i,live:c}=Ul(await r.listTracks(),a.join(" "));return i?(En(r_(t,n,{track:i,responseUrl:e.response_url})),dt(`Stopping *${z(i.name)}*\u2026`)):Hl(c,a.join(" "))}case"list":return dt(await s_(r));default:return dt(QT)}}async function i_(t,e,n,r){let{trackId:o,promptId:s,label:a}=r,i=await Kl(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 ve(),await Sr(e.botToken,{channel:i.channel,ts:c,text:`\u2713 ${a}`,blocks:[{type:"section",text:{type:"mrkdwn",text:`~${z(l||"question")}~
|
|
294
|
+
\u2713 *${z(a)}*`}}]}).catch(()=>{})}}catch(c){await Ct(n.response_url,dt(`Couldn't answer: ${c?.message||c}`))}}async function c_(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,Xs(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,dt(`Couldn't act on the offer: ${c?.message||c}`))}}async function l_(t,e){let n=e.actions?.[0];if(!n?.action_id)return;let r=await dn();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,{...dt(`Starting *${z(s)}*\u2026`),replace_original:!0}),En(Lw(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"?En(i_(t,r,e,o)):n.action_id==="bostrat-ship"&&En(c_(t,r,e,o))}async function u_(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 YT(e.channel,e.thread_ts);if(!n)return;let r=await dn();if(r)try{await t.deps.sayToLead(n.trackId,e.text.trim())}catch(o){await In(r,n.rec,`Couldn't deliver that to the lead: ${o?.message||o}`).catch(()=>{})}}function d_(t){return async e=>{if(e.type==="slash_commands")return a_(t,e.payload);if(e.type==="interactive"){await l_(t,e.payload);return}if(e.type==="events_api"){await u_(t,e.payload?.event);return}}}var f_=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 p_(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:Eo(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 m_(t,e,n){let r=n.payload||{};if(!r.promptId||e.prompt?.promptId===r.promptId)return;e.prompt&&await Sr(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 In(t,e,r.question||"The lead needs an answer",p_(n.trackId,r));e.prompt={promptId:r.promptId,ts:o.ts,question:r.question||null},await ve()}async function h_(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 ve(),await Sr(t.botToken,{channel:e.channel,ts:o,text:"answered",blocks:[{type:"section",text:{type:"mrkdwn",text:`~${z(s||"question")}~
|
|
295
|
+
\u2713 _answered_`}}]})}async function w_(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 In(t,e,r.excerpt||"Ship it?",[{type:"section",text:{type:"mrkdwn",text:`\u{1F6A2} ${z(Eo(r.excerpt||"The lead offered to ship.",300))}`}},{type:"actions",elements:s}]);e.offers=e.offers||{},e.offers[r.offerId]=a.ts,await ve()}async function g_(t,e,n){let r=n.payload||{},o=e.offers?.[r.offerId];if(!o)return;delete e.offers[r.offerId],await ve();let s=r.action==="dismiss"?"dismissed":r.action==="pr"?"\u2713 opening a PR":"\u2713 pushing";await Sr(t.botToken,{channel:e.channel,ts:o,text:s,blocks:[{type:"section",text:{type:"mrkdwn",text:`\u{1F6A2} ${s}`}}]})}async function y_(t,e,n,r){let o=n.payload||{};if(o.change==="ready"&&o.mockId){let s=await(r.readMockPng??b_)(o.mockId,o.version??null);if(!s)return;let a=(o.version??1)>1?` (v${o.version})`:"";await Mw(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 In(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 b_(t,e){let n=await $e(t);if(!n)return null;let r=an(n,e);if(!r)return null;let o=await _o.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 k_(t,e){if(!f_.has(e.type))return;let n=await Kl(e.trackId);if(!n)return;let r=await dn();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 ve(),await ql(r,n);return;case"turn.agent":{if(!o.text)return;let s=ZT(o.text);if(!s)return;await In(r,n,s,[{type:"section",text:{type:"mrkdwn",text:z(s)}},{type:"context",elements:[{type:"mrkdwn",text:`<${Es(e.trackId)}|full turn \u2192>`}]}]);return}case"prompt.raised":return m_(r,n,e);case"prompt.answered":return h_(r,n,e);case"pr.offered":return w_(r,n,e);case"pr.offer.answered":return g_(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 In(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 ve(),await ql(r,n);return;case"view.state.changed":{if(o.change!=="created"||o.restarted||!o.slug)return;await In(r,n,`Live preview ${o.name}: ${$s(o.slug)}`,[{type:"section",text:{type:"mrkdwn",text:`\u{1F50D} live preview *${z(o.name)}*: ${$s(o.slug)}
|
|
296
|
+
_runs on the Track's branch; reachable on the operator's private network only_`}}]);return}case"mock.state.changed":return y_(r,n,e,t.deps)}}function Fw(t){let e={deps:t};Lt.on("event",s=>{k_(e,s).catch(()=>{})});let n=null,r=null,o=async()=>{n||!await dn()||(r&&clearInterval(r),r=null,n=Nw({getToken:async()=>(await dn())?.appToken,onEnvelope:d_(e),log:s=>console.log(`[slack] ${s}`)}))};return r=setInterval(()=>o().catch(()=>{}),3e4),r.unref?.(),o().catch(()=>{}),()=>{r&&clearInterval(r),n?.()}}import x_ from"node:fs/promises";function S_(t){let e=new Map;for(let n of String(t||"").split(`
|
|
297
|
+
`)){let r=n.trim();if(!r||r[0]!=="{")continue;let o;try{o=JSON.parse(r)}catch{continue}let s=o?.message?.usage;if(!s)continue;let a={input:s.input_tokens||0,output:s.output_tokens||0,cacheWrite:s.cache_creation_input_tokens||0,cacheRead:s.cache_read_input_tokens||0};if(a.input+a.output+a.cacheWrite+a.cacheRead===0)continue;let i=o.message?.id||o.uuid||null;i&&e.set(i,{messageId:i,tokens:a,model:o.message?.model||o.model||null})}return[...e.values()]}function v_(t){let e=new Set;for(let n of t)if(n.type==="cost.recorded"){n.payload?.messageId&&e.add(n.payload.messageId);for(let r of n.payload?.messageIds||[])e.add(r)}return e}function A_(t,e){return t?.transcript_path?t.transcript_path:e?.cwd&&e?.sessionId?de(e.cwd,e.sessionId):e?.cwd?ni(e.cwd):null}async function Uw(t,e){let n=await e(t?.session_id,t?.cwd);if(!n?.track)return[];let{track:r,box:o}=n,s={boxId:o?.name||null,provider:"claude",source:"hook"},a=[],i=async c=>{a.push(await O(r,{...s,...c}))};switch(t?.hook_event_name){case"SessionStart":await i({type:"session.ready",actor:"system",payload:{sessionId:t.session_id}});break;case"UserPromptSubmit":await i({type:"turn.user",actor:"user",payload:{promptHead:String(t.prompt??t.user_prompt??"").slice(0,160)||null}});break;case"PostToolUse":await i({type:"tool.called",actor:"agent",payload:{tool:t.tool_name??null}});break;case"Stop":await T_(r,o,t,i);break;default:break}return a}async function T_(t,e,n,r){let o=[],s=A_(n,e);if(s)try{o=S_(await x_.readFile(s,"utf8"))}catch{}let a=o.length?o[o.length-1]:null;if(await r({type:"turn.agent",actor:"agent",payload:{model:a?.model??e?.model??null,messageId:a?.messageId??null}}),!o.length)return;let i=v_(await F(t)),c=o.filter(u=>!i.has(u.messageId));if(c.length){let u=new Map;for(let f of c){let d=f.model||"unknown",m=u.get(d)||{tokens:{input:0,output:0,cacheWrite:0,cacheRead:0},messageIds:[]};for(let h of Object.keys(m.tokens))m.tokens[h]+=f.tokens[h];m.messageIds.push(f.messageId),u.set(d,m)}for(let[f,d]of u)await r({type:"cost.recorded",actor:"system",source:"transcript",payload:{model:f,usd:Xt(f,d.tokens),tokens:d.tokens,messageId:d.messageIds[d.messageIds.length-1],messageIds:d.messageIds,apiCalls:d.messageIds.length}})}let l=a.tokens.input+a.tokens.cacheWrite+a.tokens.cacheRead;await r({type:"context.usage",actor:"system",source:"transcript",payload:{model:a.model,occupancy:l,tokens:a.tokens}})}import I_ from"node:fs";import Vl from"node:fs/promises";import R_ from"node:http";import vr from"node:path";import Aa from"node:os";import{createRequire as $_}from"node:module";import zl from"ws";import __ from"node:path";import E_ from"node:os";var Hw=process.env.BOSTRAT_STATE_DIR||__.join(process.env.HOME||E_.tmpdir(),".bostrat");function Ww(t=process.env){let e=(t.BOSTRAT_HOST||"").trim();if(e!=="all")return e||(t.BOSTRAT_PLANE_URL?"127.0.0.1":void 0)}function qw(t){let e=String(t||"").trim();if(!e)return"";let n=/^[a-z][a-z0-9+.-]*:\/\//i.test(e)?e:`wss://${e}`,r;try{r=new URL(n)}catch{return e}let o={"https:":"wss:","http:":"ws:","wss:":"wss:","ws:":"ws:"}[r.protocol];return o?(r.protocol=o,(r.pathname===""||r.pathname==="/")&&(r.pathname="/v1/node"),r.toString()):e}var va=(()=>{try{return $_(import.meta.url)("../package.json").version||null}catch{return null}})(),O_=process.env.BOSTRAT_STATE_DIR||vr.join(process.env.HOME||Aa.tmpdir(),".bostrat");function C_(t,e){let n=String(t).split(".").map(Number),r=String(e).split(".").map(Number);if(n.some(Number.isNaN)||r.some(Number.isNaN))return!1;for(let o=0;o<Math.max(n.length,r.length);o++){let s=(n[o]||0)-(r[o]||0);if(s)return s<0}return!1}function M_({home:t=process.env.HOME||Aa.homedir(),env:e=process.env}={}){let n=r=>{try{return I_.statSync(r),!0}catch{return!1}};return{claude:n(vr.join(t,".claude",".credentials.json")),codex:n(vr.join(e.CODEX_HOME||vr.join(t,".codex"),"auth.json"))}}var _t=1,Gw=1e3,N_=3e4,P_=6e4,B_=1500,D_=500,Kw=100,j_=2e3,L_=25e3;function Vw({url:t,token:e,nodeId:n=Aa.hostname(),name:r=Aa.hostname(),listTracks:o,detectAgents:s=null,selfUrl:a=`http://127.0.0.1:${process.env.PORT||3362}`,log:i=console}){if(!t||!e)throw new Error("plane-link: url and token are required");if(typeof o!="function")throw new Error("plane-link: listTracks is required");t=qw(t);let c=a.replace(/^http/,"ws"),l=new Map,u=new Map,f=vr.join(O_,"plane-link","cursor.json"),d={},m=new Map,h=null,w=!1,y=!1,b=Gw,A=null,_=null,C=null,K=null,I=!1;async function L(){try{d=JSON.parse(await Vl.readFile(f,"utf8"))}catch{d={}}}function v(){I=!0,!C&&(C=setTimeout(async()=>{if(C=null,!!I){I=!1;try{await Vl.mkdir(vr.dirname(f),{recursive:!0}),await Vl.writeFile(f,JSON.stringify(d))}catch(R){i.error("[plane-link] cursor flush failed:",R?.message||R)}}},j_),C.unref?.())}function $(R){return!h||h.readyState!==zl.OPEN?!1:(h.send(JSON.stringify(R)),!0)}function W(R){for(let{trackId:M,event:E}of R)m.set(E.id,{trackId:M,seq:E.seq});return $({v:_t,ch:"events",type:"append",events:R})}async function nt(){let R;try{R=await o()}catch(M){i.error("[plane-link] backfill listTracks failed:",M?.message||M);return}$({v:_t,ch:"ctl",type:"tracks",ids:R.map(M=>M.id).filter(M=>typeof M=="string")});for(let M of R){if(!w)return;let E;try{E=await F(M)}catch{continue}let P=d[M.id]??0,et=E.filter(lt=>lt.seq>P&&typeof lt.id=="string");for(let lt=0;lt<et.length;lt+=Kw){let bt=et.slice(lt,lt+Kw).map(Et=>({trackId:M.id,event:Et}));if(!W(bt))return}}}function g(R){!w||!R?.trackId||typeof R.id!="string"||W([{trackId:R.trackId,event:R}])}let p=new Map;async function S(R){let{reqId:M,method:E,path:P,headers:et={},body:lt}=R,bt=Object.fromEntries(Object.entries(et).filter(([,Nt])=>Nt!=null)),Et=new AbortController;p.set(M,Et);try{if(R.host){await T(R,bt,Et);return}let Nt=await fetch(a+P,{method:E,headers:bt,body:lt?Buffer.from(lt,"base64"):void 0,signal:Et.signal}),k=Object.fromEntries(Nt.headers);for(let j of["connection","keep-alive","transfer-encoding","upgrade","content-encoding","content-length"])delete k[j];if(k["content-type"]||(k["content-type"]="application/octet-stream"),$({v:_t,ch:"control",type:"http_head",reqId:M,status:Nt.status,headers:k}),Nt.body){for await(let j of Nt.body)if(!$({v:_t,ch:"control",type:"http_data",reqId:M,data:Buffer.from(j).toString("base64")}))break}$({v:_t,ch:"control",type:"http_end",reqId:M,error:null})}finally{p.delete(M)}}function T(R,M,E){let{reqId:P,method:et,path:lt,body:bt}=R,Et=new URL(a);return new Promise(Nt=>{let k=R_.request({host:Et.hostname,port:Et.port,method:et,path:lt,headers:{...M,host:R.host},signal:E.signal},j=>{let{connection:Rn,"keep-alive":Ho,"transfer-encoding":$n,upgrade:yu,...Iy}=j.headers;$({v:_t,ch:"control",type:"http_head",reqId:P,status:j.statusCode||502,headers:Iy}),j.on("data",Ry=>{$({v:_t,ch:"control",type:"http_data",reqId:P,data:Buffer.from(Ry).toString("base64")})||j.destroy()}),j.on("end",()=>{$({v:_t,ch:"control",type:"http_end",reqId:P,error:null}),Nt()}),j.on("error",()=>{$({v:_t,ch:"control",type:"http_end",reqId:P,error:"view upstream error"}),Nt()})});k.on("error",j=>{$({v:_t,ch:"control",type:"http_end",reqId:P,error:j?.message||"view request failed"}),Nt()}),bt&&k.write(Buffer.from(bt,"base64")),k.end()})}function J(){if(y)return;h=new zl(t,{headers:{authorization:`Bearer ${e}`}}),h.on("open",async()=>{let M=null;try{M=await s?.()||null}catch{M=null}$({v:_t,ch:"ctl",type:"hello",nodeId:n,name:r,platform:process.platform,version:va,providers:M_(),...M?{agents:M}:{}})}),h.on("message",M=>{let E;try{E=JSON.parse(M)}catch{return}if(E.ch==="ctl"&&E.type==="hello_ok"){w=!0,b=Gw,A=null,i.log(`[plane-link] connected to plane as ${n}`),E.minNodeVersion&&va&&C_(va,E.minNodeVersion)&&i.error(`[plane-link] this node is bostrat-node ${va}; the plane asks for \u2265${E.minNodeVersion} \u2014 update with: npm install -g bostrat-node`),K&&clearInterval(K),K=setInterval(()=>$({v:_t,ch:"ctl",type:"ping"}),L_),K.unref?.(),nt().catch(P=>i.error("[plane-link] backfill failed:",P?.message||P));return}if(E.ch==="events"&&E.type==="ack"&&Array.isArray(E.ids)){for(let P of E.ids){let et=m.get(P);et&&(m.delete(P),(d[et.trackId]??0)<et.seq&&(d[et.trackId]=et.seq))}v();return}if(E.ch==="control"&&E.type==="http_open"){S(E).catch(P=>$({v:_t,ch:"control",type:"http_end",reqId:E.reqId,error:P?.message||"tunnel error"}));return}if(E.ch==="control"&&E.type==="http_cancel"){p.get(E.reqId)?.abort();return}if(E.ch==="attach"&&E.type==="open"){let P=new zl(c+E.path,Array.isArray(E.protocols)?E.protocols:[],E.host?{headers:{host:E.host}}:void 0);l.set(E.streamId,P),u.set(E.streamId,[]),P.on("open",()=>{let lt=u.get(E.streamId)||[];u.delete(E.streamId);for(let{buf:bt,bin:Et}of lt)P.send(bt,{binary:Et})}),P.on("message",(lt,bt)=>{let Et=bt?lt:Buffer.from(String(lt));$({v:_t,ch:"attach",type:"data",streamId:E.streamId,data:Et.toString("base64"),...bt?{bin:!0}:{}})});let et=()=>{u.delete(E.streamId),l.delete(E.streamId)&&$({v:_t,ch:"attach",type:"close",streamId:E.streamId})};P.on("close",et),P.on("error",et);return}if(E.ch==="attach"&&E.type==="data"){let P=l.get(E.streamId);if(!P)return;let et=Buffer.from(E.data,"base64"),lt=E.bin===!0,bt=u.get(E.streamId);bt?bt.push({buf:et,bin:lt}):P.send(et,{binary:lt});return}if(E.ch==="attach"&&E.type==="close"){let P=l.get(E.streamId);l.delete(E.streamId),u.delete(E.streamId),P?.close();return}});let R=()=>{w&&i.log("[plane-link] disconnected from plane"),w=!1,K&&(clearInterval(K),K=null),m.clear();for(let P of l.values())P.close();l.clear(),u.clear();for(let P of p.values())P.abort();if(p.clear(),y||_)return;A==null&&(A=Date.now());let M=Date.now()-A<P_,E=M?B_+Math.random()*D_:b;_=setTimeout(()=>{_=null,J()},E),_.unref?.(),M||(b=Math.min(b*2,N_))};h.on("close",R),h.on("error",R)}return Lt.on("event",g),L().then(J),{stop:()=>{y=!0,Lt.off("event",g),_&&clearTimeout(_),C&&clearTimeout(C),K&&clearInterval(K),h?.close()},status:()=>({connected:w,nodeId:n,cursor:{...d},inflight:m.size})}}function Xl(t){let e={};for(let n of t.boxes||[]){let r=n.role&&n.role!=="lead"?` \xB7 ${n.role}`:"";e[n.name]=`${n.name} (${n.agent||"?"}${r})`}return e}var zw=()=>({input:0,output:0,cacheRead:0,cacheCreation:0}),Jw=(t,e)=>{for(let n of Object.keys(t))t[n]+=e?.[n]||0},Jl=t=>Math.round(t*1e4)/1e4;async function Xw(t){let e=[],n={},r={},o=0,s=0,a=0,i=0,c=new Set,l=zw(),u=0,f=[];for(let d of t||[]){let m=await F(d),h=Yt(m,{boxLabels:Xl(d)});if(h.turns===0){d.archivedAt?u++:f.push(d.name||d.id);continue}o+=h.totalUsd,s+=h.turns,a+=h.repricedTurns||0,i+=h.unpricedTurns||0;for(let w of h.unpricedModels||[])c.add(w);Jw(l,h.totalTokens);for(let[w,y]of Object.entries(h.byModel))n[w]||={usd:0,events:0,tokens:zw()},n[w].usd=Jl(n[w].usd+y.usd),n[w].events+=y.events,Jw(n[w].tokens,y.tokens);for(let[w,y]of Object.entries(h.byProvider))r[w]||={usd:0,events:0},r[w].usd=Jl(r[w].usd+y.usd),r[w].events+=y.events;e.push({id:d.id,name:d.name,usd:h.totalUsd,rollup:h})}return e.sort((d,m)=>m.usd-d.usd),{generatedAt:new Date().toISOString(),pricingBasis:"api-equivalent (subscription usage priced at public API rates)",fleet:{totalUsd:Jl(o),turns:s,repricedTurns:a,unpricedTurns:i,unpricedModels:[...c],totalTokens:l,byModel:n,byProvider:r,tracksWithCost:e.length,tracksSilent:u+f.length,tracksSilentArchived:u,tracksSilentLive:f},tracks:e}}var Io=t=>`$${t.toFixed(4)}`,Ar=(t,e)=>String(t).padEnd(e);function Yw(t){let{fleet:e,tracks:n}=t,r=[];r.push(`BOSTRAT COST READ-OUT \xB7 ${t.generatedAt}`),t.pricingBasis&&r.push(`basis: ${t.pricingBasis}`);let o=e.tracksSilentLive||[];r.push(`fleet total ${Io(e.totalUsd)} over ${e.turns} turn(s) \xB7 ${e.tracksWithCost} track(s) with cost, ${e.tracksSilentArchived??e.tracksSilent} silent (archived)`+(o.length?`, ${o.length} LIVE with no cost data: ${o.join(", ")}`:"")),e.repricedTurns&&r.push(`${e.repricedTurns} turn(s) imputed at current rates (recorded before their model was priced)`),e.unpricedTurns&&r.push(`\u26A0 ${e.unpricedTurns} turn(s) UNPRICED \u2014 total under-reports. Add PER_MTOK rate(s) for: ${(e.unpricedModels||[]).join(", ")}`),r.push(""),r.push("BY MODEL");let s=Object.entries(e.byModel).sort((a,i)=>i[1].usd-a[1].usd);s.length||r.push(" (no cost events yet \u2014 hooks emit on each turn of a newly started box)");for(let[a,i]of s){let c=i.tokens;r.push(` ${Ar(a,26)} ${Ar(Io(i.usd),10)} ${i.events} turn(s) \xB7 in ${c.input} out ${c.output} cacheR ${c.cacheRead} cacheW ${c.cacheCreation}`)}r.push(""),r.push("BY TRACK / TASK");for(let a of n){r.push(` ${Ar(a.name||a.id,30)} ${Io(a.usd)}`);let i=Object.values(a.rollup.byBox).sort((c,l)=>l.usd-c.usd);for(let c of i)if(r.push(` ${Ar(c.label,40)} ${Ar(Io(c.usd),10)} ${c.models.join(",")||"?"}`),c.epochs)for(let[l,u]of Object.entries(c.epochs).sort((f,d)=>Number(f[0])-Number(d[0])))r.push(` \u21B3 tenure ${l} (${u.provider||"?"})${" ".repeat(Math.max(1,24-String(u.provider||"?").length))}${Ar(Io(u.usd),10)} ${u.models.join(",")||"?"}`)}return r.join(`
|
|
298
|
+
`)}import z_ from"node:fs/promises";import J_ from"node:path";var F_=["bug-fix","orchestration","design","ops-infra","docs-writing","research","feature-build"],U_={research:[/\bresearch\b/,/\binvestigat/,/\bverif/,/\bcompar(e|ing|ison)/,/\bcompetit/,/\bdeep[ -]?dive\b/,/\banaly[sz]/,/\bassess/,/\bdiligence\b/,/\bmarket\b/,/\bstrateg/,/\broadmap\b/,/\bnorth star/,/\bwhat('| i)?s the (state|status)\b/,/\bstatus of\b/,/\bexplore\b/,/\bfeasib/,/\bevaluat/,/\bscope out\b/,/\btalk through\b/],"bug-fix":[/\bfix(e[sd]|ing)?\b/,/\bbugs?\b/,/\bbroken\b/,/\bregression\b/,/\bcrash/,/\bincident\b/,/\bdebug/,/\bdoesn'?t (work|render|load|start)/,/\bnot working\b/,/\btroubleshoot/,/\bflaky?\b/,/\brepro(duce|duction)?\b/,/\brace condition\b/,/\bleak\b/],"feature-build":[/\bimplement/,/\bbuild(ing)?\b/,/\badd(ing)?\b/,/\bcreate\b/,/\bwire (it |them )?(up|in)\b/,/\bship(ping)?\b/,/\bintegrat/,/\bsupport for\b/,/\bfeature\b/,/\bendpoint\b/,/\badapter\b/,/\bprototype\b/],design:[/\bdesign/,/\bui\b/,/\bux\b/,/\bstyl(e|es|ing)\b/,/\bcss\b/,/\blayout\b/,/\bmock-?up/,/\brender(ing|s)?\b/,/\bpolish/,/\btheme\b/,/\bfonts?\b/,/\bresponsive\b/,/\banimation/,/\bvisual/,/\bpopover\b/],orchestration:[/\bhand[ -]?(off|over)/,/\bhand (it|the lead|this) (over|off)\b/,/\bdelegat/,/\bsubtask/,/\bmerge[ -]?back\b/,/\borchestrat/,/\b(switch|change) (the )?model\b/,/\bmodel switch/],"docs-writing":[/\bdocs?\b/,/\bdocument/,/\bmemo\b/,/\breadme\b/,/\bwrite-?up\b/,/\bchangelog\b/,/\bclaude\.md\b/,/\bblog/,/\bproofread/,/\bcopy(edit|writ)/],"ops-infra":[/\bdeploy/,/\bsystemd\b/,/\bdaemon\b/,/\btmux\b/,/\bdns\b/,/\bwireguard\b/,/\brestart/,/\bnginx\b/,/\bprovision/,/\binstall/,/\bupgrade/,/\bcron\b/,/\bbackups?\b/,/\bcertificat/,/\bssh\b/,/\bsecrets?\b/]};function Ta(t){let e=String(t||"").toLowerCase();if(!e.trim())return null;let n=null;for(let r of F_){let o=0;for(let s of U_[r])s.test(e)&&o++;o>0&&(!n||o>n.score)&&(n={category:r,score:o})}return n}var H_=new Set(["Read","Grep","Glob","LS","WebSearch","WebFetch","NotebookRead"]),W_=new Set(["Edit","Write","MultiEdit","NotebookEdit"]);function Zw(t){let e=0,n=0;for(let[r,o]of Object.entries(t||{}))H_.has(r)&&(e+=o||0),W_.has(r)&&(n+=o||0);return e>=5&&n===0?{category:"research",score:1}:n>=3?{category:"feature-build",score:1}:null}function Yl(t){return String(t||"").replace(/([a-z0-9])([A-Z])/g,"$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g,"$1 $2").replace(/[-_/]+/g," ")}var _a=t=>t.payload?.ts??t.ts,q_=12e4,Je=t=>Math.round(t*1e4)/1e4,G_=()=>({usd:0,activeMs:0,turns:0,toolCalls:0,models:{},providers:{},byTrack:new Map});function K_(t,{since:e}){let{track:n,events:r,brief:o}=t,s=A=>_a(A)>=e,a=Ta([Yl(n?.name),Yl(n?.workspace?.branch),o].filter(Boolean).join(" ")),i=new Map;for(let A of r)if(A.type==="subtask.delegated"&&A.boxId&&A.payload?.task){let _=Ta(A.payload.task);_&&i.set(A.boxId,_)}let c=new Map,l=A=>{let _=A||"unattributed";return c.has(_)||c.set(_,{segments:[u(null)],lastTs:null,provider:null}),c.get(_)},u=A=>({textCat:A,tools:{},usd:0,tokens:{input:0,output:0,cacheRead:0,cacheWrite:0},models:{},provider:null,activeMs:0,turns:0,toolCalls:0}),f=[...r].sort((A,_)=>_a(A)-_a(_)),d=0,m=0,h=0,w={turns:0};for(let A of f){let _=l(A.boxId),C=_.segments[_.segments.length-1],K=_a(A);switch(_.lastTs!=null&&K>_.lastTs&&s(A)&&(C.activeMs+=Math.min(K-_.lastTs,q_)),_.lastTs=K,A.provider&&(_.provider=A.provider),A.type){case"turn.user":{let I=A.payload?.text;if(A.source==="hook"||!String(I||"").trim())break;let L=u(Ta(I));_.segments.push(L),s(A)&&(L.turns+=1);break}case"tool.called":{if(A.source==="transcript")break;let I=A.payload?.tool;I&&(C.tools[I]=(C.tools[I]||0)+1),s(A)&&(C.toolCalls+=1);break}case"cost.recorded":{if(!s(A))break;let I=A.payload||{},L={input:I.tokens?.input||0,output:I.tokens?.output||0,cacheRead:I.tokens?.cacheRead||0,cacheWrite:I.tokens?.cacheWrite??I.tokens?.cacheCreation??0},v=Number(I.usd)||0;!v&&I.tokens&&(v=Xt(I.model,L)),!v&&I.tokens&&!ds(I.model)&&(w.turns+=1),C.usd+=v,C.tokens.input+=L.input,C.tokens.output+=L.output,C.tokens.cacheRead+=L.cacheRead,C.tokens.cacheWrite+=L.cacheWrite;let $=I.model||"unknown";C.models[$]=(C.models[$]||0)+v,A.provider&&(C.provider=A.provider);break}case"handoff.done":s(A)&&(d+=1);break;case"subtask.merged":s(A)&&(m+=1);break;case"error":s(A)&&A.payload?.kind==="subtask-failed"&&(h+=1);break;default:break}}let y=new Map,b=!1;for(let[A,_]of c)for(let C of _.segments){if(!(C.usd>0||C.activeMs>0||C.turns>0||C.toolCalls>0))continue;let I=C.textCat?.category??i.get(A)?.category??Zw(C.tools)?.category??a?.category??"uncategorized";y.has(I)||y.set(I,{usd:0,activeMs:0,turns:0,toolCalls:0,models:{},providers:{}});let L=y.get(I);L.usd+=C.usd,L.activeMs+=C.activeMs,L.turns+=C.turns,L.toolCalls+=C.toolCalls;for(let[$,W]of Object.entries(C.models))L.models[$]=(L.models[$]||0)+W;let v=C.provider||_.provider;v&&(L.providers[v]=(L.providers[v]||0)+C.usd),v==="cursor"&&C.usd===0&&(b=!0)}return{perCategory:y,handoffs:d,merged:m,failed:h,sawCursorActivity:b,unpricedTurns:w.turns}}function tg(t,{windowMs:e=null,now:n=Date.now()}={}){let r=e==null?0:n-e,o=new Map,s=[],a=0,i=!1,c=0;for(let w of t||[]){let{track:y,events:b}=w;if(!b?.length){a+=1;continue}let{perCategory:A,handoffs:_,merged:C,failed:K,sawCursorActivity:I,unpricedTurns:L}=K_(w,{since:r});I&&(i=!0),c+=L;let v={trackId:y?.id,name:y?.name||y?.id,archived:!!y?.archivedAt,usd:0,activeMs:0,turns:0,categories:{},models:{},providers:{},handoffs:_,subtasksMerged:C,subtasksFailed:K};for(let[$,W]of A){v.usd+=W.usd,v.activeMs+=W.activeMs,v.turns+=W.turns,v.categories[$]={usd:Je(W.usd),activeMs:W.activeMs};for(let[p,S]of Object.entries(W.models))v.models[p]=Je((v.models[p]||0)+S);for(let[p,S]of Object.entries(W.providers))v.providers[p]=Je((v.providers[p]||0)+S);o.has($)||o.set($,G_());let nt=o.get($);nt.usd+=W.usd,nt.activeMs+=W.activeMs,nt.turns+=W.turns,nt.toolCalls+=W.toolCalls;for(let[p,S]of Object.entries(W.models))nt.models[p]=(nt.models[p]||0)+S;for(let[p,S]of Object.entries(W.providers))nt.providers[p]=(nt.providers[p]||0)+S;let g=nt.byTrack.get(y?.id)||{usd:0,activeMs:0};g.usd+=W.usd,g.activeMs+=W.activeMs,nt.byTrack.set(y?.id,g)}(v.usd>0||v.activeMs>0||v.turns>0)&&(v.usd=Je(v.usd),v.dominantCategory=V_(v.categories),s.push(v))}let l=[...o.values()].reduce((w,y)=>w+y.usd,0),u=[...o.values()].reduce((w,y)=>w+y.activeMs,0),f=l>0?"usd":"time",d=f==="usd"?l:u,m=new Map((t||[]).map(w=>[w.track?.id,w.track?.name||w.track?.id])),h=[...o.entries()].map(([w,y])=>({category:w,sharePct:d>0?Math.round((f==="usd"?y.usd:y.activeMs)/d*100):0,shareBasis:f,usd:Je(y.usd),activeMs:y.activeMs,turns:y.turns,toolCalls:y.toolCalls,models:Qw(y.models),providers:Qw(y.providers),topWorkstreams:[...y.byTrack.entries()].sort((b,A)=>A[1].usd-b[1].usd||A[1].activeMs-b[1].activeMs).slice(0,2).map(([b,A])=>({trackId:b,name:m.get(b),usd:Je(A.usd),activeMs:A.activeMs}))})).sort((w,y)=>y.usd-w.usd||y.activeMs-w.activeMs);return s.sort((w,y)=>y.usd-w.usd||y.activeMs-w.activeMs),{since:r,capturedAt:n,totalUsd:Je(l),activeMs:u,turns:[...o.values()].reduce((w,y)=>w+y.turns,0),bullets:h,workstreams:s,gaps:{tracksWithoutEvents:a,cursorCostUnknown:i,unpricedTurns:c}}}var V_=t=>{let e=null;for(let[n,r]of Object.entries(t))(!e||r.usd>e.v.usd||r.usd===e.v.usd&&r.activeMs>e.v.activeMs)&&(e={cat:n,v:r});return e?.cat??null},Qw=t=>Object.fromEntries(Object.entries(t).map(([e,n])=>[e,Je(n)]));var Tr={"24h":24*36e5,"7d":168*36e5,"30d":720*36e5,all:null};function X_(t){let e=String(t||"7d");return e in Tr?{key:e,ms:Tr[e]}:{key:"7d",ms:Tr["7d"]}}function Y_(t,e){let n=new Set((e||[]).filter(o=>o.source==="orchestrator").map(o=>`${o.type}|${o.payload?.ts??"?"}`)),r=[...e||[]];for(let o of t?.events||[]){let s=Js(o);!s||n.has(`${s.type}|${s.payload?.ts??"?"}`)||r.push({trackId:t.id,boxId:s.boxId??null,ts:o.at,type:s.type,actor:s.actor,provider:s.provider??null,source:s.source,payload:s.payload})}return r}async function Z_(t){if(!t?.cwd)return null;try{let n=(await z_.readFile(J_.join(t.cwd,".bostrat","handoff","latest.md"),"utf8")).match(/^##\s*Objective\s*\n([\s\S]*?)(?:\n##\s|\n*$)/m);return n?n[1].trim().slice(0,1e3):null}catch{return null}}async function eg(t,{window:e="7d",from:n,to:r,now:o=Date.now()}={}){let s=Number(n),a=Number(r),i=Number.isFinite(s)&&s>0,c=i&&Number.isFinite(a)&&a>s?Math.min(a,o):o,{key:l,ms:u}=i?{key:"custom",ms:c-s}:X_(e),f=[];for(let d of t||[]){let m=await F(d),h=Y_(d,m);i&&c<o&&(h=h.filter(w=>(w.ts??0)<=c)),f.push({track:d,events:h,brief:await Z_(d)})}return{window:l,...tg(f,{windowMs:u,now:c})}}var Xe=36e5,sg=24*Xe,Q_=400,tE=12,_r=t=>Math.round(t*1e4)/1e4;function eE({window:t,from:e,to:n,now:r=Date.now()}={}){let o=Number(e),s=Number(n);if(Number.isFinite(o)&&o>0){let c=Number.isFinite(s)&&s>o?Math.min(s,r):r;return{key:"custom",since:o,until:c}}let a=String(t||"7d")in Tr?String(t||"7d"):"7d",i=Tr[a];return{key:a,since:i==null?null:r-i,until:r}}var nE=(t,e)=>t!=null&&e-t<=48*Xe?Xe:sg;function ng(t,e){if(e===Xe)return Math.floor(t/Xe)*Xe;let n=new Date(t);return n.setHours(0,0,0,0),n.getTime()}function rE(t,e){if(e===Xe)return t+Xe;let n=new Date(t);return n.setDate(n.getDate()+1),n.setHours(0,0,0,0),n.getTime()}var rg=()=>({input:0,output:0,cacheRead:0,cacheCreation:0}),og=(t,e)=>{for(let n of Object.keys(t))t[n]+=e?.[n]||0},oE=t=>Object.fromEntries(Object.entries(t).map(([e,n])=>[e,_r(n)]));async function ag(t,e={}){let n=e.now??Date.now(),r=eE({...e,now:n}),o=r.since==null?sg:nE(r.since,r.until),s=v=>(r.since==null||v>=r.since)&&v<r.until,a=r.since!=null?r.until-r.since:null,i=v=>a!=null&&v>=r.since-a&&v<r.since,c={},l={},u=rg(),f=0,d=0,m=0,h=0,w=0,y=new Set,b=new Map,A=[],_=[],C=[],K=null;for(let v of t||[]){let W=(await F(v)).filter(p=>p.type==="cost.recorded");if(!W.length){v.archivedAt||C.push(v.name||v.id);continue}let nt=[];for(let p of W){let S=p.ts??0;s(S)?nt.push(p):i(S)&&(w+=ps(p).usd)}if(!nt.length)continue;let g=Yt(nt,{boxLabels:Xl(v)});f+=g.totalUsd,d+=g.turns,m+=g.repricedTurns||0,h+=g.unpricedTurns||0;for(let p of g.unpricedModels||[])y.add(p);og(u,g.totalTokens);for(let[p,S]of Object.entries(g.byModel))c[p]||={usd:0,events:0,tokens:rg()},c[p].usd=_r(c[p].usd+S.usd),c[p].events+=S.events,og(c[p].tokens,S.tokens);for(let[p,S]of Object.entries(g.byProvider))l[p]||={usd:0,events:0},l[p].usd=_r(l[p].usd+S.usd),l[p].events+=S.events;for(let p of nt){let{usd:S}=ps(p);if(!S)continue;let T=ng(p.ts??0,o),J=b.get(T)??{t:T,total:0,byProvider:{}};J.total+=S;let R=p.provider||"unknown";J.byProvider[R]=(J.byProvider[R]||0)+S,b.set(T,J),(K==null||T<K)&&(K=T)}A.push({id:v.id,name:v.name||v.id,archived:!!v.archivedAt,usd:g.totalUsd,turns:g.turns,providers:Object.keys(g.byProvider)});for(let[p,S]of Object.entries(g.byBox))_.push({trackId:v.id,trackName:v.name||v.id,boxId:p,...S})}let I=r.since!=null?ng(r.since,o):K,L=[];for(let v=I,$=0;v!=null&&v<r.until&&$<Q_;v=rE(v,o),$++){let W=b.get(v);L.push(W?{t:v,total:_r(W.total),byProvider:oE(W.byProvider)}:{t:v,total:0,byProvider:{}})}return A.sort((v,$)=>$.usd-v.usd),_.sort((v,$)=>$.usd-v.usd),{generatedAt:new Date().toISOString(),pricingBasis:"api-equivalent (subscription usage priced at public API rates)",window:r.key,since:r.since,until:r.until,bucketMs:o,series:L,fleet:{totalUsd:_r(f),turns:d,repricedTurns:m,unpricedTurns:h,unpricedModels:[...y],totalTokens:u,byModel:c,byProvider:l,tracksWithCost:A.length,tracksSilentLive:C},prior:a!=null?{totalUsd:_r(w),since:r.since-a,until:r.since}:null,tracks:A.slice(0,50),boxes:_.slice(0,tE)}}import Er from"node:fs/promises";import sE from"node:fs";import ht from"node:path";import aE from"node:os";import{execFile as iE}from"node:child_process";import{promisify as cE}from"node:util";var lE=cE(iE),ig=process.env.BOSTRAT_STATE_DIR||ht.join(process.env.HOME||aE.tmpdir(),".bostrat"),Zl=!1;async function Ro(t,e){let n=await Er.stat(t).catch(()=>null);if(!n?.isFile())return!1;let r=await Er.stat(e).catch(()=>null);return r&&r.size===n.size&&r.mtimeMs>=n.mtimeMs?!1:(await Er.mkdir(ht.dirname(e),{recursive:!0}),await Er.copyFile(t,e),!0)}var uE=new Set([".claude",".codex",".credentials.json","auth.json",".claude.json","integrations.json"]);async function $o(t,e){let n=0,r=await Er.readdir(t,{withFileTypes:!0}).catch(()=>[]);for(let o of r){if(uE.has(o.name))continue;let s=ht.join(t,o.name);o.isDirectory()?n+=await $o(s,ht.join(e,o.name)):o.isFile()&&(n+=await Ro(s,ht.join(e,o.name))?1:0)}return n}async function cg({stateDir:t=ig,dest:e=process.env.BOSTRAT_SYNC_DIR||`${ig}-sync`,remote:n=process.env.BOSTRAT_STATE_REMOTE||null,tracks:r=null}={}){try{await Er.mkdir(e,{recursive:!0});let o=0;o+=await Ro(ht.join(t,"tracks.json"),ht.join(e,"tracks.json"))?1:0,o+=await Ro(ht.join(t,"mocks.json"),ht.join(e,"mocks.json"))?1:0,o+=await Ro(ht.join(t,"runbooks.json"),ht.join(e,"runbooks.json"))?1:0,o+=await $o(ht.join(t,"runbooks"),ht.join(e,"runbooks")),o+=await $o(ht.join(t,"mock-archive"),ht.join(e,"mock-archive")),o+=await Ro(ht.join(t,"events.jsonl"),ht.join(e,"events.jsonl"))?1:0,o+=await $o(ht.join(t,"events"),ht.join(e,"events"));for(let l of r||[])!l?.cwd||!l?.id||(o+=await $o(ht.join(l.cwd,".bostrat"),ht.join(e,"workspaces",l.id,".bostrat")));let s=(...l)=>lE("git",["-C",e,...l]);sE.existsSync(ht.join(e,".git"))||(await s("init","-q"),await s("config","user.email","agent@bostrat.local"),await s("config","user.name","bostrat state sync")),n&&/^https:\/\/github\.com\//i.test(n)&&(await s("config","--get","credential.useHttpPath").then(()=>!0).catch(()=>!1)||await ac(e).catch(()=>{}));let a=!1,{stdout:i}=await s("status","--porcelain");i.trim()&&(await s("add","-A"),await s("commit","-q","-m",`state sync ${new Date().toISOString()}`),a=!0);let c=!1;if(n&&(a||Zl))try{await s("push","-q",n,"HEAD:refs/heads/main"),c=!0,Zl=!1}catch{Zl=!0}return{staged:o,committed:a,pushed:c}}catch(o){return{staged:0,committed:!1,pushed:!1,error:o?.message||String(o)}}}import{execFile as dE}from"node:child_process";import{promisify as fE}from"node:util";import{randomUUID as pE}from"node:crypto";import mE from"node:fs";import hE from"node:os";import wE from"node:path";var Ze=fE(dE),Ea=t=>new Promise(e=>setTimeout(e,t)),Qe=za,Ql=process.env.BOSTRAT_USAGE_PROBE_CWD||wE.join(hE.homedir(),".bostrat","usage-probe"),gE=Number(process.env.BOSTRAT_USAGE_IDLE_REAP_MS)||5*6e4,yE=Number(process.env.BOSTRAT_USAGE_PANEL_TIMEOUT_MS)||15e3,bE=Number(process.env.BOSTRAT_USAGE_READY_TIMEOUT_MS)||45e3,lg=Number(process.env.BOSTRAT_USAGE_CACHE_MS)||2e4,Ye=null,Oo=null,Co=null;async function kE(){try{return await Ze(N,B("has-session","-t",Qe)),!0}catch{return!1}}function xE(){Ye&&clearTimeout(Ye),Ye=setTimeout(()=>SE().catch(()=>{}),gE),Ye.unref&&Ye.unref()}async function SE(){Ye&&(clearTimeout(Ye),Ye=null);try{await Ze(N,B("kill-session","-t",Qe))}catch{}}async function vE(){let t=Date.now()+bE;for(;;){let e=await tu().catch(()=>"");if(/for agents|for shortcuts|bypass permissions on/i.test(e)){await Ea(500);return}if(Date.now()>t)throw new Error("usage probe didn't reach the prompt (trust/onboarding may be blocking)");await Ea(500)}}async function AE(){if(await kE())return;if(!await _e())throw new Error("host tmux server not running \u2014 start globo-tmux.service");mE.mkdirSync(Ql,{recursive:!0}),await Te(Ql);let t=pE();await Ze(N,B("new-session","-d","-s",Qe,"-c",Ql,"-x","200","-y","60"));let e=`${Va} --session-id ${t}`;await Ze(N,B("send-keys","-t",Qe,e,"Enter")),await vE()}async function tu(){let{stdout:t}=await Ze(N,B("capture-pane","-t",Qe,"-p"));return t}async function TE(){await AE(),await Ze(N,B("send-keys","-t",Qe,"-l","/usage")),await Ze(N,B("send-keys","-t",Qe,"Enter"));let t=Date.now()+yE,e="";for(;await Ea(400),e=await tu(),!(/Current\s+(session|week)/i.test(e)||/\d+%\s*used/i.test(e)||Date.now()>t););return await Ea(900),e=await tu(),await Ze(N,B("send-keys","-t",Qe,"Escape")).catch(()=>{}),xE(),e}function _E(t){let e=String(t||"").split(`
|
|
299
|
+
`).map(s=>s.replace(/\s+$/,"").trim()),n=s=>{let a=null,i=null;for(let c=s+1;c<Math.min(s+5,e.length)&&!/^Current\s/i.test(e[c]);c++){let l=e[c].match(/(\d+)%\s*used/i);l&&a===null&&(a=Number(l[1]));let u=e[c].match(/^Resets\s+(.+)$/i);u&&i===null&&(i=u[1].trim())}return a===null?null:{pct:a,resets:i||null}},r=s=>{let a=e.findIndex(i=>s.test(i));return a<0?null:n(a)},o=[];return e.forEach((s,a)=>{let i=s.match(/^Current week \(([^)]+)\)$/i);if(!i||/all models/i.test(i[1]))return;let c=n(a);c&&o.push({model:i[1].trim(),...c})}),{session:r(/^Current session\b/i),week:r(/^Current week \(all models\)/i)||r(/^Current week$/i),weekModels:o.length?o:null}}function EE(t){let e=String(t||"").split(`
|
|
300
|
+
`).map(s=>s.replace(/\s+$/,"")),n=e.findIndex(s=>/What's contributing/i.test(s));if(n<0)return null;let r=/(Usage credits|Esc to cancel|d to day · w to week|^\s*d to day)/i,o=[];for(let s=n+1;s<e.length&&!r.test(e[s]);s++)e[s].trim()&&o.push(e[s].trim());return o.length?o:null}async function ug({fresh:t=!1}={}){return!t&&Oo&&Date.now()-Oo.at<lg?Oo.data:Co||(Co=(async()=>{try{let e=await TE(),n=_E(e),r={...n,contributors:EE(e),subscription:!!(n.session||n.week||n.weekModels),capturedAt:Date.now()};return Oo={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 Oo={at:Date.now()-(lg-3e3),data:n},n}finally{Co=null}})(),Co)}var IE=262144;function RE(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 dg(t){if(!t||typeof t!="object")return null;let e=Number(t.used_percent);return Number.isFinite(e)?{pct:Math.round(e),resets:RE(t.resets_in_seconds)}:null}function $E(t){let e=String(t||"").split(`
|
|
301
|
+
`),n=!1;for(let r=e.length-1;r>=0;r--){let o=e[r].trim();if(!o||o[0]!=="{"||!o.includes("token_count"))continue;let s;try{s=JSON.parse(o)}catch{continue}let a=s?.payload;if(a?.type!=="token_count")continue;n=!0;let i=a.rate_limits;if(!i)return{session:null,week:null,sawTokenCount:n};if(i.primary||i.secondary)return{session:dg(i.primary),week:dg(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 eu(t){let e={provider:"codex",supported:!0,contributors:null};try{let n=await gn(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,IE),o=$E(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 fg(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 Ia({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 eu(i);if(c.subscription){a=c;break}(!a||a.error&&!c.error)&&(a=c)}r.push(a||await eu(null))}return n.has("cursor")&&r.push(await Ia({agent:"cursor"})),{providers:r,capturedAt:Date.now()}}async function Ia(t,{fresh:e=!1}={}){let n=t?.agent||"claude";return n==="codex"?eu(t):n==="cursor"?{provider:"cursor",supported:!1,subscription:!1,session:null,week:null,contributors:null,capturedAt:Date.now()}:{provider:"claude",supported:!0,...await ug({fresh:e})}}function OE(t=process.env){return!!(tr(t)&&t.BOSTRAT_PLANE_TOKEN)}var CE=t=>({authorization:`Bearer ${t.BOSTRAT_PLANE_TOKEN||""}`}),Ra=(t,e)=>Object.assign(new Error(t),{status:e});async function pg(t,e){if(!OE(e))throw Ra("node is not plane-linked \u2014 Gmail unavailable",503);let n;try{n=await fetch(`${tr(e)}${t}`,{headers:CE(e)})}catch(o){throw Ra(`plane unreachable \u2014 Gmail unavailable (${o?.message||o})`,503)}let r=await n.json().catch(()=>({}));if(!n.ok)throw Ra(r?.error||`plane replied ${n.status}`,n.status);return r}async function mg(t,{max:e=10,env:n=process.env}={}){let r=new URLSearchParams({q:t||""});return e&&r.set("max",String(e)),(await pg(`/v1/gmail/messages?${r.toString()}`,n)).messages||[]}async function hg(t,{env:e=process.env}={}){if(!t)throw Ra("message id required",400);return(await pg(`/v1/gmail/messages/${encodeURIComponent(t)}`,e)).message}function wg(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 ME(t){let[e,n]=t.split("/"),r=wg(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 NE(t){return(t||"").split(",").map(e=>e.trim()).filter(Boolean).map(ME).filter(Boolean)}function PE(t){let e=t.headers["fly-client-ip"];return typeof e=="string"&&e.trim()?e.trim():(t.socket&&t.socket.remoteAddress||"").replace(/^::ffff:/,"")}function gg(t){let e=NE(t),n=String(t||"").trim().toLowerCase()==="open";return{open:n,clientIp:PE,allowed(r){if(n)return!0;let o=wg((r||"").replace(/^::ffff:/,""));return o==null?!1:e.some(s=>s(o))}}}import yg from"node:crypto";import $a from"node:fs";import BE from"node:path";function bg(t=process.env,{stateDir:e=Hw,log:n=console}={}){let r=String(t.BOSTRAT_NODE_API_TOKEN||"").trim();if(r)return r;let o=BE.join(e,"api-token");try{let a=$a.readFileSync(o,"utf8").trim();if(a)return a}catch{}let s=yg.randomBytes(32).toString("base64url");try{$a.mkdirSync(e,{recursive:!0}),$a.writeFileSync(o,s+`
|
|
302
|
+
`,{mode:384}),$a.chmodSync(o,384)}catch(a){n.error?.(`[bostrat-agent] could not persist the node API token: ${a?.message||a}`)}return s}function Mo(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 kg(t,e){if(!t||!e)return!1;let n=Buffer.from(String(t)),r=Buffer.from(String(e));return n.length!==r.length?!1:yg.timingSafeEqual(n,r)}function xg(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 DE=[/^\/api\/github\/token\b/],jE=t=>DE.some(e=>e.test(t));function Sg({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=xg(o);return kg(c,t)?a():jE(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||Mo(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 vg(t,e,{enforce:n=!0}={}){return!n||kg(xg(t),e)?!0:Mo(t)}import LE from"node:http";import FE from"node:net";function Ag(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 Tg(t){let e=Ag(t.headers.host);return e?{slug:e,view:Ii(e)}:null}var UE=t=>`<!doctype html><meta charset="utf-8">
|
|
281
303
|
<meta http-equiv="refresh" content="3">
|
|
282
304
|
<title>${t.name} \u2014 starting</title>
|
|
283
305
|
<body style="font-family:system-ui;background:#0b0e14;color:#dce3f0;display:grid;place-items:center;height:100vh;margin:0">
|
|
284
306
|
<div style="text-align:center"><p style="font-size:2rem;margin:0">\u23F3</p>
|
|
285
|
-
<p><b>${t.name}</b> is waking up\u2026</p><p style="opacity:.6">auto-refreshing</p></div>`;function
|
|
307
|
+
<p><b>${t.name}</b> is waking up\u2026</p><p style="opacity:.6">auto-refreshing</p></div>`;function _g(t,e,n){let r=Tg(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(UE(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=LE.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 Eg(t,e,n){let r=Tg(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
|
|
286
308
|
\r
|
|
287
309
|
bostrat: no live view "${s}"\r
|
|
288
|
-
`),e.destroy(),!0;let a=
|
|
310
|
+
`),e.destroy(),!0;let a=FE.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
|
|
289
311
|
`)+`\r
|
|
290
312
|
\r
|
|
291
|
-
`),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 Rw(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}.${n_(n.host)}/`}function n_(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 Tw(e)&&ni(n[0])?n.slice(1).join("."):e}import{execFile as r_}from"node:child_process";import{promisify as o_}from"node:util";var $l=o_(r_),$w=3e4,s_=30*6e4,a_=$w,i_=10*6e4,c_=10080*6e4,l_=60*6e4,u_=8,d_=1440*6e4,f_=3,Ow="number,url,title,headRefName,headRefOid,state,mergedAt,closedAt",Rl=60*6e4,p_=15*6e4,ia=new Map,ca=new Map,m_=/could not resolve|not found|no pull requests?/i,h_=t=>m_.test(String(t?.message||t||""));function w_(t,e){return[t.ciFix?.delegatedAt,t.reconcile?.delegatedAt,t.ciFixAwait?.since,t.reconcileAwait?.since].some(n=>n&&e-n<Rl)}var Cw={fast:a_,slow:i_,decay:l_};function Mw(t,e){return t.armedAt&&e-t.armedAt<s_||w_(t,e)?"fast":e-(t.firstSeen||0)>c_?"decay":"slow"}function g_(t,e){return Cw[Mw(t,e)]}function Ao(t,e,n,r){let o=t.lanes?.get(st(e.repo,e.number))||{};return js({change:n,repo:e.repo,number:Number(e.number),githubAt:r||null,lane:o.lane||null,intervalMs:o.intervalMs||null,source:"watch"})}async function y_(t,e){he("watch");try{let{stdout:n}=await $l("gh",["pr","view",String(e),"--repo",t,"--json",Ow],{timeout:3e4,maxBuffer:4194304,env:{...process.env,...await ne({slug:t})}});return JSON.parse(n||"null")}catch(n){throw De(n)&&je(n),n}}async function b_(t){he("watch");try{let{stdout:e}=await $l("gh",["pr","list","--repo",t,"--state","all","--limit","100","--json",Ow],{timeout:3e4,maxBuffer:8388608,env:{...process.env,...await ne({slug:t})}}),n=new Map;for(let r of JSON.parse(e||"[]"))n.set(Number(r.number),r);return n}catch(e){throw De(e)&&je(e),e}}async function k_(){let t=new Map;for(let e of await ft().catch(()=>[]))t.set(e.id,e);return t}async function x_(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 S_(t,e){let n=await vn(t);return n?lr(n,e):null}async function v_(t,e,n){let r=await vn(t);if(!r)throw new Error(`no local checkout for ${t}`);return ur(r,e,{method:n})}async function A_(t,e){let n=await vn(t);if(!n)throw new Error(`no local checkout for ${t}`);return oo(n,e)}async function T_(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 Sr(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 __=10*6e4;async function E_(t,e,n){let r=t.autoMerge?.armedAt||0;if(r&&(n.now??Date.now())-r>d_){await Sr(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 Sr(t,o,e,n,`check${o.checks.failed===1?"":"s"} failed: ${a}`);return}if(o.mergeable==="CONFLICTING"){await Sr(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<__)return;await Sr(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 Sr(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 Sr(t,o,e,n,String(a?.message||a).trim()||"merge failed");return}await Pw(t,{...o,state:"MERGED"},e,n,{change:"auto-merged",githubAt:o.checks.settledAt||null})}}async function xo(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 I_(t,e){try{return await $l("git",["-C",t.cwd,"cat-file","-e",`${e}^{commit}`],{timeout:1e4}),!0}catch{return!1}}async function So(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 vo=(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 R_(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 ds(t.repo,t.number,{headSha:s}).catch(()=>{}),t.reconcile?.headSha&&s!==t.reconcile.headSha&&await ps(t.repo,t.number,{headSha:s}).catch(()=>{})),s&&s!==t.lastHeadSha){let i=!t.lastHeadSha,c=!!(t.ciFix||t.reconcile||t.ciFixAwait||t.reconcileAwait),l=!!t.baseFoldAt&&o-t.baseFoldAt<p_;await pf(t.repo,t.number,s).catch(()=>{}),t.baseFoldAt&&await ff(t.repo,t.number).catch(()=>{}),!i&&!c&&!l&&a&&!await r.localHasCommit(a,s).catch(()=>!1)&&!await So(a,"pushed",t.number,d=>d.payload?.sha===s)&&await xo(a,"pushed",{...vo(t,e),sha:s}).catch(()=>{})}if(t.ciFixAwait)if(o-(t.ciFixAwait.since||0)>Rl)await fs(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 fs(t.repo,t.number).catch(()=>{});else if(i.checks.total>0&&i.checks.pending===0){let c=Ao(r,t,"ci-fixed",i.checks.settledAt||null),l=t.ciFixAwait.since||0;a&&!await So(a,"ci-fixed",t.number,u=>(u.payload?.ts||0)>=l)&&await xo(a,"ci-fixed",{...vo(t,e),passed:i.checks.passed,githubAt:c.githubAt,latencyMs:c.latencyMs,lane:c.lane}).catch(()=>{}),await fs(t.repo,t.number).catch(()=>{})}}}if(t.reconcileAwait)if(o-(t.reconcileAwait.since||0)>Rl)await ms(t.repo,t.number).catch(()=>{});else{let i=await r.detail(t.repo,t.number).catch(()=>null);if(i){if(i.mergeable==="CONFLICTING")await ms(t.repo,t.number).catch(()=>{});else if(i.mergeable==="MERGEABLE"){Ao(r,t,"reconciled",null);let c=t.reconcileAwait.since||0;a&&!await So(a,"reconciled",t.number,l=>(l.payload?.ts||0)>=c)&&await xo(a,"reconciled",{...vo(t,e),base:i.baseRefName||null}).catch(()=>{}),await ms(t.repo,t.number).catch(()=>{})}}}}async function $_(t,e,n,r){let o=Ao(r,t,"closed",Date.parse(e?.closedAt||"")||null),s=n.get(t.trackId);s&&!await So(s,"closed",t.number)&&await xo(s,"closed",{...vo(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 O_(t,e,n){Ao(n,t,"lost",null);let r=e.get(t.trackId);r&&!await So(r,"lost",t.number)&&await xo(r,"lost",vo(t,null)).catch(()=>{}),await n.notifyIfResolved(t.repo,t.number,"lost").catch(()=>{}),await n.mark(t.repo,t.number,"lost").catch(()=>{})}async function Pw(t,e,n,r,o={}){let s=Ao(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 C_(t={}){let{now:e=Date.now(),auth:n=Bc,view:r=y_,list:o=b_,links:s=hf,trackMap:a=k_,emitMerged:i=x_,mark:c=Oe,archiveIfArmed:l=Fe,notifyIfArmed:u=Ue,notifyIfResolved:d=sl,batchThreshold:f=f_,detail:p=S_,merge:h=v_,updateBranch:m=A_,lookup:b=St,disarm:k=Gr,emitDisarmed:v=T_,localHasCommit:E=I_}=t;if(!(await n().catch(()=>null))?.authenticated||$s()>0)return 0;let I=(await s().catch(()=>[])).filter(y=>e-(ia.get(st(y.repo,y.number))||0)>=g_(y,e));if(!I.length)return 0;let $=Date.now(),_=new Map,U=new Map;for(let y of I){let A=Mw(y,e);_.set(st(y.repo,y.number),{lane:A,intervalMs:Cw[A]}),ia.set(st(y.repo,y.number),e),U.has(y.repo)||U.set(y.repo,[]),U.get(y.repo).push(y)}let V=await a().catch(()=>new Map),g={now:e,lanes:_,emitMerged:i,mark:c,archiveIfArmed:l,notifyIfArmed:u,notifyIfResolved:d,detail:p,merge:h,updateBranch:m,lookup:b,disarm:k,emitDisarmed:v,localHasCommit:E};for(let[y,A]of U){let R,D=new Set;if(A.length>f)R=await o(y).catch(()=>null);else{R=new Map;for(let T of A)try{let O=await r(y,T.number);O?.number!=null&&R.set(Number(O.number),O)}catch(O){h_(O)&&D.add(st(T.repo,T.number))}}if(R)for(let T of A){let O=st(T.repo,T.number),W=R.get(Number(T.number));if(!W){if(D.has(O)){let tt=(ca.get(O)||0)+1;ca.set(O,tt),tt>=u_&&(await O_(T,V,g),ca.delete(O),ia.delete(O))}continue}ca.delete(O),W.state==="MERGED"||W.state==="CLOSED"?(W.state==="MERGED"?await Pw(T,W,V,g):await $_(T,W,V,g),ia.delete(O)):(await R_(T,W,V,g).catch(()=>{}),T.autoMerge&&await E_(T,V,g).catch(()=>{}))}}let w={};for(let{lane:y}of _.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()-$}ms`),I.length}function Nw(){let t=!1;setInterval(async()=>{if(!t){t=!0;try{await C_()}catch{}finally{t=!1}}},$w)}import be from"node:fs/promises";import vr from"node:path";import Hw from"node:os";import Bw from"node:fs/promises";import la from"node:path";import{execFile as M_}from"node:child_process";import{promisify as P_}from"node:util";import{fileURLToPath as N_}from"node:url";var B_=P_(M_),jw=la.dirname(N_(import.meta.url)),D_=la.resolve(jw,"..","..","client","dist"),j_="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",L_=new Map([...j_].map((t,e)=>[t,e]));function F_(t){let e=[],n=0,r=0;for(let o of t){let s=L_.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 U_(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=F_(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 H_(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 Dw=new Map;async function W_(t){let e;try{e=await Bw.stat(t)}catch{return null}let n=Dw.get(t);if(n&&n.mtimeMs===e.mtimeMs)return n;try{let r=JSON.parse(await Bw.readFile(t,"utf8")),o={mtimeMs:e.mtimeMs,sources:r.sources||[],lines:U_(r.mappings||"")};return Dw.set(t,o),o}catch{return null}}var Ol=null;function q_(){return Ol||(Ol=process.env.BOSTRAT_RELEASE?Promise.resolve(process.env.BOSTRAT_RELEASE):B_("git",["rev-parse","--short","HEAD"],{cwd:la.resolve(jw,"..","..")}).then(t=>t.stdout.trim()).catch(()=>null)),Ol}var G_=/(?:https?:\/\/[^\s()]*)?\/assets\/([^\s()/]+\.js):(\d+):(\d+)/g;async function K_(t,{distDir:e=D_}={}){let n=[],r=0,o=!1;for(let s of t.matchAll(G_)){let[a,i,c,l]=s,u=await W_(la.join(e,"assets",`${i}.map`)),d=u?H_(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 Lw(t,e={}){try{if(!t?.error?.stack||!t.release||t.release==="unknown")return t;let n=e.release??await q_();if(!n||t.release!==n)return t;let{stack:r,symbolicated:o}=await K_(t.error.stack,e);o&&(t.error.stack=r,t.error.symbolicated=!0)}catch{}return t}var V_=()=>process.env.BOSTRAT_STATE_DIR||vr.join(process.env.HOME||Hw.tmpdir(),".bostrat"),Xe=()=>vr.join(V_(),"telemetry"),Ww=()=>vr.join(Xe(),".relay-offsets.json"),J_=50,z_=14,X_=3e4,Fw=500,Nt=(t,e)=>typeof t=="string"?t.slice(0,e):"";function Y_(t,e){if(!t||typeof t!="object")return null;let n=Nt(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:Nt(t.anonId,64),sessionId:Nt(t.sessionId,64),userId:Nt(t.userId,64),release:Nt(t.release,64),appVersion:Nt(t.appVersion,32),route:Nt(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:Nt(t.error.kind,256),message:Nt(t.error.message,8192),stack:Nt(t.error.stack,32768),fingerprint:Nt(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:Nt(s?.n,64),r:Nt(s?.r,128)}))),o}var qw=t=>new Date(t).toISOString().slice(0,10),Uw=0;async function Gw(t,e=Date.now()){let r=(Array.isArray(t?.events)?t.events.slice(0,J_):[]).map(s=>Y_(s,e)).filter(Boolean);if(!r.length)return 0;for(let s of r)s.error?.stack&&await Lw(s);await be.mkdir(Xe(),{recursive:!0});let o=vr.join(Xe(),`${qw(e)}.ndjson`);return await be.appendFile(o,r.map(s=>JSON.stringify(s)+`
|
|
292
|
-
`).join(""),"utf8"),e-Uw>6*3600*1e3&&(Uw=e,Z_(e).catch(()=>{})),r.length}async function Z_(t=Date.now()){let e=qw(t-z_*24*3600*1e3),n=[];try{n=await be.readdir(Xe())}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 be.rm(vr.join(Xe(),s),{force:!0});let o=await Vw();for(let s of r)delete o[s];await Jw(o)}var ua=new Map;function Kw(t,e=Date.now()){let n=t||"unknown",r=ua.get(n);return r||(ua.size>512&&ua.clear(),r={tokens:40,at:e},ua.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 Vw(){try{return JSON.parse(await be.readFile(Ww(),"utf8"))||{}}catch{return{}}}async function Jw(t){await be.mkdir(Xe(),{recursive:!0}),await be.writeFile(Ww(),JSON.stringify(t),"utf8")}function Q_(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||Hw.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 tE({fetchImpl:t=fetch}={}){let e=await Pd();if(!e)return{sent:0,disabled:!0};let n;try{n=(await be.readdir(Xe())).filter(s=>s.endsWith(".ndjson")).sort()}catch{return{sent:0,disabled:!1}}let r=await Vw(),o=0;for(let s of n){let a=vr.join(Xe(),s),i=r[s]||0,c;try{let f=await be.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(`
|
|
293
|
-
`).filter(Boolean),d=[];for(let f of u)try{d.push(JSON.parse(f))}catch{}for(let f=0;f<d.length;f+=Fw){let p=d.slice(f,f+Fw).map(m=>Q_(m)),h=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(!h||!h.ok)return{sent:o,disabled:!1};o+=p.length}r[s]=i+l+1,await Jw(r)}return{sent:o,disabled:!1}}var Cl=null,Ml=!1;function zw(){Cl||(Cl=setInterval(async()=>{if(!Ml){Ml=!0;try{await tE()}catch(t){console.error("[telemetry] relay failed:",t?.message||t)}finally{Ml=!1}}},X_),Cl.unref?.())}import fa from"node:fs/promises";import tg from"node:path";import eE from"node:os";var eg=()=>process.env.BOSTRAT_STATE_DIR||tg.join(process.env.HOME||eE.tmpdir(),".bostrat"),ng=()=>tg.join(eg(),"dictation-glossary.json"),nE=100,rE=500,oE=["bostrat","globopi","codex","tmux","worktree","Deepgram","Linear","Firecracker","Tigris","WireGuard","misous","postyx","cedar","gerbil","Anthropic","Opus","Haiku","Sonnet","keyterm","monorepo","dogfood"],pa=null,Pl=null,Nl=Promise.resolve();function Xw(){let t={},e=Date.now();for(let n of oE)t[n.toLowerCase()]={term:n,count:1,lastUsed:e,source:"seed"};return t}async function rg(){return pa||(Pl??=fa.readFile(ng(),"utf8").then(t=>JSON.parse(t)?.terms??Xw()).catch(()=>Xw()).then(t=>pa=t).finally(()=>Pl=null),Pl)}async function Yw(){let t=JSON.stringify({terms:pa},null,2),e=ng(),n=`${e}.${process.pid}.tmp`;await fa.mkdir(eg(),{recursive:!0}),await fa.writeFile(n,t),await fa.rename(n,e)}function sE(){return Nl=Nl.then(Yw,Yw),Nl.catch(()=>{})}async function Bl(){return Object.values(await rg()).sort((e,n)=>n.count-e.count||n.lastUsed-e.lastUsed)}var aE=t=>Math.max(1,Math.ceil(t.length/4));async function iE(){let t=[],e=0;for(let{term:n}of await Bl()){let r=aE(n);if(!(e+r>rE)&&(t.push(n),e+=r,t.length>=nE))break}return t}async function cE(t,e){let n=await rg(),r=t.toLowerCase(),o=n[r];n[r]={term:t,count:(o?.count??0)+1,lastUsed:Date.now(),source:o?.source??e}}var Zw=t=>(t||"").split(/\s+/).map(e=>e.replace(/^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu,"")).filter(Boolean),da=t=>t.toLowerCase();function Qw(t){return!new RegExp("\\p{L}","u").test(t)||t.length<3?!1:/^[a-z]+$/.test(t)?t.length>=4:!0}async function og(t,e){let n=Zw(t),r=Zw(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]=da(n[u])===da(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;)da(n[c])===da(r[l])?(c++,l++):a[c+1][l]>=a[c][l+1]?c++:(Qw(r[l])&&i.add(r[l]),l++);for(;l<s;l++)Qw(r[l])&&i.add(r[l]);if(!i.size)return[];for(let u of i)await cE(u,"learned");return await sE(),[...i]}var To=null;function lE(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 sg(){if(!To)return null;let t=await Lr();if(!t||t.apiKey.slice(-6)!==To.keyTail)return null;let{keyTail:e,...n}=To;return n}async function ag(t,e){let n=await Lr();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 iE())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=lE(o.status,i);c.persistent&&(To={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 To=null,{transcript:(await o.json())?.results?.channels?.[0]?.alternatives?.[0]?.transcript??""}}var dg=ct.dirname(dE(import.meta.url)),ig=Number(process.env.PORT)||3362,ma=ow(process.env),fg=Number(process.env.BOSTRAT_AUTO_TRIGGER_PCT)||80,Ar=Aw(process.env.BOSTRAT_ALLOW_IPS),S=ae();S.disable("x-powered-by");S.use((t,e,n)=>{if(Ar.allowed(Ar.clientIp(t)))return n();e.status(403).type("text/plain").send("bostrat: access restricted to the allowlist")});S.use(Ew);S.use("/api/mocks",ae.json({limit:"1mb"}));S.use("/api/mocks",ae.raw({type:"image/png",limit:"24mb"}));S.use("/api/runbooks",ae.json({limit:"1mb"}));S.use("/api/telemetry",ae.json({limit:"256kb"}));S.use("/api/hook",ae.json({limit:"2mb"}));S.use("/api/dictation",ae.raw({type:["audio/*","video/mp4","application/octet-stream"],limit:"32mb"}));S.use("/api/tracks/:id/upload",ae.raw({type:()=>!0,limit:"25mb"}));S.use(ae.json({limit:"64kb"}));S.get("/api/health",async(t,e)=>{e.json({ok:!0,tmuxSocket:Ct,hostTmuxAlive:await xe(),autoTriggerPct:fg})});S.get("/api/sessions",async(t,e)=>{e.json({sessions:await Pn()})});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 tn(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 Vt(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 $r(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 Ru(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=Ho(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 ft(),agents:Qc,tickets:await is()})});S.get("/api/cost",async(t,e)=>{try{let n=await dw(await ft());if(n.cursorUsage=Mm(),t.query.format==="text")return e.type("text/plain").send(fw(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 ww(await ft(),{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 xf(await ft()))}catch(n){e.status(500).json({error:n?.message||String(n)})}});S.get("/api/arcs/:key",async(t,e)=>{try{let n=await Sf(await ft(),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 Nh()})}catch(n){e.status(500).json({error:n?.message||String(n)})}});S.post("/api/hook",async(t,e)=>{try{let n=await rw(t.body||{},hh);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(!Kw(Ar.clientIp(t)))return e.json({ok:!0,dropped:!0});let n=await Gw(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 pe(),r=new Set((await Promise.all(n.map(a=>Y(a.path)))).filter(Boolean).map(a=>a.toLowerCase())),s=((await Vr()).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 ys(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:gs(),calls:dm(),latency:jm()})});S.post("/api/github/refresh",(t,e)=>{Mf(),e.json({ok:!0})});function Fl(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 ha({repo:t=null,number:e=null,branch:n=null}){let r=t?await Y(t):null;if(r&&e!=null){let s=await St(r,e),a=s&&await nt(s.trackId);if(a)return a}return n?(await ft()).find(s=>s.workspace?.branch===n&&Fl(s,t,r))??null:null}async function pg({repo:t=null,number:e=null,branch:n=null}){let r=await ha({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 ft()).find(a=>!a.archivedAt&&a.workspace?.branch===n&&Fl(a,t,o))??null}async function Kt(t,{number:e,url:n=null,title:r=null,branch:o=null,repo:s=null,track:a=null,extra:i={}}){let c=a||await ha({repo:s,number:e,branch:o}).catch(()=>null);if(!c)return;let l=i.repo??(s?await Y(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 hE(t){let e=(await ft()).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 St(r,o.number))continue;let s=e.find(a=>a.workspace?.branch===o.headRefName&&Fl(a,n.path,r));s&&await Mt({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 Gm();await hE(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 m=o||"merge";if(!ro.has(m))throw new Error(`invalid merge method: ${m}`);let b=Number(r);if(!Number.isInteger(b)||b<=0)throw new Error("invalid PR number");let k=await Y((await we(n)).path);if(!k)throw new Error("repo has no GitHub remote");let v=await ha({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 oo(n,b),await Mt({repo:k,number:b,trackId:v.id,url:d,headRef:l,title:u,source:"matched"}).catch(()=>{}),!await qr(k,b,{merge:{method:m}}))throw new Error("couldn't record the auto-merge arm");return await zn(k,b,i===!0),await hs(k,b,c===!0&&i!==!0),oe(),await Kt("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 ur(n,r,{method:o||"squash"});await Kt("merged",{number:f.number,url:d,title:u,branch:l,repo:n});let p=await Y(n).catch(()=>null),h=!1;if(p&&(i===!0&&await zn(p,f.number,!0).catch(()=>{}),c===!0&&i!==!0&&await hs(p,f.number,!0).catch(()=>{}),await Ue(p,f.number).catch(()=>{}),h=await Fe(p,f.number).catch(()=>!1),await Oe(p,f.number,"merged").catch(()=>{})),i===!0&&!h){let m=await ha({repo:n,number:f.number,branch:l}).catch(()=>null);m&&!m.archivedAt&&await wr(m.id).catch(()=>{})}e.json(f)}catch(f){let p=f?.message||String(f),h=/invalid|unknown repo|required|needs a Track|no GitHub remote/i.test(p)?400:502;e.status(h).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 we(n)).path).catch(()=>null),l=c?await Gr(c,i).catch(()=>null):null;try{await Lc(n,i)}catch(u){if(!l)throw u}oe(),await Kt("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 zm(n,{method:r||"merge",onMerged:a=>{Kt("merged",{number:a.number,url:a.url,title:a.title,branch:a.headRefName,repo:n}),o&&Ue(o,a.number).catch(()=>{}).then(()=>Fe(o,a.number)).catch(()=>{}).then(()=>Oe(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:Jm()})});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(!ro.has(i))throw new Error(`invalid merge method: ${i}`);let c=n?await nt(n):null;if(!c)return e.status(400).json({error:"trackId required"});let l=(await Ce()).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 zn(f.repo,f.number,o===!0).catch(()=>{}),await hs(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 h=await vn(f.repo);if(!h)throw new Error(`no local checkout for ${f.repo}`);let m=await lr(h,f.number);d.push({r:p,l:f,repoPath:h,pr:m,plan:Km(m,{update:r!==!1})})}catch(h){p.ok=!1,p.action="failed",p.reason=h?.message||String(h)}}for(let{r:f,l:p,repoPath:h,pr:m,plan:b}of Vm(d))try{if(b.action==="skip")f.action="skipped",f.reason=b.reason;else if(b.action==="merge")await ur(h,p.number,{method:i}),await Kt("merged",{number:p.number,url:m.url,title:m.title,branch:m.headRefName,repo:h,track:c}),await Ue(p.repo,p.number).catch(()=>{}),await Fe(p.repo,p.number).catch(()=>{}),await Oe(p.repo,p.number,"merged").catch(()=>{}),f.action="merged";else{if(b.action==="update-arm"&&(await oo(h,p.number).catch(()=>{}),f.action="updated-armed"),!await qr(p.repo,p.number,{merge:{method:i}}))throw new Error("couldn't record the auto-merge arm");await Kt("auto-armed",{number:p.number,url:m.url,title:m.title,branch:m.headRefName,repo:h,track:c,...f.action==="updated-armed"?{extra:{updated:!0}}:{}})}}catch(k){f.ok=!1,f.action="failed",f.reason=k?.message||String(k)}if(oe(),!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 nt(n):null;if(!r)return e.status(400).json({error:"trackId required"});let o=(await Ce()).filter(a=>a.trackId===r.id&&a.state!=="merged"&&a.state!=="closed"),s=[];for(let a of o){let i=await Gr(a.repo,a.number).catch(()=>null),c=await vn(a.repo).catch(()=>null);c&&await Lc(c,a.number).catch(()=>{}),i&&(await Kt("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}))}oe(),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 nt(n):null;if(!i)return e.status(400).json({error:"trackId required"});if(i.workspace?.kind==="multi"){let u=Rt(i.workspace),d,f=Array.isArray(a)?a.filter(Boolean):[];if(f.length)d=f.map(m=>{let b=u.find(k=>k.name===m||k.repo===m||k.base===m||k.dir===m);if(!b)throw new Error(`invalid repo "${m}" \u2014 not tagged on this Track`);return b});else if(d=await to(i),!d.length)return e.status(409).json({error:"no tagged repo has work to ship"});let p=[];for(let m of d)try{let b=await Uc(m.dir,{title:r,body:o,base:s}),k=m.repo||await Y(m.dir).catch(()=>null);k&&b.number&&await Mt({repo:k,number:b.number,trackId:i.id,url:b.url,headRef:i.workspace?.branch??null,title:r,source:"api"}),await Kt("opened",{number:b.number,url:b.url,title:r,track:i,branch:i.workspace?.branch,extra:{repo:k??m.name}}),p.push({repo:m.name,slug:k??null,ok:!0,url:b.url,number:b.number})}catch(b){p.push({repo:m.name,slug:m.repo??null,ok:!1,error:b?.message||String(b)})}let h=p.filter(m=>m.ok);return h.length?e.json({ok:!0,multi:!0,results:p,url:h[0].url,number:h[0].number}):e.status(502).json({error:p.map(m=>`${m.repo}: ${m.error}`).join("; "),results:p})}let c=await Uc(i.cwd,{title:r,body:o,base:s}),l=await Y(i.cwd).catch(()=>null);l&&c.number&&await Mt({repo:l,number:c.number,trackId:i.id,url:c.url,headRef:i.workspace?.branch??null,title:r,source:"api"}),await Kt("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 nt(t.params.id);if(!n)return e.status(404).json({error:"track not found"});let r=await to(n),o=new Map(r.map(a=>[a.base,a])),s=Rt(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 Eh(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 Ih(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 lr(n,r),s=await Y(n).catch(()=>null),a=await pg({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=Rt(a.workspace).find(k=>k.base===n||s&&k.repo===s)??null,c=a.workspace?.kind==="multi",l=await Xm(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",d=ct.join(a.cwd,".bostrat",u),f=ct.join(".bostrat",u,`pr-${o.number}-fail.log`);await Ot.promises.mkdir(d,{recursive:!0}),await Ot.promises.writeFile(ct.join(a.cwd,f),l.map(k=>`\u2550\u2550\u2550\u2550 ${k.name}${k.link?` \xB7 ${k.link}`:""} \u2550\u2550\u2550\u2550
|
|
294
|
-
${k.log??"(no fetchable log \u2014 see link)"}
|
|
313
|
+
`),n?.length&&a.write(n),e.pipe(a),a.pipe(e)}),i=()=>{a.destroy(),e.destroy()};return a.on("error",i),e.on("error",i),!0}function Ig(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}.${HE(n.host)}/`}function HE(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 Ag(e)&&Ii(n[0])?n.slice(1).join("."):e}var Rg=10,$g=200,WE=120,qE=24,GE=/^\d{4}-\d{2}-\d{2}$/;function nu(t=process.env){return(t.CHANGELOG_REPOS||"globocodes/bostrat").split(",").map(e=>e.trim()).filter(Boolean)}var KE=(t=Date.now())=>new Date(t).toISOString().slice(0,10);function Og(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 VE(t){if(typeof t!="string"||!t.trim())throw new Error("md (markdown string) required");let e=Kr(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(`
|
|
314
|
+
`)){let s=o.trim();if(!s)continue;if(s.startsWith("#")){n==null&&(n=s.replace(/^#+\s*/,"").trim().slice(0,WE)||null);continue}let a=/^[-*]\s+(.*)$/.exec(s);if(!a)continue;let i=a[1].trim(),c=null,l=new RegExp(`^\\[([^\\]]{1,${qE}})\\]\\s+(.*)$`).exec(i);if(l&&(c=l[1].trim(),i=l[2].trim()),!!i){if(i.length>$g)throw new Error(`bullet ${r.length+1} is ${i.length} chars \u2014 over the ${$g}-char cap; a changelog bullet is one line`);r.push({text:i,...c?{area:c}:{}})}}if(!r.length)throw new Error("no `- ` bullets found \u2014 an entry is a bullet list, optionally under a `# headline`");if(r.length>Rg)throw new Error(`${r.length} bullets \u2014 over the ${Rg}-bullet cap; group related changes into one line`);return{title:n||null,bullets:r}}async function Cg(t,{repos:e=nu()}={}){let n=new Set(e);return(await se()).filter(r=>r.state==="merged"&&(r.resolvedAt??0)>=t&&n.has(r.repo)).sort((r,o)=>(r.resolvedAt??0)-(o.resolvedAt??0)).map(r=>({repo:r.repo,number:r.number,title:r.title||null,url:r.url||null,mergedAt:r.resolvedAt??null}))}async function Mg(t,{md:e,day:n=null}){let r=VE(e),o=n==null||n===""?KE():String(n);if(!GE.test(o)||Number.isNaN(Date.parse(o)))throw new Error(`invalid day "${n}" \u2014 expected YYYY-MM-DD`);let s=await O({id:t.id,cwd:t.cwd},{type:"changelog.published",actor:"agent",source:"orchestrator",boxId:t.leadBox||null,payload:{ts:Date.now(),day:o,title:r.title,bullets:r.bullets}});return{day:o,title:r.title,bullets:r.bullets,eventId:s.id}}import{execFile as zE}from"node:child_process";import{promisify as JE}from"node:util";var ou=JE(zE),Ng=3e4,XE=30*6e4,YE=Ng,ZE=10*6e4,QE=10080*6e4,tI=60*6e4,eI=8,nI=1440*6e4,rI=3,Pg="number,url,title,headRefName,headRefOid,state,mergedAt,closedAt",ru=60*6e4,oI=15*6e4,Oa=new Map,Ca=new Map,sI=/could not resolve|not found|no pull requests?/i,aI=t=>sI.test(String(t?.message||t||""));function iI(t,e){return[t.ciFix?.delegatedAt,t.reconcile?.delegatedAt,t.ciFixAwait?.since,t.reconcileAwait?.since].some(n=>n&&e-n<ru)}var Bg={fast:YE,slow:ZE,decay:tI};function Dg(t,e){return t.armedAt&&e-t.armedAt<XE||iI(t,e)?"fast":e-(t.firstSeen||0)>QE?"decay":"slow"}function cI(t,e){return Bg[Dg(t,e)]}function Do(t,e,n,r){let o=t.lanes?.get(ut(e.repo,e.number))||{};return ia({change:n,repo:e.repo,number:Number(e.number),githubAt:r||null,lane:o.lane||null,intervalMs:o.intervalMs||null,source:"watch"})}async function lI(t,e){ke("watch");try{let{stdout:n}=await ou("gh",["pr","view",String(e),"--repo",t,"--json",Pg],{timeout:3e4,maxBuffer:4194304,env:{...process.env,...await ae({slug:t})}});return JSON.parse(n||"null")}catch(n){throw Fe(n)&&Ue(n),n}}async function uI(t){ke("watch");try{let{stdout:e}=await ou("gh",["pr","list","--repo",t,"--state","all","--limit","100","--json",Pg],{timeout:3e4,maxBuffer:8388608,env:{...process.env,...await ae({slug:t})}}),n=new Map;for(let r of JSON.parse(e||"[]"))n.set(Number(r.number),r);return n}catch(e){throw Fe(e)&&Ue(e),e}}async function dI(){let t=new Map;for(let e of await at().catch(()=>[]))t.set(e.id,e);return t}async function fI(t,e){await O({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"user",source:"orchestrator",boxId:null,payload:{ts:Date.now(),change:"merged",...e}})}async function pI(t,e){let n=await An(t);return n?pr(n,e):null}async function mI(t,e,n){let r=await An(t);if(!r)throw new Error(`no local checkout for ${t}`);return mr(r,e,{method:n})}async function hI(t,e){let n=await An(t);if(!n)throw new Error(`no local checkout for ${t}`);return mo(n,e)}async function wI(t,e){await O({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"user",source:"orchestrator",boxId:null,payload:{ts:Date.now(),change:"auto-canceled",...e}})}async function Ir(t,e,n,r,o){if(!await r.disarm(t.repo,t.number).catch(()=>null))return;let a=n.get(t.trackId);a&&await r.emitDisarmed(a,{number:Number(t.number),url:e?.url||t.url,title:e?.title||t.title,branch:e?.headRefName||t.headRef,repo:t.repo,reason:o}).catch(()=>{})}var gI=10*6e4;async function yI(t,e,n){let r=t.autoMerge?.armedAt||0;if(r&&(n.now??Date.now())-r>nI){await Ir(t,null,e,n,"auto-merge expired \u2014 checks never settled within 24h");return}let o=await n.detail(t.repo,t.number).catch(()=>null);if(!o)return;if(o.checks.failed>0){let a=o.checks.failing.map(i=>i.name).join(", ");await Ir(t,o,e,n,`check${o.checks.failed===1?"":"s"} failed: ${a}`);return}if(o.mergeable==="CONFLICTING"){await Ir(t,o,e,n,`conflicts with ${o.baseRefName}`);return}if(!o.checks.total){let a=Math.max(t.autoMerge?.armedAt||0,Date.parse(o.updatedAt||"")||0);if((n.now??Date.now())-a<gI)return;await Ir(t,o,e,n,o.checks.unavailable?"this token can't read the checks":"no CI checks appeared");return}if(o.checks.pending>0||o.mergeable==="UNKNOWN"||o.mergeStateStatus==="UNKNOWN"||o.isDraft)return;let s=await n.lookup(t.repo,t.number).catch(()=>null);if(s?.autoMerge){if(o.mergeStateStatus==="BEHIND"){try{await n.updateBranch(t.repo,t.number)}catch(a){let i=String(a?.message||a).trim();/no new commits|already up.to.date/i.test(i)||await Ir(t,o,e,n,i||`couldn't update with ${o.baseRefName}`)}return}try{await n.merge(t.repo,t.number,s.autoMerge.method||"merge")}catch(a){if(/already merged|not open|is closed/i.test(String(a?.message||a)))return;await Ir(t,o,e,n,String(a?.message||a).trim()||"merge failed");return}await jg(t,{...o,state:"MERGED"},e,n,{change:"auto-merged",githubAt:o.checks.settledAt||null})}}async function No(t,e,n){await O({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"user",source:"orchestrator",boxId:null,payload:{ts:Date.now(),change:e,...n}})}async function bI(t,e){try{return await ou("git",["-C",t.cwd,"cat-file","-e",`${e}^{commit}`],{timeout:1e4}),!0}catch{return!1}}async function Po(t,e,n,r=()=>!0){return(await F(t).catch(()=>[])).some(s=>s.type==="pr.state.changed"&&s.payload?.change===e&&s.payload?.number===Number(n)&&r(s))}var Bo=(t,e)=>({number:Number(t.number),url:e?.url||t.url,title:e?.title||t.title,branch:e?.headRefName||t.headRef,repo:t.repo});async function kI(t,e,n,r){let o=r.now??Date.now(),s=e.headRefOid||null,a=n.get(t.trackId)||null;if(s&&(t.ciFix?.headSha&&s!==t.ciFix.headSha&&await Ms(t.repo,t.number,{headSha:s}).catch(()=>{}),t.reconcile?.headSha&&s!==t.reconcile.headSha&&await Ps(t.repo,t.number,{headSha:s}).catch(()=>{})),s&&s!==t.lastHeadSha){let i=!t.lastHeadSha,c=!!(t.ciFix||t.reconcile||t.ciFixAwait||t.reconcileAwait),l=!!t.baseFoldAt&&o-t.baseFoldAt<oI;await op(t.repo,t.number,s).catch(()=>{}),t.baseFoldAt&&await rp(t.repo,t.number).catch(()=>{}),!i&&!c&&!l&&a&&!await r.localHasCommit(a,s).catch(()=>!1)&&!await Po(a,"pushed",t.number,f=>f.payload?.sha===s)&&await No(a,"pushed",{...Bo(t,e),sha:s}).catch(()=>{})}if(t.ciFixAwait)if(o-(t.ciFixAwait.since||0)>ru)await Ns(t.repo,t.number).catch(()=>{});else{let i=await r.detail(t.repo,t.number).catch(()=>null);if(i?.checks){if(i.checks.failed>0)await Ns(t.repo,t.number).catch(()=>{});else if(i.checks.total>0&&i.checks.pending===0){let c=Do(r,t,"ci-fixed",i.checks.settledAt||null),l=t.ciFixAwait.since||0;a&&!await Po(a,"ci-fixed",t.number,u=>(u.payload?.ts||0)>=l)&&await No(a,"ci-fixed",{...Bo(t,e),passed:i.checks.passed,githubAt:c.githubAt,latencyMs:c.latencyMs,lane:c.lane}).catch(()=>{}),await Ns(t.repo,t.number).catch(()=>{})}}}if(t.reconcileAwait)if(o-(t.reconcileAwait.since||0)>ru)await Bs(t.repo,t.number).catch(()=>{});else{let i=await r.detail(t.repo,t.number).catch(()=>null);if(i){if(i.mergeable==="CONFLICTING")await Bs(t.repo,t.number).catch(()=>{});else if(i.mergeable==="MERGEABLE"){Do(r,t,"reconciled",null);let c=t.reconcileAwait.since||0;a&&!await Po(a,"reconciled",t.number,l=>(l.payload?.ts||0)>=c)&&await No(a,"reconciled",{...Bo(t,e),base:i.baseRefName||null}).catch(()=>{}),await Bs(t.repo,t.number).catch(()=>{})}}}}async function xI(t,e,n,r){let o=Do(r,t,"closed",Date.parse(e?.closedAt||"")||null),s=n.get(t.trackId);s&&!await Po(s,"closed",t.number)&&await No(s,"closed",{...Bo(t,e),githubAt:o.githubAt,latencyMs:o.latencyMs,lane:o.lane}).catch(()=>{}),await r.notifyIfResolved(t.repo,t.number,"closed").catch(()=>{}),await r.mark(t.repo,t.number,"closed").catch(()=>{})}async function SI(t,e,n){Do(n,t,"lost",null);let r=e.get(t.trackId);r&&!await Po(r,"lost",t.number)&&await No(r,"lost",Bo(t,null)).catch(()=>{}),await n.notifyIfResolved(t.repo,t.number,"lost").catch(()=>{}),await n.mark(t.repo,t.number,"lost").catch(()=>{})}async function jg(t,e,n,r,o={}){let s=Do(r,t,o.change||"merged",o.githubAt??(Date.parse(e.mergedAt||"")||null)),a=n.get(t.trackId);a&&((await F(a).catch(()=>[])).some(l=>l.type==="pr.state.changed"&&l.payload?.change==="merged"&&l.payload?.number===Number(t.number))||await r.emitMerged(a,{number:Number(t.number),url:e.url||t.url,title:e.title||t.title,branch:e.headRefName||t.headRef,repo:t.repo,githubAt:s.githubAt,latencyMs:s.latencyMs,lane:s.lane}).catch(()=>{})),await r.notifyIfArmed(t.repo,t.number).catch(()=>{}),await r.archiveIfArmed(t.repo,t.number).catch(()=>{}),await r.mark(t.repo,t.number,"merged").catch(()=>{})}async function vI(t={}){let{now:e=Date.now(),auth:n=il,view:r=lI,list:o=uI,links:s=ap,trackMap:a=dI,emitMerged:i=fI,mark:c=Pe,archiveIfArmed:l=We,notifyIfArmed:u=qe,notifyIfResolved:f=Ol,batchThreshold:d=rI,detail:m=pI,merge:h=mI,updateBranch:w=hI,lookup:y=Tt,disarm:b=Qr,emitDisarmed:A=wI,localHasCommit:_=bI}=t;if(!(await n().catch(()=>null))?.authenticated||Qs()>0)return 0;let I=(await s().catch(()=>[])).filter(p=>e-(Oa.get(ut(p.repo,p.number))||0)>=cI(p,e));if(!I.length)return 0;let L=Date.now(),v=new Map,$=new Map;for(let p of I){let S=Dg(p,e);v.set(ut(p.repo,p.number),{lane:S,intervalMs:Bg[S]}),Oa.set(ut(p.repo,p.number),e),$.has(p.repo)||$.set(p.repo,[]),$.get(p.repo).push(p)}let W=await a().catch(()=>new Map),nt={now:e,lanes:v,emitMerged:i,mark:c,archiveIfArmed:l,notifyIfArmed:u,notifyIfResolved:f,detail:m,merge:h,updateBranch:w,lookup:y,disarm:b,emitDisarmed:A,localHasCommit:_};for(let[p,S]of $){let T,J=new Set;if(S.length>d)T=await o(p).catch(()=>null);else{T=new Map;for(let R of S)try{let M=await r(p,R.number);M?.number!=null&&T.set(Number(M.number),M)}catch(M){aI(M)&&J.add(ut(R.repo,R.number))}}if(T)for(let R of S){let M=ut(R.repo,R.number),E=T.get(Number(R.number));if(!E){if(J.has(M)){let P=(Ca.get(M)||0)+1;Ca.set(M,P),P>=eI&&(await SI(R,W,nt),Ca.delete(M),Oa.delete(M))}continue}Ca.delete(M),E.state==="MERGED"||E.state==="CLOSED"?(E.state==="MERGED"?await jg(R,E,W,nt):await xI(R,E,W,nt),Oa.delete(M)):(await kI(R,E,W,nt).catch(()=>{}),R.autoMerge&&await yI(R,W,nt).catch(()=>{}))}}let g={};for(let{lane:p}of v.values())g[p]=(g[p]||0)+1;return console.log(`[pr-watch] probed ${I.length} link(s) (${Object.entries(g).map(([p,S])=>`${p} ${S}`).join(", ")}) in ${Date.now()-L}ms`),I.length}function Lg(){let t=!1;setInterval(async()=>{if(!t){t=!0;try{await vI()}catch{}finally{t=!1}}},Ng)}import Ae from"node:fs/promises";import Rr from"node:path";import Kg from"node:os";import Fg from"node:fs/promises";import Ma from"node:path";import{execFile as AI}from"node:child_process";import{promisify as TI}from"node:util";import{fileURLToPath as _I}from"node:url";var EI=TI(AI),Hg=Ma.dirname(_I(import.meta.url)),II=Ma.resolve(Hg,"..","..","client","dist"),RI="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",$I=new Map([...RI].map((t,e)=>[t,e]));function OI(t){let e=[],n=0,r=0;for(let o of t){let s=$I.get(o);if(s===void 0)return e;n+=(s&31)<<r,s&32?r+=5:(e.push(n&1?-(n>>>1):n>>>1),n=0,r=0)}return e}function CI(t){let e=[],n=0,r=0,o=0;for(let s of t.split(";")){let a=[],i=0;if(s)for(let c of s.split(",")){let l=OI(c);l.length!==0&&(i+=l[0],l.length>=4&&(n+=l[1],r+=l[2],o+=l[3],a.push([i,n,r,o])))}e.push(a)}return e}function MI(t,e){if(!t||t.length===0||t[0][0]>e)return null;let n=0,r=t.length-1;for(;n<r;){let o=n+r+1>>1;t[o][0]<=e?n=o:r=o-1}return t[n]}var Ug=new Map;async function NI(t){let e;try{e=await Fg.stat(t)}catch{return null}let n=Ug.get(t);if(n&&n.mtimeMs===e.mtimeMs)return n;try{let r=JSON.parse(await Fg.readFile(t,"utf8")),o={mtimeMs:e.mtimeMs,sources:r.sources||[],lines:CI(r.mappings||"")};return Ug.set(t,o),o}catch{return null}}var su=null;function PI(){return su||(su=process.env.BOSTRAT_RELEASE?Promise.resolve(process.env.BOSTRAT_RELEASE):EI("git",["rev-parse","--short","HEAD"],{cwd:Ma.resolve(Hg,"..","..")}).then(t=>t.stdout.trim()).catch(()=>null)),su}var BI=/(?:https?:\/\/[^\s()]*)?\/assets\/([^\s()/]+\.js):(\d+):(\d+)/g;async function DI(t,{distDir:e=II}={}){let n=[],r=0,o=!1;for(let s of t.matchAll(BI)){let[a,i,c,l]=s,u=await NI(Ma.join(e,"assets",`${i}.map`)),f=u?MI(u.lines[Number(c)-1],Number(l)-1):null;if(n.push(t.slice(r,s.index)),f&&u.sources[f[1]]){let d=u.sources[f[1]].replace(/^(\.\.\/)+/,"");n.push(`${d}:${f[2]+1}:${f[3]+1}`),o=!0}else n.push(a);r=s.index+a.length}return n.push(t.slice(r)),{stack:n.join(""),symbolicated:o}}async function Wg(t,e={}){try{if(!t?.error?.stack||!t.release||t.release==="unknown")return t;let n=e.release??await PI();if(!n||t.release!==n)return t;let{stack:r,symbolicated:o}=await DI(t.error.stack,e);o&&(t.error.stack=r,t.error.symbolicated=!0)}catch{}return t}var jI=()=>process.env.BOSTRAT_STATE_DIR||Rr.join(process.env.HOME||Kg.tmpdir(),".bostrat"),tn=()=>Rr.join(jI(),"telemetry"),Vg=()=>Rr.join(tn(),".relay-offsets.json"),LI=50,FI=14,UI=3e4,qg=500,jt=(t,e)=>typeof t=="string"?t.slice(0,e):"";function HI(t,e){if(!t||typeof t!="object")return null;let n=jt(t.name,64);if(!n)return null;let r=Number.isFinite(t.ts)?t.ts:e,o={name:n,ts:Math.abs(r-e)<168*3600*1e3?r:e,receivedAt:e,anonId:jt(t.anonId,64),sessionId:jt(t.sessionId,64),userId:jt(t.userId,64),release:jt(t.release,64),appVersion:jt(t.appVersion,32),route:jt(t.route,128),deviceClass:t.deviceClass==="phone"||t.deviceClass==="desktop"?t.deviceClass:""};return t.props&&typeof t.props=="object"&&!Array.isArray(t.props)&&(JSON.stringify(t.props).length<=8192?o.props=t.props:o.props={clamped:!0}),t.error&&typeof t.error=="object"&&(o.error={kind:jt(t.error.kind,256),message:jt(t.error.message,8192),stack:jt(t.error.stack,32768),fingerprint:jt(t.error.fingerprint,64)}),Array.isArray(t.breadcrumbs)&&(o.breadcrumbs=t.breadcrumbs.slice(-50).map(s=>({t:Number.isFinite(s?.t)?s.t:0,n:jt(s?.n,64),r:jt(s?.r,128)}))),o}var zg=t=>new Date(t).toISOString().slice(0,10),Gg=0;async function Jg(t,e=Date.now()){let r=(Array.isArray(t?.events)?t.events.slice(0,LI):[]).map(s=>HI(s,e)).filter(Boolean);if(!r.length)return 0;for(let s of r)s.error?.stack&&await Wg(s);await Ae.mkdir(tn(),{recursive:!0});let o=Rr.join(tn(),`${zg(e)}.ndjson`);return await Ae.appendFile(o,r.map(s=>JSON.stringify(s)+`
|
|
315
|
+
`).join(""),"utf8"),e-Gg>6*3600*1e3&&(Gg=e,WI(e).catch(()=>{})),r.length}async function WI(t=Date.now()){let e=zg(t-FI*24*3600*1e3),n=[];try{n=await Ae.readdir(tn())}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 Ae.rm(Rr.join(tn(),s),{force:!0});let o=await Yg();for(let s of r)delete o[s];await Zg(o)}var Na=new Map;function Xg(t,e=Date.now()){let n=t||"unknown",r=Na.get(n);return r||(Na.size>512&&Na.clear(),r={tokens:40,at:e},Na.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 Yg(){try{return JSON.parse(await Ae.readFile(Vg(),"utf8"))||{}}catch{return{}}}async function Zg(t){await Ae.mkdir(tn(),{recursive:!0}),await Ae.writeFile(Vg(),JSON.stringify(t),"utf8")}function qI(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||Kg.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 GI({fetchImpl:t=fetch}={}){let e=await _f();if(!e)return{sent:0,disabled:!0};let n;try{n=(await Ae.readdir(tn())).filter(s=>s.endsWith(".ndjson")).sort()}catch{return{sent:0,disabled:!1}}let r=await Yg(),o=0;for(let s of n){let a=Rr.join(tn(),s),i=r[s]||0,c;try{let d=await Ae.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(`
|
|
316
|
+
`).filter(Boolean),f=[];for(let d of u)try{f.push(JSON.parse(d))}catch{}for(let d=0;d<f.length;d+=qg){let m=f.slice(d,d+qg).map(w=>qI(w)),h=await t(`${e.url.replace(/\/$/,"")}/v1/logs`,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${e.ingestKey}`},body:JSON.stringify({events:m})}).catch(()=>null);if(!h||!h.ok)return{sent:o,disabled:!1};o+=m.length}r[s]=i+l+1,await Zg(r)}return{sent:o,disabled:!1}}var au=null,iu=!1;function Qg(){au||(au=setInterval(async()=>{if(!iu){iu=!0;try{await GI()}catch(t){console.error("[telemetry] relay failed:",t?.message||t)}finally{iu=!1}}},UI),au.unref?.())}import Ba from"node:fs/promises";import oy from"node:path";import KI from"node:os";var sy=()=>process.env.BOSTRAT_STATE_DIR||oy.join(process.env.HOME||KI.tmpdir(),".bostrat"),ay=()=>oy.join(sy(),"dictation-glossary.json"),VI=100,zI=500,JI=["bostrat","globopi","codex","tmux","worktree","Deepgram","Linear","Firecracker","Tigris","WireGuard","misous","postyx","cedar","gerbil","Anthropic","Opus","Haiku","Sonnet","keyterm","monorepo","dogfood"],Da=null,cu=null,lu=Promise.resolve();function ty(){let t={},e=Date.now();for(let n of JI)t[n.toLowerCase()]={term:n,count:1,lastUsed:e,source:"seed"};return t}async function iy(){return Da||(cu??=Ba.readFile(ay(),"utf8").then(t=>JSON.parse(t)?.terms??ty()).catch(()=>ty()).then(t=>Da=t).finally(()=>cu=null),cu)}async function ey(){let t=JSON.stringify({terms:Da},null,2),e=ay(),n=`${e}.${process.pid}.tmp`;await Ba.mkdir(sy(),{recursive:!0}),await Ba.writeFile(n,t),await Ba.rename(n,e)}function XI(){return lu=lu.then(ey,ey),lu.catch(()=>{})}async function uu(){return Object.values(await iy()).sort((e,n)=>n.count-e.count||n.lastUsed-e.lastUsed)}var YI=t=>Math.max(1,Math.ceil(t.length/4));async function ZI(){let t=[],e=0;for(let{term:n}of await uu()){let r=YI(n);if(!(e+r>zI)&&(t.push(n),e+=r,t.length>=VI))break}return t}async function QI(t,e){let n=await iy(),r=t.toLowerCase(),o=n[r];n[r]={term:t,count:(o?.count??0)+1,lastUsed:Date.now(),source:o?.source??e}}var ny=t=>(t||"").split(/\s+/).map(e=>e.replace(/^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu,"")).filter(Boolean),Pa=t=>t.toLowerCase();function ry(t){return!new RegExp("\\p{L}","u").test(t)||t.length<3?!1:/^[a-z]+$/.test(t)?t.length>=4:!0}async function cy(t,e){let n=ny(t),r=ny(e);if(!n.length||!r.length)return[];let o=n.length,s=r.length,a=Array.from({length:o+1},()=>new Array(s+1).fill(0));for(let u=o-1;u>=0;u--)for(let f=s-1;f>=0;f--)a[u][f]=Pa(n[u])===Pa(r[f])?a[u+1][f+1]+1:Math.max(a[u+1][f],a[u][f+1]);let i=new Set,c=0,l=0;for(;c<o&&l<s;)Pa(n[c])===Pa(r[l])?(c++,l++):a[c+1][l]>=a[c][l+1]?c++:(ry(r[l])&&i.add(r[l]),l++);for(;l<s;l++)ry(r[l])&&i.add(r[l]);if(!i.size)return[];for(let u of i)await QI(u,"learned");return await XI(),[...i]}var jo=null;function tR(t,e){let n=null;try{n=JSON.parse(e)}catch{}let r=n?.err_code||n?.error_code||n?.category||"";return t===402||/PAYMENT_REQUIRED/i.test(r)?{status:402,code:"credits_exhausted",persistent:!0,message:"Deepgram credits are exhausted \u2014 dictation is down until you top up at console.deepgram.com or drop a new key into ~/.bostrat/integrations.json."}:t===401||t===403?{status:502,code:"key_invalid",persistent:!0,message:"Deepgram rejected the API key (revoked, or missing ASR scope) \u2014 replace it in ~/.bostrat/integrations.json."}:{status:502,code:"transcription_failed",persistent:!1,message:`Deepgram error ${t}: ${e.slice(0,300)}`}}async function ly(){if(!jo)return null;let t=await Vr();if(!t||t.apiKey.slice(-6)!==jo.keyTail)return null;let{keyTail:e,...n}=jo;return n}async function uy(t,e){let n=await Vr();if(!n){let i=new Error("Deepgram is not configured \u2014 add deepgram.apiKey to ~/.bostrat/integrations.json");throw i.status=503,i.code="not_configured",i}let r=new URL("https://api.deepgram.com/v1/listen");r.searchParams.set("model","nova-3"),r.searchParams.set("smart_format","true");for(let i of await ZI())r.searchParams.append("keyterm",i);let o=await fetch(r,{method:"POST",headers:{Authorization:`Token ${n.apiKey}`,"Content-Type":e||"application/octet-stream"},body:t});if(!o.ok){let i=await o.text().catch(()=>""),c=tR(o.status,i);c.persistent&&(jo={code:c.code,message:c.message,at:Date.now(),keyTail:n.apiKey.slice(-6)});let l=new Error(c.message);throw l.status=c.status,l.code=c.code,l}return jo=null,{transcript:(await o.json())?.results?.channels?.[0]?.alternatives?.[0]?.transcript??""}}var du={"gpt-realtime-2.1":{textIn:4,textInCached:.4,textOut:24,audioIn:32,audioInCached:.4,audioOut:64},"gpt-realtime-2":{textIn:4,textInCached:.4,textOut:24,audioIn:32,audioInCached:.4,audioOut:64},"gpt-realtime":{textIn:4,textInCached:.4,textOut:16,audioIn:32,audioInCached:.4,audioOut:64},"gpt-realtime-2.1-mini":{textIn:.6,textInCached:.06,textOut:2.4,audioIn:10,audioInCached:.3,audioOut:20},"gpt-realtime-mini":{textIn:.6,textInCached:.06,textOut:2.4,audioIn:10,audioInCached:.3,audioOut:20}};function eR(t){if(!t)return null;let e=String(t).trim();if(du[e])return e;let n=null;for(let r of Object.keys(du))e.startsWith(r)&&(!n||r.length>n.length)&&(n=r);return n}function nR(t,e={}){let n=du[eR(t)];if(!n)return 0;let r=0;for(let o of Object.keys(n))r+=(Number(e[o])||0)*n[o];return r/1e6}function rR(t,e){let n=null;try{n=JSON.parse(e)}catch{}let r=n?.error?.code||n?.error?.type||"";return t===429&&/insufficient_quota/i.test(r)?{status:402,code:"credits_exhausted",persistent:!0,message:"The OpenAI account is out of credit \u2014 voice chat is down until you top up at platform.openai.com or drop a new key into ~/.bostrat/integrations.json."}:t===401||t===403?{status:502,code:"key_invalid",persistent:!0,message:"OpenAI rejected the API key (revoked, or missing Realtime access) \u2014 replace it in ~/.bostrat/integrations.json."}:{status:502,code:"mint_failed",persistent:!1,message:`OpenAI realtime session mint failed (${t}): ${e.slice(0,300)}`}}var Lo=null;async function oR(){if(!Lo)return null;let t=await Ts();if(!t||t.apiKey.slice(-6)!==Lo.keyTail)return null;let{keyTail:e,...n}=Lo;return n}async function sR(t){let e=await fetch("https://api.openai.com/v1/realtime/client_secrets",{method:"POST",headers:{Authorization:`Bearer ${t.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify({session:{type:"realtime",model:t.model,audio:{output:{voice:t.voice}}}})});if(!e.ok){let o=await e.text().catch(()=>""),s=rR(e.status,o);s.persistent&&(Lo={code:s.code,message:s.message,at:Date.now(),keyTail:t.apiKey.slice(-6)});let a=new Error(s.message);throw a.status=s.status,a.code=s.code,a}Lo=null;let n=await e.json(),r=n?.value??n?.client_secret?.value;if(!r){let o=new Error("OpenAI returned no client secret");throw o.status=502,o.code="mint_failed",o}return{clientSecret:{value:r,expiresAt:n?.expires_at??n?.client_secret?.expires_at??null}}}var aR={openai:sR};async function dy(){let t=await Ts();return t?{configured:!0,provider:t.provider,model:t.model,voice:t.voice,problem:await oR()}:{configured:!1}}async function fy(){let t=await Ts();if(!t){let n=new Error("Voice chat is not configured \u2014 add openai.apiKey to ~/.bostrat/integrations.json");throw n.status=503,n.code="not_configured",n}let e=await aR[t.provider](t);return{provider:t.provider,model:t.model,voice:t.voice,...e}}async function py(t,{model:e,usage:n={},seconds:r}={}){let o=c=>Math.max(0,Math.round(Number(c)||0)),s={textIn:o(n.textIn),textInCached:o(n.textInCached),textOut:o(n.textOut),audioIn:o(n.audioIn),audioInCached:o(n.audioInCached),audioOut:o(n.audioOut)},a=nR(e,s),i=await O(t,{type:"cost.recorded",actor:"user",provider:"openai-realtime",payload:{model:e||"unknown",usd:a,tokens:{input:s.textIn+s.audioIn,output:s.textOut+s.audioOut,cacheRead:s.textInCached+s.audioInCached,cacheWrite:0},voice:s,seconds:Math.max(0,Math.round(Number(r)||0)),source:"voice-chat"}});return{usd:a,seq:i.seq}}var by=ct.dirname(cR(import.meta.url)),my=Number(process.env.PORT)||3362,ja=Ww(process.env),ky=Number(process.env.BOSTRAT_AUTO_TRIGGER_PCT)||80,$r=gg(process.env.BOSTRAT_ALLOW_IPS),xy=bg(),Sy=process.env.BOSTRAT_ALLOW_LAN_TOKENLESS!=="1",x=ue();x.disable("x-powered-by");x.use((t,e,n)=>{if(Mo(t)||$r.allowed($r.clientIp(t)))return n();e.status(403).type("text/plain").send("bostrat: access restricted to the allowlist")});x.use(_g);x.use(Sg({token:xy,enforce:Sy}));x.use("/api/mocks",ue.json({limit:"1mb"}));x.use("/api/mocks",ue.raw({type:"image/png",limit:"24mb"}));x.use("/api/runbooks",ue.json({limit:"1mb"}));x.use("/api/telemetry",ue.json({limit:"256kb"}));x.use("/api/hook",ue.json({limit:"2mb"}));x.use("/api/dictation",ue.raw({type:["audio/*","video/mp4","application/octet-stream"],limit:"32mb"}));x.use("/api/tracks/:id/upload",ue.raw({type:()=>!0,limit:"25mb"}));x.use(ue.json({limit:"64kb"}));x.get("/api/health",async(t,e)=>{e.json({ok:!0,tmuxSocket:Pt,hostTmuxAlive:await _e(),autoTriggerPct:ky})});x.get("/api/sessions",async(t,e)=>{e.json({sessions:await nn()})});x.post("/api/sessions",async(t,e)=>{let{displayName:n,cwd:r}=t.body||{};if(typeof n!="string")return e.status(400).json({error:"displayName (string) required"});try{let o=await rn(n,r||void 0);e.json({ok:!0,...o})}catch(o){let s=o?.message||String(o),a=/already exists/i.test(s)?409:/must be|invalid/i.test(s)?400:500;e.status(a).json({error:s})}});x.delete("/api/sessions/:name",async(t,e)=>{let n=decodeURIComponent(t.params.name);if(!mt(n))return e.status(400).json({error:"invalid session name"});if(!await X(n))return e.status(404).json({error:"session not found"});try{await Jt(n),e.json({ok:!0})}catch(r){e.status(500).json({error:`failed to kill: ${r?.message||r}`})}});x.get("/api/context/:name",async(t,e)=>{let n=decodeURIComponent(t.params.name);if(!mt(n))return e.status(400).json({error:"invalid session name"});let r=await X(n);if(!r)return e.status(404).json({error:"session not found"});let o=await Dr(r.cwd,r.sessionId);if(!o)return e.json({available:!1,cwd:r.cwd,sessionId:r.sessionId||null});e.json({available:!0,...o})});x.post("/api/handoff/:name",async(t,e)=>{let n=decodeURIComponent(t.params.name);if(!mt(n))return e.status(400).json({error:"invalid session name"});try{let r=await bd(n);e.json({ok:!0,step:r.step,file:r.file,startedAt:r.startedAt})}catch(r){e.status(409).json({error:r?.message||String(r)})}});x.get("/api/handoff/:name",(t,e)=>{let n=decodeURIComponent(t.params.name),r=cs(n);if(!r)return e.json({running:!1});e.json({running:!r.finishedAt,step:r.step,error:r.error,file:r.file,startedAt:r.startedAt,finishedAt:r.finishedAt})});var hy=10;x.get("/api/tracks",async(t,e)=>{let n=await at(),r=n.filter(s=>s.archivedAt),o=n;if(t.query.archived!=="all"&&r.length>hy){let s=new Set([...r].sort((a,i)=>(i.archivedAt||0)-(a.archivedAt||0)).slice(0,hy).map(a=>a.id));o=n.filter(a=>!a.archivedAt||s.has(a.id))}e.json({tracks:o,archivedTotal:r.length,agents:ha,agentAvailability:await Tl(),tickets:await Rs()})});x.get("/api/cost",async(t,e)=>{try{let n=await Xw(await at());if(n.cursorUsage=rl(),t.query.format==="text")return e.type("text/plain").send(Yw(n));e.json(n)}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/usage/roundup",async(t,e)=>{try{e.json(await eg(await at(),{window:t.query.window,from:t.query.from,to:t.query.to}))}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/usage/spend",async(t,e)=>{try{let n=await ag(await at(),{window:t.query.window,from:t.query.from,to:t.query.to});n.cursorUsage=rl(),n.subscriptionMonthlyUsd=Number(process.env.BOSTRAT_SUBSCRIPTION_MONTHLY_USD)||null,e.json(n)}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/usage/limits",async(t,e)=>{try{let n=t.query.fresh==="1"||t.query.fresh==="true";e.json(await fg(await at(),{fresh:n}))}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/arcs",async(t,e)=>{try{e.json(await dp(await at()))}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/arcs/:key",async(t,e)=>{try{let n=await fp(await at(),t.params.key);if(!n)return e.status(404).json({error:"unknown arc"});e.json(n)}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.post("/api/arcs/backfill-beats",async(t,e)=>{try{e.json({ok:!0,...await bw()})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.post("/api/hook",async(t,e)=>{try{let{mode:n,target:r}=await Su(t.get(qo),{byToken:Qh,legacy:()=>Zh(t.body?.session_id,t.body?.cwd)});if(n==="refused")return Go("refused",{reason:"unknown-token",hook:t.body?.hook_event_name}),e.json({ok:!1,error:"unknown hook token"});n==="token"&&t.body?.session_id&&r.box?.sessionId&&t.body.session_id!==r.box.sessionId&&Go("mismatch",{track:r.track.id,box:r.box.name}),Go(n,{track:r?.track?.id,hook:t.body?.hook_event_name});let o=await Uw(t.body||{},()=>r);e.json({ok:!0,events:o.length})}catch(n){e.json({ok:!1,error:n?.message||String(n)})}});x.post("/api/telemetry",async(t,e)=>{try{if(!Xg($r.clientIp(t)))return e.json({ok:!0,dropped:!0});let n=await Jg(t.body||{});e.json({ok:!0,events:n})}catch(n){e.json({ok:!1,error:n?.message||String(n)})}});x.get("/api/repos",async(t,e)=>{try{let n=await ye(),r=new Set((await Promise.all(n.map(a=>Z(a.path)))).filter(Boolean).map(a=>a.toLowerCase())),s=((await no()).repos||[]).filter(a=>!r.has(String(a.slug).toLowerCase())).map(a=>({slug:a.slug,name:a.slug.split("/")[1],branch:a.defaultBranch||null,private:!!a.private,remote:!0}));e.json({repos:[...n,...s]})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/github/token",async(t,e)=>{try{e.json(await Us(String(t.query.repo||"")))}catch(n){e.status(n?.status||502).json({error:n?.message||String(n)})}});x.get("/api/github/status",(t,e)=>{e.json({configured:Fs(),calls:Xm(),latency:Sh()})});x.post("/api/github/refresh",(t,e)=>{vp(),e.json({ok:!0})});x.get("/api/gmail/search",async(t,e)=>{try{let n=await mg(String(t.query.q||""),{max:Number(t.query.max)||10});e.json({messages:n})}catch(n){e.status(n?.status||502).json({error:n?.message||String(n)})}});x.get("/api/gmail/messages/:id",async(t,e)=>{try{e.json({message:await hg(String(t.params.id))})}catch(n){e.status(n?.status||502).json({error:n?.message||String(n)})}});function gu(t,e,n){let r=t.workspace;return r?.kind==="multi"?(r.repos??[]).some(o=>e&&o?.base===e||n&&o?.repo===n):!r?.base||!e||r.base===e||n&&r.repo===n}async function La({repo:t=null,number:e=null,branch:n=null}){let r=t?await Z(t):null;if(r&&e!=null){let s=await Tt(r,e),a=s&&await Q(s.trackId);if(a)return a}return n?(await at()).find(s=>s.workspace?.branch===n&&gu(s,t,r))??null:null}async function vy({repo:t=null,number:e=null,branch:n=null}){let r=await La({repo:t,number:e,branch:n}).catch(()=>null);if(r&&!r.archivedAt)return r;if(!n)return null;let o=t?await Z(t).catch(()=>null):null;return(await at()).find(a=>!a.archivedAt&&a.workspace?.branch===n&&gu(a,t,o))??null}async function zt(t,{number:e,url:n=null,title:r=null,branch:o=null,repo:s=null,track:a=null,extra:i={}}){let c=a||await La({repo:s,number:e,branch:o}).catch(()=>null);if(!c)return;let l=i.repo??(s?await Z(s).catch(()=>null):null);await O({id:c.id,cwd:c.cwd},{type:"pr.state.changed",actor:"user",source:"orchestrator",payload:{ts:Date.now(),change:t,number:e,url:n,title:r,branch:o??c.workspace?.branch??null,...l?{repo:l}:{},...i}}).catch(()=>{})}async function fR(t){let e=(await at()).filter(n=>!n.archivedAt);for(let n of t??[]){if(!n.prs?.length)continue;let r=await Z(n.path);if(r)for(let o of n.prs){if(await Tt(r,o.number))continue;let s=e.find(a=>a.workspace?.branch===o.headRefName&&gu(a,n.path,r));s&&await Bt({repo:r,number:o.number,trackId:s.id,url:o.url,headRef:o.headRefName,title:o.title,source:"matched"})}}}x.get("/api/git/prs",async(t,e)=>{try{let n=await Rh();await fR(n.repos).catch(()=>{}),e.json(n)}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.post("/api/git/prs/merge",async(t,e)=>{let{repo:n,number:r,method:o,auto:s,update:a,archive:i,notify:c,headRefName:l,title:u,url:f}=t.body||{};try{if(s){let w=o||"merge";if(!po.has(w))throw new Error(`invalid merge method: ${w}`);let y=Number(r);if(!Number.isInteger(y)||y<=0)throw new Error("invalid PR number");let b=await Z((await xe(n)).path);if(!b)throw new Error("repo has no GitHub remote");let A=await La({repo:n,number:y,branch:l}).catch(()=>null);if(!A)throw new Error("auto-merge needs a Track to watch this PR \u2014 none owns it");if(a&&await mo(n,y),await Bt({repo:b,number:y,trackId:A.id,url:f,headRef:l,title:u,source:"matched"}).catch(()=>{}),!await Zr(b,y,{merge:{method:w}}))throw new Error("couldn't record the auto-merge arm");return await Zn(b,y,i===!0),await Ds(b,y,c===!0&&i!==!0),ce(),await zt("auto-armed",{number:y,url:f,title:u,branch:l,repo:n,track:A,...a?{extra:{updated:!0}}:{}}),e.json({ok:!0,number:y,auto:!0})}let d=await mr(n,r,{method:o||"squash"});await zt("merged",{number:d.number,url:f,title:u,branch:l,repo:n});let m=await Z(n).catch(()=>null),h=!1;if(m&&(i===!0&&await Zn(m,d.number,!0).catch(()=>{}),c===!0&&i!==!0&&await Ds(m,d.number,!0).catch(()=>{}),await qe(m,d.number).catch(()=>{}),h=await We(m,d.number).catch(()=>!1),await Pe(m,d.number,"merged").catch(()=>{})),i===!0&&!h){let w=await La({repo:n,number:d.number,branch:l}).catch(()=>null);w&&!w.archivedAt&&await kr(w.id).catch(()=>{})}e.json(d)}catch(d){let m=d?.message||String(d),h=/invalid|unknown repo|required|needs a Track|no GitHub remote/i.test(m)?400:502;e.status(h).json({error:m})}});x.post("/api/git/prs/disarm-auto",async(t,e)=>{let{repo:n,number:r,headRefName:o,title:s,url:a}=t.body||{};try{let i=Number(r);if(!Number.isInteger(i)||i<=0)throw new Error("invalid PR number");let c=await Z((await xe(n)).path).catch(()=>null),l=c?await Qr(c,i).catch(()=>null):null;try{await ul(n,i)}catch(u){if(!l)throw u}ce(),await zt("auto-canceled",{number:i,url:a,title:s,branch:o,repo:n}),e.json({ok:!0,number:i})}catch(i){let c=i?.message||String(i),l=/invalid|unknown repo|required/i.test(c)?400:502;e.status(l).json({error:c})}});x.post("/api/git/prs/merge-all",async(t,e)=>{let{repo:n,method:r}=t.body||{};try{let o=await Z(n).catch(()=>null),s=await Mh(n,{method:r||"merge",onMerged:a=>{zt("merged",{number:a.number,url:a.url,title:a.title,branch:a.headRefName,repo:n}),o&&qe(o,a.number).catch(()=>{}).then(()=>We(o,a.number)).catch(()=>{}).then(()=>Pe(o,a.number,"merged")).catch(()=>{})}});e.json(s)}catch(o){let s=o?.message||String(o),a=/already running/i.test(s)?409:/invalid|unknown repo|no PRs ready/i.test(s)?400:502;e.status(a).json({error:s})}});x.get("/api/git/prs/merge-all",(t,e)=>{e.json({jobs:Ch()})});x.post("/api/git/prs/merge-track",async(t,e)=>{let{trackId:n,update:r,archive:o,notify:s,method:a}=t.body||{};try{let i=a||"merge";if(!po.has(i))throw new Error(`invalid merge method: ${i}`);if(!n)return e.status(400).json({error:"trackId required"});let c=await Q(n);if(!c)return e.status(404).json({error:`Track ${n} isn't on this node`});let l=(await se()).filter(d=>d.trackId===c.id&&d.state!=="merged"&&d.state!=="closed");if(!l.length)return e.status(409).json({error:"no open PRs on this Track"});for(let d of l)await Zn(d.repo,d.number,o===!0).catch(()=>{}),await Ds(d.repo,d.number,s===!0&&o!==!0).catch(()=>{});let u=[],f=[];for(let d of l){let m={repo:d.repo,number:d.number,ok:!0,action:"armed"};u.push(m);try{let h=await An(d.repo);if(!h)throw new Error(`no local checkout for ${d.repo}`);let w=await pr(h,d.number);f.push({r:m,l:d,repoPath:h,pr:w,plan:$h(w,{update:r!==!1})})}catch(h){m.ok=!1,m.action="failed",m.reason=h?.message||String(h)}}for(let{r:d,l:m,repoPath:h,pr:w,plan:y}of Oh(f))try{if(y.action==="skip")d.action="skipped",d.reason=y.reason;else if(y.action==="merge")await mr(h,m.number,{method:i}),await zt("merged",{number:m.number,url:w.url,title:w.title,branch:w.headRefName,repo:h,track:c}),await qe(m.repo,m.number).catch(()=>{}),await We(m.repo,m.number).catch(()=>{}),await Pe(m.repo,m.number,"merged").catch(()=>{}),d.action="merged";else{if(y.action==="update-arm"&&(await mo(h,m.number).catch(()=>{}),d.action="updated-armed"),!await Zr(m.repo,m.number,{merge:{method:i}}))throw new Error("couldn't record the auto-merge arm");await zt("auto-armed",{number:m.number,url:w.url,title:w.title,branch:w.headRefName,repo:h,track:c,...d.action==="updated-armed"?{extra:{updated:!0}}:{}})}}catch(b){d.ok=!1,d.action="failed",d.reason=b?.message||String(b)}if(ce(),!u.some(d=>d.ok))return e.status(502).json({error:u.map(d=>`${d.repo}#${d.number}: ${d.reason}`).join("; "),results:u});e.json({ok:!0,trackId:c.id,results:u})}catch(i){let c=i?.message||String(i),l=/invalid|required/i.test(c)?400:502;e.status(l).json({error:c})}});x.post("/api/git/prs/disarm-track",async(t,e)=>{let{trackId:n}=t.body||{};try{if(!n)return e.status(400).json({error:"trackId required"});let r=await Q(n);if(!r)return e.status(404).json({error:`Track ${n} isn't on this node`});let o=(await se()).filter(a=>a.trackId===r.id&&a.state!=="merged"&&a.state!=="closed"),s=[];for(let a of o){let i=await Qr(a.repo,a.number).catch(()=>null),c=await An(a.repo).catch(()=>null);c&&await ul(c,a.number).catch(()=>{}),i&&(await zt("auto-canceled",{number:a.number,url:a.url,title:a.title,branch:a.headRef,repo:c,track:r}),s.push({repo:a.repo,number:a.number}))}ce(),e.json({ok:!0,trackId:r.id,canceled:s})}catch(r){let o=r?.message||String(r),s=/invalid|required/i.test(o)?400:502;e.status(s).json({error:o})}});x.post("/api/git/prs/open",async(t,e)=>{let{trackId:n,title:r,body:o,base:s,repos:a}=t.body||{};try{if(!n)return e.status(400).json({error:"trackId required"});let i=await Q(n);if(!i)return e.status(404).json({error:`Track ${n} isn't on this node`});if(i.workspace?.kind==="multi"){let u=Ot(i.workspace),f,d=Array.isArray(a)?a.filter(Boolean):[];if(d.length)f=d.map(w=>{let y=u.find(b=>b.name===w||b.repo===w||b.base===w||b.dir===w);if(!y)throw new Error(`invalid repo "${w}" \u2014 not tagged on this Track`);return y});else if(f=await lo(i),!f.length)return e.status(409).json({error:"no tagged repo has work to ship"});let m=[];for(let w of f)try{let y=await fl(w.dir,{title:r,body:o,base:s}),b=w.repo||await Z(w.dir).catch(()=>null);b&&y.number&&await Bt({repo:b,number:y.number,trackId:i.id,url:y.url,headRef:i.workspace?.branch??null,title:r,source:"api"}),await zt("opened",{number:y.number,url:y.url,title:r,track:i,branch:i.workspace?.branch,extra:{repo:b??w.name}}),m.push({repo:w.name,slug:b??null,ok:!0,url:y.url,number:y.number})}catch(y){m.push({repo:w.name,slug:w.repo??null,ok:!1,error:y?.message||String(y)})}let h=m.filter(w=>w.ok);return h.length?e.json({ok:!0,multi:!0,results:m,url:h[0].url,number:h[0].number}):e.status(502).json({error:m.map(w=>`${w.repo}: ${w.error}`).join("; "),results:m})}let c=await fl(i.cwd,{title:r,body:o,base:s}),l=await Z(i.cwd).catch(()=>null);l&&c.number&&await Bt({repo:l,number:c.number,trackId:i.id,url:c.url,headRef:i.workspace?.branch??null,title:r,source:"api"}),await zt("opened",{number:c.number,url:c.url,title:r,track:i,branch:i.workspace?.branch,extra:{...l?{repo:l}:{}}}),e.json(c)}catch(i){let c=i?.message||String(i),l=/required|invalid/i.test(c)?400:502;e.status(l).json({error:c})}});x.get("/api/tracks/:id/repos",async(t,e)=>{try{let n=await Q(t.params.id);if(!n)return e.status(404).json({error:"track not found"});let r=await lo(n),o=new Map(r.map(a=>[a.base,a])),s=Ot(n.workspace).map(a=>{let i=o.get(a.base);return{name:a.name,base:a.base,baseRef:a.baseRef??null,repo:a.repo??null,dirty:i?.dirty??!1,ahead:i?.ahead??0,changed:!!i}});e.json({repos:s,branch:n.workspace?.branch??null,kind:n.workspace?.kind??null})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.post("/api/tracks/:id/repos",async(t,e)=>{let{repo:n}=t.body||{};try{if(!n||typeof n!="string")return e.status(400).json({error:"repo (path or owner/name) required"});let r=await dw(t.params.id,n);e.json({ok:!0,added:{name:r.name,base:r.base,baseRef:r.baseRef??null,repo:r.repo??null}})}catch(r){let o=r?.message||String(r),s=/not found/i.test(o)?404:/already tagged|needs a multi|not a usable|archived|required/i.test(o)?400:502;e.status(s).json({error:o})}});x.delete("/api/tracks/:id/repos/:name",async(t,e)=>{try{let n=await fw(t.params.id,t.params.name);e.json({ok:!0,...n})}catch(n){let r=n?.message||String(n),o=/track .* not found/i.test(r)?404:/has work|still running|last repo/i.test(r)?409:/needs a multi|not tagged/i.test(r)?400:502;e.status(o).json({error:r})}});x.post("/api/git/prs/fix",async(t,e)=>{let{repo:n,number:r}=t.body||{};try{let o=await pr(n,r),s=await Z(n).catch(()=>null),a=await vy({repo:n,number:r,branch:o.headRefName});if(!a)return e.status(404).json({error:`no active Track owns PR #${o.number} (branch "${o.headRefName}")`});let i=Ot(a.workspace).find(b=>b.base===n||s&&b.repo===s)??null,c=a.workspace?.kind==="multi",l=await Nh(n,o.number);if(!l.length)return e.status(409).json({error:"no failing checks on this PR"});let u=c&&i?ct.join("ci",i.name):"ci",f=ct.join(a.cwd,".bostrat",u),d=ct.join(".bostrat",u,`pr-${o.number}-fail.log`);await Mt.promises.mkdir(f,{recursive:!0}),await Mt.promises.writeFile(ct.join(a.cwd,d),l.map(b=>`\u2550\u2550\u2550\u2550 ${b.name}${b.link?` \xB7 ${b.link}`:""} \u2550\u2550\u2550\u2550
|
|
317
|
+
${b.log??"(no fetchable log \u2014 see link)"}
|
|
295
318
|
`).join(`
|
|
296
|
-
`));let p=l.map(k=>k.name).join(", "),h=c&&i?` in the ${i.name}/ checkout`:"",m=await ge(a.id,`CI is red on PR #${o.number} "${o.title}" (${o.url}) \u2014 branch ${o.headRefName}${h}. 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 Mt({repo:b,number:o.number,trackId:a.id,url:o.url,headRef:o.headRefName,title:o.title,source:"matched"}),await lf(b,o.number,{headSha:o.headSha,trackId:a.id}).catch(()=>{})),await Kt("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:m.leadBox,queued:!!m.queued,checks:l.map(k=>k.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 lr(n,r);if(o.mergeable!=="CONFLICTING")return e.status(409).json({error:"this PR does not conflict with its base branch"});let s=await Y(n).catch(()=>null),a=await pg({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=Rt(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 ge(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 Mt({repo:u,number:o.number,trackId:a.id,url:o.url,headRef:o.headRefName,title:o.title,source:"matched"}),await uf(u,o.number,{headSha:o.headSha,trackId:a.id}).catch(()=>{})),await Kt("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=Zp.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 nt(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 ge(a.id,Es(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&&kh(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 _o=t=>e=>({...e,url:Rw(e,t.headers)});S.get("/api/views",async(t,e)=>{try{let n=await Qo({trackId:t.query.track||void 0});e.json({views:n.map(_o(t)),maxLive:Ee})}catch(n){e.status(500).json({error:n?.message||String(n)})}});S.get("/api/views/:id",async(t,e)=>{let n=await pd(t.params.id);if(!n)return e.status(404).json({error:"view not found"});e.json({view:_o(t)(n),session:Un(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 nt(n):null;if(!i&&s&&(i=(await ft()).find(u=>u.cwd&&(s===u.cwd||s.startsWith(u.cwd+ct.sep)))||null),!i)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});let c=await ri({trackId:i.id,name:r,cmd:o,cwd:i.cwd,serveCwd:s,port:a,box:i.leadBox,branch:i.workspace?.branch??null});e.json({ok:!0,view:_o(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:_o(t)(await md(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:_o(t)(await hd(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 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 wE=[["linear",cn,di],["jira",rs,li]],Dl=new Map;S.get("/api/integrations/ticketing",async(t,e)=>{try{let n=await ci(),r={};for(let[o,s,a]of wE){let i=await s();if(!i){Dl.delete(o),r[o]={configured:!1};continue}let c=Dl.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},Dl.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 ci()})}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 Md(o),i=n?await nt(n):null;!i&&r&&(i=(await ft()).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})}});S.post("/api/tickets/link",async(t,e)=>{let{trackId:n,cwd:r,ref:o}=t.body||{};try{let s=n?await nt(n):null;if(!s&&r&&(s=(await ft()).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 Jn(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 nt(r):null;if(!o&&n&&(o=(await ft()).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 zd(),ticket:await ee(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 ee(t.params.id)})});S.post("/api/tracks/:id/ticket",async(t,e)=>{try{let n=await nt(t.params.id);if(!n)return e.status(404).json({error:"track not found"});let r=await Jn(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 Si(t.params.id)})});var wa=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 Qd(t.params.id))}catch(n){let r=n?.message||String(n);e.status(wa(r)).json({error:r})}});S.post("/api/tracks/:id/ticket/state",async(t,e)=>{try{e.json({ok:!0,...await vi(t.params.id,(t.body||{}).stateId)})}catch(n){let r=n?.message||String(n);e.status(wa(r)).json({error:r})}});S.post("/api/tracks/:id/ticket/comment",async(t,e)=>{try{e.json({ok:!0,...await tf(t.params.id,(t.body||{}).body)})}catch(n){let r=n?.message||String(n);e.status(wa(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 nt(t.params.id);if(!o)return e.status(404).json({error:"track not found"});e.json({ok:!0,...await ef(o,n,r===!0)})}catch(o){let s=o?.message||String(o);e.status(wa(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 nt(n):null;if(!s&&r&&(s=(await ft()).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 rf(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 nf()})}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 nt(t.params.id);if(!s)return e.status(404).json({error:"track not found"});e.json({ok:!0,...await of(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 Jo({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 _e(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 _e(t.params.id);if(!n)return e.status(404).json({error:"mock not found"});let r=rn(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(jt(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 _e(t.params.id);if(!n)return e.status(404).json({error:"mock not found"});let r=rn(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(jt(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 nt(o):null;if(!l&&a&&(l=(await ft()).find(p=>p.cwd&&(a===p.cwd||a.startsWith(p.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(f=>f.name===l.leadBox),d=await zu({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 Xu(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 Zu(t.params.id),o=r.length?` over ${r.map(s=>`"${s.name}"`).join(", ")}`:"";await ge(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 Qu(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 ft(),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 ts({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 ai(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 ai(t.params.id);if(!n)return e.status(404).json({error:"runbook not found"});let r=xd(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(Hn(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 nt(n):null;if(!a&&s&&(a=(await ft()).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 Ad({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 Td(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 _d(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 Ed(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/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 ag(t.body,t.headers["content-type"]))}catch(n){e.status(n?.status||500).json({error:n?.message||String(n),code:n?.code})}});S.get("/api/dictation/glossary",async(t,e)=>{e.json({configured:!!await Lr(),terms:await Bl(),problem:await sg()})});S.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 og(n,r)})});S.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 ol({name:n,cwd:r,repo:o,repos:s,agent:a,skipPermissions:i!==!1}),u=null;if(c)try{await Jn(l,c,{source:"create"})}catch(d){u=d?.message||String(d)}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})}});S.get("/api/tracks/:id",async(t,e)=>{let n=await nt(t.params.id);if(!n)return e.status(404).json({error:"track not found"});e.json({track:n,handoff:mg(po(t.params.id)),subtasks:gr(t.params.id).map(Eo)})});S.delete("/api/tracks/:id",async(t,e)=>{try{let n=await Mh(t.params.id,{cascadeMocks:t.query.mocks!=="0",cascadeRunbooks:t.query.runbooks==="1"});await Si(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})}});S.post("/api/tracks/:id/archive",async(t,e)=>{try{e.json({ok:!0,track:await wr(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})}});S.post("/api/tracks/:id/unarchive",async(t,e)=>{try{e.json({ok:!0,track:await Dh(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})}});S.get("/api/tracks/:id/messages",async(t,e)=>{let n=await tl(t.params.id);if(!n)return e.status(404).json({error:"track not found"});e.json(n)});S.get("/api/tracks/:id/events",async(t,e)=>{let n=await nt(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}
|
|
319
|
+
`));let m=l.map(b=>b.name).join(", "),h=c&&i?` in the ${i.name}/ checkout`:"",w=await Se(a.id,`CI is red on PR #${o.number} "${o.title}" (${o.url}) \u2014 branch ${o.headRefName}${h}. Failing check${l.length===1?"":"s"}: ${m}. The failure log is at ${d} in this ${c?"workspace":"worktree"} \u2014 read it, fix the root cause${c&&i?` in ${i.name}/`:""}, run the relevant tests locally, then commit and push to update the PR.`,{kind:"ci-fix"}),y=s;y&&(await Bt({repo:y,number:o.number,trackId:a.id,url:o.url,headRef:o.headRefName,title:o.title,source:"matched"}),await tp(y,o.number,{headSha:o.headSha,trackId:a.id}).catch(()=>{})),await zt("fix-delegated",{number:o.number,url:o.url,title:o.title,branch:o.headRefName,repo:n,track:a,extra:{checks:l.map(b=>b.name)}}),e.json({ok:!0,leadBox:w.leadBox,queued:!!w.queued,checks:l.map(b=>b.name),log:d})}catch(o){let s=o?.message||String(o),a=/unknown repo|invalid|required/i.test(s)?400:/not found|no active Track/i.test(s)?404:502;e.status(a).json({error:s})}});x.post("/api/git/prs/reconcile",async(t,e)=>{let{repo:n,number:r}=t.body||{};try{let o=await pr(n,r);if(o.mergeable!=="CONFLICTING")return e.status(409).json({error:"this PR does not conflict with its base branch"});let s=await Z(n).catch(()=>null),a=await vy({repo:n,number:r,branch:o.headRefName});if(!a)return e.status(404).json({error:`no active Track owns PR #${o.number} (branch "${o.headRefName}")`});let i=Ot(a.workspace).find(f=>f.base===n||s&&f.repo===s)??null,c=a.workspace?.kind==="multi"&&i?` In the ${i.name}/ checkout:`:"",l=await Se(a.id,`PR #${o.number} "${o.title}" (${o.url}) \u2014 branch ${o.headRefName} \u2014 has merge conflicts with ${o.baseRefName}.${c} Bring ${o.baseRefName} into this branch (fetch origin, then merge origin/${o.baseRefName} \u2014 don't rebase this pushed PR branch), and resolve every conflict so that BOTH sides' intent is preserved. Run the relevant tests, then commit and push to update the PR. If any conflict is a genuine ambiguity \u2014 where you can't confidently tell which side's behavior should win, or reconciling would drop work from either side \u2014 STOP and ask me here rather than guessing, laying out the specific choice and your recommendation.`,{kind:"reconcile"}),u=s;u&&(await Bt({repo:u,number:o.number,trackId:a.id,url:o.url,headRef:o.headRefName,title:o.title,source:"matched"}),await ep(u,o.number,{headSha:o.headSha,trackId:a.id}).catch(()=>{})),await zt("reconcile-delegated",{number:o.number,url:o.url,title:o.title,branch:o.headRefName,repo:n,track:a,extra:{base:o.baseRefName}}),e.json({ok:!0,leadBox:l.leadBox,queued:!!l.queued,base:o.baseRefName})}catch(o){let s=o?.message||String(o),a=/unknown repo|invalid|required/i.test(s)?400:/not found|no active Track/i.test(s)?404:502;e.status(a).json({error:s})}});x.post("/api/tracks/:id/pr-offer",async(t,e)=>{let{offerId:n,action:r}=t.body||{};try{let o=null;if(Array.isArray(t.body?.actions)){if(o=Dm.filter(f=>t.body.actions.includes(f)),!o.length)return e.status(400).json({error:"actions must include commit, rebase, push and/or pr"})}else if(r==="pr")o=["commit","push","pr"];else if(r==="push")o=["commit","push"];else if(r!=="dismiss")return e.status(400).json({error:'offerId and actions ["commit"|"rebase"|"push"|"pr"] (or action "dismiss") required'});if(!n)return e.status(400).json({error:"offerId required"});let s=o!=null&&t.body?.notify===!0,a=await Q(t.params.id);if(!a)return e.status(404).json({error:"track not found"});let i=await F(a),c=i.find(f=>f.type==="pr.offered"&&f.payload?.offerId===n);if(!c)return e.status(404).json({error:"unknown offer"});if(i.some(f=>f.type==="pr.offer.answered"&&f.payload?.offerId===n))return e.status(409).json({error:"offer already answered"});let l=null;o&&(l=await Se(a.id,Xs(o,{branch:c.payload.branch,base:c.payload.base,toPr:c.payload.toPr||null,repos:c.payload.repos||null,offeredRebase:Array.isArray(c.payload.offered)&&c.payload.offered.includes("rebase")}),{kind:o.includes("pr")?"pr-go":"push-go"}));let u=o?o.includes("pr")?"pr":o.includes("push")?"push":o.includes("commit")?"commit":"rebase":"dismiss";await O({id:a.id,cwd:a.cwd},{type:"pr.offer.answered",actor:"user",source:"orchestrator",boxId:c.boxId,payload:{ts:Date.now(),offerId:n,action:u,actions:o,...s?{notify:!0}:{}}}),s&&ow(a.id,{actions:o}),e.json({ok:!0,offerId:n,action:u,actions:o,notify:s,queued:!!l?.queued})}catch(o){let s=o?.message||String(o),a=/required|invalid/i.test(s)?400:/not found|not running|archived/i.test(s)?409:502;e.status(a).json({error:s})}});var Fo=t=>e=>({...e,url:Ig(e,t.headers)});x.get("/api/views",async(t,e)=>{try{let n=await ks({trackId:t.query.track||void 0});e.json({views:n.map(Fo(t)),maxLive:Oe})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/views/:id",async(t,e)=>{let n=await of(t.params.id);if(!n)return e.status(404).json({error:"view not found"});e.json({view:Fo(t)(n),session:qn(n.slug)})});x.post("/api/views",async(t,e)=>{let{trackId:n,name:r,cmd:o,cwd:s,port:a}=t.body||{};try{let i=n?await Q(n):null;if(!i&&s&&(i=(await at()).find(u=>u.cwd&&(s===u.cwd||s.startsWith(u.cwd+ct.sep)))||null),!i)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});let c=await Ri({trackId:i.id,name:r,cmd:o,cwd:i.cwd,serveCwd:s,port:a,box:i.leadBox,branch:i.workspace?.branch??null});e.json({ok:!0,view:Fo(t)(c)})}catch(i){let c=i?.message||String(i),l=i?.status??(/cap reached/i.test(c)?409:/required|invalid|already owned|no free/i.test(c)?400:500);e.status(l).json({error:c})}});x.post("/api/views/:id/stop",async(t,e)=>{try{e.json({ok:!0,view:Fo(t)(await sf(t.params.id))})}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});x.post("/api/views/:id/restart",async(t,e)=>{try{e.json({ok:!0,view:Fo(t)(await af(t.params.id))})}catch(n){let r=n?.message||String(n),o=/not found/i.test(r)?404:/cap reached|no known command/i.test(r)?409:500;e.status(o).json({error:r})}});x.delete("/api/views/:id",async(t,e)=>{try{e.json(await cf(t.params.id))}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});var pR=[["linear",un,Di],["jira",As,Pi]],fu=new Map;x.get("/api/integrations/ticketing",async(t,e)=>{try{let n=await Ni(),r={};for(let[o,s,a]of pR){let i=await s();if(!i){fu.delete(o),r[o]={configured:!1};continue}let c=fu.get(o);if(!c||Date.now()-c.at>300*1e3){let l;try{l={configured:!0,ok:!0,...await a(i)}}catch(u){l={configured:!0,ok:!1,error:u?.message||String(u)}}c={at:Date.now(),body:l},fu.set(o,c)}r[o]={...c.body,flow:n},o==="jira"&&i.tokenExpiresAt&&(r[o].tokenExpiresAt=i.tokenExpiresAt)}e.json(r)}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/tickets/flow",async(t,e)=>{try{e.json({flow:await Ni()})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.post("/api/tickets/flow",async(t,e)=>{let{trackId:n,cwd:r,set:o,actor:s}=t.body||{};try{let a=await Tf(o),i=n?await Q(n):null;!i&&r&&(i=(await at()).find(l=>l.cwd&&(r===l.cwd||r.startsWith(l.cwd+ct.sep)))||null),i&&await O({id:i.id,cwd:i.cwd},{type:"ticket.flow.changed",actor:s==="user"?"user":"agent",source:"orchestrator",payload:{ts:Date.now(),set:{...o},flow:a}}).catch(()=>{}),e.json({ok:!0,flow:a})}catch(a){let i=a?.message||String(a);e.status(/unknown|nothing to set/i.test(i)?400:500).json({error:i})}});x.post("/api/tickets/link",async(t,e)=>{let{trackId:n,cwd:r,ref:o}=t.body||{};try{let s=n?await Q(n):null;if(!s&&r&&(s=(await at()).find(c=>c.cwd&&(r===c.cwd||r.startsWith(c.cwd+ct.sep)))||null),!s)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});if(!o)return e.status(400).json({error:"ref (issue URL or key) required"});let a=await Yn(s,o,{source:n?"api":"cli"});e.json({ok:!0,trackId:s.id,ticket:a})}catch(s){let a=s?.message||String(s);e.status(/not a Linear|not found|no ticketing|required/i.test(a)?400:502).json({error:a})}});x.get("/api/tickets/status",async(t,e)=>{let{cwd:n,track:r}=t.query;try{let o=r?await Q(r):null;if(!o&&n&&(o=(await at()).find(a=>a.cwd&&(n===a.cwd||String(n).startsWith(a.cwd+ct.sep)))||null),!o)return e.status(400).json({error:"track (or a cwd inside a Track workspace) required"});e.json({trackId:o.id,configured:await Uf(),ticket:await oe(o.id)})}catch(o){e.status(500).json({error:o?.message||String(o)})}});x.get("/api/tracks/:id/ticket",async(t,e)=>{e.json({ticket:await oe(t.params.id)})});x.post("/api/tracks/:id/ticket",async(t,e)=>{try{let n=await Q(t.params.id);if(!n)return e.status(404).json({error:"track not found"});let r=await Yn(n,(t.body||{}).ref,{source:"api"});e.json({ok:!0,ticket:r})}catch(n){let r=n?.message||String(n);e.status(/not a Linear|not found|no ticketing|required/i.test(r)?400:502).json({error:r})}});x.delete("/api/tracks/:id/ticket",async(t,e)=>{e.json({ok:!0,removed:await zi(t.params.id)})});var Fa=t=>/no ticket linked|unknown offer|unknown state/i.test(t)?404:/already answered/i.test(t)?409:/required|no ticketing/i.test(t)?400:502;x.get("/api/tracks/:id/ticket/states",async(t,e)=>{try{e.json(await Gf(t.params.id))}catch(n){let r=n?.message||String(n);e.status(Fa(r)).json({error:r})}});x.post("/api/tracks/:id/ticket/state",async(t,e)=>{try{e.json({ok:!0,...await Ji(t.params.id,(t.body||{}).stateId)})}catch(n){let r=n?.message||String(n);e.status(Fa(r)).json({error:r})}});x.post("/api/tracks/:id/ticket/comment",async(t,e)=>{try{e.json({ok:!0,...await Kf(t.params.id,(t.body||{}).body)})}catch(n){let r=n?.message||String(n);e.status(Fa(r)).json({error:r})}});x.post("/api/tracks/:id/ticket-offer",async(t,e)=>{let{offerId:n,accept:r}=t.body||{};try{let o=await Q(t.params.id);if(!o)return e.status(404).json({error:"track not found"});e.json({ok:!0,...await Vf(o,n,r===!0)})}catch(o){let s=o?.message||String(o);e.status(Fa(s)).json({error:s})}});x.post("/api/tickets/propose",async(t,e)=>{let{trackId:n,cwd:r,plan:o}=t.body||{};try{let s=n?await Q(n):null;if(!s&&r&&(s=(await at()).find(i=>i.cwd&&(r===i.cwd||r.startsWith(i.cwd+ct.sep)))||null),!s)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});e.json({ok:!0,trackId:s.id,proposal:await Jf(s,o)})}catch(s){let a=s?.message||String(s);e.status(/no ticketing|are off|needs an epic|too many|required/i.test(a)?400:502).json({error:a})}});x.get("/api/tickets/teams",async(t,e)=>{try{e.json({teams:await zf()})}catch(n){let r=n?.message||String(n);e.status(/no ticketing/i.test(r)?400:502).json({error:r})}});x.post("/api/tracks/:id/ticket-create-offer",async(t,e)=>{let{proposalId:n,accept:r,teamId:o}=t.body||{};try{let s=await Q(t.params.id);if(!s)return e.status(404).json({error:"track not found"});e.json({ok:!0,...await Xf(s,n,r===!0,{teamId:o||null})})}catch(s){let a=s?.message||String(s);e.status(/unknown proposal|unknown team/i.test(a)?404:/already answered/i.test(a)?409:/required|no ticketing|pick one/i.test(a)?400:502).json({error:a})}});x.get("/api/mocks",async(t,e)=>{try{e.json({mocks:await hs({trackId:t.query.track||void 0,group:t.query.group||void 0})})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/mocks/:id",async(t,e)=>{let n=await $e(t.params.id);if(!n)return e.status(404).json({error:"mock not found"});e.json({mock:n})});x.get("/api/mocks/:id/png",async(t,e)=>{let n=await $e(t.params.id);if(!n)return e.status(404).json({error:"mock not found"});let r=an(n,t.query.v??null);if(!r)return e.status(409).json({error:`mock is ${n.state} \u2014 no captured version yet`});e.set("cache-control","private, max-age=31536000, immutable"),e.sendFile(Ft(n,r.v).png,o=>{o&&!e.headersSent&&e.status(404).json({error:"rendered artifact missing"})})});x.get("/api/mocks/:id/html",async(t,e)=>{let n=await $e(t.params.id);if(!n)return e.status(404).json({error:"mock not found"});let r=an(n,t.query.v??null);if(!r?.htmlRel)return e.status(404).json({error:"this version has no HTML source (image-sourced)"});e.set("content-security-policy","sandbox"),e.sendFile(Ft(n,r.v).html,o=>{o&&!e.headersSent&&e.status(404).json({error:"source artifact missing"})})});x.post("/api/mocks",async(t,e)=>{let n=Buffer.isBuffer(t.body)?t.body:null,r=n?t.query:t.body||{},{trackId:o,name:s,cwd:a,width:i,group:c}=r;try{let l=o?await Q(o):null;if(!l&&a&&(l=(await at()).find(m=>m.cwd&&(a===m.cwd||a.startsWith(m.cwd+ct.sep)))||null),!l)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});let u=(l.boxes||[]).find(d=>d.name===l.leadBox),f=await Ld({trackId:l.id,name:s,html:n?null:r.html,image:n,cwd:l.cwd,width:i,group:c,box:l.leadBox,agent:u?.agent||null});e.json({ok:!0,mock:f})}catch(l){let u=l?.message||String(l);e.status(/required|invalid|must be/i.test(u)?400:500).json({error:u})}});x.post("/api/mocks/:id/update",async(t,e)=>{let n=Buffer.isBuffer(t.body)?t.body:null;try{let r=await Fd(t.params.id,{html:n?null:(t.body||{}).html,image:n,width:n?t.query.width:(t.body||{}).width});e.json({ok:!0,mock:r})}catch(r){let o=r?.message||String(r),s=/not found/i.test(o)?404:/required|invalid|must be/i.test(o)?400:500;e.status(s).json({error:o})}});x.post("/api/mocks/:id/pick",async(t,e)=>{try{let{mock:n,siblings:r}=await Hd(t.params.id),o=r.length?` over ${r.map(s=>`"${s.name}"`).join(", ")}`:"";await Se(n.trackId,`Design decision: go with "${n.name}" (mock:${n.id})${o} \u2014 proceed with this design.`,{kind:"mock-picked"}).catch(()=>{}),e.json({ok:!0,mock:n,siblings:r})}catch(n){let r=n?.message||String(n),o=/not found/i.test(r)?404:/nothing to pick/i.test(r)?409:500;e.status(o).json({error:r})}});x.delete("/api/mocks/:id",async(t,e)=>{try{e.json(await Wd(t.params.id))}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});x.get("/api/runbooks",async(t,e)=>{try{let n=t.query.track||void 0;if(!n&&t.query.cwd){let r=await at(),o=String(t.query.cwd);if(n=r.find(s=>s.cwd&&(o===s.cwd||o.startsWith(s.cwd+ct.sep)))?.id,!n)return e.json({runbooks:[]})}e.json({runbooks:await xs({trackId:n,status:t.query.status||void 0})})}catch(n){e.status(500).json({error:n?.message||String(n)})}});x.get("/api/runbooks/:id",async(t,e)=>{let n=await Ci(t.params.id);if(!n)return e.status(404).json({error:"runbook not found"});e.json({runbook:n})});x.get("/api/runbooks/:id/md",async(t,e)=>{let n=await Ci(t.params.id);if(!n)return e.status(404).json({error:"runbook not found"});let r=pf(n,t.query.v??null);if(!r)return e.status(404).json({error:"no such version"});e.set("content-security-policy","sandbox"),e.set("cache-control","private, max-age=31536000, immutable"),e.type("text/markdown; charset=utf-8"),e.sendFile(Gn(n,r.v),o=>{o&&!e.headersSent&&e.status(404).json({error:"runbook artifact missing"})})});x.post("/api/runbooks",async(t,e)=>{let{trackId:n,name:r,md:o,cwd:s}=t.body||{};try{let a=n?await Q(n):null;if(!a&&s&&(a=(await at()).find(u=>u.cwd&&(s===u.cwd||s.startsWith(u.cwd+ct.sep)))||null),!a)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});let i=(a.boxes||[]).find(l=>l.name===a.leadBox),c=await wf({trackId:a.id,trackName:a.name,name:r,md:o,cwd:a.cwd,box:a.leadBox,agent:i?.agent||null});e.json({ok:!0,runbook:c})}catch(a){let i=a?.message||String(a);e.status(/required|invalid|over the|secret VALUE/i.test(i)?400:500).json({error:i})}});x.post("/api/runbooks/:id/update",async(t,e)=>{try{let{md:n,note:r}=t.body||{};e.json({ok:!0,runbook:await gf(t.params.id,{md:n,note:r})})}catch(n){let r=n?.message||String(n),o=/not found/i.test(r)?404:/orphaned/i.test(r)?410:/required|invalid|over the|secret VALUE/i.test(r)?400:500;e.status(o).json({error:r})}});x.post("/api/runbooks/:id/status",async(t,e)=>{try{e.json({ok:!0,runbook:await yf(t.params.id,(t.body||{}).status)})}catch(n){let r=n?.message||String(n),o=/not found/i.test(r)?404:/orphaned/i.test(r)?410:/must be/i.test(r)?400:500;e.status(o).json({error:r})}});x.delete("/api/runbooks/:id",async(t,e)=>{try{e.json(await bf(t.params.id))}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});x.get("/api/changelog/draft",async(t,e)=>{try{let n=Og(t.query.since||null);e.json({since:n,repos:nu(),prs:await Cg(n)})}catch(n){let r=n?.message||String(n);e.status(/unparseable/i.test(r)?400:500).json({error:r})}});x.post("/api/changelog/publish",async(t,e)=>{let{md:n,day:r,trackId:o,cwd:s}=t.body||{};try{let a=o?await Q(o):null;if(!a&&s&&(a=(await at()).find(c=>c.cwd&&(s===c.cwd||String(s).startsWith(c.cwd+ct.sep)))||null),!a)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});e.json({ok:!0,entry:await Mg(a,{md:n,day:r})})}catch(a){let i=a?.message||String(a);e.status(/required|invalid|no `- `|over the|secret VALUE/i.test(i)?400:500).json({error:i})}});var mR=120,pu=400,wu=null;x.post("/api/notify",async(t,e)=>{let{title:n,body:r,urgency:o,cwd:s,trackId:a}=t.body||{};try{let i=typeof r=="string"?r.trim():"";if(!i)return e.status(400).json({error:"body (the message) required"});if(i.length>pu)return e.status(400).json({error:`message is ${i.length} chars \u2014 over the ${pu} cap; a notification is a nudge, put the detail in chat`});let c=Kr(`${n||""}
|
|
320
|
+
${i}`);if(c.length)return e.status(400).json({error:`notification appears to contain a secret VALUE (${c.join(", ")}) \u2014 notifications ship off-box; never put a credential in one`});let l=a?await Q(a):null;if(!l&&s&&(l=(await 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,mR):l.name,body:f(i,pu),urgency:o==="high"?"high":"normal",trackName:l.name,ts:Date.now()};await O(l,{type:"notify.raised",actor:"agent",boxId:l.leadBox??null,provider:u?.agent||null,payload:d}),e.json({ok:!0,notification:d,delivered:wu?wu.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 uy(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 Vr(),terms:await uu(),problem:await ly()})});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 cy(n,r)})});x.get("/api/tracks/:id/voice-chat",async(t,e)=>{if(!await Q(t.params.id))return e.status(404).json({error:"track not found"});e.json(await dy())});x.post("/api/tracks/:id/voice-chat/session",async(t,e)=>{try{let n=await Q(t.params.id);if(!n)return e.status(404).json({error:"track not found"});if(n.archivedAt)return e.status(409).json({error:"track is archived"});e.json(await fy())}catch(n){e.status(n?.status||500).json({error:n?.message||String(n),code:n?.code})}});x.post("/api/tracks/:id/voice-chat/usage",async(t,e)=>{try{let n=await Q(t.params.id);if(!n)return e.status(404).json({error:"track not found"});let{model:r,usage:o,seconds:s}=t.body||{};e.json({ok:!0,...await py(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 $l({name:n,cwd:r,repo:o,repos:s,agent:a,skipPermissions:i!==!1}),u=null;if(c)try{await Yn(l,c,{source:"create"})}catch(f){u=f?.message||String(f)}e.json({ok:!0,track:l,...u?{ticketWarning:u}:{}})}catch(l){let u=l?.message||String(l);e.status(/required|unknown agent|invalid|must be/i.test(u)?400:500).json({error:u})}});x.get("/api/tracks/:id",async(t,e)=>{let n=await Q(t.params.id);if(!n)return e.status(404).json({error:"track not found"});e.json({track:n,handoff:Ay(Ao(t.params.id)),subtasks:xr(t.params.id).map(Uo)})});x.delete("/api/tracks/:id",async(t,e)=>{try{let n=await gw(t.params.id,{cascadeMocks:t.query.mocks!=="0",cascadeRunbooks:t.query.runbooks==="1"});await zi(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 kr(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 xw(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 _l(t.params.id);if(!n)return e.status(404).json({error:"track not found"});e.json(n)});x.get("/api/tracks/:id/events",async(t,e)=>{let n=await Q(t.params.id);if(!n)return e.status(404).json({error:"track not found"});e.set({"content-type":"text/event-stream","cache-control":"no-cache",connection:"keep-alive","x-accel-buffering":"no"}),e.flushHeaders();let r=i=>e.write(`id: ${i.seq}
|
|
297
321
|
data: ${JSON.stringify(i)}
|
|
298
322
|
|
|
299
|
-
`),o=Number(t.query.since??t.headers["last-event-id"])||0;for(let i of await F(n))i.seq>o&&r(i);let s=
|
|
323
|
+
`),o=Number(t.query.since??t.headers["last-event-id"])||0;for(let i of await F(n))i.seq>o&&r(i);let s=Td(n.id,r),a=setInterval(()=>e.write(`: hb
|
|
300
324
|
|
|
301
|
-
`),25e3);t.on("close",()=>{clearInterval(a),s()})});
|
|
325
|
+
`),25e3);t.on("close",()=>{clearInterval(a),s()})});x.post("/api/tracks/:id/lead/restart",async(t,e)=>{try{e.json({ok:!0,track:await vw(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 Se(t.params.id,n,{attachments:r}))}catch(o){let s=o?.message||String(o);e.status(/not found/i.test(s)?404:/archived/i.test(s)?409:/required|not running/i.test(s)?400:500).json({error:s})}});x.post("/api/tracks/:id/upload",async(t,e)=>{try{let n=await Q(t.params.id);if(!n)return e.status(404).json({error:`track "${t.params.id}" not found`});if(n.archivedAt)return e.status(409).json({error:"track is archived"});if(!n.cwd)return e.status(409).json({error:"track has no workspace yet"});let r=Buffer.isBuffer(t.body)?t.body:null,o=await jh(n.cwd,{filename:t.query.filename,buffer:r});e.json({ok:!0,attachment:o})}catch(n){let r=n?.message||String(n);e.status(/too large|unsupported|empty|required/i.test(r)?400:500).json({error:r})}});x.get("/api/tracks/:id/uploads/:file",async(t,e)=>{try{let n=await Q(t.params.id);if(!n)return e.status(404).json({error:`track "${t.params.id}" not found`});if(!n.cwd)return e.status(404).json({error:"track has no workspace"});let r=ct.basename(String(t.params.file||""));if(!r||!wl(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(hl(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 hR=/^[0-9a-fA-F-]{8,64}$/;x.get("/api/tracks/:id/chat-images/:box/:uuid/:idx",async(t,e)=>{try{let n=await Q(t.params.id);if(!n)return e.status(404).json({error:`track "${t.params.id}" not found`});let{box:r,uuid:o}=t.params,s=Number(t.params.idx);if(!hR.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=de(a.cwd,a.sessionId);if(!i)return e.status(404).json({error:"transcript not found"});let c=`"${o}"`,l=null,u=iR.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?ri(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 Rl(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:ya(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 El(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/i.test(a)?409:500;e.status(i).json({error:a})}});x.post("/api/setup/:provider/start",async(t,e)=>{try{let n=await hw({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 ew(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 aw(t.params.id,typeof n=="string"?n:""))}catch(r){let o=r?.message||String(r),s=/not found/i.test(o)?404:/required/i.test(o)?400:500;e.status(s).json({error:o})}});x.get("/api/tracks/:id/usage",async(t,e)=>{let n=await Q(t.params.id);if(!n)return e.status(404).json({error:"track not found"});let r=n.boxes?.find(s=>s.name===n.leadBox)||null,o=t.query.fresh==="1"||t.query.fresh==="true";e.json(await Ia(r,{fresh:o}))});x.post("/api/tracks/:id/model",async(t,e)=>{let{model:n}=t.body||{};try{e.json(await iw(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 Iw(t.params.id,{agent:n,task:r,skipPermissions:o!==!1,reason:s});e.json({ok:!0,subtask:Uo(a)})}catch(a){let i=a?.message||String(a),c=/not found/i.test(i)?404:/required|unknown agent|git repo|not running|can't resolve/i.test(i)?400:500;e.status(c).json({error:i})}});x.get("/api/tracks/:id/subtasks",(t,e)=>{e.json({subtasks:xr(t.params.id).map(Uo)})});x.get("/api/tracks/:id/subtask/:sid/messages",async(t,e)=>{let n=await tw(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:Uo(Bl(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:Uo($w(t.params.sid))})}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:409).json({error:r})}});function Uo(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 Ml(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 sw(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(Ay(Ao(t.params.id))??{running:!1})});function Ay(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:zo(),paths:Ru,trace:await Iu({limit:n})})});var mu=ct.resolve(by,"..","..","client","dist");Mt.existsSync(mu)&&(x.use(ue.static(mu)),x.get(/^(?!\/api\/).*/,(t,e)=>e.sendFile(ct.join(mu,"index.html"))));var Ty=lR.createServer(x),wR=new dR({noServer:!0}),_y=(t,e,n)=>{if(!(Mo(t)||$r.allowed($r.clientIp(t)))){e.write(`HTTP/1.1 403 Forbidden\r
|
|
326
|
+
\r
|
|
327
|
+
`),e.destroy();return}if(Eg(t,e,n))return;if(!vg(t,xy,{enforce:Sy})){e.write(`HTTP/1.1 401 Unauthorized\r
|
|
302
328
|
\r
|
|
303
|
-
`),e.destroy();return}
|
|
329
|
+
`),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
|
|
304
330
|
\r
|
|
305
|
-
`),e.destroy();return}let o=r.sessionName;if(!
|
|
331
|
+
`),e.destroy();return}let o=r.sessionName;if(!mt(o)){e.write(`HTTP/1.1 400 Bad Request\r
|
|
306
332
|
\r
|
|
307
|
-
`),e.destroy();return}let{cols:s,rows:a}=r;
|
|
333
|
+
`),e.destroy();return}let{cols:s,rows:a}=r;wR.handleUpgrade(t,e,n,i=>Pu(i,o,{cols:s,rows:a}))};Ty.on("upgrade",_y);var Ey=process.env.BOSTRAT_CERT_DIR||ct.join(process.env.HOME||hu.tmpdir(),".bostrat","certs"),wy=Number(process.env.BOSTRAT_TLS_PORT)||3443,gy=ct.join(Ey,"bostrat.globopi.key"),yy=ct.join(Ey,"bostrat.globopi.crt");if(Mt.existsSync(gy)&&Mt.existsSync(yy)){let t=uR.createServer({key:Mt.readFileSync(gy),cert:Mt.readFileSync(yy)},x);t.on("upgrade",_y),t.on("error",e=>{console.error(`[bostrat-agent] https listener failed (${e?.code||e?.message}) \u2014 continuing HTTP-only`)}),t.listen(wy,ja,()=>{console.log(`[bostrat-agent] https listening on ${ja||"0.0.0.0"}:${wy}`)})}Ty.listen(my,ja,()=>{console.log(`[bostrat-agent] listening on ${ja||"0.0.0.0"}:${my}`),console.log(`[bostrat-agent] tmux socket: ${Pt}`),console.log(`[bostrat-agent] auto-trigger default: ${ky}%`),console.log(`[bostrat-agent] ip allowlist: ${$r.open?"OPEN (BOSTRAT_ALLOW_IPS unset)":process.env.BOSTRAT_ALLOW_IPS}`),_w().then(t=>t&&console.log("[bostrat-agent] reconciled interrupted handoff(s)")).catch(t=>console.error("[bostrat-agent] handoff reconcile failed:",t?.message||t)),Ew().then(t=>t&&console.log("[bostrat-agent] reconciled interrupted subtask(s)")).catch(t=>console.error("[bostrat-agent] subtask reconcile failed:",t?.message||t)),Sw().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)),mw().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)),kR(),bR(),Lg(),Yf({resolveTrack:Q}),Fw({listTracks:at,createTrack:$l,archiveTrack:kr,listRepos:ye,resolveTrack:Q,sayToLead:Se,answerPrompt:El,startLeadHandoff:Ml,interruptLead:Rl,listSubtaskJobs:xr,cancelSubtask:Bl,readEvents:F,appendEvent:O,knownAgents:ha}),gR(),Pr().then(t=>{t||console.warn(`[boot] no host tmux server on ${Pt} and auto-start failed \u2014 boxes cannot spawn until one runs`)}),Qg(),process.env.BOSTRAT_PLANE_URL&&process.env.BOSTRAT_PLANE_TOKEN&&(wu=Vw({url:process.env.BOSTRAT_PLANE_URL,token:process.env.BOSTRAT_PLANE_TOKEN,nodeId:process.env.BOSTRAT_NODE_ID||hu.hostname(),name:process.env.BOSTRAT_NODE_NAME||hu.hostname(),listTracks:at,detectAgents:Tl}),console.log(`[bostrat-agent] plane link \u2192 ${process.env.BOSTRAT_PLANE_URL}`))});function gR(){yR(),bs().catch(()=>{}),setInterval(()=>bs().catch(()=>{}),1e4)}function yR(){for(let t of["bostrat-view","bostrat-mock","bostrat-ticket","bostrat-runbook","bostrat-notify","bostrat-gmail","bostrat-changelog","gh"])try{let e=ct.resolve(by,"..","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 bR(){let t=!1,e=async()=>{if(!t){t=!0;try{let n=await at(),r=await cg({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 kR(){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 _l(n.id).catch(()=>{}),await Ph(n).catch(()=>{}))}catch{}finally{t=!1}}},4e3)}
|