bostrat-node 0.2.4 → 0.2.5
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-notify +2 -0
- package/dist/cli.js +36 -56
- package/dist/index.js +131 -111
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,107 +1,109 @@
|
|
|
1
|
-
var
|
|
1
|
+
var $g=Object.defineProperty;var Jl=(t,e)=>{for(var n in e)$g(t,n,{get:e[n],enumerable:!0})};import lt from"node:path";import Ct from"node:fs";import _E from"node:readline";import{fileURLToPath as EE}from"node:url";import ie from"express";import IE from"node:http";import RE from"node:https";import Gl from"node:os";import{WebSocketServer as $E}from"ws";import{execFile as Fg}from"node:child_process";import{promisify as Ug}from"node:util";import Aa from"node:fs";import{randomUUID as Hg}from"node:crypto";import Rn from"node:fs/promises";import zl from"node:os";import Ze from"node:path";function Co(){return/^(1|true|yes|on)$/i.test(process.env.BOSTRAT_CLAUDE_USE_API_KEY||"")&&process.env.ANTHROPIC_API_KEY?.trim()||null}function Og(){return process.env.CLAUDE_CONFIG_DIR?Ze.join(process.env.CLAUDE_CONFIG_DIR,".claude.json"):Ze.join(zl.homedir(),".claude.json")}function Cg(){return process.env.CLAUDE_CONFIG_DIR?Ze.join(process.env.CLAUDE_CONFIG_DIR,"settings.json"):Ze.join(zl.homedir(),".claude","settings.json")}async function xe(t){if(!t||typeof t!="string")return;let e=Ze.resolve(t),n=Og(),r=Cg(),o={};try{o=JSON.parse(await Rn.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 Rn.mkdir(Ze.dirname(n),{recursive:!0}),await Rn.writeFile(n,JSON.stringify(o,null,2));let i={};try{i=JSON.parse(await Rn.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' --data-binary @- >/dev/null 2>&1 || true`,u=[{hooks:[{type:"command",command:l}]}];i.hooks=i.hooks||{};for(let d of["SessionStart","UserPromptSubmit","Stop"])i.hooks[d]=u;i.hooks.PostToolUse=[{matcher:"*",hooks:[{type:"command",command:l}]}]}await Rn.mkdir(Ze.dirname(r),{recursive:!0}),await Rn.writeFile(r,JSON.stringify(i,null,2))}import Sa from"node:fs/promises";import Mg from"node:os";import Mo from"node:path";function Pg(){let t=process.env.CODEX_HOME||Mo.join(Mg.homedir(),".codex");return Mo.join(t,"config.toml")}function Xl(t){return`[projects."${t}"]`}function Ng(t,e){let n=Xl(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 $n(t){if(!t||typeof t!="string")return;let e=Mo.resolve(t),n=Pg(),r="";try{r=await Sa.readFile(n,"utf8")}catch{}if(Ng(r,e))return;let o=Xl(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),d=/trust_level\s*=/.test(u)?u.replace(/trust_level\s*=\s*"[^"]*"/,'trust_level = "trusted"'):`${u.replace(/\s*$/,"")}
|
|
2
2
|
trust_level = "trusted"
|
|
3
3
|
`;r=r.slice(0,a)+d+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 Sa.mkdir(Mo.dirname(n),{recursive:!0}),await Sa.writeFile(n,r)}catch(s){console.warn(`[codex-config] could not seed folder trust for ${e}: ${s?.message||s}`)}}import Yl from"node:fs/promises";import Bg from"node:os";import Er from"node:path";function Dg(t){let n=Er.resolve(t).slice(1).replace(/\//g,"-").replace(/\./g,""),r=process.env.CURSOR_CONFIG_DIR||Er.join(Bg.homedir(),".cursor");return Er.join(r,"projects",n)}async function On(t){if(!t||typeof t!="string")return;let e=Er.resolve(t),n=Dg(e),r=Er.join(n,".workspace-trusted");await Yl.mkdir(n,{recursive:!0}),await Yl.writeFile(r,`${JSON.stringify({trustedAt:new Date().toISOString(),workspacePath:e},null,2)}
|
|
9
|
+
`,"utf8")}import Ir from"node:fs/promises";import jg from"node:os";import Ql from"node:path";var va=process.env.BOSTRAT_DEBUG_DIR||Ql.join(jg.homedir(),".bostrat","debug"),Cn=Ql.join(va,"handoff.jsonl"),Zl=Number(process.env.BOSTRAT_HANDOFF_DEBUG_MAX)||500;function Po(){let t=process.env.BOSTRAT_HANDOFF_DEBUG;return t==="1"||t==="true"}async function Lg(t){try{await Ir.mkdir(va,{recursive:!0}),await Ir.appendFile(Cn,`${JSON.stringify(t)}
|
|
10
|
+
`,"utf8");let n=(await Ir.readFile(Cn,"utf8")).split(`
|
|
11
|
+
`).filter(Boolean);n.length>Zl&&await Ir.writeFile(Cn,`${n.slice(-Zl).join(`
|
|
12
12
|
`)}
|
|
13
|
-
`,"utf8")}catch(e){console.warn(`[handoff-debug] failed to write ${
|
|
14
|
-
`).filter(Boolean).slice(-t).map(r=>{try{return JSON.parse(r)}catch{return{raw:r}}})}catch{return[]}}var
|
|
15
|
-
`).filter(Boolean).map(r=>{let[o,s,a,i,c,l,u,d,f]=r.split("|"),p=Number(i);return{name:o,title:u||null,windows:Number(s),attached:Number(a),lastActivityMs:Number.isFinite(p)?n-p*1e3:null,cwd:c||null,isBostrat:l==="1",sessionId:d||null,agent:f||(l==="1"?"claude":null)}}).filter(r=>!
|
|
16
|
-
`).map(n=>n.trim()).includes(t)}catch{return!1}}async function
|
|
17
|
-
`).filter(Boolean).length}catch{return 0}}async function
|
|
18
|
-
`);for(let n=e.length-1;n>=0;n--){let r=e[n].trim();if(!r||r[0]!=="{")continue;let o;try{o=JSON.parse(r)}catch{continue}let s=o?.message?.usage;if(!s)continue;let a=(s.input_tokens||0)+(s.cache_creation_input_tokens||0)+(s.cache_read_input_tokens||0);if(a>0)return{occupancy:a,model:o.message.model||o.model||null}}return null}async function
|
|
19
|
-
`).trim():""}function
|
|
20
|
-
`)){let i=a.trim();if(!i||i[0]!=="{")continue;let c;try{c=JSON.parse(i)}catch{continue}if(c.type==="attachment"){let
|
|
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 ${Cn}: ${e?.message||e}`)}}async function q(t={}){let e={at:new Date().toISOString(),ts:Date.now(),...t},n=[e.flow||"handoff",e.phase||e.step||"?",e.trackId?`track=${e.trackId}`:"",e.sessionName?`box=${e.sessionName}`:"",e.error?`ERR=${e.error}`:""].filter(Boolean).join(" \xB7 ");if(console.log(`[handoff-debug] ${n}`),e.sessionName&&Po())try{e.paneTail=(await wt(e.sessionName,{lines:35})).slice(-4e3)}catch{e.paneTail=null}(Po()||e.error||/^(checkpoint|spawn|hydrate|commit|done|summarize|clear|resume|ready|send|failed)/.test(String(e.phase||e.step||"")))&&await Lg(e)}async function tu({limit:t=80}={}){try{return(await Ir.readFile(Cn,"utf8")).split(`
|
|
14
|
+
`).filter(Boolean).slice(-t).map(r=>{try{return JSON.parse(r)}catch{return{raw:r}}})}catch{return[]}}var eu={DEBUG_DIR:va,LOG_FILE:Cn};var rt=Ug(Fg);function Rr(t){return new Promise(e=>setTimeout(e,t))}function Ta(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 C=process.env.BOSTRAT_TMUX_BIN||"/usr/bin/tmux",Mt=process.env.BOSTRAT_TMUX_SOCKET||"/tmp/tmux-1000/default",Wg=process.env.BOSTRAT_DEFAULT_CWD||process.env.HOME||"/",_a=process.env.BOSTRAT_AGENT_CMD||"claude --dangerously-skip-permissions",Ea="bostrat-usage-probe",qg=new Set([Ea]);function Ia(t,e){let n=Mn(t),r=/(^|\s)(--session-id|--resume|--continue|-r|-c)(\s|=|$)/.test(t);return!n||r?t:`${t} --session-id ${e}`}function Mn(t){return/(^|\/|\s)claude(\s|$)/.test(String(t||""))}function Pn(t){return/(^|\/|\s)claude(\s|$)/.test(t)?"claude":/cursor/.test(t)?"cursor":/codex/.test(t)?"codex":/gemini/.test(t)?"gemini":"agent"}async function Ra(t,{loginShell:e=!1,failOpen:n=!1}={}){if(!t)return!1;let r=e?process.env.SHELL||"/bin/sh":"sh",o=e?"-lic":"-c";try{return await rt(r,[o,'command -v "$1" >/dev/null 2>&1',"_",t],{timeout:5e3}),!0}catch(s){return s?.code===1?!1:n}}async function No(t){return Ra(String(t||"").trim().split(/\s+/)[0])}var $a={claude:"npm install -g @anthropic-ai/claude-code",codex:"npm install -g @openai/codex","cursor-agent":"curl https://cursor.com/install -fsS | bash"};function P(...t){return["-S",Mt,...t]}async function Se(){if(!Aa.existsSync(Mt))return!1;try{return await rt(C,P("list-sessions")),!0}catch(t){let e=t&&t.stderr?String(t.stderr):"";return!/no server running/i.test(e)}}async function $r(){if(await Se())return!0;try{Aa.mkdirSync(Mt.replace(/\/[^/]+$/,""),{recursive:!0}),await rt(C,P("new-session","-d","-s","keeper","sleep 2147483647"))}catch{}return await Se()}var Gg=/^[A-Za-z0-9_.-]{1,64}$/;function pt(t){return typeof t=="string"&&Gg.test(t)}async function Nn(){if(!Aa.existsSync(Mt))return[];try{let t="#{session_name}|#{session_windows}|#{session_attached}|#{session_activity}|#{pane_current_path}|#{@bostrat-session}|#{@bostrat-title}|#{@bostrat-sid}|#{@bostrat-agent}",{stdout:e}=await rt(C,P("list-sessions","-F",t)),n=Date.now();return e.trim().split(`
|
|
15
|
+
`).filter(Boolean).map(r=>{let[o,s,a,i,c,l,u,d,f]=r.split("|"),p=Number(i);return{name:o,title:u||null,windows:Number(s),attached:Number(a),lastActivityMs:Number.isFinite(p)?n-p*1e3:null,cwd:c||null,isBostrat:l==="1",sessionId:d||null,agent:f||(l==="1"?"claude":null)}}).filter(r=>!qg.has(r.name)&&!r.name.startsWith("bostrat-view-"))}catch{return[]}}async function z(t){return(await Nn()).find(n=>n.name===t)||null}var Kg=/^[A-Za-z0-9 ._↻-]+$/,Vg=64;function Jg(t){if(typeof t!="string")return!1;let e=t.trim();return e.length>=1&&e.length<=Vg&&Kg.test(e)}function Oa(t){return t.trim().replace(/\s+/g,"-").toLowerCase().replace(/[^A-Za-z0-9_.-]/g,"").replace(/^-+|-+$/g,"")}async function zg(t){try{let{stdout:e}=await rt(C,P("list-sessions","-F","#{session_name}"));return e.split(`
|
|
16
|
+
`).map(n=>n.trim()).includes(t)}catch{return!1}}async function Qe(t,e=Wg,n={}){if(!Jg(t))throw new Error("displayName must be 1\u201364 chars (letters, digits, space . _ -)");let r=t.trim(),o=Oa(r);if(!pt(o))throw new Error("resulting tmux session name is invalid");if(!await $r())throw new Error(`host tmux server not running on ${Mt} and auto-start failed \u2014 start one: ${C} -S ${Mt} new-session -d -s keeper "sleep 2147483647"`);if(await zg(o))throw new Error(`tmux session "${o}" already exists`);let s=n.agentCmd||_a,a=n.agent||Pn(s),i=!!n.resumeSessionId,c=n.resumeSessionId||Hg(),l=i?s:Ia(s,c),u=i?n.pinned===!0:/(^|\s)--session-id(\s|=)/.test(l),d=a==="claude"&&Mn(s),f=n.firstRun===!0;f||(d&&await xe(e),a==="codex"&&await $n(e),a==="cursor"&&await On(e));let p=d&&!f?Co():null,m=p?["-e",`ANTHROPIC_API_KEY=${p}`]:[];try{await rt(C,P("new-session","-d","-s",o,"-c",e,...m))}catch(h){throw p?Ta(h,p):h}return await tn(o),await rt(C,P("set-option","-t",o,"@bostrat-session","1")),await rt(C,P("set-option","-t",o,"@bostrat-title",r)),await rt(C,P("set-option","-t",o,"@bostrat-sid",c)),await rt(C,P("set-option","-t",o,"@bostrat-agent",a)),await rt(C,P("send-keys","-t",o,l,"Enter")),{name:o,title:r,cwd:e,sessionId:c,agent:a,pinned:u}}async function Jt(t){if(!pt(t))throw new Error("invalid session name");await rt(C,P("kill-session","-t",t))}async function nu(t,e){if(pt(t))try{let n=e?P("set-option","-t",t,"@bostrat-sid",e):P("set-option","-u","-t",t,"@bostrat-sid");await rt(C,n)}catch{}}async function wt(t,{lines:e=40}={}){if(!pt(t))return"";try{let n=P("capture-pane","-t",t,"-p");Number.isFinite(e)&&e>0&&n.push("-S",`-${Math.floor(e)}`);let{stdout:r}=await rt(C,n);return String(r||"")}catch{return""}}var Xg=Number(process.env.BOSTRAT_PROMPT_CAPTURE_COLS)||100,Yg=Number(process.env.BOSTRAT_PROMPT_CAPTURE_ROWS)||40,_t=Number(process.env.BOSTRAT_PROMPT_CAPTURE_LINES)||80,Zg=Number(process.env.BOSTRAT_BOX_COLS)||120,Qg=Number(process.env.BOSTRAT_BOX_ROWS)||40;async function ru(t){if(!pt(t))return 0;try{let{stdout:e}=await rt(C,P("list-clients","-t",t,"-F","1"));return String(e||"").trim().split(`
|
|
17
|
+
`).filter(Boolean).length}catch{return 0}}async function tn(t,{cols:e=Zg,rows:n=Qg}={}){if(pt(t)&&!await ru(t))try{let r=await su(t);if(r.cols===e&&r.rows===n)return;await rt(C,P("resize-window","-t",t,"-x",String(e),"-y",String(n))),await rt(C,P("set-window-option","-t",t,"window-size","latest")),await Rr(80)}catch{}}async function ou(t){if(!pt(t))return 0;try{let{stdout:e}=await rt(C,P("display-message","-t",t,"-p","#{pane_pid}"));return Number(String(e||"").trim())||0}catch{return 0}}async function su(t){if(!pt(t))return{cols:0,rows:0};try{let{stdout:e}=await rt(C,P("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 Bo(t,{cols:e=Xg,rows:n=Yg}={}){if(pt(t)&&!await ru(t))try{let r=await su(t),o=Math.max(r.cols,e),s=Math.max(r.rows,n);(o>r.cols||s>r.rows)&&(await rt(C,P("resize-window","-t",t,"-x",String(o),"-y",String(s))),await rt(C,P("set-window-option","-t",t,"window-size","latest")),await Rr(80))}catch{}}async function ve(t){return await Bo(t),wt(t,{lines:_t})}var ty=new Set(["Up","Down","Left","Right","Space","Enter","Tab","BTab","Escape","BSpace"]);async function bt(t,e,{settleMs:n=40,kind:r}={}){if(!pt(t))throw new Error("invalid session name");let o=(Array.isArray(e)?e:[e]).filter(s=>ty.has(s));for(let s=0;s<o.length;s++)await rt(C,P("send-keys","-t",t,o[s])),n>0&&s<o.length-1&&await Rr(n);return q({flow:"tmux",phase:"send-keys",sessionName:t,kind:r||"keys",keys:o.join(" ")}).catch(()=>{}),o.length}async function Do(t,e,{kind:n}={}){if(!pt(t))throw new Error("invalid session name");let r=Math.max(0,Math.floor(Number(e)||0));return r>0&&await rt(C,P("send-keys","-t",t,"-N",String(r),"BSpace")),q({flow:"tmux",phase:"send-keys",sessionName:t,kind:n||"wipe",keys:`BSpace x${r}`}).catch(()=>{}),r}async function jo(t,e,{perCharDelayMs:n=120,kind:r}={}){if(!pt(t))throw new Error("invalid session name");let o=[...String(e)];for(let s of o)await rt(C,P("send-keys","-t",t,"-l",s)),n>0&&await Rr(n);q({flow:"tmux",phase:"type",sessionName:t,kind:r||"type",textLen:o.length,textHead:String(e).slice(0,120)}).catch(()=>{})}async function kt(t,e,{enter:n=!0,submitDelayMs:r=0,kind:o}={}){if(!pt(t))throw new Error("invalid session name");if(typeof e=="string"&&e.length>0&&await rt(C,P("send-keys","-t",t,"-l",e)),n){let s=Number(r);Number.isFinite(s)&&s>0&&await Rr(s),await rt(C,P("send-keys","-t",t,"Enter"))}q({flow:"tmux",phase:"send",sessionName:t,kind:o||"text",textLen:typeof e=="string"?e.length:0,textHead:typeof e=="string"?e.slice(0,120):"",enter:n,submitDelayMs:r}).catch(()=>{})}import ey from"node-pty";async function iu(t,e,n={}){if(!pt(e)){Lo(t,`invalid session name: ${e}`),t.close(1008,"invalid session name");return}if(!await Se()){Lo(t,`host tmux server not running on ${Mt}`),t.close(1011,"tmux server unavailable");return}if(!await z(e)){Lo(t,`session "${e}" not found`),t.close(1008,"session not found");return}let r=au(n.cols,120,cu),o=au(n.rows,32,lu),s;try{s=ey.spawn(C,["-S",Mt,"attach-session","-d","-t",e],{name:"xterm-256color",cols:r,rows:o,cwd:process.env.HOME||"/",env:{...process.env,TERM:"xterm-256color"}})}catch(a){Lo(t,`failed to spawn tmux attach: ${a.message}`),t.close(1011,"spawn failed");return}s.onData(a=>{t.readyState===t.OPEN&&Ca(t,{type:"data",data:a})}),s.onExit(({exitCode:a,signal:i})=>{t.readyState===t.OPEN&&(Ca(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"&&ny(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{}tn(e).catch(()=>{})})}var cu=20,lu=6;function ny(t,e){return Number.isFinite(Number(t))&&Number.isFinite(Number(e))&&Number(t)>=cu&&Number(e)>=lu}function au(t,e,n){let r=Number(t);return!Number.isFinite(r)||r<n?e:Math.min(500,r|0)}function Ca(t,e){try{t.send(JSON.stringify(e))}catch{}}function Lo(t,e){Ca(t,{type:"error",message:e})}function uu(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 Or from"node:fs";import ry from"node:fs/promises";import Bn from"node:path";import oy from"node:os";var du={"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},sy=2e5,Ma=process.env.BOSTRAT_CLAUDE_PROJECTS||Bn.join(oy.homedir(),".claude","projects");function Pa(t){return t.replace(/[^A-Za-z0-9]/g,"-")}function Na(t){let e=Bn.join(Ma,Pa(t)),n;try{n=Or.readdirSync(e).filter(o=>o.endsWith(".jsonl"))}catch{return null}let r=null;for(let o of n){let s=Bn.join(e,o);try{let{mtimeMs:a}=Or.statSync(s);(!r||a>r.mtimeMs)&&(r={full:s,mtimeMs:a})}catch{}}return r?r.full:null}function ce(t,e){if(!e)return null;let n=Bn.join(Ma,Pa(t),`${e}.jsonl`);return Or.existsSync(n)?n:null}function fu(t){if(!t)return[];let e=Bn.join(Ma,Pa(t)),n;try{n=Or.readdirSync(e).filter(o=>o.endsWith(".jsonl"))}catch{return[]}let r=[];for(let o of n)try{let s=Or.statSync(Bn.join(e,o));r.push({id:o.slice(0,-6),birthMs:s.birthtimeMs||s.ctimeMs||s.mtimeMs})}catch{}return r}async function Et(t,e=131072){let n=await ry.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 ay(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 Cr(t,e=null,{fallbackNewest:n=!0}={}){if(!t)return null;let r,o;if(e){if(r=ce(t,e),o=!0,!r)return null}else if(n){if(r=Na(t),o=!1,!r)return null}else return null;let s;try{s=ay(await Et(r))}catch{return null}if(!s)return null;let a=Object.prototype.hasOwnProperty.call(du,s.model),i=du[s.model]||sy;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 iy(t,e){return!t||!e?null:ce(t,e)}function cy(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 ly(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 uy(t){return Array.isArray(t)&&t.length>0&&t.every(e=>e&&e.type==="tool_result")}function Ba(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 pu(t,e){if(!t.uuid)return null;let n=Ba(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 dy=/\.(png|jpe?g|gif|webp|bmp|svg|tiff?|avif|heic|heif|ico)$/i;async function Da(t,{maxBytes:e=524288,limit:n=80}={}){let r;try{r=await Et(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 m=c.attachment;m?.type==="queued_command"&&m.commandMode==="task-notification"&&typeof m.prompt=="string"&&m.prompt.trim()&&o.push({role:"user",text:m.prompt.trim(),ts:c.timestamp?Date.parse(c.timestamp):null,model:null});continue}let l=c.message;if(!l)continue;let u=l.role||c.type;if(u!=="user"&&u!=="assistant")continue;if(u==="assistant"&&Array.isArray(l.content))for(let m of l.content){let h=m?.type==="tool_use"?m.input?.file_path||m.input?.path:null;typeof h=="string"&&dy.test(h)&&s.set(m.id,h.split("/").pop())}if(uy(l.content)){let m=pu(c,s);m&&o.push({role:"assistant",text:"",images:m,ts:c.timestamp?Date.parse(c.timestamp):null,model:null});continue}let d=cy(l.content),f=u==="assistant"?ly(l.content):null,p=u==="user"?pu(c,s):null;!d&&!f&&!p||o.push({role:u,text:d,tools:f||void 0,images:p||void 0,ts:c.timestamp?Date.parse(c.timestamp):null,model:l.model||null})}return o.slice(-n)}var fy=(t,e)=>t.length>e?`${t.slice(0,e).trimEnd()}\u2026`:t;async function mu(t,e,{turns:n=16}={}){let r=iy(t,e);if(!r)return"";let o=await Da(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}:** ${fy(s.text||c,700)} ${i}`.trim()}).join(`
|
|
21
|
+
|
|
22
|
+
`):""}import Wy from"node:fs";import Pr from"node:fs/promises";import Ln from"node:path";import py from"node:fs/promises";import{execFile as my}from"node:child_process";import{promisify as hy}from"node:util";var ja=hy(my),wy=Number(process.env.BOSTRAT_CODEX_UPDATE_TIMEOUT_MS)||5*6e4,hu=new Set,wu=Promise.resolve(),La=null;function yu(t){La=typeof t=="function"?t:null}function gy(t){let e=String(t||""),n=e.indexOf("/lib/node_modules/");return n>0?e.slice(0,n):null}function bu({sessionName:t,from:e,to:n}={}){let r=String(n||"").trim();!r||hu.has(r)||(hu.add(r),wu=wu.then(()=>yy({sessionName:t,from:e,to:r})).catch(()=>{}))}async function yy({sessionName:t,from:e,to:n}){await q({flow:"codex-update",phase:"start",sessionName:t,from:e,to:n}).catch(()=>{});try{let r=(await ja("/bin/sh",["-c","command -v codex"])).stdout.trim();if(!r)throw new Error("codex not on PATH");let o=await py.realpath(r),s=gy(o),a=s?{...process.env,PATH:`${s}/bin:${process.env.PATH||""}`}:process.env;await ja("npm",["install","-g","@openai/codex"],{env:a,timeout:wy});let i=(await ja(r,["--version"])).stdout.trim().replace(/^codex-cli\s*/,"")||n;await q({flow:"codex-update",phase:"done",sessionName:t,from:e,to:n,version:i}).catch(()=>{}),await gu({sessionName:t,from:e,to:n,version:i,status:"updated"})}catch(r){let o=String(r?.message||r).slice(0,300);await q({flow:"codex-update",phase:"failed",sessionName:t,from:e,to:n,error:o}).catch(()=>{}),await gu({sessionName:t,from:e,to:n,status:"failed",error:o})}}async function gu(t){if(La)try{await La(t)}catch{}}var by=/^\s*[›>*\-❯]?\s*(\d+)[.)]\s+(.+?)\s*$/,ku=/\b(allow|approve|apply (this )?patch|run (this )?command|do you want to run|grant)\b/i;function ky(t,{provider:e,box:n}){let r=String(t).replace(/\r/g,"").split(`
|
|
23
|
+
`);if(!ku.test(t))return null;let o=[],s=!1;for(let l=r.length-1;l>=0;l--){let u=r[l].match(by);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"&&ku.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 Fo=/(^|\n)\s*›/,vu=/^\s*Welcome to Codex\b/;function Dn(t){let e=String(t??"").replace(/\r/g,""),n=e.split(`
|
|
24
|
+
`);for(let r=n.length-1;r>=0;r--)if(vu.test(n[r]))return n.slice(r).join(`
|
|
25
|
+
`);return e}function xy(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 Sy(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 xu=/^\s*(>)?\s*(\d+)\.\s+(.+?)\s*$/;function vy(t,e=""){let n=Dn(t);if(Fo.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(xu);if(!u)continue;s<0&&(s=l);let d="";for(let f=l+1;f<Math.min(l+3,r.length);f++){let p=r[f].trim();if(p){if(xu.test(r[f])||/^Press enter/.test(p))break;d=p;break}}o.push({n:Number(u[2]),cursor:!!u[1],label:u[3].trim(),desc:d})}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&&!vu.test(r[l])&&xy(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=Sy(l.label);return{label:l.label,description:[l.desc,u].filter(Boolean).join(" \xB7 ")}})}]}}var Su=/Enter this one-time code/,Ay=/^\s*([A-Z0-9]{2,10}-[A-Z0-9]{2,10})\s*$/;function Ty(t,e=""){let n=Dn(t);if(Fo.test(n)||!Su.test(n))return null;let r=n.split(`
|
|
27
|
+
`),o=r.findIndex(i=>Su.test(i)),s="";for(let i=o+1;i<r.length;i++){let c=r[i].match(Ay);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 _y(t,e=""){let n=Dn(t);return Fo.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 Ey(t,e=""){let n=Dn(t);return Fo.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 Fa=/Run Everything|Add a follow-up|Plan, search, build anything/;function Iy(t,e=""){let n=String(t);if(!/Press any key to log in/.test(n)||Fa.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 Ry=/click this link to log in|Signing in with the browser/;function $y(t,e=""){let n=String(t);if(!Ry.test(n)||Fa.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 Oy(t,e=""){let n=String(t);return!/Scan this QR code to log in/.test(n)||Fa.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 Au(t,e,n=""){if(t!=="cursor"&&t!=="codex")return null;if(t==="codex"){let r=Ty(e,n)||_y(e,n)||Ey(e,n)||vy(e,n);if(r)return r}if(t==="cursor"){let r=$y(e,n)||Oy(e,n)||Iy(e,n);if(r)return r}return ky(e,{provider:t,box:n})}function Tu(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 Cy=()=>Number(process.env.BOSTRAT_AGENT_READY_TIMEOUT_MS)||9e4,Ua=Number(process.env.BOSTRAT_AGENT_READY_POLL_MS)||500,Mr=Number(process.env.BOSTRAT_AGENT_READY_SETTLE_MS)||800,xt={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/}},My=/Trust this workspace|\[a\]\s*Trust/,Py=/Update available!\s*([\d][\w.-]*)\s*->\s*([\d][\w.-]*)/;function Ny(t){let e=String(t||"");if(!xt.codex.block.test(e))return null;let n=Dn(e);if(!xt.codex.block.test(n))return null;let r=n.match(Py);return{from:r?.[1]||null,to:r?.[2]||null}}function Ae(t){return new Promise(e=>setTimeout(e,t))}var By=()=>Number(process.env.BOSTRAT_GATE_RESEND_MS)||3e3,Uo=new Map;function _u(t,e){let n=Date.now(),r=By();for(let[s,a]of Uo)n-a>=r&&Uo.delete(s);let o=`${t}::${e}`;return Uo.has(o)?!1:(Uo.set(o,n),!0)}var Dy=68,jy=/^\s*[─━]{10,}\s*$/;function Ly(t){let e=0;for(let n of String(t).split(`
|
|
30
|
+
`))jy.test(n)&&(e=Math.max(e,n.trimEnd().length));return e}function Ha(t,e){let n=xt[t];if(!n?.busy)return"idle";if(Wa(t,e))return"unknown";if(n.up&&!n.up.test(e))return"idle";if(n.busy.test(e)||n.busyAlt&&n.busyAlt.test(e))return"busy";let r=Ly(e);return t==="claude"&&r>0&&r<Dy?"unknown":"idle"}var Fy=/\bWaiting for \d+\b[^\n]*\bto finish\b|\/workflows to (?:stop|monitor)\b/;function Iu(t,e){return t==="claude"&&Fy.test(String(e||""))}function jn(t,e){return Ha(t,e)==="busy"}function Ho(t,e){return Ha(t,e)!=="idle"}function Wo(t,e){let n=xt[t];return n?.up?n.up.test(e):!0}function Uy(t,e){let n=xt[t];return n?Wa(t,e)?!1:n.up.test(e)&&Ha(t,e)==="idle":!0}function Wa(t,e){let n=xt[t];return n?n.auth?.test(e)&&!n.up.test(e)?!0:n.block?.test(e)?t==="codex"?!0:!n.up.test(e):!1:!1}async function Eu(t,e,n){let r=Date.now()+n,o=0;for(await tn(t).catch(()=>{});;){let s=await wt(t);if(Uy(e,s)){await Ae(Mr);return}if(e==="cursor"&&My.test(s)&&!xt.cursor.up.test(s)&&o<3){if(!_u(t,"cursor-trust")){await Ae(Ua);continue}o+=1,await q({flow:"agent-ready",phase:"gate-accept",sessionName:t,agent:e,attempt:o}).catch(()=>{}),await kt(t,"a",{enter:!1,kind:"cursor-trust-accept"}),await Ae(Mr);continue}if(e==="codex"&&o<3){let a=Ny(s);if(a){if(!_u(t,`codex-update:${a.from}->${a.to}`)){await Ae(Ua);continue}o+=1,await q({flow:"agent-ready",phase:"gate-accept",sessionName:t,agent:e,attempt:o,update:`${a.from} -> ${a.to}`}).catch(()=>{}),await kt(t,"3",{enter:!1,kind:"codex-update-skip"}),bu({sessionName:t,from:a.from,to:a.to}),await Ae(Mr);continue}}if(Date.now()>r){if(Wa(e,s)){let a=`${e} is blocked on an interactive prompt`;throw e==="codex"?a=xt.codex.auth.test(s)?"codex isn't signed in on this box \u2014 answer the Sign in card in chat (Device Code works from any device) or finish signing in from the Terminal tab, then send again":"codex is stuck on its update prompt (auto-skip didn't clear it) \u2014 open the terminal and choose Skip, then send again":e==="cursor"&&xt.cursor.auth.test(s)?a="cursor isn't signed in on this box \u2014 answer the Sign in card in chat (the login link works from any device) or finish signing in from the Terminal tab, then send again":e==="claude"&&xt.claude.auth.test(s)?a="claude isn't signed in on this box \u2014 answer the setup cards in chat (or finish signing in from the Terminal tab), then send again":e==="claude"&&xt.claude.block.test(s)&&(a='claude is waiting on its "Press Enter to continue" screen \u2014 answer the Continue card in chat (or press Enter in the Terminal tab), then send again'),q({flow:"agent-ready",phase:"blocked",sessionName:t,agent:e,timeoutMs:n,gateAccepts:o,error:a}).catch(()=>{}),new Error(a)}console.warn(`[agent-ready] ${e} readiness unconfirmed in ${t} after ${n}ms \u2014 driving the box anyway.`),q({flow:"agent-ready",phase:"timeout",sessionName:t,agent:e,timeoutMs:n,gateAccepts:o,paneBlocked:!!xt[e]?.block?.test(s),paneUp:!!xt[e]?.up?.test(s),paneBusy:!!xt[e]?.busy?.test(s)}).catch(()=>{}),await Ae(Mr);return}await Ae(Ua)}}function Ru({agent:t,agentCmd:e}={}){let n=e?Pn(e):Pn(t||"");return{label:n,realClaude:e?Mn(e):n==="claude",realCodex:e?/codex/.test(e)&&!/fake-codex|fake-lead/.test(e):n==="codex",realCursor:e?/cursor/.test(e)&&!/fake-cursor|fake-box/.test(e):n==="cursor"}}function qo({cwd:t,sessionName:e,agent:n,agentCmd:r}={}){let{realCodex:o,realCursor:s}=Ru({agent:n,agentCmd:r});return!e||!o&&!s?null:Te({cwd:t,sessionName:e,agent:n,agentCmd:r}).catch(()=>{})}async function Te({cwd:t,sessionId:e,sessionName:n,agent:r,agentCmd:o,timeoutMs:s=Cy()}={}){let{label:a,realClaude:i,realCodex:c,realCursor:l}=Ru({agent:r,agentCmd:o});if(i&&t&&await xe(t),c&&t&&await $n(t),l&&t&&await On(t),(i||c||l)&&n){await Eu(n,a,s);return}let u=xt[r]?r:xt[a]?a:null;if(process.env.BOSTRAT_FAKE_POLL_READY==="1"&&n&&u){await Eu(n,u,s);return}await Ae(Mr)}var Hy=(()=>{let t=Number(process.env.BOSTRAT_CODEX_SUBMIT_SETTLE_MS??120);return Number.isFinite(t)&&t>=0?t:120})();function le(t){return t==="codex"?{enter:!0,submitDelayMs:Hy}:{enter:!0}}var Cu=".bostrat/handoff",Mu="latest.md",Pu="Checkpoint: write a work-in-progress handoff",Nu="HANDOFF WRITTEN",Bu="Resume from .bostrat/handoff/latest.md",qy="/clear",Gy=t=>`${Pu} to the EXACT absolute path ${t} (write it there specifically \u2014 NOT into any sub-project or other directory; create the directory if needed, overwrite the file) so a FRESH Claude context with none of this conversation can resume seamlessly. Use these sections: Objective; Done (with file paths); In progress (exact state); Next steps (ordered); Key files & commands; Open decisions/blockers; Resume instruction (one line). Be concrete and self-contained \u2014 assume the reader has none of this chat. When the file is written, reply only with ${Nu} and take no further action.`,Ky=`${Bu}: read it, restate the Objective and the single next step in one line, then continue the work from 'Next steps'.`,Du="Beat: write a",ju="BEAT WRITTEN",Vy=".bostrat/arc",Jy="beat.txt",$u=200,Lu=4,Fu=5,zy=36,Xy=t=>`${Du} short summary of what this workstream accomplished, to the EXACT absolute path ${t} (write it there specifically \u2014 NOT into any sub-project or other directory; create the directory if needed, overwrite the file). Format the file as: FIRST LINE = one sentence of at most 18 words, plain past tense, saying the outcome \u2014 no heading, no bullet, no trailing list. THEN, only if this workstream covered several separable pieces of work, up to ${Lu} further lines, each starting with "- " and naming ONE piece in at most ${Fu} words \u2014 a label, not a sentence (e.g. "- stale working flag on poll"). Write nothing else in the file. When the file is written, reply only with ${ju} and take no further action.`,Ou=/^(?:[-–—*•·]|\d+[.)])\s+/,Yy=(t,e,n)=>{let r=t.split(/\s+/).filter(Boolean).slice(0,e).join(" ");return r.length>n?`${r.slice(0,n-1).trimEnd()}\u2026`:r};function Zy(t){let e=String(t||"").split(/\r?\n/).map(s=>s.trim()).filter(Boolean),n=[],r=[];for(let s of e)if(Ou.test(s)){if(r.length>=Lu)continue;let a=Yy(s.replace(Ou,"").replace(/[.;,:]+$/,""),Fu,zy);a&&r.push(a)}else r.length||n.push(s);let o=n.join(" ").replace(/\s+/g," ").replace(/[:\-–—]+$/,"").trim();if(!o)throw new Error("beat file has no summary line");return o.length>$u&&(o=`${o.slice(0,$u-1).trimEnd()}\u2026`),{text:o,items:r.length>1?r:[]}}async function Uu(t,e,{timeoutMs:n=9e4}={}){let r=Ln.join(e,Vy),o=Ln.join(r,Jy);await Pr.mkdir(r,{recursive:!0});let s=await Go(o),a=await z(t);if(!a)throw new Error(`session "${t}" not found`);return await Ko(t,a,Xy(o)),await Ju(o,s,n),Zy(await Pr.readFile(o,"utf8"))}function Qy(t,e){let n=(e||"").trim();return n?t==="assistant"?n===Nu||n===ju:n.startsWith(Pu)||n===qy||n.startsWith(Bu)||n.startsWith(Du):!1}var tb=6e5;function Hu(t){let e=[];for(let r of t?.events||[]){if(r.type!=="lead-handoff"||!r.from)continue;let o=r.at??Date.now(),s=r.handoffStartedAt??o-tb;e.push({box:r.from,from:s,to:o}),r.to&&r.to!==r.from&&e.push({box:r.to,from:s,to:o})}let n=t?.handoff;return n?.fromBox&&n.startedAt&&n.finishedAt&&e.push({box:n.fromBox,from:n.startedAt,to:n.finishedAt}),n?.toBox&&n.startedAt&&n.finishedAt&&e.push({box:n.toBox,from:n.startedAt,to:n.finishedAt}),e}function Wu(t,e){let n=[];for(let r of t?.boxes||[]){let o=e?.(r.name);o?.startedAt&&n.push({box:r.name,from:o.startedAt,to:o.finishedAt||Date.now()+1e12})}return n}function qu(t,{windows:e=[]}={}){if(Qy(t.role,t.text))return!0;if(t.role!=="assistant"||!t.ts)return!1;for(let n of e)if(t.box===n.box&&t.ts>=n.from&&t.ts<=n.to)return!0;return!1}var eb=3500,nb=3e5,qa=new Map;function Vo(t){return qa.get(t)||null}async function Gu(t){let e=qa.get(t);if(e&&!e.finishedAt)throw new Error("a handoff is already running for this session");let n=await z(t);if(!n)throw new Error(`session "${t}" not found`);if(!n.cwd)throw new Error(`can't resolve cwd for "${t}"`);let r={name:t,step:"summarize",startedAt:Date.now(),finishedAt:null,error:null,file:Ln.join(n.cwd,Cu,Mu)};return qa.set(t,r),rb(r,n.cwd).catch(o=>{r.error=o?.message||String(o),r.finishedAt=Date.now()}),r}async function rb(t,e){t.step="summarize",await Ku(t.name,e),t.step="clear";let n=await z(t.name);await Ko(t.name,n,"/clear"),await zu(eb),t.step="resume",await Ko(t.name,n,Ky),t.step="done",t.finishedAt=Date.now()}async function Ku(t,e){let n=Ln.join(e,Cu),r=Ln.join(n,Mu);await Pr.mkdir(n,{recursive:!0});let o=await Go(r),s=await z(t);return await q({flow:"checkpoint",phase:"summarize-start",sessionName:t,cwd:e,file:r,beforeMtime:o}),await Ko(t,s,Gy(r)),await Ju(r,o,nb),await q({flow:"checkpoint",phase:"summarize-done",sessionName:t,file:r,afterMtime:await Go(r)}),await ob(n,r),r}async function Vu(t){let e=await z(t);if(!e)throw new Error(`session "${t}" not found`);if(!e.cwd)throw new Error(`can't resolve cwd for "${t}"`);return Ku(t,e.cwd)}async function Go(t){try{return(await Pr.stat(t)).mtimeMs}catch{return 0}}async function Ju(t,e,n){let r=Date.now()+n;for(;;){if(await Go(t)>e)return;if(Date.now()>r)throw new Error(`timed out waiting for the handoff file to be written (${t})`);await zu(2e3)}}async function ob(t,e){try{if(!Wy.existsSync(e))return;let n=new Date().toISOString().replace(/[:.]/g,"-");await Pr.copyFile(e,Ln.join(t,`${n}.md`))}catch{}}function zu(t){return new Promise(e=>setTimeout(e,t))}async function Ko(t,e,n){if(e){let o=e.agent==="claude"?process.env.BOSTRAT_AGENT_CMD||"claude":e.agent==="cursor"?process.env.BOSTRAT_CURSOR_CMD||"cursor-agent":e.agent==="codex"?process.env.BOSTRAT_CODEX_CMD||"codex":"";await Te({cwd:e.cwd,sessionId:e.sessionId,sessionName:t,agent:e.agent,agentCmd:o})}let r=await z(t);if(!r)throw new Error(`session "${t}" disappeared before the orchestration prompt could be sent`);await kt(t,n,{kind:"orchestration-drive",...le(r.agent)})}import D from"node:fs/promises";import j from"node:path";import{randomUUID as mo}from"node:crypto";import{execFile as $v}from"node:child_process";import{promisify as Ov}from"node:util";import Cf from"node:path";import Ga from"node:fs/promises";import Fn from"node:path";import sb from"node:os";import{EventEmitter as ab}from"node:events";import{randomUUID as ib}from"node:crypto";var cb=new Set(["session.ready","turn.user","turn.agent","turn.retracted","tool.called","tool.result","prompt.raised","prompt.answered","context.usage","cost.recorded","handoff.started","handoff.step","handoff.done","handoff.failed","subtask.delegated","subtask.merged","job.started","pr.state.changed","pr.offered","pr.offer.answered","box.state.changed","view.state.changed","view.spinup.failed","mock.state.changed","runbook.state.changed","ticket.linked","ticket.sync.failed","ticket.move.offered","ticket.move.answered","ticket.state.changed","ticket.commented","ticket.flow.changed","ticket.create.offered","ticket.create.answered","ticket.created","notify.raised","provider.connected","track.summary.recorded","stall.detected","error"]),lb=new Set(["user","agent","orchestrator","system"]),ub=new Set(["hook","otel","statusline","transcript","pane","orchestrator","vendor"]),jt=new ab;jt.setMaxListeners(0);var Ka=process.env.BOSTRAT_STATE_DIR||Fn.join(process.env.HOME||sb.tmpdir(),".bostrat");function zo(t){return t?.cwd?Fn.join(t.cwd,".bostrat","events"):Fn.join(Ka,"events")}var Xu=(t,e)=>Fn.join(t,`${e}.ndjson`),Jo=new Map;async function db(t,e){if(!Jo.has(e)){let o=(await F(t,e)).reduce((s,a)=>a.seq>s?a.seq:s,0);Jo.set(e,o)}let n=Jo.get(e)+1;return Jo.set(e,n),n}function fb(t,e){let n=Array.isArray(t?.epochs)?t.epochs:null;if(!(!n||n.length===0)){if(!Number.isFinite(e))return n.length-1;for(let r=n.length-1;r>=0;r--){let o=n[r].startedAt;if(o==null||o<=e)return r}return 0}}async function O(t,e){let n=typeof t=="string"?t:t?.id;if(!n)throw new Error("appendEvent: a track (or trackId) is required");if(!e||!cb.has(e.type))throw new Error(`appendEvent: unknown event type "${e?.type}"`);if(e.actor&&!lb.has(e.actor))throw new Error(`appendEvent: bad actor "${e.actor}"`);if(e.source&&!ub.has(e.source))throw new Error(`appendEvent: bad source "${e.source}"`);let r=typeof t=="string"?Fn.join(Ka,"events"):zo(t),o=e.epoch;if(o==null&&typeof t=="object"&&e.boxId&&Array.isArray(t?.boxes)){let c=t.boxes.find(l=>l.name===e.boxId);o=fb(c,Number(e.payload?.ts??e.ts)||Date.now())}let s=await db(r,n),a=e.userId??process.env.BOSTRAT_OWNER??null,i={id:e.id||ib(),trackId:n,boxId:e.boxId??null,...o!=null?{epoch:o}:{},...a?{userId:a}:{},seq:s,ts:e.ts??Date.now(),type:e.type,actor:e.actor??"system",provider:e.provider??null,source:e.source??"orchestrator",payload:e.payload??{},...e.causationId?{causationId:e.causationId}:{},...e.correlationId?{correlationId:e.correlationId}:{}};return await Ga.mkdir(r,{recursive:!0}),await Ga.appendFile(Xu(r,n),JSON.stringify(i)+`
|
|
31
|
+
`),jt.emit("event",i),jt.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"?Fn.join(Ka,"events"):zo(t));let o;try{o=await Ga.readFile(Xu(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 Yu(t,e){let n=`track:${t}`;return jt.on(n,e),()=>jt.off(n,e)}var Va={"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}},Zu={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 Qu(t){if(!t)return null;let e=String(t).trim();return Va[e]?e:Zu[e]?Zu[e]:Object.keys(Va).find(r=>e.startsWith(r))||null}var Xo=t=>Qu(t)!==null;function zt(t,e={}){let n=Va[Qu(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 td=()=>({input:0,output:0,cacheRead:0,cacheCreation:0});function pb(t){return t?{input:t.input||0,output:t.output||0,cacheRead:t.cacheRead||0,cacheWrite:t.cacheWrite??t.cacheCreation??0}:null}function ed(t,e){e&&(t.input+=e.input,t.output+=e.output,t.cacheRead+=e.cacheRead,t.cacheCreation+=e.cacheWrite)}function _e(t,{boxLabels:e={}}={}){let n=(t||[]).filter(d=>d.type==="cost.recorded"),r={},o={},s={},a=0,i=0,c=0,l=new Set,u=td();for(let d of n){let f=d.payload?.model||"unknown",p=d.provider||"unknown",m=d.boxId||"unattributed",h=pb(d.payload?.tokens),b=Number(d.payload?.usd)||0;!b&&h&&(b=zt(f,h),b>0&&i++);let x=h?h.input+h.output+h.cacheRead+h.cacheWrite:0;if(!b&&x>0&&!Xo(f)&&(c++,l.add(f)),a+=b,ed(u,h),r[f]||={usd:0,tokens:td(),events:0},r[f].usd+=b,r[f].events+=1,ed(r[f].tokens,h),o[p]||={usd:0,events:0},o[p].usd+=b,o[p].events+=1,s[m]||={usd:0,events:0,provider:p,models:new Set,label:e[m]||m},s[m].usd+=b,s[m].events+=1,f!=="unknown"&&s[m].models.add(f),d.epoch!=null){let v=(s[m].epochs||={})[d.epoch]||={usd:0,events:0,provider:p,models:new Set};v.usd+=b,v.events+=1,v.provider=p,f!=="unknown"&&v.models.add(f)}}return{totalUsd:Nr(a),totalTokens:u,turns:n.length,repricedTurns:i,unpricedTurns:c,unpricedModels:[...l],byModel:Yo(r,d=>({...d,usd:Nr(d.usd)})),byProvider:Yo(o,d=>({...d,usd:Nr(d.usd)})),byBox:Yo(s,d=>({usd:Nr(d.usd),events:d.events,provider:d.provider,label:d.label,models:[...d.models],...d.epochs?{epochs:Yo(d.epochs,f=>({usd:Nr(f.usd),events:f.events,provider:f.provider,models:[...f.models]}))}:{}}))}}var Nr=t=>Math.round(t*1e4)/1e4,Yo=(t,e)=>Object.fromEntries(Object.entries(t).map(([n,r])=>[n,e(r)]));import yt from"node:fs/promises";import It from"node:path";import Br from"node:os";import{randomUUID as wb}from"node:crypto";import{execFile as gb}from"node:child_process";import{promisify as yb}from"node:util";import{chromium as bb}from"playwright-core";import Ja from"node:fs/promises";import mb from"node:path";var hb=0;async function Xt(t,e,{mode:n}={}){await Ja.mkdir(mb.dirname(t),{recursive:!0});let r=`${t}.${process.pid}.${++hb}.tmp`;await Ja.writeFile(r,JSON.stringify(e,null,2)+`
|
|
33
|
+
`,n!=null?{mode:n}:void 0),await Ja.rename(r,t)}var rd=yb(gb),od=process.env.BOSTRAT_STATE_DIR||It.join(process.env.HOME||Br.tmpdir(),".bostrat"),sd=It.join(od,"mocks.json"),kb=process.env.BOSTRAT_MOCK_ENGINE||"chromium",xb=process.env.BOSTRAT_CHROMIUM_BIN||"/usr/bin/chromium",Sb=process.env.BOSTRAT_FIREFOX_BIN||"firefox",vb=1300,Un=2,Ab=32,ad=1500,Hn=Number(process.env.BOSTRAT_MOCK_TIMEOUT_MS)||75e3,Tb=700*1024*1024;async function _b(){if(process.platform!=="darwin")return Br.freemem();try{let{stdout:t}=await rd("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:Br.freemem()}catch{return Br.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"}},en=null,za=null;async function ue(){return en||(za||=(async()=>{try{en=JSON.parse(await yt.readFile(sd,"utf8"))}catch{en={mocks:[]}}finally{za=null}return en.mocks||=[],en})(),za)}async function Yt(){await Xt(sd,en)}function qt(t,e,n={}){return O({id:t.trackId,cwd:t.cwd},{type:"mock.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box||null,provider:t.agent||null,payload:{ts:Date.now(),change:e,mockId:t.id,name:t.name,...t.group?{group:t.group}:{},...n}}).catch(()=>{})}var Zo=t=>It.join(t,".bostrat","mocks");function Lt(t,e){let n=Zo(t.cwd);return{html:It.join(n,`${t.id}.v${e}.html`),png:It.join(n,`${t.id}.v${e}.png`)}}function nn(t,e=null){let n=t.versions||[];return e!=null?n.find(r=>r.v===Number(e))||null:n.length?n[n.length-1]:null}function Ya(t){return t.length<24||t.readUInt32BE(12)!==1229472850?null:{width:t.readUInt32BE(16),height:t.readUInt32BE(20)}}var Eb=Buffer.from([137,80,78,71,13,10,26,10]);function id(t){return Buffer.isBuffer(t)&&t.length>24&&t.subarray(0,8).equals(Eb)}async function Ib(t){let e=Buffer.alloc(24),n=await yt.open(t,"r");try{await n.read(e,0,24,0)}finally{await n.close()}return e}function Rb({imgW:t,imgH:e,bbox:n,pad:r,align:o=1}){if(!n||!(t>0)||!(e>0))return null;let s=n.right-n.left+1,a=n.bottom-n.top+1;if(s<=0||a<=0||s>=t*.9&&a>=e*.9)return null;let i=s+2*r,c=a+2*r;return i+=(o-i%o)%o,c+=(o-c%o)%o,{sx:n.left,sy:n.top,sw:s,sh:a,outW:i,outH:c,dx:Math.floor((i-s)/2),dy:Math.floor((c-a)/2)}}async function $b(t,e){return t.evaluate(async n=>{let r=new Image;await new Promise((m,h)=>{r.onload=m,r.onerror=()=>h(new Error("decode failed")),r.src=`data:image/png;base64,${n}`});let o=r.naturalWidth,s=r.naturalHeight;if(!o||!s||o*s>24e6)return null;let a=document.createElement("canvas");a.width=o,a.height=s;let i=a.getContext("2d",{willReadFrequently:!0});i.drawImage(r,0,0);let c=i.getImageData(0,0,o,s).data,l=[c[0],c[1],c[2]],u=o,d=s,f=-1,p=-1;for(let m=0;m<s;m++)for(let h=m*o*4,b=0;b<o;b++,h+=4)Math.abs(c[h]-l[0])+Math.abs(c[h+1]-l[1])+Math.abs(c[h+2]-l[2])>24&&(b<u&&(u=b),b>f&&(f=b),m<d&&(d=m),p=m);return f<0?null:{imgW:o,imgH:s,bg:l,bbox:{left:u,top:d,right:f,bottom:p}}},e.toString("base64"))}async function Ob(t,e,{bg:n,crop:r}){let o=await t.evaluate(async({b64:s,bg:a,crop:i})=>{let c=new Image;await new Promise((d,f)=>{c.onload=d,c.onerror=()=>f(new Error("decode failed")),c.src=`data:image/png;base64,${s}`});let l=document.createElement("canvas");l.width=i.outW,l.height=i.outH;let u=l.getContext("2d");return u.fillStyle=`rgb(${a[0]},${a[1]},${a[2]})`,u.fillRect(0,0,i.outW,i.outH),u.drawImage(c,i.sx,i.sy,i.sw,i.sh,i.dx,i.dy,i.sw,i.sh),l.toDataURL("image/png").split(",")[1]},{b64:e.toString("base64"),bg:n,crop:r});return Buffer.from(o,"base64")}async function Cb({htmlPath:t,outPath:e,width:n}){let r=Date.now(),o;try{o=await bb.launch({executablePath:xb,args:["--disable-gpu"],timeout:3e4})}catch(s){throw new Wt("chromium failed to launch",s?.message||"")}try{let a=await(await o.newContext({viewport:{width:n,height:800},deviceScaleFactor:Un})).newPage();a.setDefaultTimeout(Hn),await a.goto(`file://${t}`,{waitUntil:"load",timeout:Hn}),await a.waitForTimeout(250);let i;for(let l=1;;l++)try{i=await a.screenshot({fullPage:!0,timeout:Hn});break}catch(u){if(l>=3||!/captureScreenshot|Unable to capture/i.test(u?.message||""))throw u;await a.waitForTimeout(500)}try{let l=await $b(a,i),u=l&&Rb({...l,pad:Ab*Un,align:Un});u&&(i=await Ob(a,i,{bg:l.bg,crop:u}))}catch{}if(i.length<ad)throw new Wt(`capture produced a ${i.length}-byte image`);let c=Ya(i);if(!c)throw new Wt("output is not a PNG");return await yt.writeFile(e,i),{width:Math.round(c.width/Un),height:Math.round(c.height/Un),dpr:Un,renderMs:Date.now()-r}}catch(s){throw s instanceof Wt?s:new Wt(/timeout/i.test(s?.message||"")?`render timed out after ${Hn}ms`:"chromium render failed",s?.message||"")}finally{await o.close().catch(()=>{})}}async function nd({htmlPath:t,outPath:e,width:n}){let r=await yt.mkdtemp(It.join(Br.tmpdir(),"bostrat-mock-profile-")),o=Date.now(),s="";try{try{s=(await rd(Sb,["--headless","--no-remote","--profile",r,`--window-size=${n}`,"--screenshot",e,`file://${t}`],{env:{...process.env,MOZ_HEADLESS:"1"},timeout:Hn,killSignal:"SIGKILL",maxBuffer:4194304})).stderr||""}catch(c){throw s=c?.stderr||"",new Wt(c?.killed?`render timed out after ${Hn}ms`:"firefox exited non-zero",s)}let a=await yt.stat(e).catch(()=>null);if(!a||a.size<ad)throw new Wt(`render produced ${a?`a ${a.size}-byte file`:"no file"} (silent no-op \u2014 another firefox holding the profile?)`,s);let i=Ya(await Ib(e));if(!i)throw new Wt("output is not a PNG",s);return{width:i.width,height:i.height,dpr:1,renderMs:Date.now()-o}}finally{await yt.rm(r,{recursive:!0,force:!0}).catch(()=>{})}}async function Mb(t){if(kb==="firefox")return nd(t);try{return await Cb(t)}catch(e){if(/failed to launch/i.test(e?.message||""))return nd(t);throw e}}var Pb=Mb;var Xa=Promise.resolve();function cd(t){return Xa=Xa.then(t,t),Xa}async function ld(t,e){let n=await ue(),r=n.mocks.find(o=>o.id===t);if(r){r.state="rendering",await Yt(),await qt(r,"rendering",{version:e});try{let o=await _b();if(o<Tb)throw new Wt(`box is low on memory (${Math.round(o/1e6)} MB available) \u2014 not spawning a browser`);let s=Lt(r,e),a=await Pb({htmlPath:s.html,outPath:s.png,width:r.width});if(!n.mocks.some(i=>i.id===t)){await yt.rm(s.png,{force:!0}).catch(()=>{});return}ud(r,{v:e,source:"html",htmlRel:It.relative(r.cwd,s.html),pngRel:It.relative(r.cwd,s.png),width:a.width,height:a.height,dpr:a.dpr,renderMs:a.renderMs,createdAt:Date.now()}),await Yt(),await qt(r,"ready",{version:e,width:a.width,height:a.height,dpr:a.dpr})}catch(o){if(!n.mocks.some(s=>s.id===t))return;r.state="error",r.error=o?.message||String(o),await Yt(),await qt(r,"error",{version:e,error:r.error})}}}function ud(t,e){t.versions=(t.versions||[]).filter(r=>r.v!==e.v).concat(e),t.versions.sort((r,o)=>r.v-o.v);let n=t.versions[t.versions.length-1];t.readyVersion=n.v,t.height=n.height,t.dpr=n.dpr,t.renderMs=n.renderMs??null,n.v===t.version&&(t.state="ready",t.error=null)}function Nb({trackId:t,name:e,cwd:n,width:r}){if(!t)throw new Error("trackId required");if(typeof e!="string"||!e.trim())throw new Error("name (string) required");if(!n)throw new Error("cwd required");let o=r==null?vb:Number(r);if(!Number.isInteger(o)||o<320||o>4e3)throw new Error("invalid width (320-4000)");return o}async function Qo({trackId:t,group:e}={}){let n=await ue(),r=t?n.mocks.filter(o=>o.trackId===t):n.mocks;return e&&(r=r.filter(o=>o.group===e)),r.map(o=>({...o}))}async function Ee(t){let n=(await ue()).mocks.find(r=>r.id===t);return n?{...n}:null}async function dd({trackId:t,name:e,html:n,image:r,cwd:o,width:s,group:a,box:i,agent:c,prompt:l}){let u=Nb({trackId:t,name:e,cwd:o,width:s}),d=typeof n=="string"&&n.trim();if(!d&&!r)throw new Error("html (string) or image (png) required");if(r&&!id(r))throw new Error("image must be a PNG");let f=await ue(),p={id:wb().slice(0,8),trackId:t,box:i||null,agent:c||null,name:e.trim(),prompt:l?String(l).slice(0,500):null,group:a?String(a).trim():null,cwd:o,width:u,height:null,dpr:null,state:"queued",source:d?"html":"image",version:1,readyVersion:null,versions:[],error:null,renderMs:null,createdAt:Date.now(),updatedAt:Date.now()};return await yt.mkdir(Zo(o),{recursive:!0}),f.mocks.push(p),d?(await yt.writeFile(Lt(p,1).html,n),await Yt(),await qt(p,"created"),cd(()=>ld(p.id,1))):(await pd(p,1,r),await Yt(),await qt(p,"created"),await qt(p,"ready",{version:1,width:p.width,height:p.height,dpr:p.dpr})),{...p}}async function fd(t,{html:e,image:n,width:r}={}){let s=(await ue()).mocks.find(c=>c.id===t);if(!s)throw new Error("mock not found");let a=typeof e=="string"&&e.trim();if(!a&&!n)throw new Error("html (string) or image (png) required");if(n&&!id(n))throw new Error("image must be a PNG");if(r!=null){let c=Number(r);if(!Number.isInteger(c)||c<320||c>4e3)throw new Error("invalid width (320-4000)");s.width=c}let i=s.version+1;return s.version=i,s.updatedAt=Date.now(),s.source=a?"html":"image",await yt.mkdir(Zo(s.cwd),{recursive:!0}),a?(s.state="queued",await yt.writeFile(Lt(s,i).html,e),await Yt(),await qt(s,"updated",{version:i}),cd(()=>ld(s.id,i))):(await pd(s,i,n),await Yt(),await qt(s,"updated",{version:i}),await qt(s,"ready",{version:i,width:s.width,height:s.height,dpr:s.dpr})),{...s}}async function pd(t,e,n){let r=Ya(n);if(!r)throw new Error("image must be a PNG");let o=Lt(t,e);await yt.writeFile(o.png,n),ud(t,{v:e,source:"image",htmlRel:null,pngRel:It.relative(t.cwd,o.png),width:r.width,height:r.height,dpr:1,renderMs:null,createdAt:Date.now()}),t.width=r.width}async function md(t){let e=await ue(),n=e.mocks.find(o=>o.id===t);if(!n)throw new Error("mock not found");if(n.readyVersion==null)throw new Error(`mock is ${n.state} \u2014 nothing to pick yet`);let r=n.group?e.mocks.filter(o=>o.trackId===n.trackId&&o.group===n.group&&o.id!==t&&o.readyVersion!=null).map(o=>({mockId:o.id,name:o.name})):[];return await qt(n,"picked",{version:n.readyVersion,siblings:r}),{mock:{...n},siblings:r}}async function hd(t){let e=await ue(),n=e.mocks.findIndex(o=>o.id===t);if(n===-1)throw new Error("mock not found");let r=e.mocks[n];e.mocks.splice(n,1);for(let o=1;o<=r.version;o++){let s=Lt(r,o);await yt.rm(s.html,{force:!0}).catch(()=>{}),await yt.rm(s.png,{force:!0}).catch(()=>{})}return await Yt(),await qt(r,"deleted"),{ok:!0,id:t,name:r.name}}async function wd(t){let n=(await ue()).mocks.filter(i=>i.trackId===t.id);if(!n.length)return 0;let r=It.join(od,"mock-archive",t.id),o=Zo(t.cwd),s=It.join(r,".bostrat","mocks");await yt.mkdir(s,{recursive:!0});let a=await yt.readdir(o).catch(()=>[]);for(let i of a)await yt.copyFile(It.join(o,i),It.join(s,i));for(let i of n){for(let c of i.versions||[])for(let l of[c.htmlRel,c.pngRel]){if(!l)continue;if(!(await yt.stat(It.join(r,l)).catch(()=>null))?.isFile())throw new Error(`mock evacuation incomplete: ${l} missing from the archive`)}i.cwd=r,i.trackDeletedAt=Date.now(),i.trackName=t.name||t.id}return await Yt(),n.length}async function gd(t){let e=await ue(),n=e.mocks.filter(r=>r.trackId===t);return n.length&&(e.mocks=e.mocks.filter(r=>r.trackId!==t),await Yt()),n.length}import jr from"node:fs/promises";import Rt from"node:path";import Bb from"node:os";import Db from"node:net";import{randomUUID as jb}from"node:crypto";import{execFile as Lb}from"node:child_process";import{promisify as Fb}from"node:util";var Qt=Fb(Lb),Ub=process.env.BOSTRAT_STATE_DIR||Rt.join(process.env.HOME||Bb.tmpdir(),".bostrat"),Sd=Rt.join(Ub,"views.json"),[yd,bd]=(process.env.BOSTRAT_VIEW_PORTS||"4300-4349").split("-").map(t=>Number(t)||0),Ie=Number(process.env.BOSTRAT_VIEW_MAX)||3,Hb="bostrat-view-",Wn=t=>`${Hb}${t}`,kd=new Set(["bostrat","www","api","views","app"]),ts=t=>Object.assign(new Error(t),{status:400}),Wb=new Set(["npm","pnpm","yarn","bun"]),ti="package.json",qb=/[|;&`]|\$\(/,Gb=new Set(["sh","bash","zsh","dash","env","exec","nohup","time"]),Kb=/(^|\s)(-C|--cwd|--manifest-path|--dir)(=|\s)/,vd=t=>String(t).trim().split(/\s+/).filter(Boolean),Vb=/^[A-Za-z_][A-Za-z0-9_]*=/;function Jb(t){for(let e of vd(t))if(!Vb.test(e))return e;return null}function Ad(t){let e=vd(t),n=e.indexOf("--");return n===-1?e:e.slice(0,n)}function zb(t,e){let n=Ad(e);for(let r=0;r<n.length;r++){let o=/^--prefix=(.+)$/.exec(n[r]);if(o)return Rt.resolve(t,o[1]);if(n[r]==="--prefix"&&n[r+1])return Rt.resolve(t,n[r+1])}return t}var Xb=t=>jr.stat(t).then(e=>e.isDirectory(),()=>!1),Td=t=>jr.stat(t).then(e=>e.isFile(),()=>!1),es=t=>jr.realpath(t).catch(()=>Rt.resolve(t));async function Yb(t,e){if(Rt.resolve(t)===Rt.resolve(e))return!0;let[n,r]=await Promise.all([es(t),es(e)]);return n===r}async function Zb(t){let n=(await jr.readdir(t,{withFileTypes:!0}).catch(()=>[])).filter(o=>o.isDirectory()&&!o.name.startsWith(".")&&o.name!=="node_modules");return(await Promise.all(n.map(async o=>await Td(Rt.join(t,o.name,ti))?`./${o.name}`:null))).filter(Boolean)}async function Qb(t,e){let[n,r]=await Promise.all([es(t),es(e)]),o=Rt.relative(n,r);if(o&&(o.startsWith("..")||Rt.isAbsolute(o)))throw ts(`serve directory escapes the Track workspace: ${e}`)}async function tk(t,e){if(!await Xb(t))throw ts(`serve directory does not exist: ${t}`);let n=Jb(e);if(!n)return;if(!n.includes("/")&&!await Ra(n,{loginShell:!0,failOpen:!0}))throw ts(`\`${n}\` is not on the box's PATH \u2014 install it in the box first`);if(qb.test(e)||Gb.has(n)||!Wb.has(n)||!Ad(e).includes("run")||Kb.test(e))return;let r=zb(t,e);if(await Td(Rt.join(r,ti)))return;let o=await Zb(r),s=o.length?` \u2014 found one in ${o.join(", ")}; run bostrat-view from there`:"";throw ts(`no ${ti} in ${r}${s}`)}var Zt=null,Za=null;async function pe(){return Zt||(Za||=(async()=>{try{Zt=JSON.parse(await jr.readFile(Sd,"utf8"))}catch{Zt={views:[],adoptWatermarks:{}}}finally{Za=null}return Zt.views||=[],Zt.adoptWatermarks||={},Zt})(),Za)}async function te(){await Xt(Sd,Zt)}function fe(t,e,n={}){return O({id:t.trackId,cwd:t.cwd},{type:"view.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box||null,payload:{ts:Date.now(),change:e,viewId:t.id,name:t.name,slug:t.slug,port:t.port,...n}}).catch(()=>{})}var Dr=new Map,ri=Number(process.env.BOSTRAT_VIEW_JOB_TIMEOUT)||3e5,_d=12e3;function oi(t){return Dr.get(t)||null}function Ed(t,{name:e,box:n}={}){let r=Dr.get(t);if(r&&!r.finishedAt)throw new Error("a view is already being spun up for this Track");let o={trackId:t,name:e||"preview",box:n||null,step:"spinning-up",startedAt:Date.now(),finishedAt:null,viewId:null,error:null};return Dr.set(t,o),o}function ei(t,e){let n=Dr.get(t);n&&!n.finishedAt&&!n.viewId&&(n.viewId=e,n.step="booting")}function ns(t,{error:e=null}={}){let n=Dr.get(t);return!n||n.finishedAt?null:(n.finishedAt=Date.now(),e&&(n.error=e),n)}function Id(t){let e=[];for(let n of t?.events||[])n.type!=="view-spinup"||!n.box||!n.startedAt||!n.endedAt||e.push({box:n.box,from:n.startedAt,to:n.endedAt});return e}function ni(t){return String(t||"").trim().toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,40).replace(/-+$/g,"")||"view"}function ek(t,e){let n=ni(e),r=new Set(t.views.map(o=>o.slug));if(!r.has(n)&&!kd.has(n))return n;for(let o=2;;o++){let s=`${n}-${o}`;if(!r.has(s)&&!kd.has(s))return s}}function Lr(t,{timeoutMs:e=1200}={}){return new Promise(n=>{let r=Db.connect({port:t,host:"127.0.0.1"}),o=s=>{r.destroy(),n(s)};r.setTimeout(e,()=>o(!1)),r.once("connect",()=>o(!0)),r.once("error",()=>o(!1))})}async function si(t){if(process.platform!=="darwin"){await Qt("fuser",["-k",`${t}/tcp`]).catch(()=>{});return}let e="";try{({stdout:e}=await Qt("lsof",["-t","-sTCP:LISTEN","-i",`tcp:${t}`]))}catch{return}for(let n of e.split(/\s+/).filter(Boolean)){let r=Number(n);if(!(!r||r===process.pid))try{process.kill(r,"SIGKILL")}catch{}}}async function nk(t,e){let n=new Set(t.views.map(r=>r.port));if(e){let r=Number(e);if(!Number.isInteger(r)||r<1024||r>65535)throw new Error("invalid port");if(n.has(r))throw new Error(`port ${r} is already owned by a view`);return r}for(let r=yd;r<=bd;r++)if(!n.has(r)&&!await Lr(r,{timeoutMs:250}))return r;throw new Error(`no free view port in ${yd}-${bd}`)}async function rk(t){try{let{stdout:e}=await Qt(C,P("list-sessions","-F","#{session_name}"));return e.split(`
|
|
37
|
+
`).some(n=>n.trim()===Wn(t))}catch{return!1}}async function Fr(t){try{await Qt(C,P("kill-session","-t",Wn(t)))}catch{}}async function ai(t){if(!await $r())throw new Error("host tmux server not running and auto-start failed \u2014 cannot spawn a view server");let e=Wn(t.slug),n=t.serveCwd??t.cwd,r=Rt.dirname(process.execPath);await Qt(C,P("new-session","-d","-s",e,"-c",n)),await Qt(C,P("send-keys","-t",e,"-l",`export PORT=${t.port} PATH="${r}:$PATH"`)),await Qt(C,P("send-keys","-t",e,"Enter")),await Qt(C,P("send-keys","-t",e,"-l",t.cmd)),await Qt(C,P("send-keys","-t",e,"Enter"))}async function xd(t,e=12){try{let{stdout:n}=await Qt(C,P("capture-pane","-t",Wn(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 de=new Map,ok=12e4;async function sk(t){if(t.state==="stopped"||t.state==="error")return!1;let e=t.origin!=="detected",n=e?await rk(t.slug):!0;if(e&&!n){let s=t.state==="starting";return t.state=s?"error":"stopped",s&&(t.error=await xd(t.slug)||"server exited before listening"),de.delete(t.id),await fe(t,t.state,t.error?{error:t.error}:{}),!0}if(await Lr(t.port))return de.delete(t.id),t.lastHealthyAt=Date.now(),t.state!=="live"?(t.state="live",t.error=null,await fe(t,"live"),!0):!1;if(t.state==="starting")return Date.now()-t.createdAt>ok?(t.state="error",t.error=await xd(t.slug)||"never started listening",await fe(t,"error",{error:t.error}),!0):!1;let o=(de.get(t.id)||0)+1;return de.set(t.id,o),o>=3&&t.state==="live"?(t.state="unhealthy",!0):!1}var Qa=!1;async function rs(){if(!Qa){Qa=!0;try{let t=await pe(),e=!1;for(let n of t.views)await sk(n)&&(e=!0);e&&await te()}finally{Qa=!1}}}async function os({trackId:t}={}){await rs();let e=await pe();return(t?e.views.filter(r=>r.trackId===t):e.views).map(r=>({...r}))}async function Rd(t){let n=(await pe()).views.find(r=>r.id===t);return n?{...n}:null}function ii(t){return Zt&&Zt.views.find(e=>e.slug===t)||null}async function ci({trackId:t,name:e,cmd:n,cwd:r,serveCwd:o,port:s,box:a,origin:i="agent",branch:c=null}){if(!t)throw new Error("trackId required");if(typeof e!="string"||!e.trim())throw new Error("name (string) required");if(i!=="detected"&&(typeof n!="string"||!n.trim()))throw new Error("cmd (string) required");if(!r)throw new Error("cwd required");let l=o!=null,u=Rt.resolve(r,o??"."),d=i==="detected"?null:async()=>{await Qb(r,u),await tk(u,n)},f=await pe();if(i!=="detected"){let h=ni(e),b=String(n).trim(),x=f.views.find(v=>v.trackId===t&&(v.slug===h||ni(v.name)===h||v.cmd===b&&Rt.resolve(v.serveCwd??v.cwd)===u));if(x)return ak(f,x,{cmd:b,box:a,branch:c,serveCwd:l?u:void 0,validate:d})}if(d&&await d(),f.views.filter(h=>h.state==="live"||h.state==="starting").length>=Ie)throw new Error(`view cap reached (${Ie} live) \u2014 stop one first`);let m={id:jb().slice(0,8),trackId:t,box:a||null,name:e.trim(),slug:ek(f,e),cmd:n?String(n).trim():null,cwd:r,serveCwd:u,branch:c,port:await nk(f,s),origin:i,state:"starting",error:null,createdAt:Date.now(),lastHealthyAt:null};return i!=="detected"&&await ai(m),f.views.push(m),await te(),await fe(m,"created",{origin:i}),ei(t,m.id),i==="detected"&&await Lr(m.port)&&(m.state="live",m.lastHealthyAt=Date.now(),await te(),await fe(m,"live")),{...m}}async function ak(t,e,{cmd:n,box:r,branch:o,serveCwd:s,validate:a}){r&&(e.box=r),o&&(e.branch=o);let i=e.cmd!==n,c=s!==void 0&&!await Yb(e.serveCwd??e.cwd,s);if((e.state==="live"||e.state==="starting")&&!i&&!c&&await Lr(e.port))return await te(),ei(e.trackId,e.id),{...e,reused:!0};if(a&&await a(),t.views.filter(d=>d.id!==e.id&&(d.state==="live"||d.state==="starting")).length>=Ie)throw new Error(`view cap reached (${Ie} live) \u2014 stop one first`);return e.origin==="detected"?(await si(e.port),e.origin="agent"):await Fr(e.slug),e.cmd=n,s!==void 0&&(e.serveCwd=s),e.state="starting",e.error=null,e.createdAt=Date.now(),de.delete(e.id),await ai(e),await te(),await fe(e,"created",{restarted:!0,reused:!0}),ei(e.trackId,e.id),{...e,reused:!0}}async function $d(t){let n=(await pe()).views.find(r=>r.id===t);if(!n)throw new Error("view not found");return n.origin==="detected"?await si(n.port):await Fr(n.slug),n.state!=="stopped"&&(n.state="stopped",de.delete(n.id),await te(),await fe(n,"stopped")),{...n}}async function Od(t){let e=await pe(),n=e.views.find(o=>o.id===t);if(!n)throw new Error("view not found");if(!n.cmd)throw new Error("a detected view has no known command \u2014 recreate it as a managed view");if(e.views.filter(o=>o.id!==t&&(o.state==="live"||o.state==="starting")).length>=Ie)throw new Error(`view cap reached (${Ie} live) \u2014 stop one first`);return await Fr(n.slug),n.state="starting",n.error=null,n.createdAt=Date.now(),de.delete(n.id),await ai(n),await te(),await fe(n,"created",{restarted:!0}),{...n}}async function Cd(t){let e=await pe(),n=e.views.findIndex(o=>o.id===t);if(n===-1)throw new Error("view not found");let r=e.views[n];return r.origin==="detected"?await si(r.port):await Fr(r.slug),e.views.splice(n,1),de.delete(r.id),await te(),await fe(r,"deleted"),{ok:!0,id:t,slug:r.slug}}async function Md(t){let e=await pe(),n=e.views.filter(r=>r.trackId===t);for(let r of n)r.origin!=="detected"&&await Fr(r.slug),de.delete(r.id);return n.length&&(e.views=e.views.filter(r=>r.trackId!==t),await te()),n.length}var ik=/https?:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0)(?::(\d{2,5}))?(?:\/|\b)/g;async function Pd(t,e){if(!t?.id||!Array.isArray(e)||!e.length)return[];let n=await pe(),r=n.adoptWatermarks[t.id]??null,o=e.filter(c=>c.role==="assistant"&&c.ts!=null&&(r==null?!1:c.ts>r)),s=e.reduce((c,l)=>l.ts>c?l.ts:c,r??0);if(n.adoptWatermarks[t.id]!==s&&(n.adoptWatermarks[t.id]=s,await te()),!o.length)return[];let a=new Set;for(let c of o)for(let l of String(c.text||"").matchAll(ik)){let u=Number(l[1]||80);u>=1024&&u<=65535&&a.add(u)}if(!a.size)return[];let i=[];for(let c of a)if(c!==Number(process.env.PORT||3362)&&!n.views.some(l=>l.port===c)&&await Lr(c))try{i.push(await ci({trackId:t.id,name:`${t.name} preview`,cmd:null,cwd:t.cwd,port:c,origin:"detected",branch:t.workspace?.branch??null}))}catch{}return i}import on from"node:fs/promises";import Re from"node:path";import ck from"node:os";import{randomUUID as lk}from"node:crypto";var Gn=process.env.BOSTRAT_STATE_DIR||Re.join(process.env.HOME||ck.tmpdir(),".bostrat"),Bd=Re.join(Gn,"runbooks.json"),ui=Re.join(Gn,"runbooks"),Nd=256*1024,uk=[{label:"Anthropic API key",re:/\bsk-ant-[A-Za-z0-9_-]{20,}/},{label:"OpenAI-style API key",re:/\bsk-(?:proj-|svcacct-)?[A-Za-z0-9_-]{32,}\b/},{label:"Stripe secret key",re:/\bsk_(?:live|test)_[A-Za-z0-9]{16,}/},{label:"GitHub token",re:/\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{36,}|\bgithub_pat_[A-Za-z0-9_]{22,}/},{label:"AWS access key id",re:/\bAKIA[0-9A-Z]{16}\b/},{label:"Slack token",re:/\bxox[baprs]-[A-Za-z0-9-]{10,}/},{label:"private key block",re:/-----BEGIN [A-Z ]*PRIVATE KEY-----/},{label:"JWT",re:/\beyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/}];function di(t){return uk.filter(({re:e})=>e.test(t)).map(({label:e})=>e)}var rn=null,li=null;async function $e(){return rn||(li||=(async()=>{try{rn=JSON.parse(await on.readFile(Bd,"utf8"))}catch{rn={runbooks:[]}}finally{li=null}return rn.runbooks||=[],rn})(),li)}async function Kn(){await Xt(Bd,rn)}function Vn(t,e,n={}){return O({id:t.trackId,cwd:t.cwd},{type:"runbook.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box||null,provider:t.agent||null,payload:{ts:Date.now(),change:e,runbookId:t.id,name:t.name,status:t.status,...n}}).catch(()=>{})}function qn(t,e){return Re.join(ui,`${t.id}.v${e}.md`)}function Dd(t,e=null){let n=t.versions||[];return e!=null?n.find(r=>r.v===Number(e))||null:n.length?n[n.length-1]:null}function jd(t){if(typeof t!="string"||!t.trim())throw new Error("md (markdown string) required");let e=Buffer.byteLength(t,"utf8");if(e>Nd)throw new Error(`runbook is ${Math.round(e/1024)} KB \u2014 over the ${Nd/1024} KB cap; a doc that size belongs in the repo, not a runbook`);let n=di(t);if(n.length)throw new Error(`runbook appears to contain a secret VALUE (${n.join(", ")}) \u2014 runbooks ship off-box; name the secret and where it goes, never its value. Replace it with a placeholder like <your key> and retry.`);return e}function Ld(t,e){if(t.trackDeletedAt)throw new Error(`runbook is orphaned (its Track was deleted) \u2014 cannot ${e} it`)}async function ss({trackId:t,status:e}={}){let n=await $e(),r=t?n.runbooks.filter(o=>o.trackId===t):n.runbooks;return e&&(r=r.filter(o=>o.status===e)),r.map(o=>({...o}))}async function fi(t){let n=(await $e()).runbooks.find(r=>r.id===t);return n?{...n}:null}async function Fd({trackId:t,trackName:e,name:n,md:r,cwd:o,box:s,agent:a}){if(!t)throw new Error("trackId required");if(typeof n!="string"||!n.trim())throw new Error("name (string) required");let i=jd(r),c=await $e(),l={id:lk().slice(0,8),trackId:t,trackName:e||t,box:s||null,agent:a||null,name:n.trim(),status:"open",version:1,versions:[],trackDeletedAt:null,cwd:o||null,createdAt:Date.now(),updatedAt:Date.now()};return await on.mkdir(ui,{recursive:!0}),await on.writeFile(qn(l,1),r),l.versions.push({v:1,mdRel:Re.relative(Gn,qn(l,1)),bytes:i,note:null,createdAt:Date.now()}),c.runbooks.push(l),await Kn(),await Vn(l,"created",{version:1}),{...l}}async function Ud(t,{md:e,note:n}={}){let o=(await $e()).runbooks.find(i=>i.id===t);if(!o)throw new Error("runbook not found");Ld(o,"update");let s=jd(e),a=o.version+1;return o.version=a,o.updatedAt=Date.now(),await on.mkdir(ui,{recursive:!0}),await on.writeFile(qn(o,a),e),o.versions.push({v:a,mdRel:Re.relative(Gn,qn(o,a)),bytes:s,note:n?String(n).slice(0,200):null,createdAt:Date.now()}),await Kn(),await Vn(o,"updated",{version:a,...n?{note:String(n).slice(0,200)}:{}}),{...o}}async function Hd(t,e){if(e!=="open"&&e!=="done")throw new Error('status must be "open" or "done"');let r=(await $e()).runbooks.find(o=>o.id===t);if(!r)throw new Error("runbook not found");return Ld(r,e==="done"?"complete":"reopen"),r.status===e?{...r}:(r.status=e,r.updatedAt=Date.now(),await Kn(),await Vn(r,e==="done"?"done":"reopened"),{...r})}async function Wd(t){let e=await $e(),n=e.runbooks.findIndex(o=>o.id===t);if(n===-1)throw new Error("runbook not found");let r=e.runbooks[n];e.runbooks.splice(n,1);for(let o of r.versions||[])await on.rm(Re.join(Gn,o.mdRel),{force:!0}).catch(()=>{});return await Kn(),r.trackDeletedAt||await Vn(r,"deleted"),{ok:!0,id:t,name:r.name}}async function qd(t){let n=(await $e()).runbooks.filter(r=>r.trackId===t&&!r.trackDeletedAt);for(let r of n)r.trackDeletedAt=Date.now(),await Vn(r,"orphaned"),r.cwd=null;return n.length&&await Kn(),n.length}async function Gd(t){let e=await $e(),n=e.runbooks.filter(r=>r.trackId===t);for(let r of n){await Vn(r,"deleted");for(let o of r.versions||[])await on.rm(Re.join(Gn,o.mdRel),{force:!0}).catch(()=>{})}return n.length&&(e.runbooks=e.runbooks.filter(r=>r.trackId!==t),await Kn()),n.length}import Wr from"node:fs/promises";import af from"node:path";import jk from"node:os";import{randomUUID as cf}from"node:crypto";import dk from"node:fs/promises";import Kd from"node:path";import fk from"node:os";var Vd=()=>Kd.join(process.env.BOSTRAT_STATE_DIR||Kd.join(process.env.HOME||fk.tmpdir(),".bostrat"),"integrations.json");async function sn(){try{return JSON.parse(await dk.readFile(Vd(),"utf8"))||{}}catch{return{}}}var as=["linked","prOpened","prMerged","create"],pi=["prompt","auto","off"],pk={create:["prompt","off"]},Jd=t=>pk[t]??pi;function is(t){let e=t.linear||{},n=e.transitions===!0?"auto":"prompt",r={};for(let o of as){let s=Jd(o),a=s.includes(n)?n:"prompt",i=t.ticketFlow?.[o]??e.ticketFlow?.[o];r[o]=s.includes(i)?i:a}return r}async function an(){let t=await sn(),e=process.env.BOSTRAT_LINEAR_API_KEY||t.linear?.apiKey,n=process.env.BOSTRAT_LINEAR_DEFAULT_TEAM||t.linear?.defaultTeam||null;return e?{apiKey:e,ticketFlow:is(t),defaultTeam:n}:null}async function cs(){let t=await sn(),e=t.jira||{},n=process.env.BOSTRAT_JIRA_BASE_URL||e.baseUrl,r=process.env.BOSTRAT_JIRA_EMAIL||e.email,o=process.env.BOSTRAT_JIRA_API_TOKEN||e.apiToken;return!n||!r||!o?null:{baseUrl:String(n).replace(/\/$/,""),email:r,apiToken:o,tokenExpiresAt:e.tokenExpiresAt??null,ticketFlow:is(t)}}async function mi(){return is(await sn())}async function zd(t){let e=Object.entries(t||{});if(!e.length)throw new Error(`nothing to set \u2014 phases: ${as.join(", ")}`);for(let[r,o]of e){if(!as.includes(r))throw new Error(`unknown phase "${r}" \u2014 configurable phases: ${as.join(", ")}`);let s=Jd(r);if(!s.includes(o))throw new Error(s.length<pi.length?`mode "${o}" not allowed for "${r}" \u2014 ${r} is always confirmed (modes: ${s.join(", ")})`:`unknown mode "${o}" \u2014 modes: ${pi.join(", ")}`)}let n=await sn();return n.ticketFlow={...is(n),...Object.fromEntries(e)},n.linear&&(delete n.linear.ticketFlow,delete n.linear.transitions),await Xt(Vd(),n,{mode:384}),n.ticketFlow}async function Ur(){let t=(await sn()).deepgram||{},e=process.env.DEEPGRAM_API_KEY||t.apiKey;return e?{apiKey:e}:null}async function Xd(){let t=(await sn()).cedar||{},e=process.env.BOSTRAT_CEDAR_INGEST_KEY||t.ingestKey,n=process.env.BOSTRAT_CEDAR_URL||t.url||"https://cedar-api.fly.dev";return e?{url:n,ingestKey:e}:null}async function cn(){let t=(await sn()).slack||{},e=process.env.BOSTRAT_SLACK_BOT_TOKEN||t.botToken,n=process.env.BOSTRAT_SLACK_APP_TOKEN||t.appToken;return!e||!n?null:{botToken:e,appToken:n,channel:process.env.BOSTRAT_SLACK_CHANNEL||t.channel||null,maxConcurrent:Number.isInteger(t.maxConcurrent)&&t.maxConcurrent>0?t.maxConcurrent:2}}var wi={};Jl(wi,{createComment:()=>Sk,findIssueUrl:()=>gk,issueStates:()=>Tk,mdToAdf:()=>Qd,moveIssue:()=>_k,parseIssueRef:()=>wk,resolveIssue:()=>yk,uploadImage:()=>vk,upsertCard:()=>bk,whoami:()=>hi});var Jn=t=>String(t.baseUrl||"").replace(/\/$/,""),Zd=t=>`Basic ${Buffer.from(`${t.email}:${t.apiToken}`).toString("base64")}`;async function ln(t,e,n,r){let o=await fetch(`${Jn(t)}${n}`,{method:e,headers:{authorization:Zd(t),accept:"application/json",...r!==void 0?{"content-type":"application/json"}:{}},body:r!==void 0?JSON.stringify(r):void 0}),s=await o.text(),a=null;try{a=s?JSON.parse(s):null}catch{a=null}if(!o.ok){let i=a?.errorMessages?.[0]??Object.values(a?.errors??{})[0]??`HTTP ${o.status}`,c=new Error(`jira: ${i}`);throw c.status=o.status,c}return a}var mk=/\/browse\/([A-Za-z][A-Za-z0-9]{0,9}-\d+)/,hk=/^([A-Z][A-Z0-9]{0,9}-\d+)$/;function wk(t){let e=String(t||"").trim();if(/^https?:\/\//i.test(e)){let r=e.match(mk);return r?r[1].toUpperCase():null}let n=e.match(hk);return n?n[1]:null}function gk(t,e=null){let n=null;try{n=e?.baseUrl?new URL(e.baseUrl).host:null}catch{n=null}let r=/https?:\/\/[^\s)]+\/browse\/[A-Za-z][A-Za-z0-9]{0,9}-\d+[^\s)]*/g;for(let o of String(t||"").matchAll(r))try{let s=new URL(o[0]).host;if(s===n||/\.atlassian\.net$/i.test(s))return o[0]}catch{}return null}async function yk(t,e){let n;try{n=await ln(t,"GET",`/rest/api/3/issue/${encodeURIComponent(e)}?fields=summary,status`)}catch(r){throw r.status===404?new Error(`jira: issue "${e}" not found`):r}return{issueId:n.id,key:n.key,title:n.fields?.summary??null,url:`${Jn(t)}/browse/${n.key}`,state:n.fields?.status?.name??null}}async function bk(t,e,{url:n,title:r,subtitle:o}){await ln(t,"POST",`/rest/api/3/issue/${encodeURIComponent(e.key)}/remotelink`,{globalId:n,application:{type:"com.bostrat.tracks",name:"Bostrat"},object:{url:n,title:r,summary:o,icon:{title:"Bostrat"}}})}var kk=/(!\[[^\]]*\]\([^)\s]+\)|\*\*[^*\n]+\*\*|`[^`\n]+`|_[^_\n]+_|https?:\/\/[^\s)]+)/g;function xk(t){let e=[],n=0;for(let r of t.matchAll(kk)){r.index>n&&e.push({type:"text",text:t.slice(n,r.index)});let o=r[0];if(o.startsWith("![")){let[,s,a]=o.match(/^!\[([^\]]*)\]\(([^)\s]+)\)$/);e.push({type:"text",text:s||a,marks:[{type:"link",attrs:{href:a}}]})}else o.startsWith("**")?e.push({type:"text",text:o.slice(2,-2),marks:[{type:"strong"}]}):o.startsWith("`")?e.push({type:"text",text:o.slice(1,-1),marks:[{type:"code"}]}):o.startsWith("_")?e.push({type:"text",text:o.slice(1,-1),marks:[{type:"em"}]}):e.push({type:"text",text:o,marks:[{type:"link",attrs:{href:o}}]});n=r.index+o.length}return n<t.length&&e.push({type:"text",text:t.slice(n)}),e}function Qd(t){let e=String(t??"").split(/\n{2,}/).map(n=>n.trim()).filter(Boolean).map(n=>{let r=[];return n.split(`
|
|
40
|
+
`).forEach((o,s)=>{s>0&&r.push({type:"hardBreak"}),r.push(...xk(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 Sk(t,e,n){let r=await ln(t,"POST",`/rest/api/3/issue/${encodeURIComponent(e.key)}/comment`,{body:Qd(n)});return r?.id?`${Jn(t)}/browse/${e.key}?focusedCommentId=${r.id}`:null}async function hi(t){let e=await ln(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 vk(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:Zd(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 Ak={new:"unstarted",indeterminate:"started",done:"completed"},Yd=t=>Ak[t?.key]??"started";async function Tk(t,e){let n=encodeURIComponent(e.key),[r,o]=await Promise.all([ln(t,"GET",`/rest/api/3/issue/${n}?fields=status`),ln(t,"GET",`/rest/api/3/issue/${n}/transitions`)]),s=r?.fields?.status??null,a=s?{id:`status:${s.id}`,name:s.name,type:Yd(s.statusCategory)}:null,i=(o?.transitions??[]).map((c,l)=>({id:c.id,name:c.to?.name??c.name,type:Yd(c.to?.statusCategory),position:l}));return{current:a,states:i}}async function _k(t,e,n){await ln(t,"POST",`/rest/api/3/issue/${encodeURIComponent(e.key)}/transitions`,{transition:{id:String(n)}})}var ki={};Jl(ki,{createComment:()=>Pk,createIssue:()=>bi,createProject:()=>yi,findIssueUrl:()=>Ok,issueStates:()=>Bk,moveIssue:()=>Dk,parseIssueRef:()=>$k,resolveIssue:()=>Ck,teams:()=>zn,uploadImage:()=>Nk,upsertCard:()=>Mk,whoami:()=>gi});var Ek=()=>process.env.BOSTRAT_LINEAR_API_URL||"https://api.linear.app/graphql";async function ee(t,e,n={}){let r=await fetch(Ek(),{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 Ik=/linear\.app\/[^/\s]+\/issue\/([A-Za-z][A-Za-z0-9]{0,9}-\d+)/,Rk=/^([A-Z][A-Z0-9]{0,9}-\d+)$/;function $k(t){let e=String(t||"").trim(),n=e.match(Ik);if(n)return n[1].toUpperCase();let r=e.match(Rk);return r?r[1]:null}function Ok(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 Ck(t,e){let r=(await ee(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 Mk(t,e,{url:n,title:r,subtitle:o}){let s=await ee(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 Pk(t,e,n){let r=await ee(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 gi(t){let e=await ee(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 Nk(t,{filename:e,contentType:n,buffer:r}){let o=await ee(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 d=l.timestamp?Date.parse(l.timestamp):null;for(let f of u)if(!(!f||typeof f!="object"))if(f.type==="tool_use"&&f.id){if(f.name==="Agent"){let p=f.input||{};o.set(f.id,{id:f.id,kind:"agent",name:String(p.description||"").trim()||null,detail:String(p.subagent_type||"").trim()||null,ts:d,proven:!1})}else if(f.name==="Bash"&&f.input?.run_in_background===!0){let p=String(f.input.command||"").trim();o.set(f.id,{id:f.id,kind:"command",name:String(f.input.description||"").trim()||(p?
|
|
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 p=f.trim();if(!p||p[0]!=="{")continue;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 d=[];if(u.type==="function_call"&&
|
|
55
|
-
`);if(
|
|
56
|
-
`),
|
|
57
|
-
`),o=
|
|
58
|
-
`).
|
|
59
|
-
`),o=
|
|
60
|
-
`),o
|
|
61
|
-
`),
|
|
62
|
-
`)
|
|
63
|
-
`),o
|
|
64
|
-
`),o=
|
|
65
|
-
`),o
|
|
66
|
-
`)
|
|
67
|
-
`)
|
|
68
|
-
`),
|
|
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 Bk(t,e){let n=await ee(t,"query($id: String!) { issue(id: $id) { state { id name type } team { states { nodes { id name type position } } } } }",{id:e.key});if(!n.issue)throw new Error(`linear: issue "${e.key}" not found`);return{current:n.issue.state??null,states:n.issue.team?.states?.nodes??[]}}async function Dk(t,e,n){if(!(await ee(t,"mutation($id: String!, $input: IssueUpdateInput!) { issueUpdate(id: $id, input: $input) { success } }",{id:e.issueId,input:{stateId:n}})).issueUpdate?.success)throw new Error("linear: issueUpdate did not succeed")}async function zn(t){return((await ee(t,"query { teams(first: 100) { nodes { id key name } } }")).teams?.nodes??[]).map(n=>({id:n.id,key:n.key,name:n.name}))}var tf=255;async function yi(t,{teamId:e,name:n,description:r}){let o=r||null,s=!!o&&o.length>tf,a=s?`${o.slice(0,tf-1).trimEnd()}\u2026`:o,i=await ee(t,"mutation($input: ProjectCreateInput!) { projectCreate(input: $input) { success project { id url name } } }",{input:{teamIds:[e],name:n,...a?{description:a}:{},...s?{content:o}:{}}}),c=i.projectCreate?.project;if(!i.projectCreate?.success||!c)throw new Error("linear: projectCreate did not succeed");return{id:c.id,url:c.url,name:c.name}}async function bi(t,{teamId:e,title:n,description:r,projectId:o,priority:s}){let a=await ee(t,"mutation($input: IssueCreateInput!) { issueCreate(input: $input) { success issue { id identifier url title } } }",{input:{teamId:e,title:n,...r?{description:r}:{},...o?{projectId:o}:{},...Number.isInteger(s)?{priority:s}:{}}}),i=a.issueCreate?.issue;if(!a.issueCreate?.success||!i)throw new Error("linear: issueCreate did not succeed");return{id:i.id,key:i.identifier,url:i.url,title:i.title}}var lf={linear:{api:ki,config:an},jira:{api:wi,config:cs}};async function _i(){let t=[];for(let[e,{api:n,config:r}]of Object.entries(lf)){let o=await r();o&&t.push({name:e,api:n,cfg:o})}return t}async function un(t){let e=lf[t?.provider];if(!e)return null;let n=await e.config();return n?{name:t.provider,api:e.api,cfg:n}:null}function Ei(t,e){if(!t||t.name)return t;let n=e.find(r=>r.id===t.id);return n?{...t,name:n.name}:t}var uf=()=>process.env.BOSTRAT_STATE_DIR||af.join(process.env.HOME||jk.tmpdir(),".bostrat"),Ai=()=>af.join(uf(),"tickets.json"),df=()=>(process.env.BOSTRAT_PUBLIC_URL||"http://bostrat.globopi").replace(/\/$/,""),us=t=>`${df()}/track/${t}`,ls=null,xi=null,Si=Promise.resolve();async function ds(){return ls||(xi??=Wr.readFile(Ai(),"utf8").then(t=>JSON.parse(t)?.tickets??{}).catch(()=>({})).then(t=>ls=t).finally(()=>xi=null),xi)}async function ef(){let t=JSON.stringify({tickets:ls},null,2),e=`${Ai()}.${process.pid}.tmp`;await Wr.mkdir(uf(),{recursive:!0}),await Wr.writeFile(e,t),await Wr.rename(e,Ai())}async function Oe(){Si=Si.then(ef,ef),await Si.catch(()=>{})}async function ne(t){return(await ds())[t]??null}async function fs(){return{...await ds()}}async function ff(){return(await _i()).length>0}async function Xn(t,e,{source:n="api"}={}){let r=await _i();if(!r.length)throw new Error("no ticketing integration configured \u2014 add a linear or jira entry to ~/.bostrat/integrations.json");let o=null,s=null,a=null;for(let u of r){let d=u.api.parseIssueRef(e);if(d)try{s=await u.api.resolveIssue(u.cfg,d),o=u;break}catch(f){a=f}}if(!o)throw a||new Error(`"${e}" is not a Linear or Jira issue URL or key (ABC-123)`);let i=(t.boxes||[]).find(u=>u.name===t.leadBox),c={provider:o.name,key:s.key,issueId:s.issueId,url:s.url,title:s.title,state:s.state,source:n,linkedAt:Date.now(),track:{name:t.name,branch:t.workspace?.branch??null,leadAgent:i?.agent??null,cwd:t.cwd??null,...t.workspace?.kind==="multi"?{repos:(t.workspace.repos??[]).map(u=>u?.name).filter(Boolean)}:{}},pr:null};(await ds())[t.id]=c,await Oe(),await O({id:t.id,cwd:t.cwd},{type:"ticket.linked",actor:"user",source:"orchestrator",payload:{ts:Date.now(),provider:c.provider,key:c.key,url:c.url,title:c.title,linkSource:n}}).catch(()=>{}),await qr(t.id,c);let l=[];return c.track.branch&&l.push(`\`${c.track.branch}\``),c.track.repos?.length&&l.push(`repos ${c.track.repos.join(" + ")}`),c.track.leadAgent&&l.push(`lead ${c.track.leadAgent}`),await Hr(c,`**Bostrat** \u2014 Track **${c.track.name}** linked${l.length?` (${l.join(", ")})`:""}.`),await hf(t.id,c,"linked"),c}async function Ii(t){let e=await ds();return e[t]?(delete e[t],await Oe(),!0):!1}var Ti=t=>`$${t>=100?Math.round(t):t.toFixed(2)}`;async function pf(t,e){if(!t.track?.cwd)return null;let n=_e(await F({id:e,cwd:t.track.cwd}));return!n.totalUsd||n.totalUsd<.01?null:n}function Lk(t){let e=Object.entries(t.byProvider).sort((r,o)=>o[1].usd-r[1].usd).map(([r,o])=>`${r} ${Ti(o.usd)}`).join(" \xB7 "),n=Object.keys(t.byBox).length;return`${Ti(t.totalUsd)}${e?` (${e})`:""}${n>1?` across ${n} boxes`:""}`}function Fk(t,e){let n=[];return t.track.leadAgent&&n.push(`lead ${t.track.leadAgent}`),t.track.branch&&n.push(t.track.branch),t.track.repos?.length&&n.push(t.track.repos.join(" + ")),t.pr?.number&&n.push(`PR #${t.pr.number} ${t.pr.change==="merged"?"merged":"open"}`),e&&n.push(Ti(e.totalUsd)),n.join(" \xB7 ")||"linked"}async function qr(t,e){let n=await un(e);if(!n)return;let r=await pf(e,t).catch(()=>null);await n.api.upsertCard(n.cfg,e,{url:us(t),title:`Bostrat Track: ${e.track.name}`,subtitle:Fk(e,r)})}function ps(t){let e=new URL(df());return`${e.protocol}//${t}.${e.host}/`}var Uk={linked:"linked",opened:"prOpened",merged:"prMerged"};function Hk(t,e,n){let r=o=>e.filter(s=>s.type===o).sort((s,a)=>s.position-a.position);if(n==="linked"){if(["triage","backlog","unstarted"].includes(t?.type))return r("started")[0]??null}else if(n==="opened"){if(t?.type!=="completed")return r("started").find(o=>/review/i.test(o.name))??null}else if(n==="merged"&&t?.type!=="completed")return r("completed")[0]??null;return null}async function mf(t,e,{from:n,to:r,mode:o,phase:s=null,actor:a="user"}){e.state=r,await Oe(),await O({id:t,cwd:e.track?.cwd},{type:"ticket.state.changed",actor:a,source:"orchestrator",payload:{ts:Date.now(),key:e.key,url:e.url,from:n??null,to:r,mode:o,...s?{phase:s}:{}}}).catch(()=>{})}async function hf(t,e,n){let r=await un(e);if(!r)return;let o=r.cfg.ticketFlow?.[Uk[n]]??"prompt";if(o==="off")return;let{current:s,states:a}=await r.api.issueStates(r.cfg,e),i=Ei(s,a),c=Hk(i,a,n);if(!c||c.id===i?.id)return;let l=i?.name??e.state??null;if(o==="auto"){await r.api.moveIssue(r.cfg,e,c.id),await mf(t,e,{from:l,to:c.name,mode:"auto",phase:n,actor:"system"});return}await O({id:t,cwd:e.track?.cwd},{type:"ticket.move.offered",actor:"system",source:"orchestrator",payload:{ts:Date.now(),offerId:cf(),provider:e.provider,key:e.key,url:e.url,phase:n,from:l,to:{id:c.id,name:c.name,type:c.type},...e.pr?.number?{prNumber:e.pr.number}:{}}}).catch(()=>{})}async function wf(t){let e=await ne(t);if(!e)throw new Error("no ticket linked to this Track");let n=await un(e);if(!n)throw new Error("no ticketing integration configured");let{current:r,states:o}=await n.api.issueStates(n.cfg,e),s=Ei(r,o);return s?.name&&e.state!==s.name&&(e.state=s.name,await Oe()),{key:e.key,url:e.url,current:s?{id:s.id,name:s.name??null,type:s.type??null}:null,states:[...o].sort((a,i)=>a.position-i.position).map(({id:a,name:i,type:c,position:l})=>({id:a,name:i,type:c,position:l}))}}async function Ri(t,e,{mode:n="manual"}={}){if(!e)throw new Error("stateId required");let r=await ne(t);if(!r)throw new Error("no ticket linked to this Track");let o=await un(r);if(!o)throw new Error("no ticketing integration configured");let{current:s,states:a}=await o.api.issueStates(o.cfg,r),i=Ei(s,a),c=a.find(u=>u.id===e);if(!c)throw new Error("unknown state for this ticket's team");let l=i?.name??r.state??null;return i?.id===c.id?(r.state!==c.name&&(r.state=c.name,await Oe()),{key:r.key,from:l,to:c.name,moved:!1}):(await o.api.moveIssue(o.cfg,r,c.id),await mf(t,r,{from:l,to:c.name,mode:n}),{key:r.key,from:l,to:c.name,moved:!0})}async function gf(t,e){let n=await ne(t);if(!n)throw new Error("no ticket linked to this Track");let r=await un(n);if(!r)throw new Error("no ticketing integration configured");let o=String(e||"").trim();if(!o)throw new Error("comment body required");let s=await r.api.createComment(r.cfg,n,o);return await O({id:t,cwd:n.track?.cwd},{type:"ticket.commented",actor:"user",source:"orchestrator",payload:{ts:Date.now(),key:n.key,url:s??n.url,excerpt:o.length>280?`${o.slice(0,279)}\u2026`:o}}).catch(()=>{}),{key:n.key,url:s??n.url}}async function yf(t,e,n){if(!e)throw new Error("offerId required");let r=await F({id:t.id,cwd:t.cwd}),o=r.find(a=>a.type==="ticket.move.offered"&&a.payload?.offerId===e);if(!o)throw new Error("unknown offer");if(r.some(a=>a.type==="ticket.move.answered"&&a.payload?.offerId===e))throw new Error("offer already answered");let s=null;return n&&(s=await Ri(t.id,o.payload.to?.id,{mode:"offer"})),await O({id:t.id,cwd:t.cwd},{type:"ticket.move.answered",actor:"user",source:"orchestrator",payload:{ts:Date.now(),offerId:e,action:n?"moved":"dismiss",...s?{from:s.from,to:s.to}:{}}}),{offerId:e,action:n?"moved":"dismiss",...s??{}}}var nf=50;async function bf(){let t=await an();if(!t)throw new Error("no ticketing integration configured");return zn(t)}function Wk(t){let e=t&&typeof t=="object"?t:{},n=(Array.isArray(e.issues)?e.issues:[]).map(o=>o&&typeof o=="object"?o:{title:String(o||"")}).map(o=>({title:String(o.title||"").trim(),description:o.description?String(o.description):null,priority:Number.isInteger(o.priority)&&o.priority>=0&&o.priority<=4?o.priority:null})).filter(o=>o.title),r=e.epic&&String(e.epic.name||"").trim()?{name:String(e.epic.name).trim(),description:e.epic.description?String(e.epic.description):null}:null;if(!n.length&&!r)throw new Error("a plan needs an epic or at least one issue");if(n.length>nf)throw new Error(`too many issues (${n.length}); cap is ${nf} per proposal`);return{epic:r,issues:n,team:e.team?String(e.team).trim():null,linkCurrentTrack:e.linkCurrentTrack===!0}}async function qk(t,e,n,r){let o=r??await zn(t),s=i=>o.find(c=>c.id===i||c.key.toLowerCase()===String(i).toLowerCase()||c.name.toLowerCase()===String(i).toLowerCase())??null;if(e.team){let i=s(e.team);if(i)return i}let a=await ne(n.id);if(a?.key){let i=a.key.split("-")[0],c=o.find(l=>l.key===i);if(c)return c}if(t.defaultTeam){let i=s(t.defaultTeam);if(i)return i}return o.length===1?o[0]:null}async function kf(t,e){let n=await an();if(!n)throw new Error("no ticketing integration configured \u2014 add a linear apiKey to ~/.bostrat/integrations.json");if(n.ticketFlow?.create==="off")throw new Error("ticket creation offers are off (bostrat-ticket flow set create=prompt to re-enable)");let r=Wk(e),o=await zn(n),s=await qk(n,r,t,o),a=s?{id:s.id,key:s.key,name:s.name}:null,i=cf();return await O({id:t.id,cwd:t.cwd},{type:"ticket.create.offered",actor:"system",source:"orchestrator",payload:{ts:Date.now(),proposalId:i,provider:"linear",epic:r.epic,issues:r.issues,team:a,needsTeam:!s,linkCurrentTrack:r.linkCurrentTrack}}),{proposalId:i,epic:r.epic,issues:r.issues,team:a,needsTeam:!s}}async function xf(t,e,n,{teamId:r=null}={}){if(!e)throw new Error("proposalId required");let o=await F({id:t.id,cwd:t.cwd}),s=o.find(m=>m.type==="ticket.create.offered"&&m.payload?.proposalId===e);if(!s)throw new Error("unknown proposal");if(o.some(m=>m.type==="ticket.create.answered"&&m.payload?.proposalId===e))throw new Error("proposal already answered");if(!n)return await O({id:t.id,cwd:t.cwd},{type:"ticket.create.answered",actor:"user",source:"orchestrator",payload:{ts:Date.now(),proposalId:e,action:"dismiss"}}),{proposalId:e,action:"dismiss"};let a=await an();if(!a)throw new Error("no ticketing integration configured");let i=s.payload,c=i.team??null;if(!c&&r){let m=(await zn(a)).find(h=>h.id===r);if(!m)throw new Error("unknown team");c={id:m.id,key:m.key,name:m.name}}if(!c)throw new Error("a team is required to create \u2014 pick one");let l=[],u=[],d=null;if(i.epic){let m=await yi(a,{teamId:c.id,name:i.epic.name,description:i.epic.description});d=m.id,l.push({kind:"epic",title:m.name,url:m.url})}for(let m of i.issues||[])try{let h=await bi(a,{teamId:c.id,title:m.title,description:m.description,projectId:d,priority:m.priority});l.push({kind:"issue",key:h.key,title:h.title,url:h.url})}catch(h){u.push({title:m.title,message:h?.message||String(h)})}await O({id:t.id,cwd:t.cwd},{type:"ticket.created",actor:"user",source:"orchestrator",payload:{ts:Date.now(),proposalId:e,team:{key:c.key,name:c.name},items:l,failed:u}}),await O({id:t.id,cwd:t.cwd},{type:"ticket.create.answered",actor:"user",source:"orchestrator",payload:{ts:Date.now(),proposalId:e,action:"created",createdCount:l.length,failedCount:u.length}});let f=l.filter(m=>m.kind==="issue"),p=null;return i.linkCurrentTrack&&f.length===1&&!await ne(t.id)&&(p=await Xn(t,f[0].key,{source:"created"}).catch(()=>null)),{proposalId:e,action:"created",created:l,failed:u,linked:p?p.key:null}}async function Hr(t,e){let n=await un(t);n&&await n.api.createComment(n.cfg,t,e)}async function Gk(t,e,n){let r=await un(t);if(!r)return null;let o=await Ee(e);if(!o)return null;let s=nn(o,n);if(!s)return null;let a=await Wr.readFile(Lt(o,s.v).png).catch(()=>null);return a?r.api.uploadImage(r.cfg,{issue:t.key,filename:`${o.name.replace(/[^\w.-]+/g,"-")}.v${s.v}.png`,contentType:"image/png",buffer:a}):null}var rf=()=>Number(process.env.BOSTRAT_TICKET_COST_SYNC_MS??12e4),vi=new Map;function Kk(t,e){if(rf()<=0)return qr(t,e);if(vi.has(t))return;let n=setTimeout(()=>{vi.delete(t),ne(t).then(r=>r?qr(t,r):null).catch(()=>{})},rf());n.unref?.(),vi.set(t,n)}var of=new Map;async function sf(t,e,n){let r=of.get(t)??0;Date.now()-r<600*1e3||(of.set(t,Date.now()),await O({id:t,cwd:e},{type:"ticket.sync.failed",actor:"system",source:"orchestrator",payload:{ts:Date.now(),message:n?.message||String(n)}}).catch(()=>{}))}async function Vk(t,{resolveTrack:e=null}={}){let n=await ne(t.trackId),r=t.payload||{};if(!n){if(t.type!=="turn.user"||t.actor!=="user")return;let o=null;for(let a of await _i())if(o=a.api.findIssueUrl(r.text,a.cfg),o)break;if(!o)return;let s=e?await e(t.trackId):null;if(!s)return;try{await Xn(s,o,{source:"chat"})}catch(a){await sf(t.trackId,s.cwd,a)}return}if(!n.track.cwd&&e){let o=await e(t.trackId).catch(()=>null);o?.cwd&&(n.track.cwd=o.cwd,await Oe())}try{switch(t.type){case"handoff.done":n.track.leadAgent=r.toAgent??n.track.leadAgent,await Oe(),await qr(t.trackId,n);break;case"pr.state.changed":{if(!r.change)break;if(r.number&&(n.pr={number:r.number,url:r.url??n.pr?.url??null,change:r.change}),await Oe(),await qr(t.trackId,n),r.change==="opened"||r.change==="merged"){let o=r.change==="merged"?await pf(n,t.trackId).catch(()=>null):null;await Hr(n,`**Bostrat** \u2014 PR ${r.change} for **${n.track.name}**${r.url?`: ${r.url}`:r.number?` (#${r.number})`:""}`+(o?`
|
|
46
|
+
|
|
47
|
+
Agent cost for this Track: ${Lk(o)}.`:"")),await hf(t.trackId,n,r.change)}break}case"mock.state.changed":{if(r.change==="ready"&&r.mockId){let o=await Gk(n,r.mockId,r.version??null);if(!o)break;let s=(r.version??1)>1?` revised (v${r.version})`:"";await Hr(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 Hr(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 Hr(n,`**Bostrat** \u2014 live preview **${r.name}**: ${ps(r.slug)}
|
|
50
|
+
_(runs on the Track's branch; reachable on the operator's private network only)_`);break}case"cost.recorded":await Kk(t.trackId,n);break;default:break}}catch(o){let s=e?await e(t.trackId).catch(()=>null):null;await sf(t.trackId,s?.cwd??null,o)}}function Sf({resolveTrack:t}){jt.on("event",e=>{Vk(e,{resolveTrack:t}).catch(()=>{})})}import ms from"node:fs/promises";import vf from"node:path";import Jk from"node:os";var Af=process.env.BOSTRAT_STATE_DIR||vf.join(process.env.HOME||Jk.tmpdir(),".bostrat"),Oi=vf.join(Af,"pr-links.json"),it=(t,e)=>`${t}#${Number(e)}`,hs=null,$i=null,N=Promise.resolve();async function gt(){return hs||($i??=ms.readFile(Oi,"utf8").then(t=>JSON.parse(t)?.links??{}).catch(()=>({})).then(t=>hs=t).finally(()=>$i=null),$i)}async function X(){let t=JSON.stringify({links:hs},null,2),e=`${Oi}.${process.pid}.tmp`;await ms.mkdir(Af,{recursive:!0}),await ms.writeFile(e,t),await ms.rename(e,Oi)}var zk=new Set(["api","observed","reconciled"]);async function Pt({repo:t,number:e,trackId:n,url:r=null,headRef:o=null,title:s=null,source:a="matched"}){if(!t||!Number.isInteger(Number(e))||!n)return{link:null,created:!1};let i=await gt(),c=it(t,e);return i[c]?{link:i[c],created:!1}:(i[c]={repo:t,number:Number(e),trackId:n,url:r,headRef:o,title:s,source:a,announced:zk.has(a),firstSeen:Date.now()},N=N.then(X,X),await N.catch(()=>{}),{link:i[c],created:!0})}async function Gr(t,e){if(!t||e==null)return null;let n=(await gt())[it(t,e)];return!n||n.announced?null:(n.announced=!0,N=N.then(X,X),await N.catch(()=>{}),n)}async function vt(t,e){return!t||e==null?null:(await gt())[it(t,e)]??null}async function Ce(t,e,n){if(!t||e==null)return null;let r=(await gt())[it(t,e)];return r?(r.state=n,r.resolvedAt=Date.now(),delete r.armedAt,delete r.autoMerge,delete r.archiveOnMerge,delete r.notifyOnMerge,delete r.notifyPending,N=N.then(X,X),await N.catch(()=>{}),r):null}async function Tf(t,e,{headSha:n=null,trackId:r=null}={}){if(!t||e==null)return null;let o=(await gt())[it(t,e)];return o?(o.ciFix={delegatedAt:Date.now(),headSha:n,trackId:r},N=N.then(X,X),await N.catch(()=>{}),o):null}async function _f(t,e,{headSha:n=null,trackId:r=null}={}){if(!t||e==null)return null;let o=(await gt())[it(t,e)];return o?(o.reconcile={delegatedAt:Date.now(),headSha:n,trackId:r},N=N.then(X,X),await N.catch(()=>{}),o):null}async function ws(t,e,{headSha:n=null}={}){if(!t||e==null)return null;let r=(await gt())[it(t,e)];return!r||!r.ciFix?null:(r.ciFixAwait={since:Date.now(),trackId:r.ciFix.trackId??null,headSha:n},delete r.ciFix,N=N.then(X,X),await N.catch(()=>{}),r)}async function gs(t,e){if(!t||e==null)return null;let n=(await gt())[it(t,e)];return!n||!n.ciFixAwait?null:(delete n.ciFixAwait,N=N.then(X,X),await N.catch(()=>{}),n)}async function ys(t,e,{headSha:n=null}={}){if(!t||e==null)return null;let r=(await gt())[it(t,e)];return!r||!r.reconcile?null:(r.reconcileAwait={since:Date.now(),trackId:r.reconcile.trackId??null,headSha:n},delete r.reconcile,N=N.then(X,X),await N.catch(()=>{}),r)}async function bs(t,e){if(!t||e==null)return null;let n=(await gt())[it(t,e)];return!n||!n.reconcileAwait?null:(delete n.reconcileAwait,N=N.then(X,X),await N.catch(()=>{}),n)}async function Ef(t,e,{at:n=Date.now()}={}){if(!t||e==null)return null;let r=(await gt())[it(t,e)];return r?(r.baseFoldAt=n,N=N.then(X,X),await N.catch(()=>{}),r):null}async function If(t,e){if(!t||e==null)return null;let n=(await gt())[it(t,e)];return!n||!n.baseFoldAt?null:(delete n.baseFoldAt,N=N.then(X,X),await N.catch(()=>{}),n)}async function Rf(t,e,n){if(!t||e==null||!n)return null;let r=(await gt())[it(t,e)];return r?(r.lastHeadSha=n,N=N.then(X,X),await N.catch(()=>{}),r):null}async function Kr(t,e,{merge:n=null}={}){if(!t||e==null)return null;let r=(await gt())[it(t,e)];return r?(r.armedAt=Date.now(),n&&(r.autoMerge={method:n.method||"merge",armedAt:r.armedAt}),N=N.then(X,X),await N.catch(()=>{}),r):null}async function Yn(t,e,n=!0){if(!t||e==null)return null;let r=(await gt())[it(t,e)];return r?(n?r.archiveOnMerge=!0:delete r.archiveOnMerge,N=N.then(X,X),await N.catch(()=>{}),r):null}async function ks(t,e,n=!0){if(!t||e==null)return null;let r=(await gt())[it(t,e)];return r?(n?r.notifyOnMerge=!0:delete r.notifyOnMerge,N=N.then(X,X),await N.catch(()=>{}),r):null}async function $f(t,e,n){if(!t||e==null)return null;let r=(await gt())[it(t,e)];if(!r)return null;r.notifyOnMerge=!0;let o=new Map((r.notifyPending||[]).map(s=>[`${s.repo}#${s.number}`,s]));for(let s of n||[])s&&s.number!=null&&o.set(`${s.repo}#${s.number}`,s);return r.notifyPending=[...o.values()],N=N.then(X,X),await N.catch(()=>{}),r}async function Vr(t,e){if(!t||e==null)return null;let n=(await gt())[it(t,e)];return!n||!n.armedAt&&!n.autoMerge?null:(delete n.armedAt,delete n.autoMerge,delete n.archiveOnMerge,delete n.notifyOnMerge,delete n.notifyPending,N=N.then(X,X),await N.catch(()=>{}),n)}var Xk=new Set(["merged","closed","lost"]);async function Of(){return Object.values(await gt()).filter(t=>!Xk.has(t.state))}async function Me(){return Object.values(await gt()).sort((t,e)=>e.firstSeen-t.firstSeen)}var Mf=t=>Math.round(t*100)/100;function Yk(t){return!t||t.kind==="setup"?[]:$t(t.workspace).filter(e=>e&&(e.repo||e.base)).map(e=>({key:e.repo||e.base,slug:e.repo??null,base:e.base??null,name:e.repo?e.repo.split("/").pop():Cf.basename(e.base)}))}function Pf(t){let e=new Map;for(let n of t||[])for(let r of Yk(n)){let o=e.get(r.key);o||e.set(r.key,o={...r,tracks:[]}),o.tracks.push(n)}return e}function Nf(t){let e=t.events?.length?t.events[t.events.length-1].at:0;return Math.max(t.createdAt||0,t.archivedAt||0,t.lastResponseAt||0,e||0)||null}var Ci=(t,e)=>`${t} ${e}${t===1?"":"s"}`;function Zk(t){if(!Number.isFinite(t)||t<=0)return null;let e=t/864e5;if(e>=1)return`${Math.round(e)}d`;let n=t/36e5;return n>=1?`${Math.round(n)}h`:`${Math.max(1,Math.round(t/6e4))}m`}function Mi(t,e=[]){let n=[],r=t?.events||[],o=new Map,s=0;for(let u of e){if(u.type!=="pr.state.changed")continue;let d=u.payload||{};!d.repo||d.number==null||(d.change==="merged"?o.set(`${d.repo}#${d.number}`,d):d.change==="opened"&&s++)}if(o.size){let u=[...o.keys()].map(d=>{let[f,p]=d.split("#");return`${f.split("/").pop()}#${p}`});n.push(`Merged ${u.slice(0,4).join(", ")}${u.length>4?` +${u.length-4}`:""}`)}else s&&n.push(Ci(s,"PR")+" opened");let a=Object.keys(t?.subtasks||{}).length||r.filter(u=>u.type==="subtask-delegated").length;a&&n.push(Ci(a,"subtask"));let i=r.filter(u=>u.type==="lead-handoff"&&u.fromAgent&&u.toAgent);if(i.length){let u=i.slice(0,3).map(d=>`${d.fromAgent}\u2192${d.toAgent}`).join(", ");n.push(`${Ci(i.length,"handoff")} ${u}${i.length>3?"\u2026":""}`)}let c=_e(e);c.totalUsd>=.01&&n.push(`$${c.totalUsd.toFixed(2)}`);let l=Number.isFinite(t?.archivedAt)&&Number.isFinite(t?.createdAt)?Zk(t.archivedAt-t.createdAt):null;return n.length&&l&&n.push(l),n.length?n.join(" \xB7 "):null}function Qk(t){let e=[],n=[...t?.boxes||[]].sort((o,s)=>(o.createdAt||0)-(s.createdAt||0));for(let o of n){if(o.role!=="lead"&&o.role!=="handed-off")continue;let s=Array.isArray(o.epochs)&&o.epochs.length?o.epochs.map(a=>a.agent):[o.agent];for(let a of s)a&&e[e.length-1]!==a&&e.push(a)}let r=new Set(n.filter(o=>o.role==="subtask"&&o.agent&&!e.includes(o.agent)).map(o=>o.agent));return{lead:e,subtask:[...r]}}function Bf(t,e,n){let r=new Set(t.tracks.map(f=>f.id)),o=0,s=0,a=0,i=null,c=null,l=0;for(let f of t.tracks){f.archivedAt?s++:o++,f.beat?.text&&a++;let p=Nf(f);p&&(!i||p>i)&&(i=p),f.createdAt&&(!c||f.createdAt<c)&&(c=f.createdAt),l+=e.get(f.id)||0}let u=0,d=0;for(let f of n)r.has(f.trackId)&&(t.slug&&f.repo!==t.slug||t.slug&&(f.state==="merged"?d++:f.state!=="closed"&&u++));return{key:t.key,slug:t.slug,base:t.base,name:t.name,tracks:{total:t.tracks.length,live:o,archived:s,withBeat:a},prs:{open:u,merged:d},usd:Mf(l),firstActivityAt:c,lastActivityAt:i}}async function Df(t){let e=Pf(t),n=await Me().catch(()=>[]),r=new Map,o=new Set;for(let a of e.values())for(let i of a.tracks){if(o.has(i.id))continue;o.add(i.id);let c=await F(i).catch(()=>[]);r.set(i.id,_e(c).totalUsd)}let s=[...e.values()].map(a=>Bf(a,r,n)).sort((a,i)=>(i.lastActivityAt??0)-(a.lastActivityAt??0));return{generatedAt:Date.now(),arcs:s}}async function jf(t,e){let r=Pf(t).get(e);if(!r)return null;let[o,s,a,i,c]=await Promise.all([Me().catch(()=>[]),Qo().catch(()=>[]),os().catch(()=>[]),ss().catch(()=>[]),fs().catch(()=>({}))]),l=new Map,u=[];for(let d of[...r.tracks].sort((f,p)=>(p.createdAt||0)-(f.createdAt||0))){let f=await F(d).catch(()=>[]),p=_e(f);l.set(d.id,p.totalUsd);let m=c[d.id]||null;u.push({trackId:d.id,name:d.name,createdAt:d.createdAt??null,archivedAt:d.archivedAt??null,live:!d.archivedAt,beat:d.beat??null,agents:Qk(d),handoffs:(d.events||[]).filter(h=>h.type==="lead-handoff").length,subtasks:Object.keys(d.subtasks||{}).length,repos:$t(d.workspace).map(h=>h.repo||Cf.basename(h.base||"")),prs:o.filter(h=>h.trackId===d.id&&(!r.slug||h.repo===r.slug)).map(h=>({repo:h.repo,number:h.number,state:h.state??null,title:h.title??null,url:h.url??null})),mocks:s.filter(h=>h.trackId===d.id).length,views:a.filter(h=>h.trackId===d.id).length,runbooks:i.filter(h=>h.trackId===d.id).map(h=>({name:h.name,status:h.status})),ticket:m?{provider:m.provider,key:m.key,url:m.url??null}:null,usd:Mf(p.totalUsd),lastActivityAt:Nf(d)})}return{...Bf(r,l,o),generatedAt:Date.now(),segments:u}}import{execFile as tx}from"node:child_process";import{promisify as ex}from"node:util";import{randomUUID as nx}from"node:crypto";var dn=ex(tx),xs=t=>new Promise(e=>setTimeout(e,t)),rx=Number(process.env.BOSTRAT_SWAP_EXIT_TIMEOUT_MS)||15e3,Lf=Number(process.env.BOSTRAT_SWAP_SHELL_TIMEOUT_MS)||8e3,ox=250,sx=400,Ff={claude:{escape:!0,wipe:!0,text:"/exit"},codex:{escape:!0,wipe:!0,text:"/quit"},cursor:{escape:!0,wipe:!0,text:"/quit"},default:{escape:!1,wipe:!1,text:"exit"}};async function Hf(t){let e=await ou(t);if(!e)return!1;try{return await dn("pgrep",["-P",String(e)]),!1}catch(n){return n?.code===1}}async function Pi(t,e){let n=Date.now()+e;for(;;){if(await Hf(t))return!0;if(Date.now()>n)return!1;await xs(ox)}}async function ax(t,e){let n=Ff[e]||Ff.default;n.escape&&(await bt(t,["Escape"],{kind:"swap-exit"}),await xs(150)),n.wipe&&await Do(t,400,{kind:"swap-exit"}),await jo(t,n.text,{perCharDelayMs:90,kind:"swap-exit"});let r=le(e).submitDelayMs||0;await xs(Math.max(r,250)),await bt(t,["Enter"],{kind:"swap-exit"})}async function ix(t,e){try{let{stdout:n}=await dn(C,P("show-environment","-t",t,e));return!String(n||"").trimStart().startsWith("-")}catch{return!1}}async function Uf(t){await dn(C,P("respawn-pane","-k","-t",t))}async function Ni(t,e={}){if(!pt(t))throw new Error("invalid session name");let n=String(e.agentCmd||"").trim();if(!n)throw new Error("agentCmd is required");let r=await z(t);if(!r)throw new Error(`session "${t}" not found`);if(!r.cwd)throw new Error(`can't resolve cwd for "${t}"`);let o=r.agent||"agent",s=e.agent||Pn(n),a=Number(e.exitTimeoutMs)||rx;await q({flow:"swap",phase:"start",sessionName:t,from:o,to:s,cwd:r.cwd});let i=!1;if(!await Hf(t)&&(await ax(t,o),!await Pi(t,a)&&(i=!0,await q({flow:"swap",phase:"respawn-forced",sessionName:t,from:o}),await Uf(t),!await Pi(t,Lf))))throw new Error(`pane shell did not come back after respawn-pane in "${t}"`);let c=s==="claude"&&Mn(n),l=c?Co():null,u=await ix(t,"ANTHROPIC_API_KEY"),d=!1;try{l?(await dn(C,P("set-environment","-t",t,"ANTHROPIC_API_KEY",l)),d=!0):u&&(await dn(C,P("set-environment","-u","-t",t,"ANTHROPIC_API_KEY")),d=!0)}catch(b){throw l?Ta(b,l):b}if(d&&!i&&(await Uf(t),!await Pi(t,Lf)))throw new Error(`pane shell did not come back after env respawn in "${t}"`);let f=!!e.resumeSessionId,p=e.resumeSessionId||nx(),m=f?n:Ia(n,p),h=f?e.pinned===!0:/(^|\s)--session-id(\s|=)/.test(m);return c&&await xe(r.cwd),s==="codex"&&await $n(r.cwd),s==="cursor"&&await On(r.cwd),await tn(t),await dn(C,P("set-option","-t",t,"@bostrat-sid",p)),await dn(C,P("set-option","-t",t,"@bostrat-agent",s)),await xs(sx),await kt(t,m,{kind:"swap-launch"}),await q({flow:"swap",phase:"done",sessionName:t,from:o,to:s,sid:p,forced:i,envRespawned:d}),{name:t,cwd:r.cwd,sessionId:p,agent:s,pinned:h,forced:i,envRespawned:d}}import yx from"node:fs";import qf from"node:fs/promises";import Jr from"node:path";import bx from"node:os";import{fileURLToPath as kx}from"node:url";import{execFile as xx}from"node:child_process";import{promisify as Sx}from"node:util";import Di from"node:fs/promises";import Pe from"node:path";import{execFile as cx}from"node:child_process";import{promisify as lx}from"node:util";var ux=lx(cx),Wf=process.env.HOME||"/home/globo",dx=Pe.join(process.env.BOSTRAT_STATE_DIR||Pe.join(Wf,".bostrat"),"repos"),fx=[...(process.env.BOSTRAT_REPOS_ROOT||Pe.join(Wf,"Documents")).split(","),dx].map(t=>t.trim()).filter(Boolean),px=Number(process.env.BOSTRAT_REPOS_DEPTH)||3,mx=new Set(["node_modules",".git",".cache","dist","build",".next","vendor","__pycache__","venv",".venv","target"]);async function hx(t){try{return(await Di.stat(Pe.join(t,".git"))).isDirectory()?"repo":"worktree"}catch{return null}}async function wx(t){let e=i=>Di.readFile(Pe.join(t,".git",i),"utf8").then(c=>c.trim()).catch(()=>null),r=(await e("refs/remotes/origin/HEAD"))?.match(/^ref:\s*refs\/remotes\/origin\/(.+)/);if(r)return r[1].trim();let o;for(let i of["main","master"])if(await e(`refs/heads/${i}`)||(o??=await e("packed-refs")||"",new RegExp(`^[0-9a-f]{40} refs/heads/${i}$`,"m").test(o)))return i;let a=(await e("HEAD"))?.match(/^ref:\s*refs\/heads\/(.+)/);return a?a[1].trim():null}async function me(){let t=new Map;async function e(n,r){if(t.has(n))return;let o=await hx(n);if(o==="worktree")return;if(o==="repo"){t.set(n,{path:n,name:Pe.basename(n),branch:await wx(n)});return}if(r>=px)return;let s;try{s=await Di.readdir(n,{withFileTypes:!0})}catch{return}for(let a of s)!a.isDirectory()||a.name.startsWith(".")||mx.has(a.name)||await e(Pe.join(n,a.name),r+1)}for(let n of fx)await e(Pe.resolve(n),0);return[...t.values()].sort((n,r)=>n.name.localeCompare(r.name))}function gx(t){let e=String(t||"").trim().match(/github\.com[:/]+([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/i);return e?`${e[1]}/${e[2]}`:null}var Bi=new Map;async function Y(t){if(!t)return null;if(Bi.has(t))return Bi.get(t);let e=null;try{let{stdout:n}=await ux("git",["-C",t,"config","--get","remote.origin.url"],{timeout:5e3});e=gx(n)}catch{e=null}return Bi.set(t,e),e}var Gf=Sx(xx),vx=process.env.BOSTRAT_STATE_DIR||Jr.join(process.env.HOME||bx.tmpdir(),".bostrat"),Ax=Jr.join(vx,"repos"),Kf=/^[^/\s]+\/[^/\s]+$/,Tx=5*6e4,_x=6e4,Ex=10*6e4,Vf=fetch,ji=new Map,fn=null;function Li(t=process.env){let e=t.BOSTRAT_PLANE_URL||"";if(!e)return null;try{let n=new URL(e);return n.protocol=n.protocol==="ws:"?"http:":"https:",n.origin}catch{return null}}function Ss(t=process.env){return!!(Li(t)&&t.BOSTRAT_PLANE_TOKEN)}var Jf=t=>({authorization:`Bearer ${t.BOSTRAT_PLANE_TOKEN||""}`}),Zn=(t,e)=>Object.assign(new Error(t),{status:e});async function vs(t,{env:e=process.env}={}){let n=String(t||"").toLowerCase();if(!Kf.test(n))throw Zn("repo must be owner/name",400);let r=ji.get(n);if(r&&r.expiresAt-Date.now()>Tx)return r;if(!Ss(e))throw Zn("node is not plane-linked \u2014 GitHub auth unavailable",503);let o;try{o=await Vf(`${Li(e)}/v1/github/token?repo=${encodeURIComponent(t)}`,{headers:Jf(e)})}catch(i){if(r&&r.expiresAt>Date.now())return r;throw Zn(`plane unreachable \u2014 GitHub auth unavailable (${i?.message||i})`,503)}let s=await o.json().catch(()=>({}));if(!o.ok){if(o.status>=500&&r&&r.expiresAt>Date.now())return r;throw Zn(s?.error||`plane replied ${o.status}`,o.status)}let a={token:s.token,expiresAt:Number(s.expiresAt)||Date.now()+55*6e4,username:s.username??null,installationId:s.installationId??null};return ji.set(n,a),a}async function zr({env:t=process.env,force:e=!1}={}){if(!Ss(t))return{configured:!1,repos:[],username:null};if(!e&&fn&&Date.now()-fn.at<_x)return fn.data;try{let n=await Vf(`${Li(t)}/v1/github/repos`,{headers:Jf(t)});if(!n.ok)throw new Error(`plane replied ${n.status}`);let r=await n.json();return fn={at:Date.now(),data:r},r}catch(n){return fn?fn.data:{configured:!0,repos:[],username:null,error:n?.message||String(n)}}}function zf(){ji.clear(),fn=null}async function re({cwd:t=null,slug:e=null,env:n=process.env}={}){try{let r=e||(t?await Y(t):null);if(!r)return{};let{token:o}=await vs(r,{env:n});return o?{GH_TOKEN:o}:{}}catch{return{}}}async function Fi({env:t=process.env}={}){return(await zr({env:t})).username??null}var Ix=()=>kx(new URL("../bin/bostrat-git-credential",import.meta.url)),Rx=(t=process.env)=>`http://127.0.0.1:${Number(t.PORT)||3362}`;function $x(t){let[e,n]=String(t).split("/");return Jr.join(Ax,e,n)}async function Ui(t,{env:e=process.env}={}){let n=(...r)=>Gf("git",["-C",t,...r],{timeout:1e4});await n("config","--replace-all","credential.helper",""),await n("config","--add","credential.helper",`!"${Ix()}" ${Rx(e)}`),await n("config","credential.useHttpPath","true")}async function Hi(t,{env:e=process.env,baseUrl:n="https://github.com"}={}){if(!Kf.test(String(t||"")))throw Zn(`bad repo slug "${t}"`,400);let r=$x(t);if(yx.existsSync(Jr.join(r,".git")))return r;let{token:o}=await vs(t,{env:e});await qf.mkdir(Jr.dirname(r),{recursive:!0});let s="!f() { echo username=x-access-token; echo password=$BOSTRAT_GIT_TOKEN; }; f";try{await Gf("git",["-c","credential.helper=","-c",`credential.helper=${s}`,"clone","--no-tags","-q",`${n}/${t}.git`,r],{timeout:Ex,env:{...e,BOSTRAT_GIT_TOKEN:o,GIT_TERMINAL_PROMPT:"0"}})}catch(a){throw await qf.rm(r,{recursive:!0,force:!0}).catch(()=>{}),Zn(`clone of ${t} failed: ${a?.stderr?.trim?.()||a?.message||a}`,502)}return await Ui(r,{env:e}),r}import l0 from"node:fs/promises";function Ox(t){let e=[],n="",r=null;for(let o=0;o<t.length;o++){let s=t[o];if(r){n+=s,s===r&&t[o-1]!=="\\"&&(r=null);continue}if(s==="'"||s==='"'||s==="`"){r=s,n+=s;continue}if(s==="&"&&t[o+1]==="&"){e.push(n),n="",o++;continue}if(s==="|"&&t[o+1]==="|"){e.push(n),n="",o++;continue}if(s==="|"||s===";"||s===`
|
|
51
|
+
`){e.push(n),n="";continue}n+=s}return e.push(n),e.map(o=>o.trim()).filter(Boolean)}function Xr(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 Yr(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 tr=(t,e)=>t.length>e?`${t.slice(0,e).trimEnd()}\u2026`:t,St=t=>t&&!/[$`]/.test(t)?t:null,dt=t=>t.replace(/\/+$/,"").split("/").pop()||t,Qn=t=>{let e=t.replace(/\/+$/,"").split("/").filter(Boolean);return e.length>3?e.slice(-2).join("/"):t},Zf=t=>t.startsWith("-"),Xf=new Set(["-g","--glob","-t","--type","-A","-B","-C","-m","--max-count","-e","--regexp","-f","--file"]);function tt(t,e=new Set){let n=[];for(let r=1;r<t.length;r++){let o=t[r];if(o==="<<")break;if(Zf(o)){e.has(o)&&r++;continue}n.push(o)}return n}var Cx={status:()=>"Check git status",diff:()=>"Review changes",log:()=>"Review commit history",show:()=>"Inspect a commit",add:()=>"Stage files",commit:()=>"Commit changes",push:()=>"Push to remote",pull:()=>"Pull from remote",fetch:()=>"Fetch from remote",merge:()=>"Merge branches",rebase:()=>"Rebase branch",stash:()=>"Stash changes",clone:t=>t[1]?`Clone ${dt(t[1])}`:"Clone repository",checkout:()=>"Switch branches",switch:()=>"Switch branches",restore:()=>"Restore files",reset:()=>"Reset git state",branch:(t,e)=>e.includes("--show-current")?"Check current branch":"List branches",remote:()=>"Check git remotes","rev-parse":()=>"Resolve a git ref",worktree:()=>"Manage git worktrees","merge-base":()=>"Find the merge base","ls-files":()=>"List tracked files"},Mx={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 Qf(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 Px(t){let e=Qf(t),n=St(tt(t).find(r=>!/^\d+(,\d+)?p$/.test(r)));return e&&n?e.from===e.to?`Read line ${e.from} of ${dt(n)}`:`Read lines ${e.from}\u2013${e.to} of ${dt(n)}`:t.includes("-i")&&n?`Edit ${dt(n)}`:null}function Wi(t){if(t.includes("--files")){let s=St(tt(t,Xf)[0]);return s&&s!=="."?`List files in ${Qn(s)}`:"List files"}let e=tt(t,Xf),n=e[0];if(!n)return null;let r=St(e.slice(1).find(s=>s!==".")),o=tr(n,40);return r?`Search for \u201C${o}\u201D in ${Qn(r)}`:`Search for \u201C${o}\u201D`}function qi(t){let e=tt(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 Gi(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=tt(t)[0];return n?`Run ${dt(n)}`:null}function Nx(t){let e=tt(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 Yf(t){return t.find(e=>/^https?:\/\//.test(e))||null}var Bx={git:t=>{let e=tt(t,new Set(["-C","-c"])),n=Cx[e[0]];return n?n(e,t):null},gh:t=>{let e=tt(t),n=Mx[e[0]];return n?n(e):null},sed:Px,rg:Wi,grep:Wi,egrep:Wi,nl:t=>{let e=St(tt(t)[0]);return e?`Read ${dt(e)}`:null},cat:t=>{let e=St(tt(t)[0]);return e?`Read ${dt(e)}`:null},head:t=>{let e=St(tt(t,new Set(["-n","-c"])).find(n=>!/^\d+$/.test(n)));return e?`Read the start of ${dt(e)}`:null},tail:t=>{let e=St(tt(t,new Set(["-n","-c"])).find(n=>!/^\d+$/.test(n)));return e?`Read the end of ${dt(e)}`:null},wc:t=>{let e=St(tt(t)[0]);return e?`Count lines in ${dt(e)}`:"Count input lines"},ls:t=>{let e=St(tt(t)[0]);return e&&e!=="."?`List files in ${Qn(e)}`:"List files"},find:t=>St(t[1])&&!Zf(t[1])&&t[1]!=="."?`Find files in ${Qn(t[1])}`:"Find files",tree:()=>"List the directory tree",mkdir:t=>{let e=St(tt(t)[0]);return e?`Create directory ${Qn(e)}`:null},touch:t=>{let e=St(tt(t)[0]);return e?`Create ${dt(e)}`:null},rm:t=>{let e=St(tt(t)[0]);return e?`Delete ${Qn(e)}`:null},mv:t=>{let e=St(tt(t)[0]);return e?`Move ${dt(e)}`:null},cp:t=>{let e=St(tt(t)[0]);return e?`Copy ${dt(e)}`:null},chmod:t=>{let e=St(tt(t)[1]);return e?`Change permissions on ${dt(e)}`:null},diff:t=>{let e=tt(t);return e.length>=2?`Compare ${dt(e[0])} and ${dt(e[1])}`:null},curl:t=>{let e=Yf(t);return e?`Fetch ${tr(e,56)}`:"Fetch a URL"},wget:t=>{let e=Yf(t);return e?`Fetch ${tr(e,56)}`:"Fetch a URL"},npm:qi,pnpm:qi,yarn:qi,npx:t=>{let e=tt(t)[0];return e?`Run ${e}`:null},node:t=>Gi(t,"Node"),python:t=>Gi(t,"Python"),python3:t=>Gi(t,"Python"),make:t=>{let e=tt(t)[0];return e?`Run make ${e}`:"Run make"},systemctl:Nx,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=tt(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=tt(t)[0];return e?`Locate ${e}`:null},command:t=>t[1]==="-v"&&t[2]?`Locate ${t[2]}`:null,fly:t=>{let e=tt(t)[0];return e==="deploy"?"Deploy to Fly":e?`Run fly ${e}`:null}},Dx=new Set(["pwd","echo","cd","sleep","true","false","test","[","set","export","exit","read"]);function jx(t){let e=Yr(Xr(t));if(!e.length)return null;if(/^(ba|z)?sh$/.test(dt(e[0]))&&(e[1]==="-lc"||e[1]==="-c")&&e[2])return Ki(e[2]);let n=dt(e[0]);if(Dx.has(n))return null;if(e.includes("--version"))return`Check ${n} version`;if(n.endsWith(".sh"))return`Run ${n}`;let r=Bx[n];return r?r(e.map((o,s)=>s===0?n:o)):null}function Ki(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=Ox(e);for(let i=0;i<r.length-1;i++){let c=Yr(Xr(r[i])),l=Yr(Xr(r[i+1]));if(!(!c.length||!l.length)&&["nl","cat"].includes(dt(c[0]))&&dt(l[0])==="sed"){let u=Qf(l),d=tt(c)[0];if(u&&d)return tr(u.from===u.to?`Read line ${u.from} of ${dt(d)}`:`Read lines ${u.from}\u2013${u.to} of ${dt(d)}`,88)}}let o=[];for(let i of r){let c=jx(i);c&&o.push({d:c,head:dt(Yr(Xr(i))[0]||""),sub:tt(Yr(Xr(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":tr(s?s.d:o[0].d,88)}var Ji=new Set(["Bash"]);function er(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 Ne(t,e=""){let n=e.trim();return n||Ki(t)||er(t)}function Vi(t,e){if(t){if(Array.isArray(t)){for(let n of t)Vi(n,e);return}if(typeof t=="object"){if(t.type==="tool_use"&&Ji.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))Vi(n,e)}}}async function tp(t,{maxBytes:e=1048576,limit:n=400}={}){let r;try{r=await Et(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(Vi(c.content,l),!l.length)continue;let u=i.timestamp?Date.parse(i.timestamp):null;for(let d of l)o.push({...d,summary:Ne(d.command,d.description),ts:u})}return o.slice(-n)}function ep(t,e){let n=e.filter(l=>l.role==="assistant"&&(l.text||"").trim()).sort((l,u)=>(l.ts||0)-(u.ts||0)),r=[...t].sort((l,u)=>(l.ts||0)-(u.ts||0)),o=[],s=0,a=0,i=l=>{let u=l.n??a+1;return u>a&&(a=u),u};for(let l of n){let u=r.findIndex((m,h)=>h>=s&&(m.ts||0)>(l.ts||0)),d=u===-1?r.length:u,f=r.slice(s,d);if(s=d,!f.length)continue;let p=f.map(m=>({...m,n:i(m)}));o.push({id:`batch-${l.ts}-${l.box}`,anchorTs:l.ts,anchorBox:l.box,agent:l.agent,boxRole:l.boxRole,commands:p,ts:Math.max(...p.map(m=>m.ts||0)),pending:!1})}let c=r.slice(s);if(c.length){let l=c.map(u=>({...u,n:i(u)}));o.push({id:`batch-pending-${l[0]?.box||"lead"}`,anchorTs:null,anchorBox:l[l.length-1]?.box??null,agent:l[l.length-1]?.agent??"claude",boxRole:l[l.length-1]?.boxRole??"lead",commands:l,ts:Math.max(...l.map(u=>u.ts||0)),pending:!0})}return o}var Lx=/^Async (?:agent|task) launched/i,Fx=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 np(t,{maxBytes:e=2097152,limit:n=100}={}){let r;try{r=await Et(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 d=l.timestamp?Date.parse(l.timestamp):null;for(let f of u)if(!(!f||typeof f!="object"))if(f.type==="tool_use"&&f.id){if(f.name==="Agent"){let p=f.input||{};o.set(f.id,{id:f.id,kind:"agent",name:String(p.description||"").trim()||null,detail:String(p.subagent_type||"").trim()||null,ts:d,proven:!1})}else if(f.name==="Bash"&&f.input?.run_in_background===!0){let p=String(f.input.command||"").trim();o.set(f.id,{id:f.id,kind:"command",name:String(f.input.description||"").trim()||(p?er(p):null),detail:p?er(p):null,ts:d,proven:!0})}}else f.type==="tool_result"&&f.tool_use_id&&Lx.test(Fx(f.content))&&s.add(f.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 Zr from"node:fs";import Hx from"node:fs/promises";import pn from"node:path";import Wx from"node:os";import qx from"node:crypto";import Gx from"node:zlib";var As=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,nr=t=>String(t).replace(/\/+$/,"").split("/").pop()||String(t);function Be(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 zi=t=>`"${String(t).replace(/"/g,'\\"')}"`;function Ux(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 rp(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 ${Be(r,n)}${o?` (lines ${o}\u2013${s??"end"})`:""}`,140),description:o?`Read lines ${o}\u2013${s??"end"} of ${nr(r)}`:`Read ${nr(r)}`}}case"Grep":{let r=e?.pattern;if(!r)return null;let o=e?.path?Be(e.path,n):null,s=e?.glob?` --glob ${zi(e.glob)}`:"",a=Gt(String(r),40);return{command:Gt(`grep ${zi(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?Be(e.target_directory,n):null;return{command:Gt(`glob ${zi(r)}${o&&o!=="."?` ${o}`:""}`,140),description:`Find files matching \u201C${Gt(String(r),40)}\u201D`}}case"StrReplace":return e?.path?{command:Gt(`edit ${Be(e.path,n)}`,140),description:`Edit ${nr(e.path)}`}:null;case"Write":return e?.path?{command:Gt(`write ${Be(e.path,n)}`,140),description:`Write ${nr(e.path)}`}:null;case"Delete":return e?.path?{command:Gt(`delete ${Be(e.path,n)}`,140),description:`Delete ${nr(e.path)}`}:null;case"ReadLints":{let o=(Array.isArray(e?.paths)?e.paths:[]).map(s=>Be(s,n)).filter(Boolean);return{command:Gt(o.length?`lints ${o.join(" ")}`:"lints",140),description:"Check lints"}}case"ApplyPatch":{let r=Ux(e);if(!r.length)return null;let o=r.length>1?` (+${r.length-1} more)`:"";return{command:Gt(`apply_patch ${Be(r[0],n)}${o}`,140),description:r.length>1?`Patch ${r.length} files`:`Patch ${nr(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 Kx=process.env.BOSTRAT_CURSOR_CHATS||pn.join(Wx.homedir(),".cursor","chats");function ap(t){let e=qx.createHash("md5").update(String(t)).digest("hex");return pn.join(Kx,e)}function ip(t){try{let e=JSON.parse(Zr.readFileSync(pn.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 Vx(t){let e=ap(t),n;try{n=Zr.readdirSync(e,{withFileTypes:!0}).filter(o=>o.isDirectory())}catch{return null}let r=null;for(let o of n){let s=pn.join(e,o.name),a=pn.join(s,"store.db");if(!Zr.existsSync(a))continue;let i=ip(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 Ts(t,e){if(!t)return null;if(e){let n=pn.join(ap(t),e),r=pn.join(n,"store.db");if(Zr.existsSync(r))return{id:e,dir:n,store:r,...ip(n)||{createdAtMs:null,updatedAtMs:null}}}return Vx(t)}function op(t,e){let n=t.prepare("SELECT data FROM blobs WHERE id = ?").get(e);return n?Buffer.from(n.data):null}function Jx(t){if(!t)return null;let e=t;if(e.length>1&&e[0]===31&&e[1]===139)try{e=Gx.gunzipSync(e)}catch{}try{return JSON.parse(e.toString("utf8"))}catch{return null}}function zx(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 Xx(t,e){if(!Array.isArray(t))return[];let n=[];for(let r of t){if(!r||r.type!=="tool-call"||!As.has(r.toolName))continue;let o=rp(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 Yx=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():"",Zx=t=>Array.isArray(t)?t.filter(e=>e&&e.type==="tool-call"&&e.toolName).map(e=>e.toolName):[],sp=process.getBuiltinModule?.("node:sqlite"),rr={turns:[],trailingCommands:[],model:null,requestIds:[]};async function Xi(t,e){if(!sp)return rr;let{DatabaseSync:n}=sp,r;try{r=new n(t,{readOnly:!0})}catch{return rr}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 rr;let s;try{s=JSON.parse(Buffer.from(String(o.value),"hex").toString("utf8"))}catch{return rr}let a=s.lastUsedModel||null,i=s.latestRootBlobId?op(r,s.latestRootBlobId):null;if(!i)return{...rr,model:a};let c=[],l=[],u=[],d=null,f=[];for(let p of zx(i)){let m=Jx(op(r,p));if(!m)continue;let h=m.providerOptions?.cursor;if(h?.requestId&&f.push(h.requestId),h?.modelProviderMessageId&&f.push(h.modelProviderMessageId),m.role!=="assistant")continue;if(Array.isArray(m.content))for(let v of m.content){let E=v?.providerOptions?.cursor?.modelName;E&&(d=E)}l.push(...Xx(m.content,e).map((v,E)=>({...v,id:v.id||`${p}:${E}`}))),u.push(...Zx(m.content));let b=Yx(m.content);if(!b)continue;let x=[...new Set(u)];u=[],c.push({id:p,text:b,tools:x.length?x:void 0,commands:l,model:d}),l=[]}return{turns:c,trailingCommands:l,model:a,requestIds:f}}catch{return rr}finally{try{r.close()}catch{}}}function cp(t,e){let n=t.updatedAtMs;if(!n)try{n=Math.round(Zr.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 lp(t,e,{limit:n=80}={}){let r=Ts(t,e);if(!r)return[];let{turns:o,model:s}=await Xi(r.store,t);if(!o.length)return[];let a=o.slice(-n),{tsAt:i}=cp(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 up(t,e){let n=Ts(t,e);if(!n)return null;let{turns:r,requestIds:o,model:s}=await Xi(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 dp(t,e,{limit:n=80}={}){let r=Ts(t,e);if(!r)return[];let{turns:o,trailingCommands:s}=await Xi(r.store,t);if(!o.length&&!s.length)return[];let a=o.slice(-n),{end:i,tsAt:c}=cp(r,a.length||1),l=[];a.forEach((u,d)=>{for(let f of u.commands)l.push({...f,summary:Ne(f.command,f.description),ts:c(d)})});for(let u of s)l.push({...u,summary:Ne(u.command,u.description),ts:i+1});return l}async function fp(t,e){let n=Ts(t,e);if(!n)return null;if(n.updatedAtMs)return n.updatedAtMs;try{return Math.round((await Hx.stat(n.store)).mtimeMs)}catch{return null}}import pp from"node:fs";import Zi from"node:fs/promises";import Yi from"node:path";import Qx from"node:os";var t0=process.env.BOSTRAT_CODEX_SESSIONS||Yi.join(process.env.CODEX_HOME||Yi.join(Qx.homedir(),".codex"),"sessions");async function mp(t){try{return await Zi.realpath(t)}catch{return t}}function e0(t=t0){let e=[],n=r=>{let o;try{o=pp.readdirSync(r,{withFileTypes:!0})}catch{return}for(let s of o){let a=Yi.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:pp.statSync(a).mtimeMs})}catch{}}}};return n(t),e.sort((r,o)=>o.mtimeMs-r.mtimeMs)}async function n0(t,e=1048576){let n;try{n=await Zi.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 r0(t){let e=await n0(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 mn(t,e){if(!t&&!e)return null;let n=e0();if(e){let o=n.find(s=>s.id===e);return o||null}let r=await mp(t);for(let o of n){let s=await r0(o.file);if(s&&(s===t||await mp(s)===r))return o}return null}var o0=new Set(["output_text","text"]),s0=t=>typeof t=="string"?t.trim():Array.isArray(t)?t.filter(e=>e&&typeof e.text=="string"&&o0.has(e.type)).map(e=>e.text).join(`
|
|
58
|
+
`).trim():"";async function hp(t,e,{limit:n=80,maxBytes:r=524288}={}){let o=await mn(t,e);if(!o)return[];let s;try{s=await Et(o.file,r)}catch{return[]}let a=[],i=null,c=[],l=!1,u=!1,d=null;for(let f of s.split(`
|
|
59
|
+
`)){let p=f.trim();if(!p||p[0]!=="{")continue;let m;try{m=JSON.parse(p)}catch{continue}if((m.type==="turn_context"&&m.payload?.model||m.type==="session_meta"&&m.payload?.model)&&(i=m.payload.model),m.type==="event_msg"){let v=m.payload?.type;v==="task_started"?(l=!0,u=!1,d=m.payload?.turn_id||null):v==="task_complete"&&l&&(l=!1,!u&&m.payload?.last_agent_message==null&&a.push({role:"assistant",failed:!0,text:tc,sourceId:m.payload?.turn_id||d||void 0,ts:m.timestamp?Date.parse(m.timestamp):null,model:i}));continue}if(m.type!=="response_item")continue;let h=m.payload;if(!h)continue;if((h.type==="function_call"||h.type==="custom_tool_call")&&h.name){u=!0,Qi.has(h.name)||c.push(h.name);continue}if(h.type!=="message"||h.role!=="assistant")continue;u=!0;let b=s0(h.content),x=c.length?c.slice():null;c=[],!(!b&&!x)&&a.push({role:"assistant",text:b,tools:x||void 0,ts:m.timestamp?Date.parse(m.timestamp):null,model:i})}if(i)for(let f of a)f.model||(f.model=i);return a.slice(-n)}var Qr=new Set(["exec_command","shell","exec"]),Qi=new Set(["wait","write_stdin"]),tc="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 a0(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 i0(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 c0(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=i0(i.slice(c.index+c[0].length).trimStart());l&&l.trim()&&o.push(l.trim())}),o}async function wp(t,e,{limit:n=400,maxBytes:r=524288}={}){let o=await mn(t,e);if(!o)return[];let s;try{s=await Et(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 d=[];if(u.type==="function_call"&&Qr.has(u.name)){let p;try{p=JSON.parse(u.arguments||"{}")}catch{continue}let m=a0(p);m&&(d=[m])}else u.type==="custom_tool_call"&&u.name==="exec"&&(d=c0(u.input));if(!d.length)continue;let f=u.call_id||u.id||null;d.forEach((p,m)=>{a.push({id:f?m?`${f}:${m}`:f:null,tool:u.name,command:p,description:"",summary:Ne(p),ts:l.timestamp?Date.parse(l.timestamp):null})})}return a.slice(-n)}async function gp(t,e){let n=await mn(t,e);if(!n)return null;try{return Math.round((await Zi.stat(n.file)).mtimeMs)}catch{return n.mtimeMs||null}}var or=class{constructor(e,n=new Set){this.agent=e,this.cliTools=n}async readMessages(){return[]}async readCommands(){return[]}async readJobs(){return[]}async updatedAt(){return null}},ec=class extends or{transcript(e){return!e?.cwd||!e?.sessionId?null:ce(e.cwd,e.sessionId)}async readMessages(e,n){let r=this.transcript(e);return r?Da(r,n):[]}async readCommands(e,n){let r=this.transcript(e);return r?tp(r,n):[]}async readJobs(e,n){let r=this.transcript(e);return r?np(r,n):[]}async updatedAt(e){let n=this.transcript(e);if(!n)return null;try{return Math.round((await l0.stat(n)).mtimeMs)}catch{return null}}},nc=class extends or{async readMessages(e,n){return lp(e.cwd,e.sessionId,n)}async readCommands(e,n){return dp(e.cwd,e.sessionId,n)}async updatedAt(e){return fp(e.cwd,e.sessionId)}},rc=class extends or{sessionId(e){return e?.pinned?e.sessionId:null}async readMessages(e,n){return hp(e.cwd,this.sessionId(e),n)}async readCommands(e,n){return wp(e.cwd,this.sessionId(e),n)}async updatedAt(e){return gp(e.cwd,this.sessionId(e))}},u0=new or("unknown"),yp=new Map;function oc(t){return yp.set(t.agent,t),t}function to(t){return yp.get(t)||u0}oc(new ec("claude",Ji));oc(new nc("cursor",As));oc(new rc("codex",Qr));var sc=t=>e=>({...e,box:t.name,agent:t.agent,boxRole:t.role});async function bp(t,e={}){return(await to(t.agent).readMessages(t,e)).map(sc(t))}async function kp(t,e={}){return(await to(t.agent).readCommands(t,e)).map(sc(t))}async function xp(t,e={}){return(await to(t.agent).readJobs(t,e)).map(sc(t))}var Ap=/to navigate\b.*\bEsc to cancel|Enter to select\b/,cc=/Chat about this/i,hn=/^\s*(❯)?\s*(\d+)\.\s+(?:\[([ xX✔])\]\s+)?(.+?)\s*$/,Sp=/^(type something|chat about this|submit)\b/i,wn=/^[─—-]{6,}\s*$/,lc=/[\u2500-\u257F\u2550-\u256F┌┐└┘├┤│┬┴┼─✂]/;function gn(t){let e=t.search(lc);return(e>=0?t.slice(0,e):t).trim()}function d0(t){if(!t)return"";let e=t.replace(/[│┌└├─┘┤┬┴┼─✂].*$/,"").trim();return!e||/hidden|\d+\s+lines?/i.test(e)?"":e}function f0(t){let e=String(t).trim();return!!(!e||wn.test(t)||lc.test(e)||/hidden|\d+\s+lines?/i.test(e)||/notespress|press n to add/i.test(e)||/^\s*notes?\s*$/i.test(e))}function p0(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=m0(n,e);return n.forEach((a,i)=>{a.current=i===s}),{tabs:n,questionIndex:s,questionTotal:n.length,currentTabLabel:n[s].label}}function m0(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 vp(t){return t.replace(/[←→✔☐☒●▶]/g," ").replace(/\bSubmit\b/g," ").replace(/\s+/g," ").trim()}var Tp=/^\s*Switch model\?\s*$/m;function h0(t,e=""){let n=String(t);if(!Tp.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 d=r[u].match(hn);if(d)s.push({cursor:!!d[1],label:d[4].trim()});else if(s.length){if(r[u].trim()==="")break}else{let f=r[u].trim();f&&!wn.test(r[u])&&a.push(f)}}if(s.length<2)return null;let i=s.map(u=>({label:gn(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 _p=/^\s*Enter to confirm\s*·\s*Esc to cancel\s*$/,w0=/^\s*[─—▔-]{6,}\s*$/;function g0(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||!_p.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(hn);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&&!(w0.test(n[c])||wn.test(n[c]));c--){let l=n[c].trim();l&&a.unshift(l)}let i=s.map(c=>({label:gn(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 y0(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(hn);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 ac=/Resume from summary/i,b0=/^\s*This session is\b/;function k0(t,e=""){let n=String(t);if(!ac.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
65
|
+
`),o=-1;for(let u=r.length-1;u>=0;u--)if(b0.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 d=r[u].match(hn);if(d)s.push({cursor:!!d[1],label:d[4].trim()});else if(s.length){if(r[u].trim()==="")break}else{let f=r[u].trim();f&&!wn.test(r[u])&&a.push(f)}}if(s.length<2||!s.some(u=>ac.test(u.label)))return null;let i=s.map(u=>({label:gn(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 yn=/shift\+tab to cycle|⏵⏵ bypass permissions on|⏸ manual mode on/;function uc(t){let e=[];for(let r=0;r<t.length;r++){let o=t[r].match(hn);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 Ep=/Choose the text style that looks best with your terminal/,Ip=/Let's get started\./;function x0(t,e=""){let n=String(t);if(!Ep.test(n)||!Ip.test(n)||yn.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
66
|
+
`),o=uc(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:gn(s.label),description:""}))}]}:null}var Rp=/Select login method:/;function S0(t,e=""){let n=String(t);if(!Rp.test(n)||yn.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
67
|
+
`),o=uc(r);if(!o)return null;let s=o.map(a=>{let[i,...c]=a.label.split("\xB7");return{label:gn(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 dc=/Paste code here if prompted/,v0=/^[A-Za-z0-9%&=+._~#?/:\-]+$/;function A0(t,e){let n=t[e].trim();for(let r=e+1;r<t.length;r++){let o=t[r].trim();if(!o||!v0.test(o)||dc.test(t[r]))break;n+=o}return n}function T0(t,e=""){let n=String(t);if(!dc.test(n)||yn.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=A0(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 ic=/OAuth error:/,_0=/Press Enter to retry\./;function E0(t,e=""){let n=String(t);if(!ic.test(n)||!_0.test(n)||yn.test(n))return null;let s=([...n.replace(/\r/g,"").split(`
|
|
69
|
+
`)].reverse().find(a=>ic.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 $p=/Quick safety check: Is this a project you created or one you trust/;function I0(t,e=""){let n=String(t);if(!$p.test(n)||yn.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
70
|
+
`),o=uc(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:gn(s.label),description:""}))}]}:null}var Op=/^(.{0,60}?)Press\s+Enter\s+to\s+continue\s*[.…]{0,3}\s*$/;function R0(t,e=""){let n=String(t);if(yn.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(Op);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(wn.test(r[l])||lc.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 $0(t){let e=-1;for(let r=t.length-1;r>=0;r--)if(cc.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 O0(t,e=""){let n=String(t);if(!Ap.test(n)||!cc.test(n))return null;let r=n.replace(/\r/g,"").split(`
|
|
72
|
+
`),o=$0(r);if(!o)return null;let{headerLine:s,chatLine:a,optStart:i,optEnd:c}=o,l=[],u=-1;for(let T=i;T<c;T++){let U=r[T].match(hn);U&&(u<0&&(u=T),l.push({line:T,cursor:!!U[1],checked:U[3]!=null&&U[3]!==" ",hasCheckbox:U[3]!=null,label:U[4].trim(),desc:""}))}if(!l.length)return null;for(let T of l)for(let U=T.line+1;U<Math.min(r.length,T.line+4)&&!(hn.test(r[U])||wn.test(r[U]));U++){let J=r[U].trim();if(!(!J||f0(r[U])||Sp.test(J))){T.desc=d0(J);break}}let d=l.filter(T=>!Sp.test(T.label));if(!d.length)return null;let f=d.some(T=>T.hasCheckbox),p=Math.max(0,d.findIndex(T=>T.cursor)),m=s>=0?s+1:0,h=[];for(let T=m;T<u;T++){let U=r[T].trim();U&&!wn.test(r[T])&&!/[☐☒]/.test(r[T])&&h.push(U)}let b=h.join(" ")||(s>=0?vp(r[s]):"")||"The agent is asking a question",x=s>=0?p0(r[s],b):null,v=x?.currentTabLabel||(s>=0?vp(r[s]):""),E=d.map(T=>({label:gn(T.label),description:T.desc})),R=E.map(T=>T.label).join("|"),I=`claude:${x?`q${x.questionIndex+1}/${x.questionTotal}`:"q1/1"}:${v}:${b}:${R}`.slice(0,240),L=x?`claude:ask:${x.tabs.map(T=>T.label).join("|")}`.slice(0,240):I;return{provider:"claude",box:e,multiSelect:f,cursorIndex:p,promptId:I,groupId:L,...x?{questionIndex:x.questionIndex,questionTotal:x.questionTotal,currentTabLabel:x.currentTabLabel,tabs:x.tabs}:{},questions:[{question:b,header:v,multiSelect:f,options:E}]}}function sr(t,e){let n=String(e||"");return n?t==="claude"?Ap.test(n)&&cc.test(n)||Tp.test(n)||ac.test(n)||_p.test(n.trimEnd().split(`
|
|
73
|
+
`).pop()||"")?!0:yn.test(n)?!1:Ep.test(n)&&Ip.test(n)||Rp.test(n)||dc.test(n)||ic.test(n)||$p.test(n)||Op.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 bn(t,e){return!t||!e?null:t.agent==="claude"?y0(e,t.name)||O0(e,t.name)||h0(e,t.name)||k0(e,t.name)||E0(e,t.name)||T0(e,t.name)||S0(e,t.name)||x0(e,t.name)||I0(e,t.name)||R0(e,t.name)||g0(e,t.name):t.agent==="cursor"||t.agent==="codex"?Au(t.agent,e,t.name):null}async function fc(t){if(!t)return null;let e=await ve(t.name).catch(()=>"");return bn(t,e)}function Cp(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 Mp(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 ar(t){return typeof t=="string"?t.trim():""}var Pp=/^\s*─{8,}\s*$/,C0=/^❯[ ]?(.*)$/;function pc(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(Pp.test(n[c])){r=c;break}if(r<=0)return null;let o=-1;for(let c=r-1;c>=0;c--)if(Pp.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(C0);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 kn(t){return String(t||"").replace(/\s+/g," ").trim()}function Np(t,e){let n=kn(t),r=kn(e).replace(/^…\s*/,"").replace(/\s*…$/,"");return!n||!r||r.length<Math.min(n.length,12)?!1:n===r||n.includes(r)}import{execFile as M0}from"node:child_process";import{promisify as P0}from"node:util";var hc=P0(M0),Bp=64*1024*1024;async function ft(t,e,{input:n}={}){let r=hc("git",["-C",t,...e],{maxBuffer:Bp});return n!==void 0&&r.child.stdin&&(r.child.stdin.on("error",()=>{}),r.child.stdin.end(n)),r}async function _s(t){if(!t)return!1;try{let{stdout:e}=await ft(t,["rev-parse","--is-inside-work-tree"]);return e.trim()==="true"}catch{return!1}}async function Dp(t){let{stdout:e}=await ft(t,["rev-parse","--show-toplevel"]);return e.trim()}async function N0(t){try{return await ft(t,["rev-parse","--verify","HEAD"]),!0}catch{return!1}}async function wc(t,e="bostrat: subtask snapshot"){let{stdout:n}=await ft(t,["rev-parse","--absolute-git-dir"]),r=`${n.trim()}/bostrat-snapshot-index`,o={...process.env,GIT_INDEX_FILE:r},s=(a,i)=>hc("git",["-C",t,...a],{maxBuffer:Bp,env:o,...i});try{let a=await N0(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 hc("rm",["-f",r])}catch{}}}async function gc(t,e){let{stdout:n}=await ft(t,["status","--porcelain"]);return n.trim()?(await ft(t,["add","-A"]),await ft(t,["-c","user.email=agent@bostrat.local","-c","user.name=bostrat","commit","-m",e]),!0):!1}async function yc(t,{ignorePlumbing:e=!1}={}){let{stdout:n}=await ft(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 bc(t,e){if(!e)return 0;try{let{stdout:n}=await ft(t,["rev-list","--count",`${e}..HEAD`]);return Number(n.trim())||0}catch{return 0}}async function ir(t,e,n,r){return await ft(t,["worktree","add","--no-track","-b",n,e,r]),e}async function jp(t,e){try{return await ft(t,["rev-parse","--verify","--quiet",`refs/heads/${e}`]),!0}catch{return!1}}async function Lp(t,e,n){return await ft(t,["worktree","add",e,n]),e}async function Es(t){if(process.env.BOSTRAT_TRACK_BASE_REF)return process.env.BOSTRAT_TRACK_BASE_REF;try{let{stdout:e}=await ft(t,["symbolic-ref","--short","refs/remotes/origin/HEAD"]);if(e.trim())return e.trim()}catch{}for(let[e,n]of[["refs/remotes/origin/main","origin/main"],["refs/remotes/origin/master","origin/master"],["refs/heads/main","main"],["refs/heads/master","master"]])try{return await ft(t,["rev-parse","--verify","--quiet",e]),n}catch{}return"HEAD"}async function Fp(t){await ft(t,["init","-q"]),await ft(t,["-c","user.email=agent@bostrat.local","-c","user.name=bostrat","commit","--allow-empty","-qm","bostrat: track workspace"])}async function Ft(t,e){try{await ft(t,["worktree","remove","--force",e])}catch{try{await ft(t,["worktree","prune"])}catch{}}}function B0(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 kc(t,e){return!e||!e.length?t:[...t,"--",".",...e.map(n=>`:(exclude)${n}`)]}async function xc(t,e,n,r){let{stdout:o}=await ft(t,kc(["diff","--shortstat",e,n],r));return B0(o)}async function Sc(t,e,n,r){let{stdout:o}=await ft(t,kc(["diff","--name-only",e,n],r));return o.split(`
|
|
77
|
+
`).map(s=>s.trim()).filter(Boolean)}async function vc(t,e,n,r){let{stdout:o}=await ft(t,kc(["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 D0(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 ft(t,["apply",...n,"-"],{input:e}),{ok:!0}}catch(r){return{ok:!1,err:r?.stderr?.trim()||r?.message||String(r)}}}async function D0(t){try{let{stdout:e}=await ft(t,["diff","--name-only","--diff-filter=U"]);return e.split(`
|
|
78
|
+
`).map(n=>n.trim()).filter(Boolean)}catch{return[]}}import j0 from"node:fs/promises";import L0 from"node:os";import Up from"node:path";var Is=t=>new Promise(e=>setTimeout(e,t)),F0=Number(process.env.BOSTRAT_MODEL_PICKER_TIMEOUT_MS)||8e3,U0=300,Gp=[{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"}],H0=new Set([...Gp.map(t=>t.alias),"best","opus[1m]","sonnet[1m]","claude-opus-4-8","claude-sonnet-4-6","claude-haiku-4-5","claude-fable-5"]),W0=[{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"}],Kp=new Map([["low","Low"],["medium","Medium"],["high","High"],["xhigh","Extra high"]]),Vp=[{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 Jp(t){return t==="codex"?W0:t==="cursor"?Vp:Gp}function Hp(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 Wp(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 q0(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 qp(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 zp(t,e){if(typeof e!="string"||!e.trim())throw new Error("model (string) required");let n=e.trim();if(t==="codex"){let[r,o]=n.split(/[\s:]+/);if(o&&!Kp.has(o))throw new Error(`unknown codex effort "${o}" (low | medium | high | xhigh)`);return{model:r,effort:o||null}}if(t==="cursor"){let r=Vp.find(o=>o.alias===n);return{model:n,filter:r?r.label:n}}if(!H0.has(n))throw new Error(`unknown model "${n}"`);return{model:n}}async function eo(t,e,{timeoutMs:n=F0}={}){let r=Date.now()+n;for(;;){let o=await wt(t,{lines:80}).catch(()=>""),s=e(o);if(s)return s;if(Date.now()>r)return null;await Is(U0)}}async function G0(t,e){return await kt(t,`/model ${e.model}`,le("claude")),{verified:!1,note:"confirmed when the next turn's transcript model flips"}}async function K0(t,e){await Bo(t),await kt(t,"/model",{enter:!1}),await Is(250),await bt(t,["Enter"],{kind:"model-switch"});let n=await eo(t,Hp);if(!n)throw new Error("codex model picker didn't open");let r=n.find(i=>i.id===e.model);if(!r)throw await bt(t,["Escape"],{kind:"model-switch"}).catch(()=>{}),new Error(`unknown model "${e.model}" \u2014 codex offers: ${n.map(i=>i.id).join(", ")}`);let o=i=>{let c=q0(i);return!c||c.model!==e.model||e.effort&&c.effort&&c.effort.toLowerCase()!==e.effort.toLowerCase()?null:c};await kt(t,String(r.n),{enter:!1,kind:"model-switch"});let s=await eo(t,i=>Wp(i)||!Hp(i)&&o(i)&&{done:o(i)});if(!s)throw await bt(t,["Escape"],{kind:"model-switch"}).catch(()=>{}),new Error("codex reasoning-level picker didn't open");if(!s.done){let i=e.effort?Kp.get(e.effort):null,c=i?s.find(l=>l.label.toLowerCase()===i.toLowerCase()):s.find(l=>l.isDefault)||s[0];if(!c)throw await bt(t,["Escape","Escape"],{kind:"model-switch"}).catch(()=>{}),new Error(`codex doesn't offer effort "${e.effort}" for ${e.model}`);await kt(t,String(c.n),{enter:!1,kind:"model-switch"})}let a=s.done||await eo(t,i=>!Wp(i)&&o(i));if(!a)throw new Error("codex never confirmed the model change");return{verified:!0,model:a.model,effort:a.effort}}async function V0(){let t=Up.join(process.env.CURSOR_CONFIG_DIR||Up.join(L0.homedir(),".cursor"),"cli-config.json");try{return JSON.parse(await j0.readFile(t,"utf8"))?.model||null}catch{return null}}async function J0(t,e){await Bo(t),await jo(t,`/model ${e.filter}`,{kind:"model-switch"});let n=await eo(t,qp),r=c=>String(c||"").toLowerCase(),o=c=>r(c.label).startsWith(r(e.filter)),s=n?.rows.find(c=>c.active)||null;if(n&&!(s&&o(s)))for(let c=0;c<12;c++){let l=await eo(t,qp,{timeoutMs:1500});if(s=l?.rows.find(u=>u.active)||null,s&&o(s))break;if(!l||!l.rows.some(o)){s=null;break}await bt(t,["Down"],{kind:"model-switch"}),await Is(250)}if(!n||!s||!o(s)){await bt(t,["Escape"],{kind:"model-switch"}).catch(()=>{});let c=Array(`/model ${e.filter}`.length).fill("BSpace");throw await bt(t,c,{settleMs:15,kind:"model-switch"}).catch(()=>{}),new Error(n?`cursor picker has no model matching "${e.filter}"`:"cursor model picker didn't open")}await bt(t,["Enter"],{kind:"model-switch"}),await Is(800);let a=await V0();return{verified:!!a&&(a.modelId===e.model||a.displayModelId===e.model||r(a.displayName).startsWith(r(e.filter))),model:a?.modelId||null,note:"cursor applies this as the global default for new sessions too"}}async function Xp(t,e,n){return t==="codex"?K0(e,n):t==="cursor"?J0(e,n):G0(e,n)}var Nt=t=>t||null;function $s(t){let e=t||{},n=z0(e);return n&&(n.payload={ts:e.at,...n.payload}),n}function z0(t){switch(t.type){case"said":return{type:"turn.user",actor:"user",source:"orchestrator",boxId:t.box,provider:Nt(t.agent),payload:{text:t.text,boxRole:t.boxRole,...t.kind?{kind:t.kind}:{},...t.attachments?.length?{attachments:t.attachments}:{}}};case"unsaid":return{type:"turn.retracted",actor:"user",source:"orchestrator",boxId:t.box,provider:Nt(t.agent),payload:{saidTs:t.saidTs,text:t.text,boxRole:t.boxRole}};case"orchestration-prompt":return{type:"turn.user",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Nt(t.agent),payload:{text:t.prompt,kind:t.kind,boxRole:t.boxRole}};case"lead-handoff":return{type:"handoff.done",actor:"orchestrator",source:"orchestrator",boxId:t.to,provider:Nt(t.toAgent),payload:{from:t.from,fromAgent:t.fromAgent,to:t.to,toAgent:t.toAgent,reason:t.reason,handoffStartedAt:t.handoffStartedAt,fromSessionId:t.fromSessionId,toSessionId:t.toSessionId,epoch:t.epoch,singleBox:t.singleBox,forced:t.forced,recovered:t.recovered}};case"lead-handoff-failed":return{type:"handoff.failed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Nt(t.toAgent),payload:{box:t.box,fromAgent:t.fromAgent,toAgent:t.toAgent,reason:t.reason,error:t.error,rolledBack:t.rolledBack,handoffStartedAt:t.handoffStartedAt}};case"subtask-delegated":return{type:"subtask.delegated",actor:"orchestrator",source:"orchestrator",boxId:t.to,provider:Nt(t.toAgent),payload:{task:t.task,subtaskId:t.subtaskId,reason:t.reason}};case"subtask-merged":return{type:"subtask.merged",actor:"orchestrator",source:"orchestrator",boxId:t.box||t.from,provider:Nt(t.agent),payload:{...cr(t)}};case"subtask-failed":return{type:"error",actor:"system",source:"orchestrator",boxId:t.box||t.to,provider:Nt(t.agent||t.toAgent),payload:{kind:"subtask-failed",...cr(t)}};case"subtask-resumed":return{type:"box.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Nt(t.agent),payload:{change:"subtask-resumed",...cr(t)}};case"model-change":return{type:"box.state.changed",actor:"user",source:"orchestrator",boxId:t.box,payload:{change:"model",model:t.to,from:t.from??null,verified:t.verified??null}};case"codex-update":return{type:"box.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:"codex",payload:{change:"cli-update",from:t.from??null,to:t.to??null,status:t.status,...t.error?{error:t.error}:{}}};case"opened":return{type:"box.state.changed",actor:"user",source:"orchestrator",boxId:t.box,payload:{change:"opened",...cr(t)}};case"archived":case"unarchived":case"track-deleted":return{type:"box.state.changed",actor:"user",source:"orchestrator",payload:{change:t.type,...cr(t)}};case"rehydrated":return{type:"box.state.changed",actor:"orchestrator",source:"orchestrator",boxId:t.box,provider:Nt(t.agent),payload:{change:"rehydrated"}};case"retagged":return{type:"box.state.changed",actor:"user",source:"orchestrator",payload:{change:"retagged",...cr(t)}};case"rehydrate-failed":return{type:"error",actor:"system",source:"orchestrator",boxId:t.box,provider:Nt(t.agent),payload:{kind:"rehydrate-failed",reason:t.reason}};default:return null}}var Rs=Promise.resolve(),Tc=t=>(Rs=Rs.then(t).catch(()=>{}),Rs),Zp=()=>Rs;function Qp(t,e){let n=$s(e);if(!n)return;let r=e?.type==="track-deleted"?{id:t.id}:t;Tc(()=>O(r,n))}var Yp=new Set;async function tm(t){if(!(!t?.id||Yp.has(t.id))){Yp.add(t.id);try{let e=await F(t),n=new Set(e.filter(r=>r.source==="orchestrator").map(r=>`${r.type}|${r.payload?.ts??"?"}`));for(let r of t.events||[]){let o=$s(r);!o||n.has(`${o.type}|${o.payload?.ts??"?"}`)||await O(t,o)}}catch{}}}function cr(t){let{type:e,at:n,...r}=t;return r}var Ac=new Map;function em(t,e,n){!n?.promptId||Ac.get(t.id)===n.promptId||(Ac.set(t.id,n.promptId),Tc(()=>O(t,{type:"prompt.raised",actor:"agent",source:"pane",boxId:e?.name,provider:Nt(n.provider||e?.agent),payload:{promptId:n.promptId,question:n.question||n.title||null,options:(n.options||[]).map(r=>r?.label??r).slice(0,12)}})))}function nm(t,e,n,r={}){Ac.delete(t.id),Tc(()=>O(t,{type:"prompt.answered",actor:"user",source:"orchestrator",boxId:e?.name,provider:Nt(n?.provider||e?.agent),payload:{promptId:n?.promptId,...r}}))}import{randomUUID as ES}from"node:crypto";var om=/(?<!\b(?:a|an|the|this|that|another|one|each|any|no|separate|single|new|initial|final|first|second|third|last|later|next|subsequent|extra|additional|amend(?:ed)?|squash|merge|follow[\s-]?up|clean[\s-]?up)\s)commit(?!s\b)(?!\s+(?:history|log|message|hash|sha)s?\b)/.source,sm=/(?<!\b(?:a|an|the|this|that|another|one|each|any|no|separate|single|new|initial|final|first|second|third|last|later|next|subsequent|extra|additional)\s)push(?!\s+notifications?\b)(?:\s+(?:it|this|that|them|everything|the\s+\w+))?\s*(?:up)?/.source,am=/open(?:\s+up)?\s+(?:a|the|that)\s+(?:draft\s+)?(?:pr|pull\s+request)|creat(?:e|ing)\s+(?:a|the)\s+(?:draft\s+)?(?:pr|pull\s+request)|rais(?:e|ing)\s+(?:a|the)\s+(?:pr|pull\s+request)|put\s+up\s+(?:a|the)\s+(?:pr|pull\s+request)|send\s+(?:up\s+)?(?:a|the)\s+(?:pr|pull\s+request)/.source,im=/(?<!\b(?:a|an|the|this|that|another|one|each|any|no|clean|interactive)\s)rebase(?!s\b)(?:\s+(?:it|this|that|them|(?:this|that|the)\s+branch))?(?:\s+(?:on(?:to)?|against|atop|over)\s+\S+)?/.source,X0=new RegExp(`\\b(?:${om}|${sm}|${am}|${im})\\b`,"i"),Y0=/\b(?:want\s+me\s+to|would\s+you\s+like(?:\s+me)?(?:\s+to)?|should\s+i|shall\s+i|do\s+you\s+want(?:\s+me)?(?:\s+to)?|happy\s+to|ready\s+for\s+me\s+to|let\s+me\s+know\s+(?:if|when|whether|and)|say\s+the\s+word|give\s+me\s+the\s+(?:word|go[\s-]?ahead)|if\s+you(?:'d|\s+would)?\s+(?:like|want|prefer)|just\s+say\s+(?:so|yes|go))\b/i,Z0=/^(?:commit|push|open|create|raise|rebase|ready|ok(?:ay)?\s+to|good\s+to)\b/i,Q0=90;function tS(t){return String(t).split(/(?<=[.?!])\s+|\n+/).map(e=>e.trim()).filter(Boolean)}function cm(t){let e=String(t||"").trim();if(!e)return null;let n=e.slice(-600);for(let r of tS(n).reverse()){if(!X0.test(r))continue;let o=r.endsWith("?")&&r.length<=Q0&&Z0.test(r);if(Y0.test(r)||o)return r.length>200?r.slice(0,197)+"\u2026":r}return null}var lm=["commit","rebase","push","pr"],eS=new RegExp(`\\b(?:${om})\\b`,"i"),nS=new RegExp(`\\b(?:${im})\\b`,"i"),rS=new RegExp(`\\b(?:${sm})\\b`,"i"),oS=new RegExp(`\\b(?:${am})\\b`,"i"),sS=/\b(?:to|into|onto|on|updat(?:e|es|ing))\s+(?:the|that|this)\s+(?:open\s+|existing\s+|current\s+|same\s+)?(?:draft\s+)?(?:pr|pull\s+request)\b/i;function um(t){let e=String(t||""),n=[];eS.test(e)&&n.push("commit"),nS.test(e)&&n.push("rebase"),rS.test(e)&&n.push("push");let r=sS.test(e);!r&&oS.test(e)&&n.push("pr");let o=null;if(r){let s=e.match(/(?:pr|pull\s+request)\s*\(?\s*#(\d+)\)?/i)||e.match(/#(\d+)/);o={number:s?Number(s[1]):null}}return{offered:n.length?n:null,toPr:o}}function Os(t,{branch:e=null,base:n=null,toPr:r=null,repos:o=null,offeredRebase:s=!1}={}){Array.isArray(t)||(t=t==="push"?["commit","push"]:["commit","push","pr"]);let a=new Set(t),i=Array.isArray(o)&&o.length>0,c=i?o.map(p=>`${p.name}/`).join(", "):null,l=e?` on this branch (${e})`:"",u=[];if(a.has("commit")&&u.push(i?`commit the work in each changed checkout (${c})${l} with a clear commit message per repo`:`commit the work${l} with a clear commit message`),a.has("rebase")){let p=a.has("commit")?"the branch":`this branch${e?` (${e})`:""}`;u.push(i?`rebase ${p} in each changed checkout (${c}) onto its own base branch (fetch origin first), resolving any conflicts so both sides' intent is preserved`:`rebase ${p} onto ${n?`the latest ${n}`:"its base branch"} (fetch origin first), resolving any conflicts so both sides' intent is preserved`)}if(a.has("push")||a.has("pr")){let p=r&&!a.has("pr")?` (the open PR${r.number?` #${r.number}`:""} picks it up)`:"",m=a.has("rebase")?" with `--force-with-lease` (the rebase rewrote history)":"";i?u.push(a.has("commit")||a.has("rebase")?`push each one to its remote${m}${p}`:`push this branch${e?` (${e})`:""} from each changed checkout (${c}) to its remote${p}`):u.push(a.has("commit")||a.has("rebase")?`push it to the remote${m}${p}`:`push this branch${l} to the remote${p}`)}a.has("pr")&&u.push(i?"open a pull request in each changed repo with a concise title and a description of what changed and why (run `gh pr create` inside each checkout)":`open a pull request${n?` against ${n}`:""} with a concise title and a description of what changed and why (use \`gh pr create\`)`);let d=[];a.has("commit")||d.push("don't create any new commits \u2014 ship what's already committed"),s&&!a.has("rebase")&&d.push("don't rebase \u2014 leave the branch's history as it is"),!a.has("push")&&!a.has("pr")&&d.push("don't push"),a.has("pr")||d.push("don't open a pull request");let f=`Yes \u2014 go ahead: ${u.join(", then ")}.`;return d.length&&(f+=` ${d.join(", and ").replace(/^./,p=>p.toUpperCase())}.`),a.has("pr")&&(f+=i?" If a repo's PR for this branch is already open, just push that repo's update instead.":" If a PR for this branch is already open, just push the update instead."),f}var aS=3e4;function dm(t,e=Date.now()){let n=[],r=null;for(let o of t||[]){let s=o.payload||{},a=s.ts??o.ts;if(o.type==="pr.offer.answered"&&s.action==="pr"){r={box:o.boxId,from:a,lastActivity:a};continue}!r||a==null||a<r.from||(o.type==="pr.offer.answered"?r=null:o.type==="pr.state.changed"&&s.change==="opened"?(n.push({box:r.box,from:r.from,to:a,settled:!0}),r=null):o.type==="turn.user"&&o.source!=="hook"&&!s.kind&&String(s.text||"").trim()?r=null:o.boxId===r.box&&/^(?:turn|tool)\./.test(o.type)&&(r.lastActivity=Math.max(r.lastActivity,a)))}return r&&e-r.lastActivity<=aS&&n.push({box:r.box,from:r.from,to:e+1e12}),n}function fm(t,e){let n=new Set;for(let r of e||[]){if(!r.settled)continue;let o=null;for(let s of t||[])s.kind!=="message"||s.role!=="assistant"||s.box!==r.box||s.ts==null||s.ts<r.from||s.ts>r.to||(!o||s.ts>=o.ts)&&(o=s);o&&n.add(o)}return n}var rm={commit:"commit",rebase:"rebase",push:"push",pr:"open a PR"};function pm(t){let e=(Array.isArray(t)?t:[]).filter(r=>rm[r]).map(r=>rm[r]);return`The ship you were given the go-ahead for${e.length?` (${e.join(", then ")})`:""} should now be complete. This is an automated ship notification the operator armed on the offer: carry straight on with this Track's follow-up work (dependent tasks, cleanup, next steps) \u2014 don't wait for review or merge. If a ship step failed or is unfinished, finish the ship first. If nothing needs doing, say so briefly.`}import{execFile as dS}from"node:child_process";import{promisify as fS}from"node:util";import iS from"node:fs";import _c from"node:fs/promises";import gm from"node:path";import cS from"node:os";var ym=process.env.BOSTRAT_STATE_DIR||gm.join(process.env.HOME||cS.tmpdir(),".bostrat"),Cs=gm.join(ym,"github-metrics.json"),bm=60*6e4,mm=5*6e4,lS=6e4,lr=Date.now(),he=new Map,De=null,xn=0,hm=0,Ms=t=>[...t.values()].reduce((e,n)=>e+n,0);try{let t=JSON.parse(iS.readFileSync(Cs,"utf8"));t?.windowStart&&(Date.now()-t.windowStart<bm?(lr=t.windowStart,he=new Map(Object.entries(t.byCaller||{})),De=t.lastHour||null):Ms(new Map(Object.entries(t.byCaller||{})))&&(De={start:t.windowStart,total:Ms(new Map(Object.entries(t.byCaller||{}))),byCaller:t.byCaller||{}}))}catch{}var wm=Promise.resolve();function km(t,e=!1){if(!e&&t-hm<lS)return;hm=t;let n=JSON.stringify({windowStart:lr,byCaller:Object.fromEntries(he),lastHour:De});wm=wm.then(()=>_c.mkdir(ym,{recursive:!0})).then(()=>_c.writeFile(`${Cs}.tmp`,n)).then(()=>_c.rename(`${Cs}.tmp`,Cs)).catch(()=>{})}function xm(t){t-lr<bm||(De={start:lr,total:Ms(he),byCaller:Object.fromEntries(he)},De.total&&console.log(`[github] ${De.total} calls last hour`,De.byCaller),lr=t,he=new Map,km(t,!0))}function we(t="other",e=Date.now()){xm(e),he.set(t,(he.get(t)||0)+1),km(e)}function Sm(t=Date.now()){return xm(t),{windowStart:lr,total:Ms(he),byCaller:Object.fromEntries(he),lastHour:De,backoffUntil:xn>t?xn:null}}var uS=/rate limit|too quickly|abuse detection|HTTP 429/i;function je(t){return uS.test(String(t?.message||t||""))}function Le(t,e=Date.now()){return xn<=e&&console.warn(`[github] rate limited \u2014 backing off ${mm/6e4} min:`,String(t?.message||t||"").slice(0,200)),xn=Math.max(xn,e+mm),xn}function Ps(t=Date.now()){return Math.max(0,xn-t)}async function Ec(t,e){let n={working:!1,prompted:!1};if(!t)return n;let r=await wt(t,{lines:_t}).catch(()=>"");if(!jn(e,r))return Iu(e,r)?{working:!0,prompted:!1}:n;if(sr(e,r)){let o=await ve(t).catch(()=>r);if(bn({name:t,agent:e},o))return{working:!1,prompted:!0}}return{working:!0,prompted:!1}}async function Ns(t,e){return(await Ec(t,e)).working}var vm=fS(dS),Am=[4e3,15e3,45e3],pS=2500,mS=5*6e4;async function Ic(t,e,{midTurn:n=Ns,pollMs:r=pS,capMs:o=mS}={}){let s=Date.now()+o;for(;await n(t,e).catch(()=>!1);){if(Date.now()>=s)return!1;await new Promise(a=>setTimeout(a,r))}return!0}var hS=6e4,wS=9e4,gS=2e3,yS=/(?:^|[;&|]\s*|\)\s*)gh\s+(?:[^\s;&|"'`$]+\s+)*?pr\s+create\b/,bS=/(?:^|[;&|]\s*|\)\s*)gh\s+(?:[^\s;&|"'`$]+\s+)*?pr\s+merge\b/,kS=/\s--web\b/,Tm=/(?:--repo|-R)[\s=]+([\w.-]+\/[\w.-]+)/;function Rc(t,e){return t.slice(e).replace(/^[\s;&|)]+/,"").replace(/"[^"]*"|'[^']*'/g,'""').split(/[;&|]/)[0]}function _m(t){let e=String(t||""),n=e.match(yS);if(!n)return null;let r=Rc(e,n.index);return kS.test(r)?null:{repo:r.match(Tm)?.[1]??null}}var xS=new Set(["--subject","-t","--body","-b","--body-file","-F","--match-head-commit","--author-email","-A","--repo","-R"]);function Em(t){let e=String(t||""),n=e.match(bS);if(!n)return null;let r=Rc(e,n.index),o=Rc(e,n.index+n[0].length),s=null,a=o.trim().split(/\s+/).filter(Boolean);for(let i=0;i<a.length;i++){let c=a[i];if(c.startsWith("-")){xS.has(c)&&i++;continue}s=c.replace(/^["']+|["']+$/g,"")||null;break}return{repo:r.match(Tm)?.[1]??null,selector:s,auto:/\s--auto\b/.test(r)}}var SS="number,url,title,headRefName,createdAt";async function vS(t,e){let n=async s=>{we("observer");let{stdout:a}=await vm("gh",["pr","list","--repo",t,"--author","@me","--state","all","--limit","10","--json",SS],{timeout:3e4,maxBuffer:4*1024*1024,env:{...process.env,...s}}).catch(i=>{throw je(i)&&Le(i),i});return JSON.parse(a||"[]")},r=await re({slug:t}),o=await n(r);return!o.length&&r.GH_TOKEN&&(o=await n({}).catch(()=>[])),o.filter(s=>Date.parse(s.createdAt)>=e-hS).sort((s,a)=>Date.parse(a.createdAt)-Date.parse(s.createdAt))}async function AS(t,e){if(e)return[e];let n=[];for(let r of Im(t)){let o=await Y(r).catch(()=>null);o&&!n.includes(o)&&n.push(o)}for(let r of await me().catch(()=>[])){let o=await Y(r.path).catch(()=>null);o&&!n.includes(o)&&n.push(o)}return n}function Im(t){let e=t.workspace;return e?.kind==="multi"?(e.repos??[]).map(n=>n?.base).filter(Boolean):[e?.base||t.cwd].filter(Boolean)}async function TS(t,e,n){let r=t.workspace?.branch,o=[];for(let s of await AS(t,e)){let a;try{a=await vS(s,n)}catch{continue}for(let i of a){let c=await vt(s,i.number);if(!(c&&(c.trackId!==t.id||c.announced))){if(r&&i.headRefName===r)return{slug:s,pr:i};o.push({slug:s,pr:i})}}}return o.length?(o.sort((s,a)=>Date.parse(a.pr.createdAt)-Date.parse(s.pr.createdAt)),o[0]):null}var Fe=new Set;function Rm(t,e,{delays:n=Am,turnEnd:r}={}){let o=_m(e.command);if(!o)return null;let s=!!Em(e.command),a=`${t.id}|${e.id??e.ts??e.command}`;if(Fe.has(a))return null;Fe.add(a);let i=e.ts??Date.now(),c=(async()=>{try{for(let l of n){await new Promise(h=>setTimeout(h,l));let u=await TS(t,o.repo,i).catch(()=>null);if(!u)continue;let{slug:d,pr:f}=u,{link:p,created:m}=await Pt({repo:d,number:f.number,trackId:t.id,url:f.url,headRef:f.headRefName,title:f.title,source:"observed"});return!m&&!(p?.trackId===t.id&&await Gr(d,f.number))?null:(await Ic(e.box,e.agent,r),await O({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:Date.now(),change:"opened",number:f.number,url:f.url,title:f.title,branch:f.headRefName,repo:d,...s?{selfMerge:!0}:{}}}),f)}return null}finally{Fe.delete(a)}})();return c.catch(()=>Fe.delete(a)),c}var _S="number,url,title,headRefName,state";function $m(t,e,{delays:n=Am,turnEnd:r,openedWait:o}={}){let s=Em(e.command);if(!s)return null;let a=`merge|${t.id}|${e.id??e.ts??e.command}`;if(Fe.has(a))return null;Fe.add(a);let i=(async()=>{try{let c=s.repo,l=s.selector,u=l?.match(/github\.com\/([\w.-]+\/[\w.-]+)\/pull\/(\d+)/);u&&([,c,l]=u);let d=[];if(c)d=[c];else{let m=Im(t);if(m.length>1&&/^\d+$/.test(String(s.selector??"")))return null;for(let h of m){let b=await Y(h).catch(()=>null);b&&!d.includes(b)&&d.push(b)}}if(l||=t.workspace?.branch,!d.length||!l)return null;c=d[0];let f=async(m,h)=>{let b=await vt(c,m.number);return b&&b.trackId!==t.id?null:(b||await Pt({repo:c,number:m.number,trackId:t.id,url:m.url,headRef:m.headRefName,title:m.title,source:"merge-observed"}),await Kr(c,m.number),h&&((await F(t).catch(()=>[])).some(E=>E.type==="pr.state.changed"&&(E.payload?.change==="auto-armed"||E.payload?.change==="merged")&&E.payload?.number===m.number)||(await Ic(e.box,e.agent,r),await O({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:Date.now(),change:"auto-armed",number:m.number,url:m.url,title:m.title,branch:m.headRefName,repo:c}}))),m)},p=null;for(let m of n){await new Promise(I=>setTimeout(I,m));let h=[];for(let I of d)try{we("observer");let{stdout:L}=await vm("gh",["pr","view",String(l),"--repo",I,"--json",_S],{timeout:3e4,maxBuffer:4*1024*1024,env:{...process.env,...await re({slug:I})}}),T=JSON.parse(L||"null");T?.number&&h.push({slug:I,pr:T})}catch(L){je(L)&&Le(L)}let b=h.find(I=>I.pr.state==="MERGED")??(h.length===1?h[0]:null);if(!b)continue;c=b.slug;let x=b.pr;if(!x?.number)continue;if(x.state==="CLOSED")return null;if(x.state!=="MERGED"){if(p=x,s.auto)return f(x,!0);continue}let v=await vt(c,x.number);if(v&&v.trackId!==t.id)return null;v||await Pt({repo:c,number:x.number,trackId:t.id,url:x.url,headRef:x.headRefName,title:x.title,source:"merge-observed"}),await Ic(e.box,e.agent,r);let E=await F(t).catch(()=>[]);if(E.some(I=>I.type==="pr.state.changed"&&I.payload?.change==="merged"&&I.payload?.number===x.number))return null;let R=I=>I.some(L=>L.type==="pr.state.changed"&&L.payload?.change==="opened"&&L.payload?.number===x.number),st=0;if(!R(E)){if(_m(e.command)&&await Gr(c,x.number)){let I=Date.now();st=I+1,await O({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:I,change:"opened",number:x.number,url:x.url,title:x.title,branch:x.headRefName,repo:c,selfMerge:!0}})}else if((await vt(c,x.number))?.announced){let I=o?.capMs??wS,L=o?.pollMs??gS,T=Date.now()+I;for(;Date.now()<T&&(await new Promise(U=>setTimeout(U,L)),!R(await F(t).catch(()=>[]))););}}return await O({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"agent",source:"transcript",boxId:e.box??null,provider:e.agent??null,payload:{ts:Math.max(Date.now(),st),change:"merged",number:x.number,url:x.url,title:x.title,branch:x.headRefName,repo:c}}),await He(c,x.number).catch(()=>{}),await Ue(c,x.number).catch(()=>{}),await Ce(c,x.number,"merged"),x}return p?f(p,!1):null}finally{Fe.delete(a)}})();return i.catch(()=>Fe.delete(a)),i}function Oc(t){return t.sourceId?`${t.role}|id:${t.sourceId}`:`${t.role}|${t.ts??"?"}|${String(t.text||"").slice(0,40)}`}function Om(t){return t.id?`cmd|${t.box}|id:${t.id}`:`cmd|${t.box}|${t.ts??"?"}|${String(t.command||"").slice(0,40)}`}function Cm(t){return`job|${t.box}|id:${t.id}`}var $c=new Map,Bs=new Map;async function Cc(t){return $c.has(t.id)?$c.get(t.id):(Bs.has(t.id)||Bs.set(t.id,F(t).then(e=>{let n={keys:new Set,cmdN:0};for(let r of e)if((r.type==="turn.user"||r.type==="turn.agent")&&r.payload?.text!=null){let o=r.type==="turn.user"?"user":"assistant";r.payload.sourceId&&n.keys.add(Oc({role:o,sourceId:r.payload.sourceId})),n.keys.add(Oc({role:o,ts:r.payload.ts,text:r.payload.text}))}else if(r.type==="tool.called"&&r.source==="transcript"&&r.payload?.command){let o=r.payload;n.keys.add(Om({id:o.id,box:r.boxId,ts:o.ts,command:o.command})),Number(o.n)>n.cmdN&&(n.cmdN=Number(o.n))}else r.type==="job.started"&&r.payload?.id&&n.keys.add(Cm({id:r.payload.id,box:r.boxId}));return $c.set(t.id,n),n}).finally(()=>Bs.delete(t.id))),Bs.get(t.id))}async function Mm(t,e){try{let n=(await Cc(t)).keys;for(let r of e||[]){if(!r||r.ts==null||!String(r.text||"").trim()&&!r.images?.length)continue;let o=Oc(r);if(!n.has(o)&&(n.add(o),await O(t,{type:r.role==="user"?"turn.user":"turn.agent",actor:r.role==="user"?"user":"agent",source:"transcript",boxId:r.box,provider:r.agent||null,payload:{text:r.text,ts:r.ts,model:r.model||null,tools:r.tools||void 0,boxRole:r.boxRole,sourceId:r.sourceId||void 0,note:r.note||void 0,images:r.images?.length?r.images:void 0}}),r.role==="assistant"&&r.box===t.leadBox&&t.workspace?.branch)){let s=cm(r.text);if(s){let{offered:a,toPr:i}=um(s),c;if(t.workspace?.kind==="multi"){let l=await no(t).catch(()=>[]);c=l.length?l.map(u=>({name:u.name,slug:u.repo??null})):void 0}await O(t,{type:"pr.offered",actor:"agent",source:"transcript",boxId:r.box,provider:r.agent||null,payload:{offerId:ES(),ts:r.ts,excerpt:s,branch:t.workspace.branch,base:t.workspace.baseRef?.replace(/^origin\//,"")||null,boxRole:r.boxRole,offered:a||void 0,toPr:i||void 0,repos:c}})}}}}catch{}}async function Pm(t,e){try{let n=await Cc(t),r=[...e||[]].filter(o=>o&&String(o.command||"").trim()).sort((o,s)=>(o.ts||0)-(s.ts||0));for(let o of r){let s=Om(o);n.keys.has(s)||(n.keys.add(s),n.cmdN+=1,await O(t,{type:"tool.called",actor:"agent",source:"transcript",boxId:o.box,provider:o.agent||null,payload:{id:o.id??null,tool:o.tool||null,command:o.command,description:o.description||"",summary:o.summary||o.command,ts:o.ts??null,boxRole:o.boxRole,n:n.cmdN}}),Rm(t,o),$m(t,o))}}catch{}}async function Nm(t,e){try{let n=await Cc(t),r=[...e||[]].filter(o=>o&&o.id).sort((o,s)=>(o.ts||0)-(s.ts||0));for(let o of r){let s=Cm(o);n.keys.has(s)||(n.keys.add(s),await O(t,{type:"job.started",actor:"agent",source:"transcript",boxId:o.box,provider:o.agent||null,payload:{id:o.id,kind:o.kind||"agent",name:o.name||null,detail:o.detail||null,ts:o.ts??null,boxRole:o.boxRole}}))}}catch{}}var IS=/^Base directory for this skill:\s*(\S+)/,RS=/^Run the "([^"]+)" workflow\./,$S=/<command-name>\s*\/?([a-z0-9:_-]+)\s*<\/command-name>/i,OS=/^<local-command-(?:stdout|caveat)>/i,CS=/^\[Image: original \d+x\d+, displayed at \d+x\d+\.\s*Multiply coordinates by [\d.]+ to map to original image\.\]$/,MS=/^<task-notification>/i,PS=/<summary>([\s\S]*?)<\/summary>/i,NS=/<task-id>([\s\S]*?)<\/task-id>/i,BS=/<tool-use-id>([\s\S]*?)<\/tool-use-id>/i,DS=/<status>([\s\S]*?)<\/status>/i,jS=/<result>([\s\S]*?)(?:<\/result>|$)/i;var Mc=new Set(["model","clear","compact","login"]);function Bm(t){return Mc.has(String(t||"").replace(/^\//,"").toLowerCase())}var LS=/^\/([a-z0-9:_-]+)$/i;function Ds(t){let e=String(t||"").trim();if(!e)return null;let n=e.match(IS);if(n)return{kind:"skill",name:n[1].split("/").filter(Boolean).pop()||"skill"};let r=e.match(RS);if(r)return{kind:"skill",name:r[1]};let o=e.match($S);if(o){let a=o[1].toLowerCase();return Mc.has(a)?{kind:"suppress"}:{kind:"command",command:`/${a}`}}let s=e.match(LS);if(s&&Mc.has(s[1].toLowerCase()))return{kind:"suppress"};if(OS.test(e))return{kind:"suppress"};if(CS.test(e))return{kind:"suppress"};if(MS.test(e)){let a=c=>FS(e.match(c)?.[1].trim()||"")||null,i=a(jS);return{kind:"task",summary:a(PS)||"",taskId:a(NS),toolUseId:a(BS),status:a(DS)?.toLowerCase()||null,resultHead:i?i.length>600?`${i.slice(0,600).trimEnd()}\u2026`:i:null}}return null}function FS(t){return t.replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/'/g,"'").replace(/&/g,"&")}function Dm(t){return t?.kind==="skill"?`Ran the ${t.name} skill`:t?.kind==="command"?`Ran ${t.command}`:t?.kind==="task"?t.summary||"A background task finished":""}function jm(t){return t?.kind==="skill"?"skill-run":t?.kind==="task"?"task-done":"command-run"}var nt=t=>t.payload?.ts??t.ts,US=new Set(["opened","ci-fixed","reconciled","pushed"]),Lm=t=>`${t.repo??""}#${t.number}`;function js(t){let e=[],n=new Set,r=new Set,o=new Map,s=new Map,a=new Set,i=new Map,c=new Set,l=new Map,u=/^Agent "([\s\S]+)" finished$/,d=new Map;for(let g of t||[]){if(g.type!=="tool.called"||g.source!=="transcript")continue;let w=String(g.payload?.command||"");if(/\bbostrat-mock\s+(?:add|update)\b/.test(w))for(let y of w.matchAll(/[\w./~-]+\.(?:png|jpe?g|gif|webp|bmp|svg)\b/gi)){let A=y[0].split("/").pop().toLowerCase();d.has(A)||d.set(A,[]),d.get(A).push(g.payload?.ts??g.ts)}}let f=3*6e4,p=20*6e4,m=(g,w)=>!!(g?.name&&d.get(String(g.name).toLowerCase()))?.some(A=>A>=w-f&&A<=w+p);for(let g of t||[])if(g.type==="turn.retracted"){r.add(`${g.boxId}|${g.payload?.saidTs}`);let w=kn(g.payload?.text);w&&(o.has(g.boxId)||o.set(g.boxId,[]),o.get(g.boxId).push(w))}else if(g.type==="turn.user"&&g.source==="orchestrator"){let w=kn(g.payload?.text);w&&(s.has(g.boxId)||s.set(g.boxId,new Set),s.get(g.boxId).add(w))}else if(g.type==="job.started"&&g.payload?.id)a.add(g.payload.id),(g.payload.kind||"agent")==="agent"&&g.payload.name&&c.add(g.payload.name);else if(g.type==="turn.user"&&g.payload?.note?.kind==="task"){let w=g.payload.note,y={status:w.status||null,summary:w.summary||null,resultHead:w.resultHead??null,ts:nt(g)};w.toolUseId&&i.set(w.toolUseId,y);let A=String(w.summary||"").match(u);A&&l.set(A[1],y)}let h=new Map,b=new Map,x=null,v=new Map,E=new Map,R=new Map,st=new Map,I=new Map,L=new Map,T=new Map,U=new Map,J=(g,w)=>{let y=o.get(g);if(!y?.length)return!1;let A=kn(w),K=s.get(g);return y.some(B=>{if(!A.startsWith(B))return!1;let $=kn(A.slice(B.length));return!!$&&!!K?.has($)})};for(let g of t||[]){let w=g.payload||{};switch(g.type){case"turn.user":{if(g.source==="hook"||!String(w.text||"").trim()&&!w.images?.length)break;{let y=w.note||Ds(w.text);if(y){if(y.kind==="task"){if(y.toolUseId&&a.has(y.toolUseId))break;let A=String(y.summary||"").match(u);if(!y.toolUseId&&A&&c.has(A[1]))break}if(y.kind==="command"&&Bm(y.command))break;y.kind!=="suppress"&&e.push({id:g.id,kind:"event",type:jm(y),name:y.name??null,command:y.command??null,summary:y.summary??null,ts:nt(g),box:g.boxId,agent:g.provider});break}}if(r.has(`${g.boxId}|${nt(g)}`)||g.source==="transcript"&&J(g.boxId,w.text))break;if(w.kind==="merge-notify"||w.kind==="ship-notify"){e.push({id:g.id,kind:"event",type:w.kind==="ship-notify"?"ship-notified":"merge-notified",text:w.text,ts:nt(g),box:g.boxId,agent:g.provider});break}if(w.kind==="mock-picked"||w.kind==="ci-fix"||w.kind==="reconcile"||w.kind==="pr-go"||w.kind==="push-go"||w.kind==="spin-up-view")break;for(let y of v.values())y.status="stale";v.clear(),e.push({id:g.id,kind:"message",role:"user",text:w.text,ts:nt(g),box:g.boxId,agent:g.provider,boxRole:w.boxRole,...g.actor==="orchestrator"?{orchestration:w.kind||!0}:{},...w.sourceId?{sourceId:w.sourceId}:{},...w.attachments?.length?{attachments:w.attachments}:{},...w.images?.length?{images:w.images}:{}});break}case"turn.agent":{T.clear();let y=(w.images||[]).filter(K=>!m(K,nt(g)));if(!String(w.text||"").trim()&&!y.length)break;if(g.provider==="cursor"){let K=`${g.boxId}|${w.text}`;if(n.has(K))break;n.add(K)}let A=g.provider==="codex"?(w.tools||[]).filter(K=>!Qi.has(K)&&!Qr.has(K)):w.tools||[];e.push({id:g.id,kind:"message",role:"assistant",text:w.text,ts:nt(g),box:g.boxId,agent:g.provider,boxRole:w.boxRole,...A.length?{tools:A}:{},...w.model?{model:w.model}:{},...w.sourceId?{sourceId:w.sourceId}:{},...y.length?{images:y}:{}});break}case"job.started":{if(!w.id)break;let y=nt(g),A=i.get(w.id);if(!A&&(w.kind||"agent")==="agent"&&w.name){let _=l.get(w.name);_&&_.ts>=y&&(A=_)}let K={jobId:w.id,jobKind:w.kind||"agent",name:w.name||null,detail:w.detail||null,status:A?A.status&&A.status!=="completed"?"failed":"done":"running",startedTs:y,finishedTs:A?.ts??null,summary:A?.summary??null,resultHead:A?.resultHead??null},B=U.get(g.boxId);if(B&&B.jobs.some(_=>(_.finishedTs??1/0)>=y))B.jobs.push(K);else{let _={id:g.id,kind:"job-dock",dockId:w.id,ts:y,box:g.boxId,agent:g.provider,jobs:[K]};U.set(g.boxId,_),e.push(_)}break}case"handoff.done":e.push(We(g,"lead-handoff"));break;case"subtask.delegated":e.push({...We(g,"subtask-delegated"),to:g.boxId,toAgent:g.provider});break;case"subtask.merged":e.push(We(g,"subtask-merged"));break;case"pr.state.changed":{if(!w.change)break;if(w.change==="opened"){for(let A of v.values())A.status="stale";v.clear()}let y=We(g,`pr-${w.change}`);if(US.has(w.change)&&w.number!=null){let A=Lm(w);if(h.set(A,{number:w.number,url:w.url??null,title:w.title??null,repo:w.repo??null,branch:w.branch??null}),h.size>=2){for(let B of b.values())for(let $ of B)$.grouped=!0;y.groupPrs=[...h.values()];let K=[y];for(let B of h.keys())b.set(B,K)}else{let K=b.get(A)??[];K.push(y),b.set(A,K)}}else if(w.change==="merged"||w.change==="closed"||w.change==="lost"){let A=Lm(w),K=x&&!x.complete||h.size>=2;if(h.has(A))h.delete(A),b.delete(A);else if(w.number!=null)for(let B of[...h.keys()])B.endsWith(`#${w.number}`)&&(h.delete(B),b.delete(B));if(K&&w.number!=null){(!x||x.complete)&&(x={id:g.id,kind:"event",type:"pr-group-settled",prs:[],remaining:[],complete:!1,at:nt(g),ts:nt(g),box:g.boxId,agent:g.provider},e.push(x)),x.prs.push({number:w.number,url:w.url??null,title:w.title??null,repo:w.repo??null,branch:w.branch??null,change:w.change,ts:nt(g)}),x.remaining=[...h.values()],x.complete=h.size===0;break}}e.push(y);break}case"pr.offered":{if(!w.offerId)break;for(let A of v.values())A.status="stale";v.clear();let y={id:g.id,kind:"pr-offer",offerId:w.offerId,excerpt:w.excerpt||null,branch:w.branch||null,base:w.base||null,offered:Array.isArray(w.offered)&&w.offered.length?w.offered:null,toPr:w.toPr&&typeof w.toPr=="object"?{number:w.toPr.number??null}:null,repos:Array.isArray(w.repos)&&w.repos.length?w.repos:null,status:"open",ts:nt(g),box:g.boxId,agent:g.provider};v.set(w.offerId,y),E.set(w.offerId,y),e.push(y);break}case"pr.offer.answered":{let y=E.get(w.offerId);y&&(y.status=w.action||"dismiss",y.actions=Array.isArray(w.actions)?w.actions:null,y.notify=w.notify===!0,v.delete(w.offerId));break}case"box.state.changed":{let y=w.change==="model"?"model-change":w.change;if(!y)break;let A=We(g,y);y==="model-change"&&(A.to=w.model),e.push(A);break}case"view.state.changed":{if(w.change!=="created"||w.restarted)break;e.push({id:g.id,kind:"view-card",viewId:w.viewId,name:w.name,slug:w.slug,port:w.port,origin:w.origin,ts:nt(g),box:g.boxId,agent:g.provider});break}case"view.spinup.failed":{e.push({id:g.id,kind:"view-spinup-failed",name:w.name||null,reason:w.reason||"no-view",ts:nt(g),box:g.boxId,agent:g.provider});break}case"mock.state.changed":{if(w.change==="picked"){e.push({id:g.id,kind:"mock-picked",mockId:w.mockId,name:w.name,group:w.group??null,version:w.version??null,siblings:w.siblings??[],ts:nt(g),box:g.boxId,agent:g.provider});break}if(w.change!=="created")break;e.push({id:g.id,kind:"mock-card",mockId:w.mockId,name:w.name,group:w.group??null,ts:nt(g),box:g.boxId,agent:g.provider});break}case"runbook.state.changed":{if(w.change!=="created")break;e.push({id:g.id,kind:"runbook-card",runbookId:w.runbookId,name:w.name,ts:nt(g),box:g.boxId,agent:g.provider});break}case"ticket.linked":{e.push({id:g.id,kind:"ticket-card",provider:w.provider||"linear",key:w.key,url:w.url,title:w.title??null,linkSource:w.linkSource??null,ts:nt(g),box:g.boxId,agent:g.provider});break}case"ticket.move.offered":{if(!w.offerId)break;for(let A of R.values())A.key===w.key&&(A.status="stale",R.delete(A.offerId));let y={id:g.id,kind:"ticket-offer",offerId:w.offerId,provider:w.provider||"linear",key:w.key,url:w.url??null,phase:w.phase??null,from:w.from??null,to:w.to?.name??null,prNumber:w.prNumber??null,status:"open",ts:nt(g),box:g.boxId,agent:g.provider};R.set(w.offerId,y),st.set(w.offerId,y),e.push(y);break}case"ticket.move.answered":{let y=st.get(w.offerId);y&&(y.status=w.action==="moved"?"moved":"dismiss",R.delete(w.offerId));break}case"ticket.state.changed":{for(let y of R.values())y.key===w.key&&y.to===w.to&&(y.status="stale",R.delete(y.offerId));if(w.mode==="offer")break;e.push(We(g,"ticket-state"));break}case"ticket.commented":{e.push({id:g.id,kind:"ticket-comment",key:w.key,url:w.url??null,excerpt:w.excerpt??null,ts:nt(g),box:g.boxId,agent:g.provider});break}case"ticket.flow.changed":e.push(We(g,"ticket-flow"));break;case"ticket.create.offered":{if(!w.proposalId)break;for(let A of I.values())A.status="stale";I.clear();let y={id:g.id,kind:"ticket-create-offer",proposalId:w.proposalId,provider:w.provider||"linear",epic:w.epic??null,issues:Array.isArray(w.issues)?w.issues:[],team:w.team??null,needsTeam:!!w.needsTeam,linkCurrentTrack:!!w.linkCurrentTrack,status:"open",ts:nt(g),box:g.boxId,agent:g.provider};I.set(w.proposalId,y),L.set(w.proposalId,y),e.push(y);break}case"ticket.create.answered":{let y=L.get(w.proposalId);y&&(y.status=w.action==="created"?"created":"dismiss",y.createdCount=w.createdCount??null,y.failedCount=w.failedCount??null,I.delete(w.proposalId));break}case"ticket.created":{let y={items:Array.isArray(w.items)?w.items:[],failed:Array.isArray(w.failed)?w.failed:[],team:w.team??null},A=w.proposalId?L.get(w.proposalId):null;if(A){A.created=y;break}e.push({id:g.id,kind:"ticket-created",...y,ts:nt(g),box:g.boxId,agent:g.provider});break}case"error":{if(!w.kind)break;e.push(We(g,w.kind));break}case"provider.connected":{e.push({id:g.id,kind:"event",type:"provider-connected",provider:w.provider||g.provider,ts:nt(g),at:nt(g),box:g.boxId,agent:g.provider});break}case"prompt.answered":{if(w.control||!w.question)break;let y=w.groupId||w.promptId;if(!y)break;let A=T.get(y);A||(A={id:g.id,kind:"prompt",groupId:y,provider:g.provider,box:g.boxId,agent:g.provider,questionTotal:w.questionTotal??1,questions:[],ts:nt(g)},T.set(y,A));let K=w.questionIndex??0,B={index:K,header:w.header||"",question:w.question,multiSelect:!!w.multiSelect,chosen:Array.isArray(w.chosen)?w.chosen:[]},$=A.questions.findIndex(M=>M.index===K);$>=0?A.questions[$]=B:A.questions.push(B),A.questions.sort((M,at)=>M.index-at.index),A.questionTotal=Math.max(A.questionTotal,w.questionTotal??1),new Set(A.questions.map(M=>M.index)).size>=A.questionTotal&&(e.push(A),T.delete(y));break}default:break}}return WS(e.sort((g,w)=>(g.ts??0)-(w.ts??0)))}var HS=2*6e4;function WS(t){let e=new Map,n=[];for(let r of t){if(r.kind!=="message"||!r.box){n.push(r);continue}let o=r.role==="assistant"&&!String(r.text||"").trim()&&r.images?.length,s=e.get(r.box);if(o&&s&&(r.ts??0)-s.lastTs<=HS){s.item.images.push(...r.images),s.lastTs=r.ts??s.lastTs;continue}if(o){let a={...r,images:[...r.images]};e.set(r.box,{item:a,lastTs:r.ts??0}),n.push(a)}else e.delete(r.box),n.push(r)}return n}function Pc(t,e=null){let n=[];for(let o of t||[]){if(o.type!=="tool.called"||o.source!=="transcript")continue;let s=o.payload||{};String(s.command||"").trim()&&n.push({id:s.id??null,tool:s.tool||null,command:s.command,description:s.description||"",summary:qS(s),ts:s.ts??o.ts,n:s.n,box:o.boxId,agent:o.provider,boxRole:s.boxRole})}if(!n.length)return[];let r=(e??js(t)).filter(o=>o.kind==="message");return ep(n,r)}function qS(t){let e=String(t.summary||"").trim();return e&&e!==t.command&&e!==er(t.command)?e:Ne(t.command,t.description||"")}function We(t,e){let{ts:n,change:r,...o}=t.payload||{};return{id:t.id,kind:"event",type:e,...o,at:nt(t),ts:nt(t)}}function GS(t){let e=[],n=null;for(let r of String(t||"").split(`
|
|
83
|
+
`)){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 Nc=new Map;async function KS(t,e){let n=`${t.id}:${e.name}`;if(!Nc.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);Nc.set(n,o)}return Nc.get(n)}async function Fm(t,e){try{if(e?.agent!=="codex"||!e.cwd)return;let n=await mn(e.cwd,e.pinned?e.sessionId:null);if(!n)return;let r=await KS(t,e),o=GS(await Et(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:zt(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 Ls from"node:fs";import Dc from"node:path";import VS from"node:os";var JS=process.env.BOSTRAT_STATE_DIR||Dc.join(process.env.HOME||VS.tmpdir(),".bostrat"),Hm=(t=process.env)=>!!t.BOSTRAT_CURSOR_USAGE_COOKIE,zS="https://cursor.com/api/dashboard/get-filtered-usage-events",Um=100;async function XS({sinceMs:t,cookie:e,fetchImpl:n=fetch}){let r=[];for(let o=1;o<=20;o++){let s=await n(zS,{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:Um})});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<Um)break}return r}function YS(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 ZS=5*6e4,qe={at:0,rows:null,error:null},Fs=()=>Dc.join(JS,"cursor-usage.json");function Wm(){try{return JSON.parse(Ls.readFileSync(Fs(),"utf8"))}catch{return{sinceMs:0,seen:[]}}}function QS(t){try{Ls.mkdirSync(Dc.dirname(Fs()),{recursive:!0});let e=Fs()+".tmp";Ls.writeFileSync(e,JSON.stringify(t)),Ls.renameSync(e,Fs())}catch{}}var tv=5e3;async function ev({fetchImpl:t,now:e=Date.now()}={}){if(qe.rows&&e-qe.at<ZS)return qe.rows;let n=process.env.BOSTRAT_CURSOR_USAGE_COOKIE;if(!n)return null;let r=Wm(),o=Math.max(r.sinceMs-6e4,e-24*36e5);try{let s=await XS({sinceMs:o,cookie:n,fetchImpl:t});qe={at:e,rows:s.map(YS).filter(Boolean),error:null}}catch(s){qe={at:e,rows:null,error:String(s?.message||s)}}return qe.rows}var qm=()=>({enabled:Hm(),lastPollAt:qe.at||null,error:qe.error}),Bc=new Map;async function nv(t,e){let n=`${t.id}:${e.name}`;if(!Bc.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);Bc.set(n,o)}return Bc.get(n)}async function Gm(t,e,{fetchImpl:n}={}){try{if(e?.agent!=="cursor"||!e.cwd||!Hm())return;let r=await ev({fetchImpl:n});if(!r||!r.length)return;let o=await up(e.cwd,e.pinned?e.sessionId:null);if(!o?.chatId)return;let s=await nv(t,e),a=Wm(),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=zt(u,d)),!f&&l.chargedCents!=null&&(f=l.chargedCents/100),await O(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}})}QS({sinceMs:c,seen:[...i].slice(-tv)})}catch{}}import{execFile as iv}from"node:child_process";import{promisify as cv}from"node:util";import Km from"node:fs/promises";import Vm from"node:path";import rv from"node:os";var Jm=process.env.BOSTRAT_STATE_DIR||Vm.join(process.env.HOME||rv.tmpdir(),".bostrat"),ov=Vm.join(Jm,"github-latency.ndjson"),sv=100,Us=[],jc=new Map;function Hs({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};Us.push(l),Us.length>sv&&Us.shift();let u=jc.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)),jc.set(t,u),av(l),l}async function av(t){try{await Km.mkdir(Jm,{recursive:!0}),await Km.appendFile(ov,JSON.stringify(t)+`
|
|
84
|
+
`)}catch{}}function zm(){let t={};for(let[e,n]of jc)t[e]={count:n.count,measured:n.measured,avgMs:n.measured?Math.round(n.totalMs/n.measured):null,maxMs:n.measured?n.maxMs:null};return{byChange:t,recent:Us.slice(-20)}}var Gs=cv(iv),lv={timeout:3e4,maxBuffer:8*1024*1024};async function Ut(t,e,n={}){let{caller:r="prs",...o}=n,s=await re({cwd:t});we(r);try{let{stdout:a}=await Gs("gh",e,{cwd:t,...lv,...o,env:{...process.env,...s,...o.env||{}}});return String(a||"")}catch(a){throw je(a)&&Le(a),a}}var Ws=null;async function Uc(){if(Ws&&Date.now()-Ws.at<6e4)return Ws.data;let t;try{we("auth");let e=await Gs("gh",["auth","status"],{timeout:1e4});t={installed:!0,authenticated:!0,login:`${e.stdout||""}${e.stderr||""}`.match(/account\s+(\S+)/)?.[1]??null}}catch(e){t=e?.code==="ENOENT"?{installed:!1,authenticated:!1,login:null}:{installed:!0,authenticated:!1,login:null}}if(t.installed&&!t.authenticated)try{let e=await zr();e.configured&&(t={installed:!0,authenticated:!0,login:e.username,via:"broker"})}catch{}return Ws={at:Date.now(),data:t},t}async function uv(t){try{let{stdout:e}=await Gs("git",["-C",t,"config","--get","remote.origin.url"],{timeout:5e3});return/github\.com/.test(e)}catch{return!1}}function Ym(t){let e=t.name||t.context||"check",n=t.detailsUrl||t.targetUrl||null,r=t.completedAt||t.createdAt||null,o;return t.__typename==="StatusContext"?o=t.state==="SUCCESS"?"pass":t.state==="PENDING"||t.state==="EXPECTED"?"pending":"fail":t.status&&t.status!=="COMPLETED"?o="pending":["SUCCESS","NEUTRAL","SKIPPED"].includes(t.conclusion)?o="pass":o="fail",{name:e,state:o,url:n,completedAt:r}}function Lc(t){let e=(t||[]).map(Ym),n=e.filter(s=>s.state==="pending").length,r=e.map(s=>Date.parse(s.completedAt||"")).filter(s=>s>0),o=e.length&&!n&&r.length?Math.max(...r):null;return{total:e.length,passed:e.filter(s=>s.state==="pass").length,failed:e.filter(s=>s.state==="fail").length,pending:n,failing:e.filter(s=>s.state==="fail").map(({name:s,url:a})=>({name:s,url:a})),settledAt:o}}var Hc="number,title,url,headRefName,headRefOid,baseRefName,isDraft,author,reviewDecision,mergeable,mergeStateStatus,statusCheckRollup,autoMergeRequest,additions,deletions,createdAt,updatedAt",Zm=Hc.replace("statusCheckRollup,","");function Wc(t){return/Resource not accessible by personal access token/.test(String(t?.stderr||t?.message||""))}async function Qm(t,e){if(!e)return null;let n=await Y(t);if(!n)return null;try{let r=await Ut(t,["api",`repos/${n}/actions/runs?head_sha=${encodeURIComponent(e)}&per_page=100`,"--jq",".workflow_runs"]),o=new Map;for(let s of JSON.parse(r||"[]"))o.has(s.name)||o.set(s.name,s);return[...o.values()]}catch{return null}}function th(t){return(t||[]).map(e=>({name:e.name,detailsUrl:e.html_url||null,status:String(e.status||"").toUpperCase(),conclusion:e.conclusion?String(e.conclusion).toUpperCase():null,completedAt:e.status==="completed"&&e.updated_at||null}))}async function eh(t,e){let n=await Qm(t,e);return n===null?{...Lc([]),unavailable:!0}:Lc(th(n))}function qs(t,e=Lc(t.statusCheckRollup)){return{number:t.number,title:t.title,url:t.url,author:t.author?.login??null,headRefName:t.headRefName,headSha:t.headRefOid??null,baseRefName:t.baseRefName,isDraft:!!t.isDraft,reviewDecision:t.reviewDecision||null,mergeable:t.mergeable||"UNKNOWN",mergeStateStatus:t.mergeStateStatus||"UNKNOWN",additions:t.additions??0,deletions:t.deletions??0,createdAt:t.createdAt,updatedAt:t.updatedAt,checks:e,green:e.failed===0&&e.pending===0&&t.mergeStateStatus==="CLEAN",autoMergeArmed:!!t.autoMergeRequest,mergeabilityPending:!1}}var dv=9e4,ur=new Map;function fv(t,e,n){let r=new Set;for(let o of e){let s=`${t}#${o.number}`;if(o.mergeable!=="UNKNOWN"&&o.mergeStateStatus!=="UNKNOWN"){ur.delete(s);continue}r.add(s),ur.has(s)||ur.set(s,n),o.mergeabilityPending=n-ur.get(s)<dv}for(let o of ur.keys())o.startsWith(`${t}#`)&&!r.has(o)&&ur.delete(o);return e}async function dr(t,e){let n=await ge(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");try{let o=await Ut(n.path,["pr","view",String(r),"--json",Hc]);return qs(JSON.parse(o))}catch(o){if(!Wc(o))throw o;let s=await Ut(n.path,["pr","view",String(r),"--json",Zm]),a=JSON.parse(s);return qs(a,await eh(n.path,a.headRefOid))}}var Xm=(t,e)=>t.number-e.number;async function oo(t,{caller:e="prs"}={}){try{let n=await Ut(t,["pr","list","--json",Hc,"--limit","50"],{caller:e});return JSON.parse(n||"[]").map(r=>qs(r)).sort(Xm)}catch(n){if(!Wc(n))throw n;let r=await Ut(t,["pr","list","--json",Zm,"--limit","50"],{caller:e});return(await Promise.all(JSON.parse(r||"[]").map(async o=>qs(o,await eh(t,o.headRefOid))))).sort(Xm)}}var pv=3e4,mv=1500,ro=new Map;function se(){ro.clear()}async function hv(t,e){if(!t)return e;for(let n of e){let r=await vt(t,n.number).catch(()=>null);if(!r)continue;r.trackId&&(n.trackId=r.trackId),r.autoMerge&&(n.autoMergeArmed=!0),r.archiveOnMerge&&(n.archiveOnMerge=!0),r.notifyOnMerge&&(n.notifyOnMerge=!0);let o=s=>n.headSha&&s.headSha&&n.headSha!==s.headSha;r.ciFix&&(o(r.ciFix)?await ws(t,n.number,{headSha:n.headSha}).catch(()=>{}):n.ciFix={delegatedAt:r.ciFix.delegatedAt,trackId:r.ciFix.trackId??null}),r.reconcile&&(o(r.reconcile)?await ys(t,n.number,{headSha:n.headSha}).catch(()=>{}):n.reconcile={delegatedAt:r.reconcile.delegatedAt,trackId:r.reconcile.trackId??null})}return e}async function nh(){let t=await Uc();if(!t.authenticated)return{auth:t,repos:[]};let e=[];for(let a of await me())await uv(a.path)&&e.push(a);let n=Date.now(),r=Ps(n)>0,o=await Promise.all(e.map(async a=>{let i=ro.get(a.path),c=i?.pending?mv:pv;if(i&&(r||n-i.at<c))return i.entry;try{let l=await Y(a.path).catch(()=>null),u=fv(a.path,await oo(a.path,{caller:"tab"}),n);if(await hv(l,u),i?.entry?.prs)for(let p of u)i.entry.prs.find(h=>h.number===p.number)?.checks?.pending>0&&p.checks.total>0&&p.checks.pending===0&&Hs({change:p.checks.failed>0?"checks-failed":"checks-green",repo:l||a.name,number:p.number,githubAt:p.checks.settledAt,source:"tab"});let d={name:a.name,path:a.path,prs:u},f=u.some(p=>p.mergeabilityPending||p.checks.pending>0||p.autoMergeArmed);return ro.set(a.path,{at:Date.now(),entry:d,pending:f}),d}catch(l){return i?i.entry:{name:a.name,path:a.path,prs:[],error:oe(l)}}})),s=new Set(e.map(a=>a.path));for(let a of ro.keys())s.has(a)||ro.delete(a);return{auth:t,repos:o}}async function ge(t){let e=(await me()).find(n=>n.path===t);if(!e)throw new Error(`unknown repo: ${t}`);return e}async function Sn(t){if(!t)return null;for(let e of await me())if(await Y(e.path).catch(()=>null)===t)return e.path;return null}var so=new Set(["squash","merge","rebase"]);async function fr(t,e,{method:n="squash"}={}){let r=await ge(t);if(!so.has(n))throw new Error(`invalid merge method: ${n}`);let o=Number(e);if(!Number.isInteger(o)||o<=0)throw new Error("invalid PR number");let s=["pr","merge",String(o),`--${n}`];if(n!=="rebase"&&(await re({cwd:r.path})).GH_TOKEN){let a=await Fi().catch(()=>null);a&&s.push("--body",`Merged by bostrat on behalf of @${a}`)}try{await Ut(r.path,s)}catch(a){throw new Error(oe(a))}finally{se()}return{ok:!0,number:o}}async function ao(t,e){let n=await ge(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");try{await Ut(n.path,["api","-X","PUT",`repos/{owner}/{repo}/pulls/${r}/update-branch`])}catch(s){throw new Error(oe(s))}finally{se()}let o=await Y(n.path).catch(()=>null);return o&&await Ef(o,r).catch(()=>{}),{ok:!0,number:r}}async function qc(t,e){let n=await ge(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");try{await Ut(n.path,["pr","merge",String(r),"--disable-auto"])}catch(o){throw new Error(oe(o))}finally{se()}return{ok:!0,number:r}}var wv=2500,gv=9e4,yv=t=>new Promise(e=>setTimeout(e,t));function Gc(t){return t.green&&!t.isDraft}function bv(t){return t.isDraft?"draft":t.mergeable==="CONFLICTING"?`conflicts with ${t.baseRefName}`:t.checks.failed>0?"checks failing":t.checks.pending>0?"checks running":t.mergeable==="UNKNOWN"||t.mergeStateStatus==="UNKNOWN"?"mergeability unresolved":t.mergeStateStatus==="BEHIND"?`behind ${t.baseRefName}`:t.mergeStateStatus==="BLOCKED"?"blocked by branch protection":`not clean (${t.mergeStateStatus})`}function rh(t,{update:e=!0}={}){return t.isDraft?{action:"skip",reason:"draft"}:Gc(t)?{action:"merge"}:e&&t.mergeStateStatus==="BEHIND"&&t.mergeable!=="CONFLICTING"&&t.checks.failed===0?{action:"update-arm"}:{action:"arm"}}function oh(t){return[...t.filter(e=>e.plan.action!=="merge"),...t.filter(e=>e.plan.action==="merge")]}var Fc=new Map;function sh(){let t={};for(let[e,n]of Fc)t[e]={...n};return t}async function ah(t,{method:e="merge",onMerged:n=null}={}){let r=await ge(t);if(!so.has(e))throw new Error(`invalid merge method: ${e}`);if(Fc.get(r.path)?.running)throw new Error("a merge-all is already running for this repo");let o=(await oo(r.path)).filter(Gc).sort((a,i)=>Date.parse(a.createdAt)-Date.parse(i.createdAt));if(!o.length)throw new Error("no PRs ready to merge");let s={running:!0,startedAt:Date.now(),finishedAt:null,method:e,total:o.length,current:o[0].number,queue:o.map(a=>({number:a.number,title:a.title})),merged:[],skipped:[],failed:[]};return Fc.set(r.path,s),xv(s,o,{listPrs:()=>oo(r.path),merge:a=>fr(r.path,a,{method:e}),sleep:yv,onMerged:n}).catch(()=>{}),{ok:!0,repo:r.path,total:s.total,queue:o.map(a=>a.number)}}async function kv(t,e){let n=Date.now()+(e.settleMaxMs??gv),r=null;for(;;){let o=null;try{if(o=await e.listPrs(),r=null,!o.some(a=>t.includes(a.number)&&(a.mergeable==="UNKNOWN"||a.mergeStateStatus==="UNKNOWN")))return o}catch(s){r=s}if(Date.now()>=n){if(o)return o;if(r)throw r;return[]}await e.sleep(wv)}}async function xv(t,e,n){try{let r=e;for(let o=0;o<e.length;o++){let s=e[o];if(t.current=s.number,o>0)try{r=await kv(e.slice(o).map(i=>i.number),n)}catch(i){for(let c of e.slice(o))t.failed.push({number:c.number,title:c.title,reason:`couldn't re-check PR state: ${oe(i)}`});break}let a=r.find(i=>i.number===s.number);if(!a){t.skipped.push({number:s.number,title:s.title,reason:"no longer open"});continue}if(!Gc(a)){t.skipped.push({number:a.number,title:a.title,reason:bv(a)});continue}try{await n.merge(a.number),t.merged.push({number:a.number,title:a.title});try{await n.onMerged?.(a)}catch{}}catch(i){t.failed.push({number:a.number,title:a.title,reason:oe(i)})}}}finally{t.current=null,t.running=!1,t.finishedAt=Date.now(),se()}}async function Kc(t,{title:e,body:n="",base:r=null}={}){if(typeof e!="string"||!e.trim())throw new Error("title required");try{await Gs("git",["-C",t,"push","-u","origin","HEAD"],{timeout:12e4,maxBuffer:4*1024*1024})}catch(l){throw new Error(`push failed: ${oe(l)}`)}let o=n||"";if((await re({cwd:t})).GH_TOKEN){let l=await Fi().catch(()=>null);l&&(o=`${o?`${o}
|
|
85
|
+
|
|
86
|
+
`:""}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 Ut(t,s)}catch(l){throw new Error(oe(l))}finally{se()}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 ih(t,e){let n=await ge(t),r=Number(e);if(!Number.isInteger(r)||r<=0)throw new Error("invalid PR number");let o;try{let i;try{i=await Ut(n.path,["pr","checks",String(r),"--json","name,state,link,bucket"])}catch(c){if(i=String(c?.stdout||""),!i.trim())throw new Error(oe(c))}o=JSON.parse(i||"[]").filter(c=>c.bucket==="fail")}catch(i){if(!Wc(i))throw i;let c=await Ut(n.path,["pr","view",String(r),"--json","headRefOid"]),l=await Qm(n.path,JSON.parse(c).headRefOid);if(l===null)throw new Error(oe(i));o=th(l).map(Ym).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=Sv(i.link),l=null;c&&!s.has(c)&&(s.add(c),l=await Ut(n.path,["run","view",c,"--log-failed"]).then(u=>vv(u)).catch(()=>null)),a.push({name:i.name,link:i.link||null,log:l})}return a}function Sv(t){return String(t||"").match(/\/actions\/runs\/(\d+)/)?.[1]??null}function vv(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 oe(t){return String(t?.stderr||t?.message||t||"").trim().split(`
|
|
91
|
+
`).find(n=>n.trim())||"gh failed"}var Av=6e3,Ks=new Map;function Vc(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 ch(t){let e=t?.workspace?.branch;if(!e)return;let n;try{n=await F(t)}catch{return}if(Vc(n)==null)return;let r=(t.boxes||[]).find(c=>c.name===t.leadBox);if(await Ns(t.leadBox,r?.agent).catch(()=>!1))return;let o=Date.now();if(o-(Ks.get(t.id)||0)<Av)return;Ks.set(t.id,o);let s;try{s=await oo(t.cwd,{caller:"reconcile"})}catch{return}let a=(s||[]).find(c=>c.headRefName===e);if(!a)return;let i=await Y(t.cwd).catch(()=>null);if(i){let{link:c,created:l}=await Pt({repo:i,number:a.number,trackId:t.id,url:a.url,headRef:e,title:a.title,source:"reconciled"});if(!l&&!(c?.trackId===t.id&&await Gr(i,a.number))){Ks.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}}).catch(()=>{}),Ks.delete(t.id)}import lh from"node:fs/promises";import Vs from"node:path";import{randomUUID as Tv}from"node:crypto";var dh=".bostrat/uploads",Jc=t=>Vs.join(t,dh),uh=Number(process.env.BOSTRAT_MAX_UPLOAD_BYTES)||24*1024*1024,_v=new Set(["png","jpg","jpeg","gif","webp","svg","bmp","tif","tiff","heic","heif","avif","ico"]),Ev=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 zc(t){let e=String(t||"").toLowerCase().replace(/^\./,"");return _v.has(e)?"image":Ev.has(e)?"doc":null}function Iv(t){let r=Vs.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 fh(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>uh)throw new Error(`file too large \u2014 max ${Math.floor(uh/(1024*1024))} MB`);let r=Iv(e),o=Vs.extname(r).slice(1),s=zc(o);if(!s)throw new Error(`unsupported file type "${o||"unknown"}" \u2014 attach an image or a document`);let a=Tv().slice(0,8),i=Jc(t);await lh.mkdir(i,{recursive:!0});let c=`${a}-${r}`;return await lh.writeFile(Vs.join(i,c),n),{id:a,name:r,rel:`${dh}/${c}`,kind:s,size:n.length}}function ph(t){let e=t.map(r=>`\`${r}\``).join(", ");return`[Attached \u2014 please read ${t.length===1?"this file":`these ${t.length} files`}: ${e}]`}var Rv=/\[Attached — please read (?:this file|these \d+ files): [^\]]*\]$/;function Xc(t){return t.role==="user"&&Rv.test((t.text||"").trim())}function pr(t,e,n=Date.now()){return e?t.idleSince==null?(t.idleSince=n,!1):n-t.idleSince>=6500:(t.idleSince=null,!1)}var Cv=Ov($v),Ge=process.env.BOSTRAT_STATE_DIR||j.join(process.env.HOME||"/tmp",".bostrat"),xh=j.join(Ge,"tracks.json"),Mv=j.join(Ge,"events.jsonl"),nl=j.join(Ge,"workspaces"),Pv=process.env.BOSTRAT_DEFAULT_REPO||null,Xs={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"}},Ys=Object.keys(Xs);function Nv(t,{skipPermissions:e=!0}={}){let n=Xs[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 Sh(t,e,{skipPermissions:n=!0}={}){let r=Xs[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 ho(t){let e=Xs[t];if(e)return process.env[e.envVar]||e.base}var vn={at:0,value:null};async function ol({maxAgeMs:t=6e4}={}){if(vn.value&&Date.now()-vn.at<t)return vn.value;let e=await Promise.all(Ys.map(async n=>[n,await No(ho(n))]));return vn.value=Object.fromEntries(e),vn.at=Date.now(),vn.value}async function Zs(t,e=!0){let n=Nv(t,{skipPermissions:e});if(!await No(n)){vn.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.":$a[r]?`Install it on this node: ${$a[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 Qs=".bostrat/handoff/latest.md";function Bv(t,e=Qs){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 Dv=/\(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 mh(t){return t.role==="user"&&Dv.test((t.text||"").trim())}var Js=".bostrat/handoff/subtask-result.md",co=".bostrat/handoff/lead-context.md",jv=(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 ${co} \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 ${Js} (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 ${co} \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 ${Js} 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.`,Lv=(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."),ae=null,Yc=null;async function H(){return ae||(Yc||=(async()=>{try{ae=JSON.parse(await D.readFile(xh,"utf8"))}catch{ae={tracks:[]}}finally{Yc=null}return ae})(),Yc)}async function V(){await Xt(xh,ae)}var Z=(t,e)=>t.tracks.find(n=>n.id===e)||null,Zc=Promise.resolve();function Fv(t,e){let n=JSON.stringify({trackId:t,...e})+`
|
|
102
|
+
`;return Zc=Zc.then(()=>D.mkdir(Ge,{recursive:!0})).then(()=>D.appendFile(Mv,n)).catch(()=>{}),Zc}var ot=(t,e,n={})=>{let r={at:Date.now(),type:e,...n};return t.events.push(r),Fv(t.id,r),Qp(t,r),r};yu(async({sessionName:t,from:e,to:n,version:r,status:o,error:s})=>{let i=(await H()).tracks.find(c=>c.leadBox===t||(c.boxes||[]).some(l=>l.name===t));i&&(ot(i,"codex-update",{box:t,from:e,to:r||n,status:o,...s?{error:s}:{}}),await V().catch(()=>{}))});var Qc=(t,e,n)=>e!=null&&n.some(r=>t===r.box&&e>=r.from&&e<=r.to),Uv=new Set(["stuck","wrong-approach","too-slow","want-different-model","context-refresh","cost","parallelize","other"]);function vh(t){if(!t)return null;let e=typeof t=="string"?[t]:Array.isArray(t?.codes)&&t.codes.length?t.codes:t?.code?[t.code]:[],n=[...new Set(e.filter(s=>Uv.has(s)))];if(!n.length)return null;let r=typeof t=="object"?t?.note:null,o=r&&String(r).slice(0,500).trim()||null;return{code:n[0],...n.length>1?{codes:n}:{},note:o}}async function Ah(t,e,n,{kind:r}={}){let o=await z(e);if(!o)throw new Error(`box "${e}" is not running`);let s=ho(o.agent);await Te({cwd:o.cwd,sessionId:o.sessionId,sessionName:e,agent:o.agent,agentCmd:s}),await kt(e,n,{...le(o.agent),kind:r||"orchestration"}),await q({flow:"orchestration",phase:"sent",trackId:t,sessionName:e,agent:o.agent,kind:r||"orchestration",promptHead:n.slice(0,160)});let a=await H(),i=Z(a,t);if(!i)return;let c=i.boxes.find(l=>l.name===e);ot(i,"orchestration-prompt",{box:e,agent:c?.agent||o.agent,boxRole:c?.role,prompt:n,kind:r||"orchestration"}),await V()}var An=new Set(["merged","conflict","canceled","failed","handed-off"]),hh=new Map;async function Hv(t){let e=j.join(zo(t),`${t.id}.ndjson`),n;try{n=await D.stat(e)}catch{return null}let r=hh.get(t.id);if(r&&r.mtimeMs===n.mtimeMs)return r.at;let o=null;for(let s of await F(t)){if(s.type!=="turn.agent")continue;let a=s.payload?.ts??s.ts;a&&(!o||a>o)&&(o=a)}return hh.set(t.id,{mtimeMs:n.mtimeMs,at:o}),o}async function Wv(t,e){let n=e.find(r=>r.name===t.leadBox);return n?.state!=="live"?{working:!1,prompted:!1}:Ec(n.name,n.agent)}async function Ke(t){let e=new Map((await Nn()).map(a=>[a.name,a])),n=t.boxes.map(a=>{let i=e.get(a.name),c=An.has(a.state)?a.state:i?a.role==="lead"?"live":a.state:"stopped";return{...a,state:c,cwd:i?.cwd||a.cwd,agent:i?.agent||a.agent,sessionId:i?.sessionId||a.sessionId}}),[r,{working:o,prompted:s}]=await Promise.all([Hv(t),Wv(t,n)]);return{...t,boxes:n,lastResponseAt:r,working:o,prompted:s}}var qv=5e3;function Gv(t,e,n){let r=[...t].sort((l,u)=>l.createdAt-u.createdAt),o=[...e].sort((l,u)=>l.birthMs-u.birthMs),s=new Set,a=[];for(let l of r)l.sessionId&&n(l.sessionId)&&!s.has(l.sessionId)?s.add(l.sessionId):a.push(l);let i=new Map(r.map((l,u)=>[l,u])),c=[];for(let l of a){if(l.sessionId&&n(l.sessionId)){c.push({box:l.name,sessionId:null});continue}if(!l.sessionId||l.pinned)continue;let u=i.get(l),d=l.createdAt-qv,f=r[u+1]?.createdAt??1/0,p=o.find(m=>!s.has(m.id)&&m.birthMs>=d&&m.birthMs<f);p&&(s.add(p.id),c.push({box:l.name,sessionId:p.id}))}return c}async function Th(t){let e=new Map;for(let r of t.tracks)for(let o of r.boxes)o.agent==="claude"&&o.cwd&&(e.has(o.cwd)||e.set(o.cwd,[]),e.get(o.cwd).push({track:r,box:o}));let n=!1;for(let[r,o]of e){let s=o.map(f=>f.box.sessionId),a=fu(r),i=new Set(a.map(f=>f.id)),c=s.every(f=>f&&i.has(f)),l=new Set(s).size===s.length;if(c&&l)continue;let u=o.map(f=>f.box),d=Gv(u,a,f=>i.has(f));for(let f of d){let p=u.find(m=>m.name===f.box);p&&(p.sessionId=f.sessionId),await nu(f.box,f.sessionId),n=!0}}return n}async function ut(){let t=await H();return await Th(t)&&await V(),Promise.all(t.tracks.map(Ke))}async function _h(t,e){let n=await H();if(t)for(let r of n.tracks||[]){let o=r.boxes.find(s=>s.sessionId&&s.sessionId===t);if(o)return{track:r,box:o}}if(e)for(let r of n.tracks||[]){let o=r.boxes.find(s=>s.cwd===e);if(o)return{track:r,box:o}}return null}async function et(t){let e=await H(),n=Z(e,t);return n?(await Th(e)&&await V(),Ke(n)):null}var Kv={claude:[/^\[Request interrupted by user(?: for tool use)?\]$/i],codex:[/^\[?Request interrupted/i,/^Conversation interrupted/i],cursor:[/^\[?Request interrupted/i]};function wh(t){if(t.role!=="user")return!1;let e=(t.text||"").trim();return(Kv[t.agent]||[]).some(n=>n.test(e))}var Vv=[/^\/model(?:\s|$)/i,/<command-(?:name|message|args)>[^<]*\bmodel\b/i,/^Model changed to\b/i];function gh(t){let e=(t.text||"").trim();return e?Vv.some(n=>n.test(e)):!1}var Jv=10*6e4;async function sl(t,e={}){let n=await et(t);if(!n)return null;let r=new Set([...n.events.filter(k=>k.type==="orchestration-prompt"&&k.prompt).map(k=>String(k.prompt).trim()),...n.events.filter(k=>k.type==="said"&&k.text).map(k=>String(k.text).trim())]),o=wo(n.id),s=!!(o&&!o.finishedAt&&o.step!=="done"&&!o.error),a=s&&o?.startedAt?[...o.fromBox?[{box:o.fromBox,from:o.startedAt,to:Date.now()+1e12}]:[],...o.toBox?[{box:o.toBox,from:o.startedAt,to:Date.now()+1e12}]:[]]:[],i=oi(n.id),c=!!(i&&!i.finishedAt),l=c&&i.startedAt&&i.box?[{box:i.box,from:i.startedAt,to:Date.now()+1e12}]:[],u=[...Id(n),...l],f={windows:[...Hu(n),...Wu(n,Vo),...a,...u]},m=(await Promise.all(n.boxes.map(k=>bp(k,e)))).flat(),h=m.map(k=>{let W={kind:"message",...k};if(W.role==="assistant"&&(W.text||"").trim()&&W.tools?.length){let En=to(W.agent).cliTools,Oo=W.tools.filter(In=>!En.has(In));return{...W,tools:Oo.length?Oo:void 0}}return W}).map(k=>{if(k.role!=="user")return k;let W=Ds(k.text);return W?W.kind==="suppress"?null:{...k,note:W,text:Dm(W)}:k}).filter(Boolean).filter(k=>!(k.role==="user"&&r.has((k.text||"").trim()))).filter(k=>!wh(k)).filter(k=>!gh(k)).filter(k=>!mh(k)).filter(k=>!Xc(k)).filter(k=>k.role!=="assistant"||!!(k.text||"").trim()||!!k.images?.length).filter(k=>!qu(k,f)),b=h.filter(k=>k.failed&&k.text===tc);if(b.length){let k=new Map;for(let W of b){if(!k.has(W.box)){let In=[...(await wt(W.box,{lines:_t}).catch(()=>"")).matchAll(/^\s*■\s+(.+?)\s*$/gm)].map(xa=>xa[1]);k.set(W.box,In.length?In[In.length-1]:null)}let En=k.get(W.box);En&&(W.text=`\u25A0 ${En}`)}}let x=await Promise.all(n.boxes.map(k=>xp(k,e)));await Nm(n,x.flat()),await Mm(n,h),await tm(n),await Pd(n,h).catch(()=>{});for(let k of n.boxes)k.agent==="codex"&&k.state==="live"&&await Fm(n,k),k.agent==="cursor"&&k.state==="live"&&await Gm(n,k);let v=await Promise.all(n.boxes.map(k=>kp(k,e)));await Pm(n,v.flat().filter(k=>!Qc(k.box,k.ts,u)));let E=await F(n),R=c&&i.viewId?i.viewId:null,st=dm(E),I=js(E).filter(k=>!(k.kind==="message"&&(wh(k)||gh(k)||mh(k)||Xc(k)))&&!(k.kind==="view-card"&&R&&k.viewId===R)),L=fm(I,st),T=I.filter(k=>!(k.kind==="message"&&k.role==="assistant"&&!L.has(k)&&Qc(k.box,k.ts,st))),U=Pc(E,T).map(k=>({kind:"command-batch",id:k.id,ts:k.pending?k.ts:(k.anchorTs??k.ts)-1,anchorTs:k.anchorTs,anchorBox:k.anchorBox,agent:k.agent,boxRole:k.boxRole,commands:k.commands,pending:k.pending})).filter(k=>!Qc(k.anchorBox,k.anchorTs??k.ts,u)),J=[...T,...U].filter(k=>k.ts!=null).sort((k,W)=>k.ts-W.ts),g=zv(n),w=g.size?J.filter(k=>k.kind==="event"||!g.has(k.box??k.anchorBox)):J,y=n.boxes.find(k=>k.name===n.leadBox)||null,A=y?await Cr(y.cwd,y.sessionId,{fallbackNewest:!1}):null;if(!A&&y){let k=[...E].reverse().find(W=>W.type==="context.usage"&&W.boxId===y.name);k?.payload?.window&&(A={pct:k.payload.pct??(k.payload.occupancy!=null?k.payload.occupancy/k.payload.window:null),occupancy:k.payload.occupancy??null,window:k.payload.window,windowKnown:!0,model:k.payload.model||null})}let K=[...m].reverse().find(k=>k.role==="assistant"&&k.model),B=K?.model||null,$=[...n.events||[]].reverse().find(k=>k.type==="model-change"),_=A?.model||B;$&&(!K?.ts||$.at>=K.ts)&&(_=$.to);let M=y?await wt(y.name,{lines:_t}).catch(()=>""):"",at=y&&sr(y.agent,M)?await ve(y.name).catch(()=>M):M,Q=y?bn(y,at):null;Q&&em(n,y,Q);let ht=!!y&&!Q&&jn(y.agent,M);y&&!y.readyAt&&(ht||Q||M&&Wo(y.agent,M))&&(y.readyAt=Date.now(),await V());let Tt=!!y&&!y.readyAt&&Date.now()-(n.createdAt||0)<Jv;if(n.kind==="setup"&&n.setup?.provider&&!n.setup.connectedAt&&y&&Wo(y.agent,M)&&!Q){let k=await H(),W=Z(k,n.id);W?.setup&&!W.setup.connectedAt&&(W.setup.connectedAt=Date.now(),n.setup.connectedAt=W.setup.connectedAt,await O(W,{type:"provider.connected",actor:"system",source:"pane",boxId:y.name,provider:y.agent,payload:{provider:y.agent,ts:W.setup.connectedAt}}).catch(()=>{}),await V())}let Dt=s?{running:!0,step:o.step,toAgent:o.toAgent||null}:null,Ht=null;if(c){let k=Date.now()-i.startedAt;if(i.viewId&&!ht){let W=ns(n.id);W&&(ot(n,"view-spinup",{box:i.box,startedAt:i.startedAt,endedAt:W.finishedAt}),await V())}else if(Q||k>_d&&!ht&&!i.viewId||k>ri){let W=Q?"needs-input":k>ri?"timeout":"no-view";ns(n.id,{error:W})&&await O(n,{type:"view.spinup.failed",actor:"orchestrator",source:"orchestrator",boxId:i.box,provider:y?.agent||null,payload:{ts:i.startedAt,name:i.name,reason:W}}).catch(()=>{})}else Ht={running:!0,step:i.step,name:i.name}}return{trackId:n.id,leadBox:n.leadBox,items:w,context:A?{pct:A.pct,occupancy:A.occupancy,window:A.window,windowKnown:A.windowKnown,model:A.model}:null,models:{current:_,options:Jp(y?.agent)},pendingPrompt:Q,working:ht,leadStarting:Tt,handoff:Dt,view:Ht,queued:Yv(n.id).filter(k=>k.kind!=="ship-notify")}}function zv(t){let e=new Set;for(let n of t.boxes||[])n.role==="subtask"&&n.name&&e.add(n.name);for(let n of Object.values(t.subtasks||{}))n?.box&&e.add(n.box);return e}async function Eh(t,e){let n=await et(t);if(!n)return null;let r=br(t).find(d=>d.id===e)||null;if(!r)return null;let o=r.box||null;if(!o)return{items:[],working:!1,box:null,agent:r.agent,task:r.task};let s=await F(n),a=js(s).filter(d=>d.box===o),i=Pc(s.filter(d=>d.boxId===o),a.filter(d=>d.kind==="message")).map(d=>({kind:"command-batch",id:d.id,ts:d.pending?d.ts:(d.anchorTs??d.ts)-1,anchorTs:d.anchorTs,anchorBox:d.anchorBox,agent:d.agent,boxRole:d.boxRole,commands:d.commands,pending:d.pending})),c=[...a,...i].filter(d=>d.ts!=null).sort((d,f)=>d.ts-f.ts),l=n.boxes.find(d=>d.name===o)||null,u=!1;if(l&&l.state==="live"){let d=await wt(o,{lines:_t}).catch(()=>"");u=jn(l.agent||r.agent,d)}return{items:c,working:u,box:o,agent:r.agent,task:r.task}}async function al(t,{promptId:e,answers:n,text:r}={}){let o=Z(await H(),t);if(!o)throw new Error(`track "${t}" not found`);let s=o.boxes.find(f=>f.name===o.leadBox);if(!s)throw new Error(`track "${t}" has no lead box`);if(!await z(o.leadBox))throw new Error(`lead box "${o.leadBox}" is not running`);let i=await fc(s);if(!i)throw new Error("no pending prompt to answer");if(e&&e!==i.promptId)throw new Error("prompt changed since it was shown \u2014 reload and answer the current prompt");let c;if(i.answerMode==="text"){let f=typeof r=="string"?r.trim():"";if(!f)throw new Error("this prompt needs a code \u2014 paste it into the card");if(/\s/.test(f)||f.length>2048)throw new Error("that doesn't look like a sign-in code \u2014 paste the code exactly as shown");await kt(o.leadBox,f,{enter:!0,kind:"answer-prompt"}),c={mode:"text-input",value:"\u2039code\u203A",count:1,redacted:!0}}else if(i.answerMode==="esc")await bt(o.leadBox,["Escape"],{kind:"answer-prompt"}),c={mode:"keys",value:"Escape",count:1};else if(i.provider==="claude"||i.answerMode==="keys"){let f=Cp(i,n||[]);await bt(o.leadBox,f,{kind:"answer-prompt"}),c={mode:"keys",value:f.join(" "),count:f.length}}else{let f=Tu(i,n||[]);await kt(o.leadBox,f,{enter:!0,kind:"answer-prompt"}),c={mode:"text",value:f,count:1}}let l=i.questions?.[0]||{},u=(Array.isArray(n?.[0])?n[0]:[]).map(f=>l.options?.[f]).filter(Boolean).map(f=>({label:f.label,description:f.description||""})),d=i.review?"review":/^claude:model-confirm:/.test(i.promptId||"")?"model-confirm":/^claude:resume-confirm:/.test(i.promptId||"")?"resume-confirm":void 0;return c={...c,groupId:i.groupId||i.promptId,questionIndex:i.questionIndex??0,questionTotal:i.questionTotal??1,header:l.header||"",question:l.question||"",multiSelect:!!l.multiSelect,chosen:u,...d?{control:d}:{}},await q({flow:"prompt",phase:"answered",sessionName:o.leadBox,agent:s.agent,provider:i.provider,promptId:i.promptId,...c}).catch(()=>{}),nm(o,s,i,c),{ok:!0,promptId:i.promptId,sent:c.count}}async function Ih(t,{promptId:e,questionIndex:n}={}){let r=Z(await H(),t);if(!r)throw new Error(`track "${t}" not found`);let o=r.boxes.find(l=>l.name===r.leadBox);if(!o)throw new Error(`track "${t}" has no lead box`);if(!await z(r.leadBox))throw new Error(`lead box "${r.leadBox}" is not running`);let a=await fc(o);if(!a)throw new Error("no pending prompt to navigate");if(a.provider!=="claude")throw new Error("prompt navigation not supported for this provider");if(a.review)throw new Error("cannot navigate on the review screen \u2014 answer or cancel");let i=a.questionTotal??1;if(i<2)throw new Error("not a multi-question prompt");if(!Number.isInteger(n)||n<0||n>=i)throw new Error("questionIndex out of range");if(e&&e!==a.promptId)throw new Error("prompt changed since it was shown \u2014 reload and try again");let c=Mp(a,n);return c.length?(await bt(r.leadBox,c,{kind:"navigate-prompt"}),await q({flow:"prompt",phase:"navigated",sessionName:r.leadBox,agent:o.agent,provider:a.provider,promptId:a.promptId,fromIndex:a.questionIndex??0,toIndex:n,mode:"keys",value:c.join(" "),count:c.length}).catch(()=>{}),{ok:!0,promptId:a.promptId,sent:c.length}):{ok:!0,promptId:a.promptId,sent:0}}var Xv=1500,lo=new Map;function il(t){let e=lo.get(t);return e||(e={items:[],timer:null,flushing:!1,idleSince:null},lo.set(t,e)),e}function Yv(t){return(lo.get(t)?.items??[]).map(e=>({...e}))}function ta(t){let e=lo.get(t);if(!e)return[];let n=e.items.splice(0);return e.idleSince=null,e.timer&&(clearInterval(e.timer),e.timer=null),n}function Rh(t){let e=il(t);e.timer||(e.timer=setInterval(()=>Zv(t).catch(()=>{}),Xv))}async function Zv(t){let e=lo.get(t);if(!(!e||e.flushing)){if(!e.items.length){e.timer&&(clearInterval(e.timer),e.timer=null);return}e.flushing=!0;try{let n=Z(await H(),t);if(!n||n.archivedAt){ta(t);return}let r=wo(t);if(r&&!r.finishedAt&&r.step!=="done"&&!r.error){pr(e,!1);return}let o=await z(n.leadBox);if(!o){pr(e,!1);return}let s=n.boxes.find(c=>c.name===n.leadBox),a=await wt(n.leadBox,{lines:_t}).catch(()=>"");if(Ho(s?.agent,a)){pr(e,!1);return}if(sr(s?.agent,a)){let c=await ve(n.leadBox).catch(()=>a);if(bn(s,c)){pr(e,!1);return}}if(!pr(e,!0))return;let i=e.items.shift();e.idleSince=null,await $h(n,s,o,i.text,i.kind??null,i.attachments??null),await V()}finally{e.flushing=!1}}}async function $h(t,e,n,r,o=null,s=null){let a=r,i=!1;t.pendingHandoffHydrate&&(a=Bv(r,t.pendingHandoffHydrate.artifact||Qs),delete t.pendingHandoffHydrate,i=!0);let c=Array.isArray(s)?s.filter(u=>u&&u.rel):[];if(c.length){let u=ph(c.map(d=>d.rel));a=a.trim()?`${a.trim()}
|
|
101
103
|
|
|
102
|
-
${u}`:u}let l=ot(t,"said",{box:t.leadBox,agent:e?.agent,boxRole:e?.role,text:r,...o?{kind:o}:{},...c.length?{attachments:c}:{}});delete t.draft;try{await
|
|
104
|
+
${u}`:u}let l=ot(t,"said",{box:t.leadBox,agent:e?.agent,boxRole:e?.role,text:r,...o?{kind:o}:{},...c.length?{attachments:c}:{}});delete t.draft;try{await Te({cwd:n.cwd,sessionId:n.sessionId,sessionName:t.leadBox,agent:n.agent,agentCmd:ho(n.agent)}),await kt(t.leadBox,a,{...le(e?.agent),kind:i?"lead-handoff-first-prompt":"say"})}catch(u){throw ot(t,"unsaid",{box:t.leadBox,agent:e?.agent,boxRole:e?.role,saidTs:l.at,text:r}),u}i&&await q({flow:"lead-handoff",phase:"first-prompt",trackId:t.id,sessionName:t.leadBox,agent:e?.agent,userTextLen:r.length})}function Oh(t,{actions:e}={}){il(t).items.push({id:mo(),text:pm(e),ts:Date.now(),kind:"ship-notify"}),Rh(t)}async function ye(t,e,{kind:n=null,attachments:r=null}={}){let o=Array.isArray(r)?r.filter(f=>f&&typeof f.rel=="string"):[];if((typeof e!="string"||!ar(e))&&!o.length)throw new Error("text (string) or an attachment required");let s=Z(await H(),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 z(s.leadBox);if(!a&&await dl(s)&&(await V(),a=await z(s.leadBox)),!a)throw new Error(`lead box "${s.leadBox}" is not running`);let i=s.boxes.find(f=>f.name===s.leadBox),c=ar(e),l=il(t),u=await wt(s.leadBox,{lines:_t}).catch(()=>""),d=l.items.length>0||Ho(i?.agent,u);if(!d&&sr(i?.agent,u)){let f=await ve(s.leadBox).catch(()=>u);d=!!bn(i,f)}if(d){let f={id:mo(),text:c,ts:Date.now(),...n?{kind:n}:{},...o.length?{attachments:o}:{}};return l.items.push(f),Rh(t),delete s.draft,await V(),{ok:!0,leadBox:s.leadBox,queued:!0,id:f.id,position:l.items.length}}return await $h(s,i,a,c,n,o),await V(),{ok:!0,leadBox:s.leadBox}}var yh="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 Ch(t,{hint:e}={}){let n=Z(await H(),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=oi(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";Ed(n.id,{name:s,box:n.leadBox});let a=String(e||"").trim()?`${yh}
|
|
103
105
|
|
|
104
|
-
What to preview: ${String(e).trim()}`:
|
|
106
|
+
What to preview: ${String(e).trim()}`:yh;try{await ye(t,a,{kind:"spin-up-view"})}catch(i){throw ns(n.id,{error:"drive-failed"}),i}return{ok:!0,running:!0}}async function cl(t){let e=Z(await H(),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(c=>c.name===e.leadBox),r=ta(t),o=await z(e.leadBox),s=o?await eA(e.leadBox,n?.agent):{stopped:!1,sends:0},a=o&&s.stopped?await oA(e,n):null,i=o?await nA(e).catch(()=>null):null;return await q({flow:"interrupt",phase:"sent",trackId:t,sessionName:e.leadBox,agent:n?.agent,dropped:r.length,escSends:s.sends,stopped:s.stopped,unsent:!!a,shipDismissed:!!i}).catch(()=>{}),{ok:!0,interrupted:!!o,stopped:s.stopped,dropped:r.length,restored:a}}var Qv=[300,350,400],tA=3;async function eA(t,e){let n=0;for(let r=0;r<tA;r++){await bt(t,["Escape"],{kind:"interrupt"}),n++;for(let o of Qv){await na(o);let s=await wt(t,{lines:_t}).catch(()=>"");if(!jn(e,s))return{stopped:!0,sends:n}}}return{stopped:!1,sends:n}}async function nA(t){let e=Vc(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 rA=[250,400,500,600];async function oA(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 rA){await na(r);let o=await wt(t.leadBox,{lines:_t}).catch(()=>""),s=pc(e.agent,o);if(s&&Np(n.text,s))return await sA(t.leadBox,e.agent,n.text.length+8),t.draft={text:n.text,updatedAt:Date.now()},ot(t,"unsaid",{box:t.leadBox,agent:e.agent,boxRole:e.role,saidTs:n.at,text:n.text}),await V(),n.text}return null}async function sA(t,e,n){for(let r=0;r<3;r++){await Do(t,n,{kind:"unsend-wipe"}).catch(()=>{}),await na(200);let o=await wt(t,{lines:_t}).catch(()=>""),s=pc(e,o);if(!s)return!0;n=s.length+8}return!1}async function Mh(t,e){if(typeof e!="string")throw new Error("text (string) required");let n=Z(await H(),t);if(!n)throw new Error(`track "${t}" not found`);let r=ar(e);return r?n.draft={text:r,updatedAt:Date.now()}:delete n.draft,await V(),{ok:!0,draft:n.draft||null}}async function Ph(t,e){if(typeof e!="string"||!e.trim())throw new Error("model (string) required");let n=e.trim(),r=Z(await H(),t);if(!r)throw new Error(`track "${t}" not found`);let o=await z(r.leadBox);if(!o)throw new Error(`lead box "${r.leadBox}" is not running`);let a=r.boxes.find(d=>d.name===r.leadBox)?.agent||o.agent||"claude",i=zp(a,n),l=[...r.events||[]].reverse().find(d=>d.type==="model-change")?.to??null;l||(l=(await Cr(o.cwd,o.sessionId,{fallbackNewest:!1}).catch(()=>null))?.model??null),await Te({cwd:o.cwd,sessionId:o.sessionId,sessionName:r.leadBox,agent:o.agent,agentCmd:ho(o.agent)});let u=await Xp(a,r.leadBox,i);return ot(r,"model-change",{to:n,from:l,box:r.leadBox,agent:a,verified:!!u.verified}),await V(),{ok:!0,requested:n,leadBox:r.leadBox,...u}}async function Nh(t){for(let e of t){if(!e||!await _s(e))continue;let n=await Dp(e).catch(()=>null);if(!n)continue;if((await D.stat(j.join(n,".git")).catch(()=>null))?.isDirectory())return n}return null}function $t(t){let e=t;return e?e.kind==="multi"?Array.isArray(e.repos)?e.repos.filter(Boolean):[]:e.base?[{name:j.basename(e.base),dir:e.dir??null,base:e.base,baseRef:e.baseRef??null,repo:e.repo??null}]:[]:[]}async function no(t){let e=[];for(let n of $t(t?.workspace)){if(!n?.dir)continue;let r=await yc(n.dir,{ignorePlumbing:!0}).catch(()=>!1),o=await bc(n.dir,n.baseRef).catch(()=>0);(r||o>0)&&e.push({...n,dirty:r,ahead:o})}return e}async function Bh(t){let e=t;typeof e=="string"&&!j.isAbsolute(e)&&/^[^/\s]+\/[^/\s]+$/.test(e)&&(e=await Hi(e));let n=await Nh([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 aA(t,e,n,r){let o=`track/${ml(e)}-${t}`,s=[];try{await D.mkdir(n,{recursive:!0});let a=new Set([".bostrat"]);for(let i of r){let c=await Bh(i);if(s.some(p=>p.base===c))throw new Error(`repo tagged twice: ${i}`);let l=await Y(c).catch(()=>null),u=j.basename(c);a.has(u)&&l&&(u=l.replace("/","-"));for(let p=2;a.has(u);p++)u=`${j.basename(c)}-${p}`;a.add(u);let d=j.join(n,u),f=await Es(c);await ir(c,d,o,f),s.push({name:u,dir:d,base:c,baseRef:f,repo:l})}return await ea(n,s,o),{dir:n,kind:"multi",branch:o,repos:s}}catch(a){for(let i of s)await Ft(i.base,i.dir).catch(()=>{});throw await D.rm(n,{recursive:!0,force:!0}).catch(()=>{}),a}}var Dh=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 jh(t,e){let n=await H(),r=Z(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 Bh(typeof e=="string"?e.trim():e);if((o.repos??[]).some(f=>f?.base===s))throw new Error(`repo already tagged: ${e}`);let a=await Y(s).catch(()=>null),i=new Set([".bostrat",...(o.repos??[]).map(f=>f?.name).filter(Boolean)]),c=j.basename(s);i.has(c)&&a&&(c=a.replace("/","-"));for(let f=2;i.has(c);f++)c=`${j.basename(s)}-${f}`;let l=j.join(o.dir,c),u=await Es(s);await jp(s,o.branch)?await Lp(s,l,o.branch):await ir(s,l,o.branch,u);let d={name:c,dir:l,base:s,baseRef:u,repo:a};return o.repos=[...(o.repos??[]).filter(Boolean),d],await ea(o.dir,o.repos,o.branch),ot(r,"retagged",{action:"add",repo:a??s,name:c,workspace:Dh(o)}),await V(),d}async function Lh(t,e){let n=await H(),r=Z(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 Kt.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 yc(s.dir,{ignorePlumbing:!0}).catch(()=>!1),i=await bc(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 Ft(s.base,s.dir).catch(()=>{}),await D.rm(s.dir,{recursive:!0,force:!0}).catch(()=>{}),o.repos=(o.repos??[]).filter(c=>c&&c!==s),await ea(o.dir,o.repos,o.branch),ot(r,"retagged",{action:"remove",repo:s.repo??s.base,name:s.name,workspace:Dh(o)}),await V(),{removed:s.name}}async function iA(t,e,{repo:n,repos:r,cwd:o}={}){await D.mkdir(nl,{recursive:!0});let s=j.join(nl,t),a=Array.isArray(r)?r.map(c=>typeof c=="string"?c.trim():"").filter(Boolean):[];if(a.length>=2)return aA(t,e,s,a);a.length===1&&!n&&(n=a[0]),typeof n=="string"&&!j.isAbsolute(n)&&/^[^/\s]+\/[^/\s]+$/.test(n)&&(n=await Hi(n));let i=await Nh([n,o,Pv]);if(i){let c=`track/${ml(e)}-${t}`;try{let l=await Es(i);await ir(i,s,c,l),await rl(s);let u=await Y(i).catch(()=>null);return{dir:s,base:i,branch:c,kind:"worktree",baseRef:l,repo:u}}catch{await D.rm(s,{recursive:!0,force:!0}).catch(()=>{})}}return await D.mkdir(s,{recursive:!0}),await Fp(s),await rl(s),{dir:s,base:null,branch:null,kind:"repo"}}var uo="# bostrat workspace",io=`${uo}
|
|
105
107
|
|
|
106
108
|
When asked to run, preview, or demo this app so the operator can review it: do NOT
|
|
107
109
|
start an ad-hoc dev server and do NOT share a localhost URL (the operator is on
|
|
@@ -213,6 +215,23 @@ add a near-duplicate. When every step is confirmed, run
|
|
|
213
215
|
than creating another. Runbooks are read-only for the operator: they act and
|
|
214
216
|
report back in chat; you are the only writer.
|
|
215
217
|
|
|
218
|
+
When something happens that the operator would want to know about RIGHT NOW and
|
|
219
|
+
they are plausibly away from the screen, you can reach their phone:
|
|
220
|
+
|
|
221
|
+
bostrat-notify "<message>" [--title "<title>"] [--urgent]
|
|
222
|
+
|
|
223
|
+
Use this SPARINGLY and only for: long work finishing, a real blocker that needs
|
|
224
|
+
their decision, or a failure that wastes their time if it goes unnoticed. NEVER
|
|
225
|
+
for progress updates, never to announce that you are starting, and never more
|
|
226
|
+
than once per task unless the situation genuinely changed. If they are plausibly
|
|
227
|
+
watching the chat, just say it in chat \u2014 the chat is ALWAYS the default and a
|
|
228
|
+
notification is the exception. Write the message so it stands alone on a lock
|
|
229
|
+
screen (they cannot see the chat from there): say what happened and what it
|
|
230
|
+
means, not "done" or "see chat". Never put a secret value in one \u2014 notifications
|
|
231
|
+
travel off-box, and the server rejects token-shaped content. Anything worth
|
|
232
|
+
notifying about belongs in your chat message too; the notification is a nudge
|
|
233
|
+
toward the chat, never the only record.
|
|
234
|
+
|
|
216
235
|
When you FINISH a piece of implementation work (feature built, bug fixed, checks
|
|
217
236
|
green) and the operator has not already told you to ship it: do NOT commit, push,
|
|
218
237
|
or open a PR on your own. Instead END your final message with a short, explicit
|
|
@@ -223,9 +242,9 @@ which steps to run (and which to skip). An offer buried mid-message, phrased as
|
|
|
223
242
|
statement, or omitted entirely renders no controls \u2014 so close EVERY completed
|
|
224
243
|
implementation turn this way, every time. When the operator has already asked you
|
|
225
244
|
to commit / push / open the PR, just do it; do not re-offer.
|
|
226
|
-
`;async function
|
|
227
|
-
`)),
|
|
228
|
-
`);return`${
|
|
245
|
+
`;async function rl(t){try{await D.writeFile(j.join(t,"AGENTS.md"),io,{flag:"wx"})}catch{return}await uA(t,["/AGENTS.md"])}var tl=t=>typeof t=="string"&&(t===uo||t.startsWith(`${uo}
|
|
246
|
+
`)),cA="# bostrat multi-repo workspace";function lA(t,e){let n=t.map(r=>`- \`${r.name}/\`${r.repo?` \u2014 github.com/${r.repo}`:""}${r.baseRef?` (forked from ${r.baseRef})`:""}`).join(`
|
|
247
|
+
`);return`${cA}
|
|
229
248
|
|
|
230
249
|
This directory is NOT a git repo. It is a bostrat Track workspace holding one
|
|
231
250
|
git worktree per tagged repo, ALL on the same branch \`${e}\`:
|
|
@@ -237,25 +256,25 @@ push / open PRs) in each repo separately, always on that shared branch. Work
|
|
|
237
256
|
only needs to land in the repos it actually touches \u2014 an untouched repo's
|
|
238
257
|
branch simply stays at its fork point and never gets pushed. When asked to run
|
|
239
258
|
or preview an app, run it from that app's own repo directory.
|
|
240
|
-
`}async function
|
|
241
|
-
`,`${
|
|
259
|
+
`}async function ea(t,e,n){let r=lA(e,n);for(let o of["CLAUDE.md","AGENTS.md"])await D.writeFile(j.join(t,o),r).catch(()=>{})}var el=io.replace(`${uo}
|
|
260
|
+
`,`${uo}
|
|
242
261
|
|
|
243
262
|
(Global copy managed by bostrat \u2014 rewritten on service start; edits will be lost.)
|
|
244
263
|
SCOPE: these instructions apply ONLY when your working directory is under
|
|
245
264
|
~/.bostrat/workspaces/ or ~/.bostrat/worktrees/ \u2014 a bostrat-orchestrated box.
|
|
246
265
|
When working anywhere else, ignore everything below.
|
|
247
|
-
`);async function
|
|
248
|
-
`).includes(s));o.length&&await
|
|
266
|
+
`);async function Fh(t){let{stdout:e}=await Cv("git",["-C",t,"rev-parse","--git-path","info/exclude"]);return j.resolve(t,e.trim())}async function uA(t,e){try{let n=await Fh(t);await D.mkdir(j.dirname(n),{recursive:!0});let r=await D.readFile(n,"utf8").catch(()=>""),o=e.filter(s=>!r.split(`
|
|
267
|
+
`).includes(s));o.length&&await D.appendFile(n,(r.endsWith(`
|
|
249
268
|
`)||!r?"":`
|
|
250
269
|
`)+o.join(`
|
|
251
270
|
`)+`
|
|
252
|
-
`)}catch{}}async function
|
|
271
|
+
`)}catch{}}async function dA(t,e){try{let n=await Fh(t),r=await D.readFile(n,"utf8"),o=r.split(`
|
|
253
272
|
`).filter(s=>s!==e);o.length!==r.split(`
|
|
254
|
-
`).length&&await
|
|
255
|
-
`))}catch{}}async function
|
|
273
|
+
`).length&&await D.writeFile(n,o.join(`
|
|
274
|
+
`))}catch{}}async function Uh(){let t={refreshed:0,removed:0,codexGlobal:null};try{await D.mkdir(Ge,{recursive:!0});for(let r of["CLAUDE.md","AGENTS.md"])await D.writeFile(j.join(Ge,r),io).catch(()=>{});let e=process.env.CODEX_HOME||j.join(process.env.HOME||"/tmp",".codex");if((await D.stat(e).catch(()=>null))?.isDirectory()){let r=j.join(e,"AGENTS.md"),o=await D.readFile(r,"utf8").catch(()=>null);o===null?(await D.writeFile(r,el,{flag:"wx"}).catch(()=>{}),t.codexGlobal="created"):tl(o)&&o!==el&&(await D.writeFile(r,el).catch(()=>{}),t.codexGlobal="refreshed")}for(let r of[nl,wr]){let o=await D.readdir(r,{withFileTypes:!0}).catch(()=>[]);for(let s of o){if(!s.isDirectory())continue;let a=j.join(r,s.name),i=j.join(a,"AGENTS.md"),c=await D.readFile(i,"utf8").catch(()=>null);c!==null&&tl(c)&&c!==io&&(await D.writeFile(i,io).catch(()=>{}),t.refreshed++);let l=j.join(a,"CLAUDE.md"),u=await D.readFile(l,"utf8").catch(()=>null);u!==null&&tl(u)&&(await D.rm(l,{force:!0}).catch(()=>{}),await dA(a,"/CLAUDE.md"),t.removed++)}}}catch{}return t}async function fA(t){let e=t.workspace;if(!(!e||!e.dir))try{if(e.kind==="multi"){for(let n of $t(e))n.base&&n.dir&&await Ft(n.base,n.dir).catch(()=>{});await D.rm(e.dir,{recursive:!0,force:!0})}else e.kind==="worktree"&&e.base?await Ft(e.base,e.dir):await D.rm(e.dir,{recursive:!0,force:!0})}catch{}}async function ll({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 Zs(o,s),i=await H(),c=mo().slice(0,8),l=await iA(c,t,{repo:n,repos:r,cwd:e}),u=new Set(i.tracks.flatMap(m=>(m.boxes||[]).map(h=>h.name))),d=`${t.trim()} lead`;u.has(Oa(d))&&(d=`${t.trim().slice(0,50).trim()} ${c} lead`);let f=await Qe(d,l.dir,{agentCmd:a,agent:o});qo({cwd:f.cwd,sessionName:f.name,agent:o,agentCmd:a});let p={id:c,name:t.trim(),cwd:f.cwd,workspace:l,leadBox:f.name,boxes:[Yh(f,"lead","live")],events:[],createdAt:Date.now()};return ot(p,"opened",{agent:o,box:f.name,skipPermissions:s,name:p.name,cwd:p.cwd,workspace:{base:l.base??null,branch:l.branch,kind:l.kind,...l.baseRef?{baseRef:l.baseRef}:{},...l.repo?{repo:l.repo}:{},...l.repos?{repos:l.repos.map(({name:m,base:h,baseRef:b,repo:x})=>({name:m,base:h,baseRef:b,repo:x}))}:{}}}),i.tracks.push(p),await V(),Ke(p)}var pA=new Set(["claude","codex","cursor"]);async function Hh({provider:t="claude",fresh:e=!1}={}){if(!pA.has(t))throw new Error(`unknown setup provider "${t}" (claude | codex | cursor)`);let r=(await H()).tracks.find(u=>u.kind==="setup"&&u.setup?.provider===t&&!u.archivedAt);if(r){if(!e){let u=await Ke(r);if(u.boxes.find(f=>f.name===u.leadBox)?.state==="live")return u}await yr(r.id).catch(()=>{})}let o=(await Zs(t,!1)).replace(/\s+--(?:dangerously-skip-permissions|dangerously-bypass-approvals-and-sandbox)\b/g,""),s=mo().slice(0,8),a=j.join(Ge,"setup",s);await D.mkdir(a,{recursive:!0});let i=await Qe(`Connect ${t} ${s}`,a,{agentCmd:o,agent:t,firstRun:!0});t==="codex"&&qo({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:[Yh(i,"lead","live")],events:[],createdAt:Date.now()};return ot(c,"opened",{agent:t,box:i.name,skipPermissions:!1,trackKind:"setup",name:c.name,cwd:c.cwd,workspace:{base:null,branch:null,kind:"scratch"}}),(await H()).tracks.push(c),await V(),Ke(c)}async function Wh(t,e,n){await Md(t).catch(()=>{});let r=fo.get(t);r&&!r.finishedAt&&(r.error=n,r.finishedAt=Date.now()),fo.delete(t),e.handoff&&!e.handoff.finishedAt&&(e.handoff.step="error",e.handoff.error=n,e.handoff.finishedAt=Date.now());for(let a of br(t))a.finishedAt||(a.canceled=!0,a.finishedAt=Date.now()),await wl(a).catch(()=>{}),Kt.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 H(),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 qh(t,{cascadeMocks:e=!0,cascadeRunbooks:n=!1}={}){let r=await H(),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 wd(s),await Wh(t,s,"track deleted"),n?await Gd(t).catch(()=>{}):await qd(t).catch(()=>{}),await Zp(),await fA(s),e&&await gd(t).catch(()=>{}),ot(s,"track-deleted",{name:s.name,boxes:s.boxes.length,ageMs:s.createdAt?Date.now()-s.createdAt:null}),r.tracks.splice(o,1),await V(),{ok:!0,id:t,name:s.name,boxes:s.boxes.length}}async function yr(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 already archived`);return n.archivedAt=Date.now(),ot(n,"archived",{boxes:n.boxes.length}),await V(),n.kind==="setup"?await Gh(t):hA(t).catch(()=>{}),Ke(n)}var mA=9e4;async function hA(t){try{await wA(t)}catch{}await Gh(t).catch(()=>{})}async function Gh(t){let e=await H(),n=Z(e,t);if(!(!n||!n.archivedAt)){await Wh(t,n,"track archived");for(let r of n.boxes)An.has(r.state)||(r.state="stopped");await V()}}async function wA(t){let e=await H(),n=Z(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 z(n.leadBox).catch(()=>null):null;if(i?.cwd&&a)try{({text:r,items:o}=await Uu(n.leadBox,i.cwd,{timeoutMs:mA})),s="lead-cli"}catch{}if(r||(r=Mi(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 V()}async function Kh(){let t=await H(),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=Mi(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 V(),{recorded:e,skipped:n,empty:r}}async function Ue(t,e){let n=await vt(t,e).catch(()=>null);if(!n?.archiveOnMerge||!n.trackId)return!1;let r=await Vh(n);if(r.length){for(let o of r)await Yn(o.repo,o.number,!0).catch(()=>{});return!1}try{return await yr(n.trackId),!0}catch(o){if(/already archived/i.test(String(o?.message||o)))return!1;throw o}}async function Vh(t){return(await Me().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 He(t,e){return ul(t,e,"merged")}async function ul(t,e,n="merged"){let r=await vt(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 Vh(r);if(s.length){let i=[...r.notifyPending||[],o];for(let c of s)await $f(c.repo,c.number,i).catch(()=>{});return!1}let a=[...r.notifyPending||[],o];try{return await ye(r.trackId,gA(a),{kind:"merge-notify"}),!0}catch{return!1}}var bh={merged:"merged",closed:"closed on GitHub WITHOUT merging",lost:"no longer found on GitHub (deleted, or its repo left the grant)"};function gA(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 ${bh[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 ${bh[i.outcome]??i.outcome}`).join(`
|
|
256
275
|
`),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
276
|
${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}
|
|
277
|
+
${a}`}async function dl(t){if(t.archivedAt)return!1;let e=t.boxes.find(r=>r.name===t.leadBox);if(!e||An.has(e.state)||await z(e.name))return!1;let n=Sh(e.agent,e.sessionId);if(!await No(n))return ot(t,"rehydrate-failed",{box:e.name,agent:e.agent,reason:`agent "${e.agent}" not available on this box`}),!1;try{return await Qe(e.name,e.cwd,{agentCmd:n,agent:e.agent,resumeSessionId:e.sessionId,pinned:e.pinned===!0}),qo({cwd:e.cwd,sessionName:e.name,agent:e.agent,agentCmd:n}),e.state="live",ot(t,"rehydrated",{box:e.name,agent:e.agent}),!0}catch(r){return ot(t,"rehydrate-failed",{box:e.name,agent:e.agent,reason:String(r?.message||r)}),!1}}async function Jh(t){let e=await H(),n=Z(e,t);if(!n)throw new Error(`track "${t}" not found`);if(!n.archivedAt)throw new Error(`track "${t}" is not archived`);return delete n.archivedAt,ot(n,"unarchived",{}),await dl(n),await V(),Ke(n)}async function zh(){let t=await H(),e=new Set((await Nn()).map(s=>s.name)),n=new Set((t.tracks||[]).filter(s=>!s.archivedAt).flatMap(s=>(s.boxes||[]).map(a=>a.name))),r=0,o=!1;for(let s of t.tracks||[])if(s.archivedAt)for(let a of s.boxes)e.has(a.name)&&!n.has(a.name)&&(await Jt(a.name).catch(()=>{}),ot(s,"orphan-reaped",{box:a.name,agent:a.agent}),r++,o=!0),!An.has(a.state)&&a.state!=="stopped"&&(a.state="stopped",o=!0);return o&&await V(),r}async function Xh(t){let e=await H(),n=Z(e,t);if(!n)throw new Error(`track "${t}" not found`);if(n.archivedAt)throw new Error(`track "${t}" is archived \u2014 restore it before restarting the lead`);let r=n.boxes.find(a=>a.name===n.leadBox);if(!r)throw new Error(`track "${t}" has no lead box`);if(An.has(r.state))throw new Error("lead box is terminal \u2014 nothing to restart");if(await z(n.leadBox)){let a=await wt(n.leadBox,{lines:_t}).catch(()=>"");if(Ho(r.agent,a))throw new Error("lead is mid-turn \u2014 retry when it goes idle");await Jt(n.leadBox),r.state="stopped"}let s=await dl(n);if(await V(),!s)throw new Error("rehydrate failed \u2014 see the track's rehydrate-failed event");return Ke(n)}var Yh=(t,e,n)=>({name:t.name,agent:t.agent,role:e,state:n,cwd:t.cwd,sessionId:t.sessionId,pinned:t.pinned===!0,createdAt:Date.now()});function Zh(t){return(!Array.isArray(t.epochs)||t.epochs.length===0)&&(t.epochs=[{agent:t.agent??null,sessionId:t.sessionId??null,pinned:t.pinned===!0,startedAt:t.createdAt??null,endedAt:null}]),t.epochs}var fo=new Map,wo=t=>fo.get(t)||(ae?Z(ae,t)?.handoff:null)||null;async function zs(t,e){let n=await H(),r=Z(n,t);r&&(r.handoff={...r.handoff||{},...e},await V())}function yA(t,e=Date.now()){let n=t?.handoff;if(!n||n.finishedAt)return null;let r=n.step,o=!!n.toBox&&n.toBox===n.fromBox,s=t.boxes?.find?.(i=>i.name===t.leadBox)||null;return(o?t.leadBox===n.fromBox&&!!n.startedAt&&Array.isArray(s?.epochs)&&s.epochs.some(i=>i.startedAt!=null&&i.startedAt>=n.startedAt):!!n.toBox&&t.leadBox===n.toBox)?{...n,step:"done",finishedAt:e,recovered:`committed before restart (was "${r}")`}:{...n,step:"error",finishedAt:e,error:`interrupted by an agent restart during "${r}"; lead "${t.leadBox}" retained \u2014 retry the handoff`,recovered:"rolled-back"}}async function Qh(){let t=await H(),e=!1;for(let n of t.tracks||[]){let r=n.handoff,o=yA(n);if(!o)continue;e=!0;let s=!!r.toBox&&r.toBox===r.fromBox;if(s&&o.step==="error"){let c=await z(r.fromBox).catch(()=>null),l=n.boxes.find(u=>u.name===r.fromBox);if(c&&l&&c.agent===r.toAgent&&r.toAgent!==l.agent){let u=Date.now(),d=Zh(l),f=d[d.length-1],p=f.agent,m=f.sessionId??null;f.endedAt=u,r.reason&&(f.endReason=r.reason);let h=c.agent==="claude"&&!!c.sessionId;d.push({agent:r.toAgent,sessionId:c.sessionId||null,pinned:h,startedAt:u,endedAt:null}),l.agent=r.toAgent,l.sessionId=c.sessionId||null,l.pinned=h,l.state="live",n.pendingHandoffHydrate={artifact:Qs,at:u},ot(n,"lead-handoff",{from:r.fromBox,to:r.fromBox,fromAgent:p,toAgent:r.toAgent,fromSessionId:m,toSessionId:c.sessionId||null,epoch:d.length-1,singleBox:!0,skipPermissions:r.skipPermissions,reason:r.reason??null,handoffStartedAt:r.startedAt,recovered:!0}),n.handoff={...r,step:"done",finishedAt:u,recovered:"swap adopted from live tmux tags after restart"},await q({flow:"lead-handoff",phase:"reconciled",trackId:n.id,outcome:"done",recovered:n.handoff.recovered,fromBox:r.fromBox,toBox:r.toBox}).catch(()=>{});continue}}let a=!s&&o.recovered==="rolled-back"&&r.toBox&&r.toBox!==n.leadBox?r.toBox:null,i=!s&&o.step==="done"?r.fromBox:null;n.handoff=o,await q({flow:"lead-handoff",phase:"reconciled",trackId:n.id,outcome:o.step,recovered:o.recovered,fromBox:o.fromBox,toBox:o.toBox}).catch(()=>{}),i&&await Jt(i).catch(()=>{}),a&&await Jt(a).catch(()=>{})}return e&&await V(),e}async function fl(t,e,n=!0,r=null){let o=fo.get(t);if(o&&!o.finishedAt)throw new Error("a lead handoff is already running for this Track");let s=Z(await H(),t);if(!s)throw new Error(`track "${t}" not found`);let a=await Zs(e,n),i=await z(s.leadBox);if(!i)throw new Error(`lead box "${s.leadBox}" is not running`);let c={trackId:t,step:"checkpoint",fromBox:s.leadBox,toAgent:e,skipPermissions:n,reason:vh(r),toBox:s.leadBox,startedAt:Date.now(),finishedAt:null,error:null};return fo.set(t,c),await zs(t,{step:c.step,fromBox:c.fromBox,toAgent:e,toBox:c.toBox,skipPermissions:n,startedAt:c.startedAt,finishedAt:null,error:null}),bA(c,s,i,a,e).catch(async l=>{c.error=l?.message||String(l),c.finishedAt=Date.now(),console.error(`[lead-handoff ${t}] FAILED at step "${c.step}": ${c.error}`),await zs(t,{step:c.step,error:c.error,finishedAt:c.finishedAt}).catch(()=>{}),q({flow:"lead-handoff",phase:"failed",trackId:t,step:c.step,error:c.error,fromBox:c.fromBox,toAgent:c.toAgent,toBox:c.toBox}).catch(()=>{})}),q({flow:"lead-handoff",phase:"started",trackId:t,fromBox:c.fromBox,toAgent:e,cwd:i.cwd}).catch(()=>{}),c}async function bA(t,e,n,r,o){let s=async(E,R={})=>{t.step=E,console.log(`[lead-handoff ${t.trackId}] step \u2192 ${E}`),await zs(t.trackId,{step:E,toBox:t.toBox}),await q({flow:"lead-handoff",phase:E,trackId:t.trackId,fromBox:t.fromBox,toBox:t.toBox,toAgent:o,sessionName:R.sessionName,...R})};console.log(`[lead-handoff ${t.trackId}] ${t.fromBox}: ${n.agent} \u2192 ${o} IN PLACE`);let a=await H(),i=Z(a,t.trackId)?.boxes.find(E=>E.name===t.fromBox)||null,c=i?.agent||n.agent,l=i?.sessionId??n.sessionId??null,u=i?.pinned===!0;await s("checkpoint",{sessionName:t.fromBox}),await Vu(t.fromBox),await s("swap",{cwd:n.cwd,agentCmd:r});let d=await Ni(t.fromBox,{agentCmd:r,agent:o});await s("hydrate",{sessionName:t.fromBox}),await Te({cwd:n.cwd,sessionId:d.sessionId,sessionName:t.fromBox,agent:o,agentCmd:ho(o)});let f=await wt(t.fromBox,{lines:_t}).catch(()=>"");if(!Wo(o,f)){await s("rollback",{sessionName:t.fromBox,fromAgent:c});let E=Sh(c,l,{skipPermissions:t.skipPermissions});await Ni(t.fromBox,{agentCmd:E,agent:c,resumeSessionId:l,pinned:u});let R=await H(),st=Z(R,t.trackId);if(st){let I=st.boxes.find(L=>L.name===t.fromBox);I&&(I.state="live"),ot(st,"lead-handoff-failed",{box:t.fromBox,fromAgent:c,toAgent:o,reason:t.reason,error:`incoming ${o} CLI never drew its composer`,rolledBack:!0,handoffStartedAt:t.startedAt}),await V()}throw new Error(`incoming ${o} CLI never drew its composer \u2014 rolled back to ${c} (--resume, context intact)`)}await s("commit");let p=await H(),m=Z(p,t.trackId);if(!m)throw new Error(`track "${t.trackId}" not found during handoff commit \u2014 was it deleted or archived mid-flight?`);let h=m.boxes.find(E=>E.name===t.fromBox);if(!h)throw new Error(`lead box row "${t.fromBox}" missing during handoff commit`);let b=Date.now(),x=Zh(h),v=x[x.length-1];v.endedAt=b,t.reason&&(v.endReason=t.reason),x.push({agent:o,sessionId:d.sessionId,pinned:d.pinned,startedAt:b,endedAt:null}),h.agent=o,h.sessionId=d.sessionId,h.pinned=d.pinned,h.state="live",h.role="lead",m.pendingHandoffHydrate={artifact:Qs,at:b},ot(m,"lead-handoff",{from:t.fromBox,to:t.fromBox,fromAgent:c,toAgent:o,fromSessionId:l,toSessionId:d.sessionId,epoch:x.length-1,singleBox:!0,forced:d.forced||void 0,skipPermissions:t.skipPermissions,reason:t.reason,handoffStartedAt:t.startedAt}),await V(),await s("done",{sessionName:t.fromBox}),await zs(t.trackId,{finishedAt:Date.now(),error:null}),t.finishedAt=Date.now()}var kA=Number(process.env.BOSTRAT_SUBTASK_TIMEOUT_MS)||18e5,xA=Number(process.env.BOSTRAT_SUBTASK_POLL_MS)||3e3,wr=j.join(Ge,"worktrees"),mr=[co,Js],Kt=new Map;var br=t=>{let e=new Map([...Kt.values()].filter(r=>r.trackId===t).map(r=>[r.id,r])),n=ae?Z(ae,t):null;if(n?.subtasks)for(let[r,o]of Object.entries(n.subtasks))e.has(r)||e.set(r,o);return[...e.values()].sort((r,o)=>r.startedAt-o.startedAt)},pl=t=>({id:t.id,trackId:t.trackId,trackName:t.trackName,agent:t.agent,task:t.task,skipPermissions:t.skipPermissions,reason:t.reason||null,step:t.step,failedStep:t.failedStep||null,box:t.box,branch:t.branch,base:t.base,repos:t.repos||null,dir:t.dir,stat:t.stat,files:t.files||null,conflicts:t.conflicts||[],applied:t.applied,error:t.error,canceled:t.canceled,startedAt:t.startedAt,finishedAt:t.finishedAt});async function SA(t,e,n){let r=await H(),o=Z(r,t);o&&(o.subtasks=o.subtasks||{},o.subtasks[e]={...o.subtasks[e]||{},...n},await V())}var gr=t=>SA(t.trackId,t.id,pl(t));async function hr(t,e){t.step=e,await gr(t)}function vA(t,e=Date.now()){if(!t||t.finishedAt)return null;let n=t.step;return["snapshot","spawn","working"].includes(n)?{...t,step:"error",finishedAt:e,error:`interrupted by an agent restart during "${n}"; no changes were applied to the lead \u2014 retry the delegation`,recovered:"rolled-back"}:{...t,step:"error",finishedAt:e,error:`interrupted by an agent restart during "${n}"; the lead's working tree may hold a PARTIAL merge \u2014 review git status. The subtask's work is preserved on branch ${t.branch}`,recovered:"partial-merge"}}function AA(t){return!!t&&!t.finishedAt&&!t.canceled&&t.step==="working"&&!!t.box&&!!t.dir&&!!t.base&&!!t.branch}async function TA(t,e,n){if(!AA(n)||Kt.has(e)||!await z(n.box).catch(()=>null))return!1;try{await D.access(n.dir)}catch{return!1}let o=await z(t.leadBox).catch(()=>null);if(!o?.cwd)return!1;let s={...n,id:e,leadBox:t.leadBox,step:"working",canceled:!1,forceMerge:!1};return Kt.set(e,s),ow(s,o).catch(a=>nw(s,a)),!0}async function tw(){let t=await H(),e=!1;for(let n of t.tracks||[]){for(let[r,o]of Object.entries(n.subtasks||{})){if(Kt.has(r))continue;if(await TA(n,r,o)){e=!0,ot(n,"subtask-resumed",{box:o.box,agent:o.agent,task:o.task,subtaskId:r,resumedStep:o.step}),await q({flow:"subtask",phase:"resumed",trackId:n.id,subtaskId:r,box:o.box,branch:o.branch}).catch(()=>{});continue}let s=o.step,a=vA(o);if(!a)continue;e=!0,n.subtasks[r]=a;let i=n.boxes.find(c=>c.subtaskId===r);i&&!An.has(i.state)&&(i.state="canceled"),ot(n,"subtask-failed",{from:o.box,fromAgent:o.agent,task:o.task,subtaskId:r,failedStep:s,error:a.error,recovered:a.recovered,durationMs:o.startedAt?a.finishedAt-o.startedAt:null}),await q({flow:"subtask",phase:"reconciled",trackId:n.id,subtaskId:r,outcome:a.step,recovered:a.recovered,box:o.box,branch:o.branch}).catch(()=>{}),await kh(n,o.box,o.dir)}for(let r of n.boxes){if(r.role!=="subtask"||An.has(r.state)||Kt.has(r.subtaskId)||n.subtasks?.[r.subtaskId])continue;e=!0,r.state="canceled";let o=r.subtaskId?j.join(wr,`${n.id}-${r.subtaskId}`):null;await q({flow:"subtask",phase:"reaped-legacy-orphan",trackId:n.id,subtaskId:r.subtaskId,box:r.name}).catch(()=>{}),await kh(n,r.name,o)}}return e=await _A(t)||e,e&&await V(),e}async function kh(t,e,n){if(e&&await Jt(e).catch(()=>{}),!n)return;if(t.workspace?.kind==="multi"){for(let o of $t(t.workspace)){let s=j.join(n,o.name);await Ft(o.base,s).then(()=>!0,()=>!1)||await Ft(o.dir,s).catch(()=>{})}await D.rm(n,{recursive:!0,force:!0}).catch(()=>{});return}let r=await z(t.leadBox).catch(()=>null);r?.cwd&&await Ft(r.cwd,n).catch(()=>{})}async function _A(t){let e;try{e=await D.readdir(wr)}catch{return!1}let n=new Set;for(let o of t.tracks||[]){for(let s of Object.keys(o.subtasks||{}))n.add(`${o.id}-${s}`);for(let s of o.boxes||[])s.subtaskId&&n.add(`${o.id}-${s.subtaskId}`)}for(let o of Kt.values())o.dir&&n.add(j.basename(o.dir));let r=!1;for(let o of e){if(n.has(o))continue;let s=j.join(wr,o),a=(t.tracks||[]).find(i=>o.startsWith(`${i.id}-`));if(a?.workspace?.kind==="multi"){for(let i of $t(a.workspace))await Ft(i.base,j.join(s,i.name)).catch(()=>{});await D.rm(s,{recursive:!0,force:!0}).catch(()=>{})}else{let i=a?await z(a.leadBox).catch(()=>null):null;i?.cwd?await Ft(i.cwd,s).catch(()=>{}):await D.rm(s,{recursive:!0,force:!0}).catch(()=>{})}await q({flow:"subtask",phase:"swept-orphan-worktree",dir:s}).catch(()=>{}),r=!0}return r}var ml=t=>t.trim().replace(/\s+/g,"-").replace(/[^A-Za-z0-9._-]/g,"").toLowerCase()||"track",po=class extends Error{};async function ew(t,{agent:e,task:n,skipPermissions:r=!0,reason:o=null}={}){if(typeof n!="string"||!ar(n))throw new Error("task (string) required");let s=await Zs(e,r),a=Z(await H(),t);if(!a)throw new Error(`track "${t}" not found`);let i=await z(a.leadBox);if(!i)throw new Error(`lead box "${a.leadBox}" is not running`);if(!i.cwd)throw new Error("can't resolve the lead's working directory");let c=a.workspace?.kind==="multi",l=null;if(c){let f=$t(a.workspace);if(!f.length)throw new Error("this multi-repo Track has no tagged repos to delegate into");for(let p of f)if(!p?.dir||!await _s(p.dir))throw new Error(`tagged checkout "${p?.name??"?"}" is missing from the lead workspace (${p?.dir??"no dir"})`);l=f.map(p=>({name:p.name,leadDir:p.dir,base:null}))}else if(!await _s(i.cwd))throw new Error(`subtask delegation needs the lead's workspace to be a git repo (got ${i.cwd})`);let u=mo().slice(0,8),d={id:u,trackId:t,trackName:a.name,agent:e,task:ar(n),skipPermissions:r,reason:vh(o),step:"snapshot",leadBox:a.leadBox,box:null,branch:`bostrat/${ml(a.name)}-${u}`,base:null,repos:l,dir:j.join(wr,`${t}-${u}`),stat:null,conflicts:[],applied:null,startedAt:Date.now(),finishedAt:null,error:null,canceled:!1,forceMerge:!1};return Kt.set(u,d),await gr(d),EA(d,i,s).catch(f=>nw(d,f)),d}async function nw(t,e){t.failedStep=t.step,t.error=e instanceof po?"canceled":e?.message||String(e),t.step=e instanceof po?"canceled":"error",t.finishedAt=Date.now(),await wl(t).catch(()=>{}),await gr(t).catch(()=>{}),await OA(t).catch(()=>{})}function hl(t){let e=Kt.get(t);if(!e)throw new Error(`subtask "${t}" not found`);if(e.finishedAt)throw new Error("subtask already finished");return e.canceled=!0,e}function rw(t){let e=Kt.get(t);if(!e)throw new Error(`subtask "${t}" not found`);if(e.finishedAt)throw new Error("subtask already finished");if(e.step!=="working")throw new Error(`subtask is not waiting to merge (step: ${e.step})`);return e.forceMerge=!0,e}async function EA(t,e,n){if(await hr(t,"snapshot"),t.repos){for(let s of t.repos)s.base=await wc(s.leadDir,`bostrat: snapshot for subtask ${t.id}`);await gr(t)}else t.base=await wc(e.cwd,`bostrat: snapshot for subtask ${t.id}`);if(await hr(t,"spawn"),await D.mkdir(wr,{recursive:!0}),t.repos){await D.mkdir(t.dir,{recursive:!0});for(let a of t.repos)await ir(a.leadDir,j.join(t.dir,a.name),t.branch,a.base);let s=$t((Z(await H(),t.trackId)||{}).workspace);await ea(t.dir,t.repos.map(a=>{let i=s.find(c=>c?.name===a.name);return{name:a.name,repo:i?.repo??null,baseRef:null}}),t.branch)}else await ir(e.cwd,t.dir,t.branch,t.base),await rl(t.dir);await IA(t,e);let r=t.trackName.slice(0,38).trim(),o=await Qe(`${r} ${t.agent} sub ${t.id}`,t.dir,{agentCmd:n,agent:t.agent});t.box=o.name,await RA(t,o),await Ah(t.trackId,o.name,jv(t.task,!!t.repos),{kind:"subtask"}),await ow(t,e)}async function ow(t,e){await hr(t,"working");let n=j.join(t.dir,Js);if(await MA(n,t),t.canceled)throw new po;if(await hr(t,"merge"),t.repos){let o={files:0,insertions:0,deletions:0},s=[],a=[],i=!0;for(let c of t.repos){let l=j.join(t.dir,c.name);await gc(l,`bostrat: subtask ${t.id} result (auto-committed)`),c.stat=await xc(c.leadDir,c.base,t.branch,mr),c.files=await Sc(c.leadDir,c.base,t.branch,mr);let u=await vc(c.leadDir,c.base,t.branch,mr);c.applied=u.applied,c.conflicts=u.conflicts??[],o.files+=c.stat.files,o.insertions+=c.stat.insertions,o.deletions+=c.stat.deletions,s.push(...c.files.map(d=>j.join(c.name,d))),a.push(...c.conflicts.map(d=>d.startsWith("patch did not apply cleanly")?`${c.name}: ${d}`:j.join(c.name,d))),u.applied||(i=!1)}t.stat=o,t.files=s,t.conflicts=a,t.applied=i}else{await gc(t.dir,`bostrat: subtask ${t.id} result (auto-committed)`),t.stat=await xc(e.cwd,t.base,t.branch,mr),t.files=await Sc(e.cwd,t.base,t.branch,mr);let o=await vc(e.cwd,t.base,t.branch,mr);t.applied=o.applied,t.conflicts=o.conflicts}await gr(t);let r=await CA(t,e,n);await hr(t,"resume"),await Ah(t.trackId,t.leadBox,Lv(t.task,t.stat,t.conflicts,r),{kind:"subtask-resume"}),await hr(t,"reap"),await wl(t),t.step="done",t.finishedAt=Date.now(),await $A(t),await gr(t)}async function IA(t,e){let n=j.join(t.dir,j.dirname(co));await D.mkdir(n,{recursive:!0});let r="";try{r=await mu(e.cwd,e.sessionId)}catch{}if(!r)try{r=await D.readFile(j.join(e.cwd,".bostrat/handoff/latest.md"),"utf8")}catch{r="_(no prior conversation digest available \u2014 rely on the working tree.)_"}let o=`# Lead context for subtask ${t.id}
|
|
259
278
|
|
|
260
279
|
**Delegated task:** ${t.task}
|
|
261
280
|
|
|
@@ -266,42 +285,43 @@ ${a}`}async function al(t){if(t.archivedAt)return!1;let e=t.boxes.find(r=>r.name
|
|
|
266
285
|
## Conversation so far (lead \u2192 you)
|
|
267
286
|
|
|
268
287
|
${r}
|
|
269
|
-
`;await
|
|
270
|
-
${n}`}},{type:"section",text:{type:"mrkdwn",text:r}},{type:"context",elements:[{type:"mrkdwn",text:`<${
|
|
288
|
+
`;await D.writeFile(j.join(t.dir,co),o)}async function RA(t,e){let n=await H(),r=Z(n,t.trackId);r&&(r.boxes.push({name:e.name,agent:e.agent,role:"subtask",state:"live",cwd:e.cwd,sessionId:e.sessionId,createdAt:Date.now(),subtaskId:t.id,branch:t.branch,task:t.task}),ot(r,"subtask-delegated",{to:e.name,toAgent:t.agent,task:t.task,subtaskId:t.id,skipPermissions:t.skipPermissions,reason:t.reason}),r.subtasks=r.subtasks||{},r.subtasks[t.id]={...r.subtasks[t.id]||{},...pl(t)},await V())}async function $A(t){let e=await H(),n=Z(e,t.trackId);if(!n)return;let r=n.boxes.find(o=>o.subtaskId===t.id);r&&(r.state=t.conflicts.length?"conflict":"merged",r.stat=t.stat),ot(n,"subtask-merged",{from:t.box,fromAgent:t.agent,task:t.task,subtaskId:t.id,stat:t.stat,conflicts:t.conflicts.length,files:t.files,durationMs:t.finishedAt?t.finishedAt-t.startedAt:null}),await V()}async function OA(t){let e=await H(),n=Z(e,t.trackId);if(!n)return;let r=t.canceled||t.error==="canceled",o=n.boxes.find(s=>s.subtaskId===t.id);o&&(o.state=r?"canceled":"failed"),ot(n,r?"subtask-canceled":"subtask-failed",{from:t.box,fromAgent:t.agent,task:t.task,subtaskId:t.id,failedStep:t.failedStep||t.step,error:t.error,durationMs:t.finishedAt?t.finishedAt-t.startedAt:null}),n.subtasks=n.subtasks||{},n.subtasks[t.id]={...n.subtasks[t.id]||{},...pl(t)},await V()}async function CA(t,e,n){let r=`.bostrat/handoff/subtask-${t.id}.md`;try{let o=j.join(e.cwd,r);await D.mkdir(j.dirname(o),{recursive:!0}),await D.copyFile(n,o)}catch{}return r}async function wl(t){if(t.box&&await Jt(t.box).catch(()=>{}),t.repos&&t.dir){for(let e of t.repos)await Ft(e.leadDir,j.join(t.dir,e.name)).catch(()=>{});await D.rm(t.dir,{recursive:!0,force:!0}).catch(()=>{})}else if(t.dir){let n=(await z(t.leadBox).catch(()=>null))?.cwd;n&&await Ft(n,t.dir).catch(()=>{})}}async function MA(t,e){let n=(e.startedAt||Date.now())+kA;for(;;){if(e.canceled||e.forceMerge)return;try{await D.access(t);return}catch{}if(Date.now()>n)throw new Error("subtask timed out without signaling completion (no result digest written)");await na(xA)}}function na(t){return new Promise(e=>setTimeout(e,t))}import go from"node:fs/promises";import sa from"node:path";import jA from"node:os";import PA from"ws";var sw=()=>process.env.BOSTRAT_SLACK_API_BASE||"https://slack.com/api";async function ra(t,e,n={}){let r=await fetch(`${sw()}/${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 gl=(t,e)=>ra("chat.postMessage",t,e),kr=(t,e)=>ra("chat.update",t,e);async function Ot(t,e){await fetch(t,{method:"POST",headers:{"content-type":"application/json; charset=utf-8"},body:JSON.stringify(e)}).catch(()=>{})}async function NA(t,e,n={}){let r=await fetch(`${sw()}/${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 aw(t,{filename:e,buffer:n,title:r,channelId:o,threadTs:s,initialComment:a}){let{upload_url:i,file_id:c}=await NA("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 ra("files.completeUploadExternal",t,{files:[{id:c,title:r}],channel_id:o,thread_ts:s,...a?{initial_comment:a}:{}})}var BA=2200;function iw({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 ra("apps.connections.open",u))}catch(u){return n(`connections.open failed: ${u?.message||u}`),c()}o=new PA(l),o.on("message",u=>{let d;try{d=JSON.parse(u)}catch{return}if(d.type==="hello"){a=1e3,n("socket connected");return}if(d.type==="disconnect"){try{o.close()}catch{}return}if(!d.envelope_id)return;let f=h=>{try{o.send(JSON.stringify(h?{envelope_id:d.envelope_id,payload:h}:{envelope_id:d.envelope_id}))}catch{}},p=!1,m=setTimeout(()=>{p=!0,f()},BA);Promise.resolve().then(()=>e(d)).then(h=>{clearTimeout(m),!p&&(p=!0,f(d.accepts_response_payload?h:void 0))},h=>{clearTimeout(m),p||(p=!0,f()),n(`envelope handler failed: ${h?.message||h}`)})}),o.on("close",c),o.on("error",u=>{n(`socket error: ${u?.message||u}`);try{o.close()}catch{}})}function c(){if(r||s)return;let l=a;a=Math.min(a*2,6e4),s=setTimeout(()=>{s=null,i()},l)}return i(),()=>{r=!0,s&&clearTimeout(s);try{o?.close()}catch{}}}var uw=()=>process.env.BOSTRAT_STATE_DIR||sa.join(process.env.HOME||jA.tmpdir(),".bostrat"),Sl=()=>sa.join(uw(),"slack.json"),oa=null,yl=null,bl=Promise.resolve();async function Al(){return oa||(yl??=go.readFile(Sl(),"utf8").then(t=>JSON.parse(t)?.threads??{}).catch(()=>({})).then(t=>oa=t).finally(()=>yl=null),yl)}async function cw(){let t=JSON.stringify({threads:oa},null,2),e=`${Sl()}.${process.pid}.tmp`;await go.mkdir(uw(),{recursive:!0}),await go.writeFile(e,t),await go.rename(e,Sl())}async function be(){bl=bl.then(cw,cw),await bl.catch(()=>{})}async function Tl(t){return(await Al())[t]??null}async function LA(t,e){let n=await Al();for(let[r,o]of Object.entries(n))if(o.channel===t&&o.ts===e)return{trackId:r,rec:o};return null}var G=t=>String(t??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");function yo(t,e){let n=String(t??"").trim();return n.length<=e?n:`${n.slice(0,e-1).trimEnd()}\u2026`}function FA(t,e=500){let n=String(t??"").trim().split(/\n\s*\n/)[0]??"";return yo(n.replace(/\n+/g," "),e)}function dw(t){let e=`*${G(t.name)}*${t.archivedAt?" \xB7 _archived_":""}`,n=`${G(t.repoLabel)} \xB7 \`${G(t.branch||"?")}\` \xB7 lead *${G(t.lead)}*`,r=t.kickoff?`> ${G(yo(t.kickoff,300))}`:"_no kickoff yet \u2014 reply in this thread to steer the lead_";return[{type:"section",text:{type:"mrkdwn",text:`${e}
|
|
289
|
+
${n}`}},{type:"section",text:{type:"mrkdwn",text:r}},{type:"context",elements:[{type:"mrkdwn",text:`<${us(t.trackId)}|open the Track> \xB7 reachable on the operator's private network only`}]}]}async function vl(t,e){await kr(t.botToken,{channel:e.channel,ts:e.ts,text:`Track: ${e.name}`,blocks:dw(e)})}async function _n(t,e,n,r=void 0){return gl(t.botToken,{channel:e.channel,thread_ts:e.ts,text:n,...r?{blocks:r}:{},unfurl_links:!1})}var lw=new Set;function Tn(t){lw.add(t),t.catch(()=>{}).finally(()=>lw.delete(t))}var ct=t=>({response_type:"ephemeral",text:t}),UA="*/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._",HA=["claude","cursor","codex"];function kl(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 xl=(t,e)=>ct(`No live Track matches \u201C${G(e)}\u201D. Live: ${t.map(n=>`\`${G(n.name)}\``).join(" \xB7 ")||"none"}`);async function fw(t,e,{name:n,repo:r,responseUrl:o}){let s=t.deps;try{let a=(await s.listTracks()).filter(d=>!d.archivedAt);if(a.length>=e.maxConcurrent){await Ot(o,{...ct(`At capacity \u2014 ${a.length} live Tracks (limit ${e.maxConcurrent}). Archive or stop something first.`),replace_original:!0});return}if(!e.channel){await Ot(o,{...ct("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 gl(e.botToken,{channel:e.channel,text:`Track started: ${l.name}`,blocks:dw(l),unfurl_links:!1});l.ts=u.ts,(await Al())[i.id]=l,await be(),await Ot(o,{...ct(`\u2713 *${G(l.name)}* started \u2014 root message posted.`),replace_original:!0})}catch(a){await Ot(o,{...ct(`Start failed: ${a?.message||a}`),replace_original:!0})}}async function WA(t,e,{track:n,responseUrl:r}){let o=t.deps;try{await o.archiveTrack(n.id);let s=await Tl(n.id);s&&(s.archivedAt=Date.now(),await be(),await vl(e,s).catch(()=>{})),await Ot(r,{...ct(`\u2713 *${G(n.name)}* archived.`),replace_original:!0})}catch(s){await Ot(r,{...ct(`Archive failed: ${s?.message||s}`),replace_original:!0})}}async function qA(t,e,{track:n,agent:r,responseUrl:o}){try{await t.deps.startLeadHandoff(n.id,r),await Ot(o,{...ct(`\u2713 Handing *${G(n.name)}* to ${G(r)} \u2014 the root message updates when the baton lands.`),replace_original:!0})}catch(s){await Ot(o,{...ct(`Handoff failed: ${s?.message||s}`),replace_original:!0})}}async function GA(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 Ot(r,{...ct(`\u2713 Stopped *${G(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 Ot(r,{...ct(`Stop failed: ${s?.message||s}`),replace_original:!0})}}function KA(t,e){let n=[...e.slice(0,99).map(r=>({text:{type:"plain_text",text:yo(`${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 *${G(t)}*:`},accessory:{type:"static_select",action_id:"bostrat-start-repo",placeholder:{type:"plain_text",text:"Choose a repo"},options:n}}]}async function VA(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 *${G(s.name)}* \u2014 ${G(i)}${a.branch?` \`${G(a.branch)}\``:""} \xB7 lead ${G(c)} \xB7 ${l}`}),o=e.length-n.length;return r.join(`
|
|
271
290
|
`)+(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:
|
|
291
|
+
_${o} archived_`:"")}async function JA(t,e){let n=await cn();if(!n)return ct("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 ct("Usage: `/bostrat start <name> [repo]`");let i=await r.listRepos(),c=a[a.length-1]?.toLowerCase(),l=a.length>1?i.find(d=>d.name.toLowerCase()===c):null,u=(l?a.slice(0,-1):a).join(" ");return l?(Tn(fw(t,n,{name:u,repo:l.path,responseUrl:e.response_url})),ct(`Starting *${G(u)}* on ${G(l.name)}\u2026`)):{response_type:"ephemeral",text:`Pick a repo for \u201C${u}\u201D`,blocks:KA(u,i)}}case"archive":{if(!a.length)return ct("Usage: `/bostrat archive <track>`");let{match:i,live:c}=kl(await r.listTracks(),a.join(" "));return i?(Tn(WA(t,n,{track:i,responseUrl:e.response_url})),ct(`Archiving *${G(i.name)}*\u2026`)):xl(c,a.join(" "))}case"handoff":{let i=r.knownAgents??HA,c=a.at(-1)?.toLowerCase();if(a.length<2||!i.includes(c))return ct(`Usage: \`/bostrat handoff <track> <${i.join("|")}>\``);let l=a.slice(0,-1).join(" "),{match:u,live:d}=kl(await r.listTracks(),l);return u?(Tn(qA(t,n,{track:u,agent:c,responseUrl:e.response_url})),ct(`Handing *${G(u.name)}* to ${G(c)}\u2026`)):xl(d,l)}case"stop":{if(!a.length)return ct("Usage: `/bostrat stop <track>`");let{match:i,live:c}=kl(await r.listTracks(),a.join(" "));return i?(Tn(GA(t,n,{track:i,responseUrl:e.response_url})),ct(`Stopping *${G(i.name)}*\u2026`)):xl(c,a.join(" "))}case"list":return ct(await VA(r));default:return ct(UA)}}async function zA(t,e,n,r){let{trackId:o,promptId:s,label:a}=r,i=await Tl(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 be(),await kr(e.botToken,{channel:i.channel,ts:c,text:`\u2713 ${a}`,blocks:[{type:"section",text:{type:"mrkdwn",text:`~${G(l||"question")}~
|
|
292
|
+
\u2713 *${G(a)}*`}}]}).catch(()=>{})}}catch(c){await Ot(n.response_url,ct(`Couldn't answer: ${c?.message||c}`))}}async function XA(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(f=>f.type==="pr.offered"&&f.payload?.offerId===s);if(!u)throw new Error("unknown offer");if(l.some(f=>f.type==="pr.offer.answered"&&f.payload?.offerId===s))throw new Error("offer already answered");let d=a==="pr"?["commit","push","pr"]:a==="push"?["commit","push"]:null;d&&await i.sayToLead(c.id,Os(d,{branch:u.payload.branch,base:u.payload.base}),{kind:d.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:d?a:"dismiss",actions:d}})}catch(c){await Ot(n.response_url,ct(`Couldn't act on the offer: ${c?.message||c}`))}}async function YA(t,e){let n=e.actions?.[0];if(!n?.action_id)return;let r=await cn();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 Ot(e.response_url,{...ct(`Starting *${G(s)}*\u2026`),replace_original:!0}),Tn(fw(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"?Tn(zA(t,r,e,o)):n.action_id==="bostrat-ship"&&Tn(XA(t,r,e,o))}async function ZA(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 LA(e.channel,e.thread_ts);if(!n)return;let r=await cn();if(r)try{await t.deps.sayToLead(n.trackId,e.text.trim())}catch(o){await _n(r,n.rec,`Couldn't deliver that to the lead: ${o?.message||o}`).catch(()=>{})}}function QA(t){return async e=>{if(e.type==="slash_commands")return JA(t,e.payload);if(e.type==="interactive"){await YA(t,e.payload);return}if(e.type==="events_api"){await ZA(t,e.payload?.event);return}}}var tT=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 eT(t,e){let n=[{type:"section",text:{type:"mrkdwn",text:`\u2753 ${G(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:yo(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 nT(t,e,n){let r=n.payload||{};if(!r.promptId||e.prompt?.promptId===r.promptId)return;e.prompt&&await kr(t.botToken,{channel:e.channel,ts:e.prompt.ts,text:"superseded",blocks:[{type:"section",text:{type:"mrkdwn",text:`~${G(e.prompt.question||"question")}~ _superseded_`}}]}).catch(()=>{});let o=await _n(t,e,r.question||"The lead needs an answer",eT(n.trackId,r));e.prompt={promptId:r.promptId,ts:o.ts,question:r.question||null},await be()}async function rT(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 be(),await kr(t.botToken,{channel:e.channel,ts:o,text:"answered",blocks:[{type:"section",text:{type:"mrkdwn",text:`~${G(s||"question")}~
|
|
293
|
+
\u2713 _answered_`}}]})}async function oT(t,e,n){let r=n.payload||{};if(!r.offerId||e.offers?.[r.offerId])return;let o=(i,c,l)=>({type:"button",action_id:"bostrat-ship",text:{type:"plain_text",text:c},value:JSON.stringify({trackId:n.trackId,offerId:r.offerId,action:i}),...l?{style:l}:{}}),s=r.toPr?[o("push",`Commit & push${r.toPr.number?` to #${r.toPr.number}`:""}`,"primary"),o("dismiss","Not now")]:[o("pr","Open PR","primary"),o("push","Just push"),o("dismiss","Not now")],a=await _n(t,e,r.excerpt||"Ship it?",[{type:"section",text:{type:"mrkdwn",text:`\u{1F6A2} ${G(yo(r.excerpt||"The lead offered to ship.",300))}`}},{type:"actions",elements:s}]);e.offers=e.offers||{},e.offers[r.offerId]=a.ts,await be()}async function sT(t,e,n){let r=n.payload||{},o=e.offers?.[r.offerId];if(!o)return;delete e.offers[r.offerId],await be();let s=r.action==="dismiss"?"dismissed":r.action==="pr"?"\u2713 opening a PR":"\u2713 pushing";await kr(t.botToken,{channel:e.channel,ts:o,text:s,blocks:[{type:"section",text:{type:"mrkdwn",text:`\u{1F6A2} ${s}`}}]})}async function aT(t,e,n,r){let o=n.payload||{};if(o.change==="ready"&&o.mockId){let s=await(r.readMockPng??iT)(o.mockId,o.version??null);if(!s)return;let a=(o.version??1)>1?` (v${o.version})`:"";await aw(t.botToken,{filename:s.filename,buffer:s.buffer,title:`${o.name}${a}`,channelId:e.channel,threadTs:e.ts,initialComment:`\u{1F3A8} mock *${G(o.name)}*${a}${o.group?` (group \`${G(o.group)}\`)`:""}`})}else if(o.change==="picked"){let s=(o.siblings??[]).map(a=>`*${G(a.name)}*`).join(", ");await _n(t,e,`Design decision: ${o.name} picked`,[{type:"section",text:{type:"mrkdwn",text:`\u{1F3A8} design decision: *${G(o.name)}* picked${s?` over ${s}`:""}`}}])}}async function iT(t,e){let n=await Ee(t);if(!n)return null;let r=nn(n,e);if(!r)return null;let o=await go.readFile(Lt(n,r.v).png).catch(()=>null);return o?{filename:`${n.name.replace(/[^\w.-]+/g,"-")}.v${r.v}.png`,buffer:o}:null}async function cT(t,e){if(!tT.has(e.type))return;let n=await Tl(e.trackId);if(!n)return;let r=await cn();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 be(),await vl(r,n);return;case"turn.agent":{if(!o.text)return;let s=FA(o.text);if(!s)return;await _n(r,n,s,[{type:"section",text:{type:"mrkdwn",text:G(s)}},{type:"context",elements:[{type:"mrkdwn",text:`<${us(e.trackId)}|full turn \u2192>`}]}]);return}case"prompt.raised":return nT(r,n,e);case"prompt.answered":return rT(r,n,e);case"pr.offered":return oT(r,n,e);case"pr.offer.answered":return sT(r,n,e);case"pr.state.changed":{if(o.change!=="opened"&&o.change!=="merged")return;let s=o.change==="opened"?"PR opened":"PR merged";await _n(r,n,`${s}${o.url?`: ${o.url}`:o.number?` (#${o.number})`:""}`);return}case"handoff.done":if(!o.toAgent||o.toAgent===n.lead)return;n.lead=o.toAgent,await be(),await vl(r,n);return;case"view.state.changed":{if(o.change!=="created"||o.restarted||!o.slug)return;await _n(r,n,`Live preview ${o.name}: ${ps(o.slug)}`,[{type:"section",text:{type:"mrkdwn",text:`\u{1F50D} live preview *${G(o.name)}*: ${ps(o.slug)}
|
|
294
|
+
_runs on the Track's branch; reachable on the operator's private network only_`}}]);return}case"mock.state.changed":return aT(r,n,e,t.deps)}}function pw(t){let e={deps:t};jt.on("event",s=>{cT(e,s).catch(()=>{})});let n=null,r=null,o=async()=>{n||!await cn()||(r&&clearInterval(r),r=null,n=iw({getToken:async()=>(await cn())?.appToken,onEnvelope:QA(e),log:s=>console.log(`[slack] ${s}`)}))};return r=setInterval(()=>o().catch(()=>{}),3e4),r.unref?.(),o().catch(()=>{}),()=>{r&&clearInterval(r),n?.()}}import lT from"node:fs/promises";function uT(t){let e=new Map;for(let n of String(t||"").split(`
|
|
295
|
+
`)){let r=n.trim();if(!r||r[0]!=="{")continue;let o;try{o=JSON.parse(r)}catch{continue}let s=o?.message?.usage;if(!s)continue;let a={input:s.input_tokens||0,output:s.output_tokens||0,cacheWrite:s.cache_creation_input_tokens||0,cacheRead:s.cache_read_input_tokens||0};if(a.input+a.output+a.cacheWrite+a.cacheRead===0)continue;let i=o.message?.id||o.uuid||null;i&&e.set(i,{messageId:i,tokens:a,model:o.message?.model||o.model||null})}return[...e.values()]}function dT(t){let e=new Set;for(let n of t)if(n.type==="cost.recorded"){n.payload?.messageId&&e.add(n.payload.messageId);for(let r of n.payload?.messageIds||[])e.add(r)}return e}function fT(t,e){return t?.transcript_path?t.transcript_path:e?.cwd&&e?.sessionId?ce(e.cwd,e.sessionId):e?.cwd?Na(e.cwd):null}async function mw(t,e){let n=await e(t?.session_id,t?.cwd);if(!n?.track)return[];let{track:r,box:o}=n,s={boxId:o?.name||null,provider:"claude",source:"hook"},a=[],i=async c=>{a.push(await O(r,{...s,...c}))};switch(t?.hook_event_name){case"SessionStart":await i({type:"session.ready",actor:"system",payload:{sessionId:t.session_id}});break;case"UserPromptSubmit":await i({type:"turn.user",actor:"user",payload:{promptHead:String(t.prompt??t.user_prompt??"").slice(0,160)||null}});break;case"PostToolUse":await i({type:"tool.called",actor:"agent",payload:{tool:t.tool_name??null}});break;case"Stop":await pT(r,o,t,i);break;default:break}return a}async function pT(t,e,n,r){let o=[],s=fT(n,e);if(s)try{o=uT(await lT.readFile(s,"utf8"))}catch{}let a=o.length?o[o.length-1]:null;if(await r({type:"turn.agent",actor:"agent",payload:{model:a?.model??e?.model??null,messageId:a?.messageId??null}}),!o.length)return;let i=dT(await F(t)),c=o.filter(u=>!i.has(u.messageId));if(c.length){let u=new Map;for(let d of c){let f=d.model||"unknown",p=u.get(f)||{tokens:{input:0,output:0,cacheWrite:0,cacheRead:0},messageIds:[]};for(let m of Object.keys(p.tokens))p.tokens[m]+=d.tokens[m];p.messageIds.push(d.messageId),u.set(f,p)}for(let[d,f]of u)await r({type:"cost.recorded",actor:"system",source:"transcript",payload:{model:d,usd:zt(d,f.tokens),tokens:f.tokens,messageId:f.messageIds[f.messageIds.length-1],messageIds:f.messageIds,apiCalls:f.messageIds.length}})}let l=a.tokens.input+a.tokens.cacheWrite+a.tokens.cacheRead;await r({type:"context.usage",actor:"system",source:"transcript",payload:{model:a.model,occupancy:l,tokens:a.tokens}})}import wT from"node:fs";import _l from"node:fs/promises";import gT from"node:http";import xr from"node:path";import ia from"node:os";import{createRequire as yT}from"node:module";import El from"ws";import mT from"node:path";import hT from"node:os";var qM=process.env.BOSTRAT_STATE_DIR||mT.join(process.env.HOME||hT.tmpdir(),".bostrat");function hw(t=process.env){let e=(t.BOSTRAT_HOST||"").trim();if(e!=="all")return e||(t.BOSTRAT_PLANE_URL?"127.0.0.1":void 0)}function ww(t){let e=String(t||"").trim();if(!e)return"";let n=/^[a-z][a-z0-9+.-]*:\/\//i.test(e)?e:`wss://${e}`,r;try{r=new URL(n)}catch{return e}let o={"https:":"wss:","http:":"ws:","wss:":"wss:","ws:":"ws:"}[r.protocol];return o?(r.protocol=o,(r.pathname===""||r.pathname==="/")&&(r.pathname="/v1/node"),r.toString()):e}var aa=(()=>{try{return yT(import.meta.url)("../package.json").version||null}catch{return null}})(),bT=process.env.BOSTRAT_STATE_DIR||xr.join(process.env.HOME||ia.tmpdir(),".bostrat");function kT(t,e){let n=String(t).split(".").map(Number),r=String(e).split(".").map(Number);if(n.some(Number.isNaN)||r.some(Number.isNaN))return!1;for(let o=0;o<Math.max(n.length,r.length);o++){let s=(n[o]||0)-(r[o]||0);if(s)return s<0}return!1}function xT({home:t=process.env.HOME||ia.homedir(),env:e=process.env}={}){let n=r=>{try{return wT.statSync(r),!0}catch{return!1}};return{claude:n(xr.join(t,".claude",".credentials.json")),codex:n(xr.join(e.CODEX_HOME||xr.join(t,".codex"),"auth.json"))}}var At=1,gw=1e3,ST=3e4,yw=100,vT=2e3,AT=25e3;function bw({url:t,token:e,nodeId:n=ia.hostname(),name:r=ia.hostname(),listTracks:o,detectAgents:s=null,selfUrl:a=`http://127.0.0.1:${process.env.PORT||3362}`,log:i=console}){if(!t||!e)throw new Error("plane-link: url and token are required");if(typeof o!="function")throw new Error("plane-link: listTracks is required");t=ww(t);let c=a.replace(/^http/,"ws"),l=new Map,u=new Map,d=xr.join(bT,"plane-link","cursor.json"),f={},p=new Map,m=null,h=!1,b=!1,x=gw,v=null,E=null,R=null,st=!1;async function I(){try{f=JSON.parse(await _l.readFile(d,"utf8"))}catch{f={}}}function L(){st=!0,!E&&(E=setTimeout(async()=>{if(E=null,!!st){st=!1;try{await _l.mkdir(xr.dirname(d),{recursive:!0}),await _l.writeFile(d,JSON.stringify(f))}catch(B){i.error("[plane-link] cursor flush failed:",B?.message||B)}}},vT),E.unref?.())}function T(B){return!m||m.readyState!==El.OPEN?!1:(m.send(JSON.stringify(B)),!0)}function U(B){for(let{trackId:$,event:_}of B)p.set(_.id,{trackId:$,seq:_.seq});return T({v:At,ch:"events",type:"append",events:B})}async function J(){let B;try{B=await o()}catch($){i.error("[plane-link] backfill listTracks failed:",$?.message||$);return}T({v:At,ch:"ctl",type:"tracks",ids:B.map($=>$.id).filter($=>typeof $=="string")});for(let $ of B){if(!h)return;let _;try{_=await F($)}catch{continue}let M=f[$.id]??0,at=_.filter(Q=>Q.seq>M&&typeof Q.id=="string");for(let Q=0;Q<at.length;Q+=yw){let ht=at.slice(Q,Q+yw).map(Tt=>({trackId:$.id,event:Tt}));if(!U(ht))return}}}function g(B){!h||!B?.trackId||typeof B.id!="string"||U([{trackId:B.trackId,event:B}])}let w=new Map;async function y(B){let{reqId:$,method:_,path:M,headers:at={},body:Q}=B,ht=Object.fromEntries(Object.entries(at).filter(([,Dt])=>Dt!=null)),Tt=new AbortController;w.set($,Tt);try{if(B.host){await A(B,ht,Tt);return}let Dt=await fetch(a+M,{method:_,headers:ht,body:Q?Buffer.from(Q,"base64"):void 0,signal:Tt.signal}),Ht=Object.fromEntries(Dt.headers);for(let k of["connection","keep-alive","transfer-encoding","upgrade","content-encoding","content-length"])delete Ht[k];if(Ht["content-type"]||(Ht["content-type"]="application/octet-stream"),T({v:At,ch:"control",type:"http_head",reqId:$,status:Dt.status,headers:Ht}),Dt.body){for await(let k of Dt.body)if(!T({v:At,ch:"control",type:"http_data",reqId:$,data:Buffer.from(k).toString("base64")}))break}T({v:At,ch:"control",type:"http_end",reqId:$,error:null})}finally{w.delete($)}}function A(B,$,_){let{reqId:M,method:at,path:Q,body:ht}=B,Tt=new URL(a);return new Promise(Dt=>{let Ht=gT.request({host:Tt.hostname,port:Tt.port,method:at,path:Q,headers:{...$,host:B.host},signal:_.signal},k=>{let{connection:W,"keep-alive":En,"transfer-encoding":Oo,upgrade:In,...xa}=k.headers;T({v:At,ch:"control",type:"http_head",reqId:M,status:k.statusCode||502,headers:xa}),k.on("data",Rg=>{T({v:At,ch:"control",type:"http_data",reqId:M,data:Buffer.from(Rg).toString("base64")})||k.destroy()}),k.on("end",()=>{T({v:At,ch:"control",type:"http_end",reqId:M,error:null}),Dt()}),k.on("error",()=>{T({v:At,ch:"control",type:"http_end",reqId:M,error:"view upstream error"}),Dt()})});Ht.on("error",k=>{T({v:At,ch:"control",type:"http_end",reqId:M,error:k?.message||"view request failed"}),Dt()}),ht&&Ht.write(Buffer.from(ht,"base64")),Ht.end()})}function K(){if(b)return;m=new El(t,{headers:{authorization:`Bearer ${e}`}}),m.on("open",async()=>{let $=null;try{$=await s?.()||null}catch{$=null}T({v:At,ch:"ctl",type:"hello",nodeId:n,name:r,platform:process.platform,version:aa,providers:xT(),...$?{agents:$}:{}})}),m.on("message",$=>{let _;try{_=JSON.parse($)}catch{return}if(_.ch==="ctl"&&_.type==="hello_ok"){h=!0,x=gw,i.log(`[plane-link] connected to plane as ${n}`),_.minNodeVersion&&aa&&kT(aa,_.minNodeVersion)&&i.error(`[plane-link] this node is bostrat-node ${aa}; the plane asks for \u2265${_.minNodeVersion} \u2014 update with: npm install -g bostrat-node`),R&&clearInterval(R),R=setInterval(()=>T({v:At,ch:"ctl",type:"ping"}),AT),R.unref?.(),J().catch(M=>i.error("[plane-link] backfill failed:",M?.message||M));return}if(_.ch==="events"&&_.type==="ack"&&Array.isArray(_.ids)){for(let M of _.ids){let at=p.get(M);at&&(p.delete(M),(f[at.trackId]??0)<at.seq&&(f[at.trackId]=at.seq))}L();return}if(_.ch==="control"&&_.type==="http_open"){y(_).catch(M=>T({v:At,ch:"control",type:"http_end",reqId:_.reqId,error:M?.message||"tunnel error"}));return}if(_.ch==="control"&&_.type==="http_cancel"){w.get(_.reqId)?.abort();return}if(_.ch==="attach"&&_.type==="open"){let M=new El(c+_.path,Array.isArray(_.protocols)?_.protocols:[],_.host?{headers:{host:_.host}}:void 0);l.set(_.streamId,M),u.set(_.streamId,[]),M.on("open",()=>{let Q=u.get(_.streamId)||[];u.delete(_.streamId);for(let{buf:ht,bin:Tt}of Q)M.send(ht,{binary:Tt})}),M.on("message",(Q,ht)=>{let Tt=ht?Q:Buffer.from(String(Q));T({v:At,ch:"attach",type:"data",streamId:_.streamId,data:Tt.toString("base64"),...ht?{bin:!0}:{}})});let at=()=>{u.delete(_.streamId),l.delete(_.streamId)&&T({v:At,ch:"attach",type:"close",streamId:_.streamId})};M.on("close",at),M.on("error",at);return}if(_.ch==="attach"&&_.type==="data"){let M=l.get(_.streamId);if(!M)return;let at=Buffer.from(_.data,"base64"),Q=_.bin===!0,ht=u.get(_.streamId);ht?ht.push({buf:at,bin:Q}):M.send(at,{binary:Q});return}if(_.ch==="attach"&&_.type==="close"){let M=l.get(_.streamId);l.delete(_.streamId),u.delete(_.streamId),M?.close();return}});let B=()=>{h&&i.log("[plane-link] disconnected from plane"),h=!1,R&&(clearInterval(R),R=null),p.clear();for(let $ of l.values())$.close();l.clear(),u.clear();for(let $ of w.values())$.abort();w.clear(),!(b||v)&&(v=setTimeout(()=>{v=null,K()},x),v.unref?.(),x=Math.min(x*2,ST))};m.on("close",B),m.on("error",B)}return jt.on("event",g),I().then(K),{stop:()=>{b=!0,jt.off("event",g),v&&clearTimeout(v),E&&clearTimeout(E),R&&clearInterval(R),m?.close()},status:()=>({connected:h,nodeId:n,cursor:{...f},inflight:p.size})}}function TT(t){let e={};for(let n of t.boxes||[]){let r=n.role&&n.role!=="lead"?` \xB7 ${n.role}`:"";e[n.name]=`${n.name} (${n.agent||"?"}${r})`}return e}var kw=()=>({input:0,output:0,cacheRead:0,cacheCreation:0}),xw=(t,e)=>{for(let n of Object.keys(t))t[n]+=e?.[n]||0},Il=t=>Math.round(t*1e4)/1e4;async function Sw(t){let e=[],n={},r={},o=0,s=0,a=0,i=0,c=new Set,l=kw(),u=0,d=[];for(let f of t||[]){let p=await F(f),m=_e(p,{boxLabels:TT(f)});if(m.turns===0){f.archivedAt?u++:d.push(f.name||f.id);continue}o+=m.totalUsd,s+=m.turns,a+=m.repricedTurns||0,i+=m.unpricedTurns||0;for(let h of m.unpricedModels||[])c.add(h);xw(l,m.totalTokens);for(let[h,b]of Object.entries(m.byModel))n[h]||={usd:0,events:0,tokens:kw()},n[h].usd=Il(n[h].usd+b.usd),n[h].events+=b.events,xw(n[h].tokens,b.tokens);for(let[h,b]of Object.entries(m.byProvider))r[h]||={usd:0,events:0},r[h].usd=Il(r[h].usd+b.usd),r[h].events+=b.events;e.push({id:f.id,name:f.name,usd:m.totalUsd,rollup:m})}return e.sort((f,p)=>p.usd-f.usd),{generatedAt:new Date().toISOString(),pricingBasis:"api-equivalent (subscription usage priced at public API rates)",fleet:{totalUsd:Il(o),turns:s,repricedTurns:a,unpricedTurns:i,unpricedModels:[...c],totalTokens:l,byModel:n,byProvider:r,tracksWithCost:e.length,tracksSilent:u+d.length,tracksSilentArchived:u,tracksSilentLive:d},tracks:e}}var bo=t=>`$${t.toFixed(4)}`,Sr=(t,e)=>String(t).padEnd(e);function vw(t){let{fleet:e,tracks:n}=t,r=[];r.push(`BOSTRAT COST READ-OUT \xB7 ${t.generatedAt}`),t.pricingBasis&&r.push(`basis: ${t.pricingBasis}`);let o=e.tracksSilentLive||[];r.push(`fleet total ${bo(e.totalUsd)} over ${e.turns} turn(s) \xB7 ${e.tracksWithCost} track(s) with cost, ${e.tracksSilentArchived??e.tracksSilent} silent (archived)`+(o.length?`, ${o.length} LIVE with no cost data: ${o.join(", ")}`:"")),e.repricedTurns&&r.push(`${e.repricedTurns} turn(s) imputed at current rates (recorded before their model was priced)`),e.unpricedTurns&&r.push(`\u26A0 ${e.unpricedTurns} turn(s) UNPRICED \u2014 total under-reports. Add PER_MTOK rate(s) for: ${(e.unpricedModels||[]).join(", ")}`),r.push(""),r.push("BY MODEL");let s=Object.entries(e.byModel).sort((a,i)=>i[1].usd-a[1].usd);s.length||r.push(" (no cost events yet \u2014 hooks emit on each turn of a newly started box)");for(let[a,i]of s){let c=i.tokens;r.push(` ${Sr(a,26)} ${Sr(bo(i.usd),10)} ${i.events} turn(s) \xB7 in ${c.input} out ${c.output} cacheR ${c.cacheRead} cacheW ${c.cacheCreation}`)}r.push(""),r.push("BY TRACK / TASK");for(let a of n){r.push(` ${Sr(a.name||a.id,30)} ${bo(a.usd)}`);let i=Object.values(a.rollup.byBox).sort((c,l)=>l.usd-c.usd);for(let c of i)if(r.push(` ${Sr(c.label,40)} ${Sr(bo(c.usd),10)} ${c.models.join(",")||"?"}`),c.epochs)for(let[l,u]of Object.entries(c.epochs).sort((d,f)=>Number(d[0])-Number(f[0])))r.push(` \u21B3 tenure ${l} (${u.provider||"?"})${" ".repeat(Math.max(1,24-String(u.provider||"?").length))}${Sr(bo(u.usd),10)} ${u.models.join(",")||"?"}`)}return r.join(`
|
|
296
|
+
`)}import PT from"node:fs/promises";import NT from"node:path";var _T=["bug-fix","orchestration","design","ops-infra","docs-writing","research","feature-build"],ET={research:[/\bresearch\b/,/\binvestigat/,/\bverif/,/\bcompar(e|ing|ison)/,/\bcompetit/,/\bdeep[ -]?dive\b/,/\banaly[sz]/,/\bassess/,/\bdiligence\b/,/\bmarket\b/,/\bstrateg/,/\broadmap\b/,/\bnorth star/,/\bwhat('| i)?s the (state|status)\b/,/\bstatus of\b/,/\bexplore\b/,/\bfeasib/,/\bevaluat/,/\bscope out\b/,/\btalk through\b/],"bug-fix":[/\bfix(e[sd]|ing)?\b/,/\bbugs?\b/,/\bbroken\b/,/\bregression\b/,/\bcrash/,/\bincident\b/,/\bdebug/,/\bdoesn'?t (work|render|load|start)/,/\bnot working\b/,/\btroubleshoot/,/\bflaky?\b/,/\brepro(duce|duction)?\b/,/\brace condition\b/,/\bleak\b/],"feature-build":[/\bimplement/,/\bbuild(ing)?\b/,/\badd(ing)?\b/,/\bcreate\b/,/\bwire (it |them )?(up|in)\b/,/\bship(ping)?\b/,/\bintegrat/,/\bsupport for\b/,/\bfeature\b/,/\bendpoint\b/,/\badapter\b/,/\bprototype\b/],design:[/\bdesign/,/\bui\b/,/\bux\b/,/\bstyl(e|es|ing)\b/,/\bcss\b/,/\blayout\b/,/\bmock-?up/,/\brender(ing|s)?\b/,/\bpolish/,/\btheme\b/,/\bfonts?\b/,/\bresponsive\b/,/\banimation/,/\bvisual/,/\bpopover\b/],orchestration:[/\bhand[ -]?(off|over)/,/\bhand (it|the lead|this) (over|off)\b/,/\bdelegat/,/\bsubtask/,/\bmerge[ -]?back\b/,/\borchestrat/,/\b(switch|change) (the )?model\b/,/\bmodel switch/],"docs-writing":[/\bdocs?\b/,/\bdocument/,/\bmemo\b/,/\breadme\b/,/\bwrite-?up\b/,/\bchangelog\b/,/\bclaude\.md\b/,/\bblog/,/\bproofread/,/\bcopy(edit|writ)/],"ops-infra":[/\bdeploy/,/\bsystemd\b/,/\bdaemon\b/,/\btmux\b/,/\bdns\b/,/\bwireguard\b/,/\brestart/,/\bnginx\b/,/\bprovision/,/\binstall/,/\bupgrade/,/\bcron\b/,/\bbackups?\b/,/\bcertificat/,/\bssh\b/,/\bsecrets?\b/]};function ca(t){let e=String(t||"").toLowerCase();if(!e.trim())return null;let n=null;for(let r of _T){let o=0;for(let s of ET[r])s.test(e)&&o++;o>0&&(!n||o>n.score)&&(n={category:r,score:o})}return n}var IT=new Set(["Read","Grep","Glob","LS","WebSearch","WebFetch","NotebookRead"]),RT=new Set(["Edit","Write","MultiEdit","NotebookEdit"]);function Aw(t){let e=0,n=0;for(let[r,o]of Object.entries(t||{}))IT.has(r)&&(e+=o||0),RT.has(r)&&(n+=o||0);return e>=5&&n===0?{category:"research",score:1}:n>=3?{category:"feature-build",score:1}:null}function Rl(t){return String(t||"").replace(/([a-z0-9])([A-Z])/g,"$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g,"$1 $2").replace(/[-_/]+/g," ")}var la=t=>t.payload?.ts??t.ts,$T=12e4,Ve=t=>Math.round(t*1e4)/1e4,OT=()=>({usd:0,activeMs:0,turns:0,toolCalls:0,models:{},providers:{},byTrack:new Map});function CT(t,{since:e}){let{track:n,events:r,brief:o}=t,s=v=>la(v)>=e,a=ca([Rl(n?.name),Rl(n?.workspace?.branch),o].filter(Boolean).join(" ")),i=new Map;for(let v of r)if(v.type==="subtask.delegated"&&v.boxId&&v.payload?.task){let E=ca(v.payload.task);E&&i.set(v.boxId,E)}let c=new Map,l=v=>{let E=v||"unattributed";return c.has(E)||c.set(E,{segments:[u(null)],lastTs:null,provider:null}),c.get(E)},u=v=>({textCat:v,tools:{},usd:0,tokens:{input:0,output:0,cacheRead:0,cacheWrite:0},models:{},provider:null,activeMs:0,turns:0,toolCalls:0}),d=[...r].sort((v,E)=>la(v)-la(E)),f=0,p=0,m=0,h={turns:0};for(let v of d){let E=l(v.boxId),R=E.segments[E.segments.length-1],st=la(v);switch(E.lastTs!=null&&st>E.lastTs&&s(v)&&(R.activeMs+=Math.min(st-E.lastTs,$T)),E.lastTs=st,v.provider&&(E.provider=v.provider),v.type){case"turn.user":{let I=v.payload?.text;if(v.source==="hook"||!String(I||"").trim())break;let L=u(ca(I));E.segments.push(L),s(v)&&(L.turns+=1);break}case"tool.called":{if(v.source==="transcript")break;let I=v.payload?.tool;I&&(R.tools[I]=(R.tools[I]||0)+1),s(v)&&(R.toolCalls+=1);break}case"cost.recorded":{if(!s(v))break;let I=v.payload||{},L={input:I.tokens?.input||0,output:I.tokens?.output||0,cacheRead:I.tokens?.cacheRead||0,cacheWrite:I.tokens?.cacheWrite??I.tokens?.cacheCreation??0},T=Number(I.usd)||0;!T&&I.tokens&&(T=zt(I.model,L)),!T&&I.tokens&&!Xo(I.model)&&(h.turns+=1),R.usd+=T,R.tokens.input+=L.input,R.tokens.output+=L.output,R.tokens.cacheRead+=L.cacheRead,R.tokens.cacheWrite+=L.cacheWrite;let U=I.model||"unknown";R.models[U]=(R.models[U]||0)+T,v.provider&&(R.provider=v.provider);break}case"handoff.done":s(v)&&(f+=1);break;case"subtask.merged":s(v)&&(p+=1);break;case"error":s(v)&&v.payload?.kind==="subtask-failed"&&(m+=1);break;default:break}}let b=new Map,x=!1;for(let[v,E]of c)for(let R of E.segments){if(!(R.usd>0||R.activeMs>0||R.turns>0||R.toolCalls>0))continue;let I=R.textCat?.category??i.get(v)?.category??Aw(R.tools)?.category??a?.category??"uncategorized";b.has(I)||b.set(I,{usd:0,activeMs:0,turns:0,toolCalls:0,models:{},providers:{}});let L=b.get(I);L.usd+=R.usd,L.activeMs+=R.activeMs,L.turns+=R.turns,L.toolCalls+=R.toolCalls;for(let[U,J]of Object.entries(R.models))L.models[U]=(L.models[U]||0)+J;let T=R.provider||E.provider;T&&(L.providers[T]=(L.providers[T]||0)+R.usd),T==="cursor"&&R.usd===0&&(x=!0)}return{perCategory:b,handoffs:f,merged:p,failed:m,sawCursorActivity:x,unpricedTurns:h.turns}}function _w(t,{windowMs:e=null,now:n=Date.now()}={}){let r=e==null?0:n-e,o=new Map,s=[],a=0,i=!1,c=0;for(let h of t||[]){let{track:b,events:x}=h;if(!x?.length){a+=1;continue}let{perCategory:v,handoffs:E,merged:R,failed:st,sawCursorActivity:I,unpricedTurns:L}=CT(h,{since:r});I&&(i=!0),c+=L;let T={trackId:b?.id,name:b?.name||b?.id,archived:!!b?.archivedAt,usd:0,activeMs:0,turns:0,categories:{},models:{},providers:{},handoffs:E,subtasksMerged:R,subtasksFailed:st};for(let[U,J]of v){T.usd+=J.usd,T.activeMs+=J.activeMs,T.turns+=J.turns,T.categories[U]={usd:Ve(J.usd),activeMs:J.activeMs};for(let[y,A]of Object.entries(J.models))T.models[y]=Ve((T.models[y]||0)+A);for(let[y,A]of Object.entries(J.providers))T.providers[y]=Ve((T.providers[y]||0)+A);o.has(U)||o.set(U,OT());let g=o.get(U);g.usd+=J.usd,g.activeMs+=J.activeMs,g.turns+=J.turns,g.toolCalls+=J.toolCalls;for(let[y,A]of Object.entries(J.models))g.models[y]=(g.models[y]||0)+A;for(let[y,A]of Object.entries(J.providers))g.providers[y]=(g.providers[y]||0)+A;let w=g.byTrack.get(b?.id)||{usd:0,activeMs:0};w.usd+=J.usd,w.activeMs+=J.activeMs,g.byTrack.set(b?.id,w)}(T.usd>0||T.activeMs>0||T.turns>0)&&(T.usd=Ve(T.usd),T.dominantCategory=MT(T.categories),s.push(T))}let l=[...o.values()].reduce((h,b)=>h+b.usd,0),u=[...o.values()].reduce((h,b)=>h+b.activeMs,0),d=l>0?"usd":"time",f=d==="usd"?l:u,p=new Map((t||[]).map(h=>[h.track?.id,h.track?.name||h.track?.id])),m=[...o.entries()].map(([h,b])=>({category:h,sharePct:f>0?Math.round((d==="usd"?b.usd:b.activeMs)/f*100):0,shareBasis:d,usd:Ve(b.usd),activeMs:b.activeMs,turns:b.turns,toolCalls:b.toolCalls,models:Tw(b.models),providers:Tw(b.providers),topWorkstreams:[...b.byTrack.entries()].sort((x,v)=>v[1].usd-x[1].usd||v[1].activeMs-x[1].activeMs).slice(0,2).map(([x,v])=>({trackId:x,name:p.get(x),usd:Ve(v.usd),activeMs:v.activeMs}))})).sort((h,b)=>b.usd-h.usd||b.activeMs-h.activeMs);return s.sort((h,b)=>b.usd-h.usd||b.activeMs-h.activeMs),{since:r,capturedAt:n,totalUsd:Ve(l),activeMs:u,turns:[...o.values()].reduce((h,b)=>h+b.turns,0),bullets:m,workstreams:s,gaps:{tracksWithoutEvents:a,cursorCostUnknown:i,unpricedTurns:c}}}var MT=t=>{let e=null;for(let[n,r]of Object.entries(t))(!e||r.usd>e.v.usd||r.usd===e.v.usd&&r.activeMs>e.v.activeMs)&&(e={cat:n,v:r});return e?.cat??null},Tw=t=>Object.fromEntries(Object.entries(t).map(([e,n])=>[e,Ve(n)]));var $l={"24h":24*36e5,"7d":168*36e5,"30d":720*36e5,all:null};function BT(t){let e=String(t||"7d");return e in $l?{key:e,ms:$l[e]}:{key:"7d",ms:$l["7d"]}}function DT(t,e){let n=new Set((e||[]).filter(o=>o.source==="orchestrator").map(o=>`${o.type}|${o.payload?.ts??"?"}`)),r=[...e||[]];for(let o of t?.events||[]){let s=$s(o);!s||n.has(`${s.type}|${s.payload?.ts??"?"}`)||r.push({trackId:t.id,boxId:s.boxId??null,ts:o.at,type:s.type,actor:s.actor,provider:s.provider??null,source:s.source,payload:s.payload})}return r}async function jT(t){if(!t?.cwd)return null;try{let n=(await PT.readFile(NT.join(t.cwd,".bostrat","handoff","latest.md"),"utf8")).match(/^##\s*Objective\s*\n([\s\S]*?)(?:\n##\s|\n*$)/m);return n?n[1].trim().slice(0,1e3):null}catch{return null}}async function Ew(t,{window:e="7d",now:n=Date.now()}={}){let{key:r,ms:o}=BT(e),s=[];for(let a of t||[]){let i=await F(a);s.push({track:a,events:DT(a,i),brief:await jT(a)})}return{window:r,..._w(s,{windowMs:o,now:n})}}import vr from"node:fs/promises";import LT from"node:fs";import mt from"node:path";import FT from"node:os";import{execFile as UT}from"node:child_process";import{promisify as HT}from"node:util";var WT=HT(UT),Iw=process.env.BOSTRAT_STATE_DIR||mt.join(process.env.HOME||FT.tmpdir(),".bostrat"),Ol=!1;async function ko(t,e){let n=await vr.stat(t).catch(()=>null);if(!n?.isFile())return!1;let r=await vr.stat(e).catch(()=>null);return r&&r.size===n.size&&r.mtimeMs>=n.mtimeMs?!1:(await vr.mkdir(mt.dirname(e),{recursive:!0}),await vr.copyFile(t,e),!0)}var qT=new Set([".claude",".codex",".credentials.json","auth.json",".claude.json","integrations.json"]);async function xo(t,e){let n=0,r=await vr.readdir(t,{withFileTypes:!0}).catch(()=>[]);for(let o of r){if(qT.has(o.name))continue;let s=mt.join(t,o.name);o.isDirectory()?n+=await xo(s,mt.join(e,o.name)):o.isFile()&&(n+=await ko(s,mt.join(e,o.name))?1:0)}return n}async function Rw({stateDir:t=Iw,dest:e=process.env.BOSTRAT_SYNC_DIR||`${Iw}-sync`,remote:n=process.env.BOSTRAT_STATE_REMOTE||null,tracks:r=null}={}){try{await vr.mkdir(e,{recursive:!0});let o=0;o+=await ko(mt.join(t,"tracks.json"),mt.join(e,"tracks.json"))?1:0,o+=await ko(mt.join(t,"mocks.json"),mt.join(e,"mocks.json"))?1:0,o+=await ko(mt.join(t,"runbooks.json"),mt.join(e,"runbooks.json"))?1:0,o+=await xo(mt.join(t,"runbooks"),mt.join(e,"runbooks")),o+=await xo(mt.join(t,"mock-archive"),mt.join(e,"mock-archive")),o+=await ko(mt.join(t,"events.jsonl"),mt.join(e,"events.jsonl"))?1:0,o+=await xo(mt.join(t,"events"),mt.join(e,"events"));for(let l of r||[])!l?.cwd||!l?.id||(o+=await xo(mt.join(l.cwd,".bostrat"),mt.join(e,"workspaces",l.id,".bostrat")));let s=(...l)=>WT("git",["-C",e,...l]);LT.existsSync(mt.join(e,".git"))||(await s("init","-q"),await s("config","user.email","agent@bostrat.local"),await s("config","user.name","bostrat state sync")),n&&/^https:\/\/github\.com\//i.test(n)&&(await s("config","--get","credential.useHttpPath").then(()=>!0).catch(()=>!1)||await Ui(e).catch(()=>{}));let a=!1,{stdout:i}=await s("status","--porcelain");i.trim()&&(await s("add","-A"),await s("commit","-q","-m",`state sync ${new Date().toISOString()}`),a=!0);let c=!1;if(n&&(a||Ol))try{await s("push","-q",n,"HEAD:refs/heads/main"),c=!0,Ol=!1}catch{Ol=!0}return{staged:o,committed:a,pushed:c}}catch(o){return{staged:0,committed:!1,pushed:!1,error:o?.message||String(o)}}}import{execFile as GT}from"node:child_process";import{promisify as KT}from"node:util";import{randomUUID as VT}from"node:crypto";import JT from"node:fs";import zT from"node:os";import XT from"node:path";var ze=KT(GT),ua=t=>new Promise(e=>setTimeout(e,t)),Xe=Ea,Cl=process.env.BOSTRAT_USAGE_PROBE_CWD||XT.join(zT.homedir(),".bostrat","usage-probe"),YT=Number(process.env.BOSTRAT_USAGE_IDLE_REAP_MS)||5*6e4,ZT=Number(process.env.BOSTRAT_USAGE_PANEL_TIMEOUT_MS)||15e3,QT=Number(process.env.BOSTRAT_USAGE_READY_TIMEOUT_MS)||45e3,$w=Number(process.env.BOSTRAT_USAGE_CACHE_MS)||2e4,Je=null,So=null,vo=null;async function t_(){try{return await ze(C,P("has-session","-t",Xe)),!0}catch{return!1}}function e_(){Je&&clearTimeout(Je),Je=setTimeout(()=>n_().catch(()=>{}),YT),Je.unref&&Je.unref()}async function n_(){Je&&(clearTimeout(Je),Je=null);try{await ze(C,P("kill-session","-t",Xe))}catch{}}async function r_(){let t=Date.now()+QT;for(;;){let e=await Ml().catch(()=>"");if(/for agents|for shortcuts|bypass permissions on/i.test(e)){await ua(500);return}if(Date.now()>t)throw new Error("usage probe didn't reach the prompt (trust/onboarding may be blocking)");await ua(500)}}async function o_(){if(await t_())return;if(!await Se())throw new Error("host tmux server not running \u2014 start globo-tmux.service");JT.mkdirSync(Cl,{recursive:!0}),await xe(Cl);let t=VT();await ze(C,P("new-session","-d","-s",Xe,"-c",Cl,"-x","200","-y","60"));let e=`${_a} --session-id ${t}`;await ze(C,P("send-keys","-t",Xe,e,"Enter")),await r_()}async function Ml(){let{stdout:t}=await ze(C,P("capture-pane","-t",Xe,"-p"));return t}async function s_(){await o_(),await ze(C,P("send-keys","-t",Xe,"-l","/usage")),await ze(C,P("send-keys","-t",Xe,"Enter"));let t=Date.now()+ZT,e="";for(;await ua(400),e=await Ml(),!(/Current\s+(session|week)/i.test(e)||/\d+%\s*used/i.test(e)||Date.now()>t););return await ua(900),e=await Ml(),await ze(C,P("send-keys","-t",Xe,"Escape")).catch(()=>{}),e_(),e}function a_(t){let e=String(t||"").split(`
|
|
297
|
+
`).map(s=>s.replace(/\s+$/,"").trim()),n=s=>{let a=null,i=null;for(let c=s+1;c<Math.min(s+5,e.length)&&!/^Current\s/i.test(e[c]);c++){let l=e[c].match(/(\d+)%\s*used/i);l&&a===null&&(a=Number(l[1]));let u=e[c].match(/^Resets\s+(.+)$/i);u&&i===null&&(i=u[1].trim())}return a===null?null:{pct:a,resets:i||null}},r=s=>{let a=e.findIndex(i=>s.test(i));return a<0?null:n(a)},o=[];return e.forEach((s,a)=>{let i=s.match(/^Current week \(([^)]+)\)$/i);if(!i||/all models/i.test(i[1]))return;let c=n(a);c&&o.push({model:i[1].trim(),...c})}),{session:r(/^Current session\b/i),week:r(/^Current week \(all models\)/i)||r(/^Current week$/i),weekModels:o.length?o:null}}function i_(t){let e=String(t||"").split(`
|
|
298
|
+
`).map(s=>s.replace(/\s+$/,"")),n=e.findIndex(s=>/What's contributing/i.test(s));if(n<0)return null;let r=/(Usage credits|Esc to cancel|d to day · w to week|^\s*d to day)/i,o=[];for(let s=n+1;s<e.length&&!r.test(e[s]);s++)e[s].trim()&&o.push(e[s].trim());return o.length?o:null}async function Ow({fresh:t=!1}={}){return!t&&So&&Date.now()-So.at<$w?So.data:vo||(vo=(async()=>{try{let e=await s_(),n=a_(e),r={...n,contributors:i_(e),subscription:!!(n.session||n.week||n.weekModels),capturedAt:Date.now()};return So={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 So={at:Date.now()-($w-3e3),data:n},n}finally{vo=null}})(),vo)}var c_=262144;function l_(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 Cw(t){if(!t||typeof t!="object")return null;let e=Number(t.used_percent);return Number.isFinite(e)?{pct:Math.round(e),resets:l_(t.resets_in_seconds)}:null}function u_(t){let e=String(t||"").split(`
|
|
299
|
+
`),n=!1;for(let r=e.length-1;r>=0;r--){let o=e[r].trim();if(!o||o[0]!=="{"||!o.includes("token_count"))continue;let s;try{s=JSON.parse(o)}catch{continue}let a=s?.payload;if(a?.type!=="token_count")continue;n=!0;let i=a.rate_limits;if(!i)return{session:null,week:null,sawTokenCount:n};if(i.primary||i.secondary)return{session:Cw(i.primary),week:Cw(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 d_(t){let e={provider:"codex",supported:!0,contributors:null};try{let n=await mn(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 Et(n.file,c_),o=u_(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 Mw(t,{fresh:e=!1}={}){let n=t?.agent||"claude";return n==="codex"?d_(t):n==="cursor"?{provider:"cursor",supported:!1,subscription:!1,session:null,week:null,contributors:null,capturedAt:Date.now()}:{provider:"claude",supported:!0,...await Ow({fresh:e})}}function Pw(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 f_(t){let[e,n]=t.split("/"),r=Pw(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 p_(t){return(t||"").split(",").map(e=>e.trim()).filter(Boolean).map(f_).filter(Boolean)}function m_(t){let e=t.headers["fly-client-ip"];if(typeof e=="string"&&e.trim())return e.trim();let n=t.headers["x-forwarded-for"];return typeof n=="string"&&n.trim()?n.split(",")[0].trim():(t.socket&&t.socket.remoteAddress||"").replace(/^::ffff:/,"")}function Nw(t){let e=p_(t),n=e.length===0;return{open:n,clientIp:m_,allowed(r){if(n)return!0;let o=Pw((r||"").replace(/^::ffff:/,""));return o==null?!1:e.some(s=>s(o))}}}import h_ from"node:http";import w_ from"node:net";function Bw(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 Dw(t){let e=Bw(t.headers.host);return e?{slug:e,view:ii(e)}:null}var g_=t=>`<!doctype html><meta charset="utf-8">
|
|
281
300
|
<meta http-equiv="refresh" content="3">
|
|
282
301
|
<title>${t.name} \u2014 starting</title>
|
|
283
302
|
<body style="font-family:system-ui;background:#0b0e14;color:#dce3f0;display:grid;place-items:center;height:100vh;margin:0">
|
|
284
303
|
<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
|
|
304
|
+
<p><b>${t.name}</b> is waking up\u2026</p><p style="opacity:.6">auto-refreshing</p></div>`;function jw(t,e,n){let r=Dw(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(g_(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=h_.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 Lw(t,e,n){let r=Dw(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
305
|
\r
|
|
287
306
|
bostrat: no live view "${s}"\r
|
|
288
|
-
`),e.destroy(),!0;let a=
|
|
307
|
+
`),e.destroy(),!0;let a=w_.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
308
|
`)+`\r
|
|
290
309
|
\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-
|
|
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
|
-
${
|
|
310
|
+
`),n?.length&&a.write(n),e.pipe(a),a.pipe(e)}),i=()=>{a.destroy(),e.destroy()};return a.on("error",i),e.on("error",i),!0}function Fw(t,e){let n=typeof e=="string"?{host:e}:e||{},r=n["x-bostrat-view-base"];return typeof r=="string"&&r?`${n["x-forwarded-proto"]==="https"?"https":"http"}://${t.slug}.${r.replace(/:\d+$/,"")}/`:`http://${t.slug}.${y_(n.host)}/`}function y_(t){if(process.env.BOSTRAT_PUBLIC_HOST)return String(process.env.BOSTRAT_PUBLIC_HOST).replace(/:\d+$/,"");let e=String(t||"").replace(/:\d+$/,"").toLowerCase();if(!e||!e.includes(".")||/^[\d.]+$/.test(e))return"bostrat.globopi";let n=e.split(".");return Bw(e)&&ii(n[0])?n.slice(1).join("."):e}import{execFile as b_}from"node:child_process";import{promisify as k_}from"node:util";var Nl=k_(b_),Uw=3e4,x_=30*6e4,S_=Uw,v_=10*6e4,A_=10080*6e4,T_=60*6e4,__=8,E_=1440*6e4,I_=3,Hw="number,url,title,headRefName,headRefOid,state,mergedAt,closedAt",Pl=60*6e4,R_=15*6e4,da=new Map,fa=new Map,$_=/could not resolve|not found|no pull requests?/i,O_=t=>$_.test(String(t?.message||t||""));function C_(t,e){return[t.ciFix?.delegatedAt,t.reconcile?.delegatedAt,t.ciFixAwait?.since,t.reconcileAwait?.since].some(n=>n&&e-n<Pl)}var Ww={fast:S_,slow:v_,decay:T_};function qw(t,e){return t.armedAt&&e-t.armedAt<x_||C_(t,e)?"fast":e-(t.firstSeen||0)>A_?"decay":"slow"}function M_(t,e){return Ww[qw(t,e)]}function Eo(t,e,n,r){let o=t.lanes?.get(it(e.repo,e.number))||{};return Hs({change:n,repo:e.repo,number:Number(e.number),githubAt:r||null,lane:o.lane||null,intervalMs:o.intervalMs||null,source:"watch"})}async function P_(t,e){we("watch");try{let{stdout:n}=await Nl("gh",["pr","view",String(e),"--repo",t,"--json",Hw],{timeout:3e4,maxBuffer:4194304,env:{...process.env,...await re({slug:t})}});return JSON.parse(n||"null")}catch(n){throw je(n)&&Le(n),n}}async function N_(t){we("watch");try{let{stdout:e}=await Nl("gh",["pr","list","--repo",t,"--state","all","--limit","100","--json",Hw],{timeout:3e4,maxBuffer:8388608,env:{...process.env,...await re({slug:t})}}),n=new Map;for(let r of JSON.parse(e||"[]"))n.set(Number(r.number),r);return n}catch(e){throw je(e)&&Le(e),e}}async function B_(){let t=new Map;for(let e of await ut().catch(()=>[]))t.set(e.id,e);return t}async function D_(t,e){await O({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"user",source:"orchestrator",boxId:null,payload:{ts:Date.now(),change:"merged",...e}})}async function j_(t,e){let n=await Sn(t);return n?dr(n,e):null}async function L_(t,e,n){let r=await Sn(t);if(!r)throw new Error(`no local checkout for ${t}`);return fr(r,e,{method:n})}async function F_(t,e){let n=await Sn(t);if(!n)throw new Error(`no local checkout for ${t}`);return ao(n,e)}async function U_(t,e){await O({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"user",source:"orchestrator",boxId:null,payload:{ts:Date.now(),change:"auto-canceled",...e}})}async function Ar(t,e,n,r,o){if(!await r.disarm(t.repo,t.number).catch(()=>null))return;let a=n.get(t.trackId);a&&await r.emitDisarmed(a,{number:Number(t.number),url:e?.url||t.url,title:e?.title||t.title,branch:e?.headRefName||t.headRef,repo:t.repo,reason:o}).catch(()=>{})}var H_=10*6e4;async function W_(t,e,n){let r=t.autoMerge?.armedAt||0;if(r&&(n.now??Date.now())-r>E_){await Ar(t,null,e,n,"auto-merge expired \u2014 checks never settled within 24h");return}let o=await n.detail(t.repo,t.number).catch(()=>null);if(!o)return;if(o.checks.failed>0){let a=o.checks.failing.map(i=>i.name).join(", ");await Ar(t,o,e,n,`check${o.checks.failed===1?"":"s"} failed: ${a}`);return}if(o.mergeable==="CONFLICTING"){await Ar(t,o,e,n,`conflicts with ${o.baseRefName}`);return}if(!o.checks.total){let a=Math.max(t.autoMerge?.armedAt||0,Date.parse(o.updatedAt||"")||0);if((n.now??Date.now())-a<H_)return;await Ar(t,o,e,n,o.checks.unavailable?"this token can't read the checks":"no CI checks appeared");return}if(o.checks.pending>0||o.mergeable==="UNKNOWN"||o.mergeStateStatus==="UNKNOWN"||o.isDraft)return;let s=await n.lookup(t.repo,t.number).catch(()=>null);if(s?.autoMerge){if(o.mergeStateStatus==="BEHIND"){try{await n.updateBranch(t.repo,t.number)}catch(a){let i=String(a?.message||a).trim();/no new commits|already up.to.date/i.test(i)||await Ar(t,o,e,n,i||`couldn't update with ${o.baseRefName}`)}return}try{await n.merge(t.repo,t.number,s.autoMerge.method||"merge")}catch(a){if(/already merged|not open|is closed/i.test(String(a?.message||a)))return;await Ar(t,o,e,n,String(a?.message||a).trim()||"merge failed");return}await Gw(t,{...o,state:"MERGED"},e,n,{change:"auto-merged",githubAt:o.checks.settledAt||null})}}async function Ao(t,e,n){await O({id:t.id,cwd:t.cwd},{type:"pr.state.changed",actor:"user",source:"orchestrator",boxId:null,payload:{ts:Date.now(),change:e,...n}})}async function q_(t,e){try{return await Nl("git",["-C",t.cwd,"cat-file","-e",`${e}^{commit}`],{timeout:1e4}),!0}catch{return!1}}async function To(t,e,n,r=()=>!0){return(await F(t).catch(()=>[])).some(s=>s.type==="pr.state.changed"&&s.payload?.change===e&&s.payload?.number===Number(n)&&r(s))}var _o=(t,e)=>({number:Number(t.number),url:e?.url||t.url,title:e?.title||t.title,branch:e?.headRefName||t.headRef,repo:t.repo});async function G_(t,e,n,r){let o=r.now??Date.now(),s=e.headRefOid||null,a=n.get(t.trackId)||null;if(s&&(t.ciFix?.headSha&&s!==t.ciFix.headSha&&await ws(t.repo,t.number,{headSha:s}).catch(()=>{}),t.reconcile?.headSha&&s!==t.reconcile.headSha&&await ys(t.repo,t.number,{headSha:s}).catch(()=>{})),s&&s!==t.lastHeadSha){let i=!t.lastHeadSha,c=!!(t.ciFix||t.reconcile||t.ciFixAwait||t.reconcileAwait),l=!!t.baseFoldAt&&o-t.baseFoldAt<R_;await Rf(t.repo,t.number,s).catch(()=>{}),t.baseFoldAt&&await If(t.repo,t.number).catch(()=>{}),!i&&!c&&!l&&a&&!await r.localHasCommit(a,s).catch(()=>!1)&&!await To(a,"pushed",t.number,d=>d.payload?.sha===s)&&await Ao(a,"pushed",{..._o(t,e),sha:s}).catch(()=>{})}if(t.ciFixAwait)if(o-(t.ciFixAwait.since||0)>Pl)await gs(t.repo,t.number).catch(()=>{});else{let i=await r.detail(t.repo,t.number).catch(()=>null);if(i?.checks){if(i.checks.failed>0)await gs(t.repo,t.number).catch(()=>{});else if(i.checks.total>0&&i.checks.pending===0){let c=Eo(r,t,"ci-fixed",i.checks.settledAt||null),l=t.ciFixAwait.since||0;a&&!await To(a,"ci-fixed",t.number,u=>(u.payload?.ts||0)>=l)&&await Ao(a,"ci-fixed",{..._o(t,e),passed:i.checks.passed,githubAt:c.githubAt,latencyMs:c.latencyMs,lane:c.lane}).catch(()=>{}),await gs(t.repo,t.number).catch(()=>{})}}}if(t.reconcileAwait)if(o-(t.reconcileAwait.since||0)>Pl)await bs(t.repo,t.number).catch(()=>{});else{let i=await r.detail(t.repo,t.number).catch(()=>null);if(i){if(i.mergeable==="CONFLICTING")await bs(t.repo,t.number).catch(()=>{});else if(i.mergeable==="MERGEABLE"){Eo(r,t,"reconciled",null);let c=t.reconcileAwait.since||0;a&&!await To(a,"reconciled",t.number,l=>(l.payload?.ts||0)>=c)&&await Ao(a,"reconciled",{..._o(t,e),base:i.baseRefName||null}).catch(()=>{}),await bs(t.repo,t.number).catch(()=>{})}}}}async function K_(t,e,n,r){let o=Eo(r,t,"closed",Date.parse(e?.closedAt||"")||null),s=n.get(t.trackId);s&&!await To(s,"closed",t.number)&&await Ao(s,"closed",{..._o(t,e),githubAt:o.githubAt,latencyMs:o.latencyMs,lane:o.lane}).catch(()=>{}),await r.notifyIfResolved(t.repo,t.number,"closed").catch(()=>{}),await r.mark(t.repo,t.number,"closed").catch(()=>{})}async function V_(t,e,n){Eo(n,t,"lost",null);let r=e.get(t.trackId);r&&!await To(r,"lost",t.number)&&await Ao(r,"lost",_o(t,null)).catch(()=>{}),await n.notifyIfResolved(t.repo,t.number,"lost").catch(()=>{}),await n.mark(t.repo,t.number,"lost").catch(()=>{})}async function Gw(t,e,n,r,o={}){let s=Eo(r,t,o.change||"merged",o.githubAt??(Date.parse(e.mergedAt||"")||null)),a=n.get(t.trackId);a&&((await F(a).catch(()=>[])).some(l=>l.type==="pr.state.changed"&&l.payload?.change==="merged"&&l.payload?.number===Number(t.number))||await r.emitMerged(a,{number:Number(t.number),url:e.url||t.url,title:e.title||t.title,branch:e.headRefName||t.headRef,repo:t.repo,githubAt:s.githubAt,latencyMs:s.latencyMs,lane:s.lane}).catch(()=>{})),await r.notifyIfArmed(t.repo,t.number).catch(()=>{}),await r.archiveIfArmed(t.repo,t.number).catch(()=>{}),await r.mark(t.repo,t.number,"merged").catch(()=>{})}async function J_(t={}){let{now:e=Date.now(),auth:n=Uc,view:r=P_,list:o=N_,links:s=Of,trackMap:a=B_,emitMerged:i=D_,mark:c=Ce,archiveIfArmed:l=Ue,notifyIfArmed:u=He,notifyIfResolved:d=ul,batchThreshold:f=I_,detail:p=j_,merge:m=L_,updateBranch:h=F_,lookup:b=vt,disarm:x=Vr,emitDisarmed:v=U_,localHasCommit:E=q_}=t;if(!(await n().catch(()=>null))?.authenticated||Ps()>0)return 0;let I=(await s().catch(()=>[])).filter(y=>e-(da.get(it(y.repo,y.number))||0)>=M_(y,e));if(!I.length)return 0;let L=Date.now(),T=new Map,U=new Map;for(let y of I){let A=qw(y,e);T.set(it(y.repo,y.number),{lane:A,intervalMs:Ww[A]}),da.set(it(y.repo,y.number),e),U.has(y.repo)||U.set(y.repo,[]),U.get(y.repo).push(y)}let J=await a().catch(()=>new Map),g={now:e,lanes:T,emitMerged:i,mark:c,archiveIfArmed:l,notifyIfArmed:u,notifyIfResolved:d,detail:p,merge:m,updateBranch:h,lookup:b,disarm:x,emitDisarmed:v,localHasCommit:E};for(let[y,A]of U){let K,B=new Set;if(A.length>f)K=await o(y).catch(()=>null);else{K=new Map;for(let $ of A)try{let _=await r(y,$.number);_?.number!=null&&K.set(Number(_.number),_)}catch(_){O_(_)&&B.add(it($.repo,$.number))}}if(K)for(let $ of A){let _=it($.repo,$.number),M=K.get(Number($.number));if(!M){if(B.has(_)){let at=(fa.get(_)||0)+1;fa.set(_,at),at>=__&&(await V_($,J,g),fa.delete(_),da.delete(_))}continue}fa.delete(_),M.state==="MERGED"||M.state==="CLOSED"?(M.state==="MERGED"?await Gw($,M,J,g):await K_($,M,J,g),da.delete(_)):(await G_($,M,J,g).catch(()=>{}),$.autoMerge&&await W_($,J,g).catch(()=>{}))}}let w={};for(let{lane:y}of T.values())w[y]=(w[y]||0)+1;return console.log(`[pr-watch] probed ${I.length} link(s) (${Object.entries(w).map(([y,A])=>`${y} ${A}`).join(", ")}) in ${Date.now()-L}ms`),I.length}function Kw(){let t=!1;setInterval(async()=>{if(!t){t=!0;try{await J_()}catch{}finally{t=!1}}},Uw)}import ke from"node:fs/promises";import Tr from"node:path";import Qw from"node:os";import Vw from"node:fs/promises";import pa from"node:path";import{execFile as z_}from"node:child_process";import{promisify as X_}from"node:util";import{fileURLToPath as Y_}from"node:url";var Z_=X_(z_),zw=pa.dirname(Y_(import.meta.url)),Q_=pa.resolve(zw,"..","..","client","dist"),tE="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",eE=new Map([...tE].map((t,e)=>[t,e]));function nE(t){let e=[],n=0,r=0;for(let o of t){let s=eE.get(o);if(s===void 0)return e;n+=(s&31)<<r,s&32?r+=5:(e.push(n&1?-(n>>>1):n>>>1),n=0,r=0)}return e}function rE(t){let e=[],n=0,r=0,o=0;for(let s of t.split(";")){let a=[],i=0;if(s)for(let c of s.split(",")){let l=nE(c);l.length!==0&&(i+=l[0],l.length>=4&&(n+=l[1],r+=l[2],o+=l[3],a.push([i,n,r,o])))}e.push(a)}return e}function oE(t,e){if(!t||t.length===0||t[0][0]>e)return null;let n=0,r=t.length-1;for(;n<r;){let o=n+r+1>>1;t[o][0]<=e?n=o:r=o-1}return t[n]}var Jw=new Map;async function sE(t){let e;try{e=await Vw.stat(t)}catch{return null}let n=Jw.get(t);if(n&&n.mtimeMs===e.mtimeMs)return n;try{let r=JSON.parse(await Vw.readFile(t,"utf8")),o={mtimeMs:e.mtimeMs,sources:r.sources||[],lines:rE(r.mappings||"")};return Jw.set(t,o),o}catch{return null}}var Bl=null;function aE(){return Bl||(Bl=process.env.BOSTRAT_RELEASE?Promise.resolve(process.env.BOSTRAT_RELEASE):Z_("git",["rev-parse","--short","HEAD"],{cwd:pa.resolve(zw,"..","..")}).then(t=>t.stdout.trim()).catch(()=>null)),Bl}var iE=/(?:https?:\/\/[^\s()]*)?\/assets\/([^\s()/]+\.js):(\d+):(\d+)/g;async function cE(t,{distDir:e=Q_}={}){let n=[],r=0,o=!1;for(let s of t.matchAll(iE)){let[a,i,c,l]=s,u=await sE(pa.join(e,"assets",`${i}.map`)),d=u?oE(u.lines[Number(c)-1],Number(l)-1):null;if(n.push(t.slice(r,s.index)),d&&u.sources[d[1]]){let f=u.sources[d[1]].replace(/^(\.\.\/)+/,"");n.push(`${f}:${d[2]+1}:${d[3]+1}`),o=!0}else n.push(a);r=s.index+a.length}return n.push(t.slice(r)),{stack:n.join(""),symbolicated:o}}async function Xw(t,e={}){try{if(!t?.error?.stack||!t.release||t.release==="unknown")return t;let n=e.release??await aE();if(!n||t.release!==n)return t;let{stack:r,symbolicated:o}=await cE(t.error.stack,e);o&&(t.error.stack=r,t.error.symbolicated=!0)}catch{}return t}var lE=()=>process.env.BOSTRAT_STATE_DIR||Tr.join(process.env.HOME||Qw.tmpdir(),".bostrat"),Ye=()=>Tr.join(lE(),"telemetry"),tg=()=>Tr.join(Ye(),".relay-offsets.json"),uE=50,dE=14,fE=3e4,Yw=500,Bt=(t,e)=>typeof t=="string"?t.slice(0,e):"";function pE(t,e){if(!t||typeof t!="object")return null;let n=Bt(t.name,64);if(!n)return null;let r=Number.isFinite(t.ts)?t.ts:e,o={name:n,ts:Math.abs(r-e)<168*3600*1e3?r:e,receivedAt:e,anonId:Bt(t.anonId,64),sessionId:Bt(t.sessionId,64),userId:Bt(t.userId,64),release:Bt(t.release,64),appVersion:Bt(t.appVersion,32),route:Bt(t.route,128),deviceClass:t.deviceClass==="phone"||t.deviceClass==="desktop"?t.deviceClass:""};return t.props&&typeof t.props=="object"&&!Array.isArray(t.props)&&(JSON.stringify(t.props).length<=8192?o.props=t.props:o.props={clamped:!0}),t.error&&typeof t.error=="object"&&(o.error={kind:Bt(t.error.kind,256),message:Bt(t.error.message,8192),stack:Bt(t.error.stack,32768),fingerprint:Bt(t.error.fingerprint,64)}),Array.isArray(t.breadcrumbs)&&(o.breadcrumbs=t.breadcrumbs.slice(-50).map(s=>({t:Number.isFinite(s?.t)?s.t:0,n:Bt(s?.n,64),r:Bt(s?.r,128)}))),o}var eg=t=>new Date(t).toISOString().slice(0,10),Zw=0;async function ng(t,e=Date.now()){let r=(Array.isArray(t?.events)?t.events.slice(0,uE):[]).map(s=>pE(s,e)).filter(Boolean);if(!r.length)return 0;for(let s of r)s.error?.stack&&await Xw(s);await ke.mkdir(Ye(),{recursive:!0});let o=Tr.join(Ye(),`${eg(e)}.ndjson`);return await ke.appendFile(o,r.map(s=>JSON.stringify(s)+`
|
|
311
|
+
`).join(""),"utf8"),e-Zw>6*3600*1e3&&(Zw=e,mE(e).catch(()=>{})),r.length}async function mE(t=Date.now()){let e=eg(t-dE*24*3600*1e3),n=[];try{n=await ke.readdir(Ye())}catch{return}let r=n.filter(s=>s.endsWith(".ndjson")&&s.slice(0,10)<e);if(!r.length)return;for(let s of r)await ke.rm(Tr.join(Ye(),s),{force:!0});let o=await og();for(let s of r)delete o[s];await sg(o)}var ma=new Map;function rg(t,e=Date.now()){let n=t||"unknown",r=ma.get(n);return r||(ma.size>512&&ma.clear(),r={tokens:40,at:e},ma.set(n,r)),r.tokens=Math.min(40,r.tokens+(e-r.at)/6e4*20),r.at=e,r.tokens<1?!1:(r.tokens-=1,!0)}async function og(){try{return JSON.parse(await ke.readFile(tg(),"utf8"))||{}}catch{return{}}}async function sg(t){await ke.mkdir(Ye(),{recursive:!0}),await ke.writeFile(tg(),JSON.stringify(t),"utf8")}function hE(t,{env:e,host:n}={}){let r=t.name==="app.error"&&t.error,o={};for(let[a,i]of[["event",t.name],["route",t.route],["session",t.sessionId],["anon",t.anonId],["user",t.userId],["release",t.release],["appVersion",t.appVersion],["device",t.deviceClass]])i&&(o[a]=i);let s={timestamp:new Date(t.ts).toISOString(),service:"bostrat-client",source:"treetop-js",env:e||process.env.BOSTRAT_ENV||"pi",host:n||Qw.hostname(),level:r?"error":"info",message:r?`${t.error.kind||"Error"}: ${t.error.message||"(no message)"}`.slice(0,8192):t.name,tags:o,attributes:{...t.props?{props:t.props}:{},receivedAt:t.receivedAt}};return r&&(s.error={kind:t.error.kind,message:t.error.message,stack:t.error.stack},t.error.fingerprint&&(s.fingerprint=t.error.fingerprint),t.error.symbolicated&&(s.attributes.symbolicated=!0),t.breadcrumbs&&(s.attributes.breadcrumbs=t.breadcrumbs)),s}async function wE({fetchImpl:t=fetch}={}){let e=await Xd();if(!e)return{sent:0,disabled:!0};let n;try{n=(await ke.readdir(Ye())).filter(s=>s.endsWith(".ndjson")).sort()}catch{return{sent:0,disabled:!1}}let r=await og(),o=0;for(let s of n){let a=Tr.join(Ye(),s),i=r[s]||0,c;try{let f=await ke.open(a,"r");try{let p=(await f.stat()).size;if(p<=i)continue;c=Buffer.alloc(p-i),await f.read(c,0,c.length,i)}finally{await f.close()}}catch{continue}let l=c.lastIndexOf(10);if(l<0)continue;let u=c.toString("utf8",0,l+1).split(`
|
|
312
|
+
`).filter(Boolean),d=[];for(let f of u)try{d.push(JSON.parse(f))}catch{}for(let f=0;f<d.length;f+=Yw){let p=d.slice(f,f+Yw).map(h=>hE(h)),m=await t(`${e.url.replace(/\/$/,"")}/v1/logs`,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${e.ingestKey}`},body:JSON.stringify({events:p})}).catch(()=>null);if(!m||!m.ok)return{sent:o,disabled:!1};o+=p.length}r[s]=i+l+1,await sg(r)}return{sent:o,disabled:!1}}var Dl=null,jl=!1;function ag(){Dl||(Dl=setInterval(async()=>{if(!jl){jl=!0;try{await wE()}catch(t){console.error("[telemetry] relay failed:",t?.message||t)}finally{jl=!1}}},fE),Dl.unref?.())}import wa from"node:fs/promises";import dg from"node:path";import gE from"node:os";var fg=()=>process.env.BOSTRAT_STATE_DIR||dg.join(process.env.HOME||gE.tmpdir(),".bostrat"),pg=()=>dg.join(fg(),"dictation-glossary.json"),yE=100,bE=500,kE=["bostrat","globopi","codex","tmux","worktree","Deepgram","Linear","Firecracker","Tigris","WireGuard","misous","postyx","cedar","gerbil","Anthropic","Opus","Haiku","Sonnet","keyterm","monorepo","dogfood"],ga=null,Ll=null,Fl=Promise.resolve();function ig(){let t={},e=Date.now();for(let n of kE)t[n.toLowerCase()]={term:n,count:1,lastUsed:e,source:"seed"};return t}async function mg(){return ga||(Ll??=wa.readFile(pg(),"utf8").then(t=>JSON.parse(t)?.terms??ig()).catch(()=>ig()).then(t=>ga=t).finally(()=>Ll=null),Ll)}async function cg(){let t=JSON.stringify({terms:ga},null,2),e=pg(),n=`${e}.${process.pid}.tmp`;await wa.mkdir(fg(),{recursive:!0}),await wa.writeFile(n,t),await wa.rename(n,e)}function xE(){return Fl=Fl.then(cg,cg),Fl.catch(()=>{})}async function Ul(){return Object.values(await mg()).sort((e,n)=>n.count-e.count||n.lastUsed-e.lastUsed)}var SE=t=>Math.max(1,Math.ceil(t.length/4));async function vE(){let t=[],e=0;for(let{term:n}of await Ul()){let r=SE(n);if(!(e+r>bE)&&(t.push(n),e+=r,t.length>=yE))break}return t}async function AE(t,e){let n=await mg(),r=t.toLowerCase(),o=n[r];n[r]={term:t,count:(o?.count??0)+1,lastUsed:Date.now(),source:o?.source??e}}var lg=t=>(t||"").split(/\s+/).map(e=>e.replace(/^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu,"")).filter(Boolean),ha=t=>t.toLowerCase();function ug(t){return!new RegExp("\\p{L}","u").test(t)||t.length<3?!1:/^[a-z]+$/.test(t)?t.length>=4:!0}async function hg(t,e){let n=lg(t),r=lg(e);if(!n.length||!r.length)return[];let o=n.length,s=r.length,a=Array.from({length:o+1},()=>new Array(s+1).fill(0));for(let u=o-1;u>=0;u--)for(let d=s-1;d>=0;d--)a[u][d]=ha(n[u])===ha(r[d])?a[u+1][d+1]+1:Math.max(a[u+1][d],a[u][d+1]);let i=new Set,c=0,l=0;for(;c<o&&l<s;)ha(n[c])===ha(r[l])?(c++,l++):a[c+1][l]>=a[c][l+1]?c++:(ug(r[l])&&i.add(r[l]),l++);for(;l<s;l++)ug(r[l])&&i.add(r[l]);if(!i.size)return[];for(let u of i)await AE(u,"learned");return await xE(),[...i]}var Io=null;function TE(t,e){let n=null;try{n=JSON.parse(e)}catch{}let r=n?.err_code||n?.error_code||n?.category||"";return t===402||/PAYMENT_REQUIRED/i.test(r)?{status:402,code:"credits_exhausted",persistent:!0,message:"Deepgram credits are exhausted \u2014 dictation is down until you top up at console.deepgram.com or drop a new key into ~/.bostrat/integrations.json."}:t===401||t===403?{status:502,code:"key_invalid",persistent:!0,message:"Deepgram rejected the API key (revoked, or missing ASR scope) \u2014 replace it in ~/.bostrat/integrations.json."}:{status:502,code:"transcription_failed",persistent:!1,message:`Deepgram error ${t}: ${e.slice(0,300)}`}}async function wg(){if(!Io)return null;let t=await Ur();if(!t||t.apiKey.slice(-6)!==Io.keyTail)return null;let{keyTail:e,...n}=Io;return n}async function gg(t,e){let n=await Ur();if(!n){let i=new Error("Deepgram is not configured \u2014 add deepgram.apiKey to ~/.bostrat/integrations.json");throw i.status=503,i.code="not_configured",i}let r=new URL("https://api.deepgram.com/v1/listen");r.searchParams.set("model","nova-3"),r.searchParams.set("smart_format","true");for(let i of await vE())r.searchParams.append("keyterm",i);let o=await fetch(r,{method:"POST",headers:{Authorization:`Token ${n.apiKey}`,"Content-Type":e||"application/octet-stream"},body:t});if(!o.ok){let i=await o.text().catch(()=>""),c=TE(o.status,i);c.persistent&&(Io={code:c.code,message:c.message,at:Date.now(),keyTail:n.apiKey.slice(-6)});let l=new Error(c.message);throw l.status=c.status,l.code=c.code,l}return Io=null,{transcript:(await o.json())?.results?.channels?.[0]?.alternatives?.[0]?.transcript??""}}var Sg=lt.dirname(EE(import.meta.url)),yg=Number(process.env.PORT)||3362,ya=hw(process.env),vg=Number(process.env.BOSTRAT_AUTO_TRIGGER_PCT)||80,_r=Nw(process.env.BOSTRAT_ALLOW_IPS),S=ie();S.disable("x-powered-by");S.use((t,e,n)=>{if(_r.allowed(_r.clientIp(t)))return n();e.status(403).type("text/plain").send("bostrat: access restricted to the allowlist")});S.use(jw);S.use("/api/mocks",ie.json({limit:"1mb"}));S.use("/api/mocks",ie.raw({type:"image/png",limit:"24mb"}));S.use("/api/runbooks",ie.json({limit:"1mb"}));S.use("/api/telemetry",ie.json({limit:"256kb"}));S.use("/api/hook",ie.json({limit:"2mb"}));S.use("/api/dictation",ie.raw({type:["audio/*","video/mp4","application/octet-stream"],limit:"32mb"}));S.use("/api/tracks/:id/upload",ie.raw({type:()=>!0,limit:"25mb"}));S.use(ie.json({limit:"64kb"}));S.get("/api/health",async(t,e)=>{e.json({ok:!0,tmuxSocket:Mt,hostTmuxAlive:await Se(),autoTriggerPct:vg})});S.get("/api/sessions",async(t,e)=>{e.json({sessions:await Nn()})});S.post("/api/sessions",async(t,e)=>{let{displayName:n,cwd:r}=t.body||{};if(typeof n!="string")return e.status(400).json({error:"displayName (string) required"});try{let o=await Qe(n,r||void 0);e.json({ok:!0,...o})}catch(o){let s=o?.message||String(o),a=/already exists/i.test(s)?409:/must be|invalid/i.test(s)?400:500;e.status(a).json({error:s})}});S.delete("/api/sessions/:name",async(t,e)=>{let n=decodeURIComponent(t.params.name);if(!pt(n))return e.status(400).json({error:"invalid session name"});if(!await z(n))return e.status(404).json({error:"session not found"});try{await Jt(n),e.json({ok:!0})}catch(r){e.status(500).json({error:`failed to kill: ${r?.message||r}`})}});S.get("/api/context/:name",async(t,e)=>{let n=decodeURIComponent(t.params.name);if(!pt(n))return e.status(400).json({error:"invalid session name"});let r=await z(n);if(!r)return e.status(404).json({error:"session not found"});let o=await Cr(r.cwd,r.sessionId);if(!o)return e.json({available:!1,cwd:r.cwd,sessionId:r.sessionId||null});e.json({available:!0,...o})});S.post("/api/handoff/:name",async(t,e)=>{let n=decodeURIComponent(t.params.name);if(!pt(n))return e.status(400).json({error:"invalid session name"});try{let r=await Gu(n);e.json({ok:!0,step:r.step,file:r.file,startedAt:r.startedAt})}catch(r){e.status(409).json({error:r?.message||String(r)})}});S.get("/api/handoff/:name",(t,e)=>{let n=decodeURIComponent(t.params.name),r=Vo(n);if(!r)return e.json({running:!1});e.json({running:!r.finishedAt,step:r.step,error:r.error,file:r.file,startedAt:r.startedAt,finishedAt:r.finishedAt})});S.get("/api/tracks",async(t,e)=>{e.json({tracks:await ut(),agents:Ys,agentAvailability:await ol(),tickets:await fs()})});S.get("/api/cost",async(t,e)=>{try{let n=await Sw(await ut());if(n.cursorUsage=qm(),t.query.format==="text")return e.type("text/plain").send(vw(n));e.json(n)}catch(n){e.status(500).json({error:n?.message||String(n)})}});S.get("/api/usage/roundup",async(t,e)=>{try{e.json(await Ew(await ut(),{window:t.query.window}))}catch(n){e.status(500).json({error:n?.message||String(n)})}});S.get("/api/arcs",async(t,e)=>{try{e.json(await Df(await ut()))}catch(n){e.status(500).json({error:n?.message||String(n)})}});S.get("/api/arcs/:key",async(t,e)=>{try{let n=await jf(await ut(),t.params.key);if(!n)return e.status(404).json({error:"unknown arc"});e.json(n)}catch(n){e.status(500).json({error:n?.message||String(n)})}});S.post("/api/arcs/backfill-beats",async(t,e)=>{try{e.json({ok:!0,...await Kh()})}catch(n){e.status(500).json({error:n?.message||String(n)})}});S.post("/api/hook",async(t,e)=>{try{let n=await mw(t.body||{},_h);e.json({ok:!0,events:n.length})}catch(n){e.json({ok:!1,error:n?.message||String(n)})}});S.post("/api/telemetry",async(t,e)=>{try{if(!rg(_r.clientIp(t)))return e.json({ok:!0,dropped:!0});let n=await ng(t.body||{});e.json({ok:!0,events:n})}catch(n){e.json({ok:!1,error:n?.message||String(n)})}});S.get("/api/repos",async(t,e)=>{try{let n=await me(),r=new Set((await Promise.all(n.map(a=>Y(a.path)))).filter(Boolean).map(a=>a.toLowerCase())),s=((await zr()).repos||[]).filter(a=>!r.has(String(a.slug).toLowerCase())).map(a=>({slug:a.slug,name:a.slug.split("/")[1],branch:a.defaultBranch||null,private:!!a.private,remote:!0}));e.json({repos:[...n,...s]})}catch(n){e.status(500).json({error:n?.message||String(n)})}});S.get("/api/github/token",async(t,e)=>{try{e.json(await vs(String(t.query.repo||"")))}catch(n){e.status(n?.status||502).json({error:n?.message||String(n)})}});S.get("/api/github/status",(t,e)=>{e.json({configured:Ss(),calls:Sm(),latency:zm()})});S.post("/api/github/refresh",(t,e)=>{zf(),e.json({ok:!0})});function Vl(t,e,n){let r=t.workspace;return r?.kind==="multi"?(r.repos??[]).some(o=>e&&o?.base===e||n&&o?.repo===n):!r?.base||!e||r.base===e||n&&r.repo===n}async function ba({repo:t=null,number:e=null,branch:n=null}){let r=t?await Y(t):null;if(r&&e!=null){let s=await vt(r,e),a=s&&await et(s.trackId);if(a)return a}return n?(await ut()).find(s=>s.workspace?.branch===n&&Vl(s,t,r))??null:null}async function Ag({repo:t=null,number:e=null,branch:n=null}){let r=await ba({repo:t,number:e,branch:n}).catch(()=>null);if(r&&!r.archivedAt)return r;if(!n)return null;let o=t?await Y(t).catch(()=>null):null;return(await ut()).find(a=>!a.archivedAt&&a.workspace?.branch===n&&Vl(a,t,o))??null}async function Vt(t,{number:e,url:n=null,title:r=null,branch:o=null,repo:s=null,track:a=null,extra:i={}}){let c=a||await ba({repo:s,number:e,branch:o}).catch(()=>null);if(!c)return;let l=i.repo??(s?await Y(s).catch(()=>null):null);await O({id:c.id,cwd:c.cwd},{type:"pr.state.changed",actor:"user",source:"orchestrator",payload:{ts:Date.now(),change:t,number:e,url:n,title:r,branch:o??c.workspace?.branch??null,...l?{repo:l}:{},...i}}).catch(()=>{})}async function OE(t){let e=(await ut()).filter(n=>!n.archivedAt);for(let n of t??[]){if(!n.prs?.length)continue;let r=await Y(n.path);if(r)for(let o of n.prs){if(await vt(r,o.number))continue;let s=e.find(a=>a.workspace?.branch===o.headRefName&&Vl(a,n.path,r));s&&await Pt({repo:r,number:o.number,trackId:s.id,url:o.url,headRef:o.headRefName,title:o.title,source:"matched"})}}}S.get("/api/git/prs",async(t,e)=>{try{let n=await nh();await OE(n.repos).catch(()=>{}),e.json(n)}catch(n){e.status(500).json({error:n?.message||String(n)})}});S.post("/api/git/prs/merge",async(t,e)=>{let{repo:n,number:r,method:o,auto:s,update:a,archive:i,notify:c,headRefName:l,title:u,url:d}=t.body||{};try{if(s){let h=o||"merge";if(!so.has(h))throw new Error(`invalid merge method: ${h}`);let b=Number(r);if(!Number.isInteger(b)||b<=0)throw new Error("invalid PR number");let x=await Y((await ge(n)).path);if(!x)throw new Error("repo has no GitHub remote");let v=await ba({repo:n,number:b,branch:l}).catch(()=>null);if(!v)throw new Error("auto-merge needs a Track to watch this PR \u2014 none owns it");if(a&&await ao(n,b),await Pt({repo:x,number:b,trackId:v.id,url:d,headRef:l,title:u,source:"matched"}).catch(()=>{}),!await Kr(x,b,{merge:{method:h}}))throw new Error("couldn't record the auto-merge arm");return await Yn(x,b,i===!0),await ks(x,b,c===!0&&i!==!0),se(),await Vt("auto-armed",{number:b,url:d,title:u,branch:l,repo:n,track:v,...a?{extra:{updated:!0}}:{}}),e.json({ok:!0,number:b,auto:!0})}let f=await fr(n,r,{method:o||"squash"});await Vt("merged",{number:f.number,url:d,title:u,branch:l,repo:n});let p=await Y(n).catch(()=>null),m=!1;if(p&&(i===!0&&await Yn(p,f.number,!0).catch(()=>{}),c===!0&&i!==!0&&await ks(p,f.number,!0).catch(()=>{}),await He(p,f.number).catch(()=>{}),m=await Ue(p,f.number).catch(()=>!1),await Ce(p,f.number,"merged").catch(()=>{})),i===!0&&!m){let h=await ba({repo:n,number:f.number,branch:l}).catch(()=>null);h&&!h.archivedAt&&await yr(h.id).catch(()=>{})}e.json(f)}catch(f){let p=f?.message||String(f),m=/invalid|unknown repo|required|needs a Track|no GitHub remote/i.test(p)?400:502;e.status(m).json({error:p})}});S.post("/api/git/prs/disarm-auto",async(t,e)=>{let{repo:n,number:r,headRefName:o,title:s,url:a}=t.body||{};try{let i=Number(r);if(!Number.isInteger(i)||i<=0)throw new Error("invalid PR number");let c=await Y((await ge(n)).path).catch(()=>null),l=c?await Vr(c,i).catch(()=>null):null;try{await qc(n,i)}catch(u){if(!l)throw u}se(),await Vt("auto-canceled",{number:i,url:a,title:s,branch:o,repo:n}),e.json({ok:!0,number:i})}catch(i){let c=i?.message||String(i),l=/invalid|unknown repo|required/i.test(c)?400:502;e.status(l).json({error:c})}});S.post("/api/git/prs/merge-all",async(t,e)=>{let{repo:n,method:r}=t.body||{};try{let o=await Y(n).catch(()=>null),s=await ah(n,{method:r||"merge",onMerged:a=>{Vt("merged",{number:a.number,url:a.url,title:a.title,branch:a.headRefName,repo:n}),o&&He(o,a.number).catch(()=>{}).then(()=>Ue(o,a.number)).catch(()=>{}).then(()=>Ce(o,a.number,"merged")).catch(()=>{})}});e.json(s)}catch(o){let s=o?.message||String(o),a=/already running/i.test(s)?409:/invalid|unknown repo|no PRs ready/i.test(s)?400:502;e.status(a).json({error:s})}});S.get("/api/git/prs/merge-all",(t,e)=>{e.json({jobs:sh()})});S.post("/api/git/prs/merge-track",async(t,e)=>{let{trackId:n,update:r,archive:o,notify:s,method:a}=t.body||{};try{let i=a||"merge";if(!so.has(i))throw new Error(`invalid merge method: ${i}`);let c=n?await et(n):null;if(!c)return e.status(400).json({error:"trackId required"});let l=(await Me()).filter(f=>f.trackId===c.id&&f.state!=="merged"&&f.state!=="closed");if(!l.length)return e.status(409).json({error:"no open PRs on this Track"});for(let f of l)await Yn(f.repo,f.number,o===!0).catch(()=>{}),await ks(f.repo,f.number,s===!0&&o!==!0).catch(()=>{});let u=[],d=[];for(let f of l){let p={repo:f.repo,number:f.number,ok:!0,action:"armed"};u.push(p);try{let m=await Sn(f.repo);if(!m)throw new Error(`no local checkout for ${f.repo}`);let h=await dr(m,f.number);d.push({r:p,l:f,repoPath:m,pr:h,plan:rh(h,{update:r!==!1})})}catch(m){p.ok=!1,p.action="failed",p.reason=m?.message||String(m)}}for(let{r:f,l:p,repoPath:m,pr:h,plan:b}of oh(d))try{if(b.action==="skip")f.action="skipped",f.reason=b.reason;else if(b.action==="merge")await fr(m,p.number,{method:i}),await Vt("merged",{number:p.number,url:h.url,title:h.title,branch:h.headRefName,repo:m,track:c}),await He(p.repo,p.number).catch(()=>{}),await Ue(p.repo,p.number).catch(()=>{}),await Ce(p.repo,p.number,"merged").catch(()=>{}),f.action="merged";else{if(b.action==="update-arm"&&(await ao(m,p.number).catch(()=>{}),f.action="updated-armed"),!await Kr(p.repo,p.number,{merge:{method:i}}))throw new Error("couldn't record the auto-merge arm");await Vt("auto-armed",{number:p.number,url:h.url,title:h.title,branch:h.headRefName,repo:m,track:c,...f.action==="updated-armed"?{extra:{updated:!0}}:{}})}}catch(x){f.ok=!1,f.action="failed",f.reason=x?.message||String(x)}if(se(),!u.some(f=>f.ok))return e.status(502).json({error:u.map(f=>`${f.repo}#${f.number}: ${f.reason}`).join("; "),results:u});e.json({ok:!0,trackId:c.id,results:u})}catch(i){let c=i?.message||String(i),l=/invalid|required/i.test(c)?400:502;e.status(l).json({error:c})}});S.post("/api/git/prs/disarm-track",async(t,e)=>{let{trackId:n}=t.body||{};try{let r=n?await et(n):null;if(!r)return e.status(400).json({error:"trackId required"});let o=(await Me()).filter(a=>a.trackId===r.id&&a.state!=="merged"&&a.state!=="closed"),s=[];for(let a of o){let i=await Vr(a.repo,a.number).catch(()=>null),c=await Sn(a.repo).catch(()=>null);c&&await qc(c,a.number).catch(()=>{}),i&&(await Vt("auto-canceled",{number:a.number,url:a.url,title:a.title,branch:a.headRef,repo:c,track:r}),s.push({repo:a.repo,number:a.number}))}se(),e.json({ok:!0,trackId:r.id,canceled:s})}catch(r){let o=r?.message||String(r),s=/invalid|required/i.test(o)?400:502;e.status(s).json({error:o})}});S.post("/api/git/prs/open",async(t,e)=>{let{trackId:n,title:r,body:o,base:s,repos:a}=t.body||{};try{let i=n?await et(n):null;if(!i)return e.status(400).json({error:"trackId required"});if(i.workspace?.kind==="multi"){let u=$t(i.workspace),d,f=Array.isArray(a)?a.filter(Boolean):[];if(f.length)d=f.map(h=>{let b=u.find(x=>x.name===h||x.repo===h||x.base===h||x.dir===h);if(!b)throw new Error(`invalid repo "${h}" \u2014 not tagged on this Track`);return b});else if(d=await no(i),!d.length)return e.status(409).json({error:"no tagged repo has work to ship"});let p=[];for(let h of d)try{let b=await Kc(h.dir,{title:r,body:o,base:s}),x=h.repo||await Y(h.dir).catch(()=>null);x&&b.number&&await Pt({repo:x,number:b.number,trackId:i.id,url:b.url,headRef:i.workspace?.branch??null,title:r,source:"api"}),await Vt("opened",{number:b.number,url:b.url,title:r,track:i,branch:i.workspace?.branch,extra:{repo:x??h.name}}),p.push({repo:h.name,slug:x??null,ok:!0,url:b.url,number:b.number})}catch(b){p.push({repo:h.name,slug:h.repo??null,ok:!1,error:b?.message||String(b)})}let m=p.filter(h=>h.ok);return m.length?e.json({ok:!0,multi:!0,results:p,url:m[0].url,number:m[0].number}):e.status(502).json({error:p.map(h=>`${h.repo}: ${h.error}`).join("; "),results:p})}let c=await Kc(i.cwd,{title:r,body:o,base:s}),l=await Y(i.cwd).catch(()=>null);l&&c.number&&await Pt({repo:l,number:c.number,trackId:i.id,url:c.url,headRef:i.workspace?.branch??null,title:r,source:"api"}),await Vt("opened",{number:c.number,url:c.url,title:r,track:i,branch:i.workspace?.branch}),e.json(c)}catch(i){let c=i?.message||String(i),l=/required|invalid/i.test(c)?400:502;e.status(l).json({error:c})}});S.get("/api/tracks/:id/repos",async(t,e)=>{try{let n=await et(t.params.id);if(!n)return e.status(404).json({error:"track not found"});let r=await no(n),o=new Map(r.map(a=>[a.base,a])),s=$t(n.workspace).map(a=>{let i=o.get(a.base);return{name:a.name,base:a.base,baseRef:a.baseRef??null,repo:a.repo??null,dirty:i?.dirty??!1,ahead:i?.ahead??0,changed:!!i}});e.json({repos:s,branch:n.workspace?.branch??null,kind:n.workspace?.kind??null})}catch(n){e.status(500).json({error:n?.message||String(n)})}});S.post("/api/tracks/:id/repos",async(t,e)=>{let{repo:n}=t.body||{};try{if(!n||typeof n!="string")return e.status(400).json({error:"repo (path or owner/name) required"});let r=await jh(t.params.id,n);e.json({ok:!0,added:{name:r.name,base:r.base,baseRef:r.baseRef??null,repo:r.repo??null}})}catch(r){let o=r?.message||String(r),s=/not found/i.test(o)?404:/already tagged|needs a multi|not a usable|archived|required/i.test(o)?400:502;e.status(s).json({error:o})}});S.delete("/api/tracks/:id/repos/:name",async(t,e)=>{try{let n=await Lh(t.params.id,t.params.name);e.json({ok:!0,...n})}catch(n){let r=n?.message||String(n),o=/track .* not found/i.test(r)?404:/has work|still running|last repo/i.test(r)?409:/needs a multi|not tagged/i.test(r)?400:502;e.status(o).json({error:r})}});S.post("/api/git/prs/fix",async(t,e)=>{let{repo:n,number:r}=t.body||{};try{let o=await dr(n,r),s=await Y(n).catch(()=>null),a=await Ag({repo:n,number:r,branch:o.headRefName});if(!a)return e.status(404).json({error:`no active Track owns PR #${o.number} (branch "${o.headRefName}")`});let i=$t(a.workspace).find(x=>x.base===n||s&&x.repo===s)??null,c=a.workspace?.kind==="multi",l=await ih(n,o.number);if(!l.length)return e.status(409).json({error:"no failing checks on this PR"});let u=c&&i?lt.join("ci",i.name):"ci",d=lt.join(a.cwd,".bostrat",u),f=lt.join(".bostrat",u,`pr-${o.number}-fail.log`);await Ct.promises.mkdir(d,{recursive:!0}),await Ct.promises.writeFile(lt.join(a.cwd,f),l.map(x=>`\u2550\u2550\u2550\u2550 ${x.name}${x.link?` \xB7 ${x.link}`:""} \u2550\u2550\u2550\u2550
|
|
313
|
+
${x.log??"(no fetchable log \u2014 see link)"}
|
|
295
314
|
`).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}
|
|
315
|
+
`));let p=l.map(x=>x.name).join(", "),m=c&&i?` in the ${i.name}/ checkout`:"",h=await ye(a.id,`CI is red on PR #${o.number} "${o.title}" (${o.url}) \u2014 branch ${o.headRefName}${m}. Failing check${l.length===1?"":"s"}: ${p}. The failure log is at ${f} in this ${c?"workspace":"worktree"} \u2014 read it, fix the root cause${c&&i?` in ${i.name}/`:""}, run the relevant tests locally, then commit and push to update the PR.`,{kind:"ci-fix"}),b=s;b&&(await Pt({repo:b,number:o.number,trackId:a.id,url:o.url,headRef:o.headRefName,title:o.title,source:"matched"}),await Tf(b,o.number,{headSha:o.headSha,trackId:a.id}).catch(()=>{})),await Vt("fix-delegated",{number:o.number,url:o.url,title:o.title,branch:o.headRefName,repo:n,track:a,extra:{checks:l.map(x=>x.name)}}),e.json({ok:!0,leadBox:h.leadBox,queued:!!h.queued,checks:l.map(x=>x.name),log:f})}catch(o){let s=o?.message||String(o),a=/unknown repo|invalid|required/i.test(s)?400:/not found|no active Track/i.test(s)?404:502;e.status(a).json({error:s})}});S.post("/api/git/prs/reconcile",async(t,e)=>{let{repo:n,number:r}=t.body||{};try{let o=await dr(n,r);if(o.mergeable!=="CONFLICTING")return e.status(409).json({error:"this PR does not conflict with its base branch"});let s=await Y(n).catch(()=>null),a=await Ag({repo:n,number:r,branch:o.headRefName});if(!a)return e.status(404).json({error:`no active Track owns PR #${o.number} (branch "${o.headRefName}")`});let i=$t(a.workspace).find(d=>d.base===n||s&&d.repo===s)??null,c=a.workspace?.kind==="multi"&&i?` In the ${i.name}/ checkout:`:"",l=await ye(a.id,`PR #${o.number} "${o.title}" (${o.url}) \u2014 branch ${o.headRefName} \u2014 has merge conflicts with ${o.baseRefName}.${c} Bring ${o.baseRefName} into this branch (fetch origin, then merge origin/${o.baseRefName} \u2014 don't rebase this pushed PR branch), and resolve every conflict so that BOTH sides' intent is preserved. Run the relevant tests, then commit and push to update the PR. If any conflict is a genuine ambiguity \u2014 where you can't confidently tell which side's behavior should win, or reconciling would drop work from either side \u2014 STOP and ask me here rather than guessing, laying out the specific choice and your recommendation.`,{kind:"reconcile"}),u=s;u&&(await Pt({repo:u,number:o.number,trackId:a.id,url:o.url,headRef:o.headRefName,title:o.title,source:"matched"}),await _f(u,o.number,{headSha:o.headSha,trackId:a.id}).catch(()=>{})),await Vt("reconcile-delegated",{number:o.number,url:o.url,title:o.title,branch:o.headRefName,repo:n,track:a,extra:{base:o.baseRefName}}),e.json({ok:!0,leadBox:l.leadBox,queued:!!l.queued,base:o.baseRefName})}catch(o){let s=o?.message||String(o),a=/unknown repo|invalid|required/i.test(s)?400:/not found|no active Track/i.test(s)?404:502;e.status(a).json({error:s})}});S.post("/api/tracks/:id/pr-offer",async(t,e)=>{let{offerId:n,action:r}=t.body||{};try{let o=null;if(Array.isArray(t.body?.actions)){if(o=lm.filter(d=>t.body.actions.includes(d)),!o.length)return e.status(400).json({error:"actions must include commit, rebase, push and/or pr"})}else if(r==="pr")o=["commit","push","pr"];else if(r==="push")o=["commit","push"];else if(r!=="dismiss")return e.status(400).json({error:'offerId and actions ["commit"|"rebase"|"push"|"pr"] (or action "dismiss") required'});if(!n)return e.status(400).json({error:"offerId required"});let s=o!=null&&t.body?.notify===!0,a=await et(t.params.id);if(!a)return e.status(404).json({error:"track not found"});let i=await F(a),c=i.find(d=>d.type==="pr.offered"&&d.payload?.offerId===n);if(!c)return e.status(404).json({error:"unknown offer"});if(i.some(d=>d.type==="pr.offer.answered"&&d.payload?.offerId===n))return e.status(409).json({error:"offer already answered"});let l=null;o&&(l=await ye(a.id,Os(o,{branch:c.payload.branch,base:c.payload.base,toPr:c.payload.toPr||null,repos:c.payload.repos||null,offeredRebase:Array.isArray(c.payload.offered)&&c.payload.offered.includes("rebase")}),{kind:o.includes("pr")?"pr-go":"push-go"}));let u=o?o.includes("pr")?"pr":o.includes("push")?"push":o.includes("commit")?"commit":"rebase":"dismiss";await O({id:a.id,cwd:a.cwd},{type:"pr.offer.answered",actor:"user",source:"orchestrator",boxId:c.boxId,payload:{ts:Date.now(),offerId:n,action:u,actions:o,...s?{notify:!0}:{}}}),s&&Oh(a.id,{actions:o}),e.json({ok:!0,offerId:n,action:u,actions:o,notify:s,queued:!!l?.queued})}catch(o){let s=o?.message||String(o),a=/required|invalid/i.test(s)?400:/not found|not running|archived/i.test(s)?409:502;e.status(a).json({error:s})}});var Ro=t=>e=>({...e,url:Fw(e,t.headers)});S.get("/api/views",async(t,e)=>{try{let n=await os({trackId:t.query.track||void 0});e.json({views:n.map(Ro(t)),maxLive:Ie})}catch(n){e.status(500).json({error:n?.message||String(n)})}});S.get("/api/views/:id",async(t,e)=>{let n=await Rd(t.params.id);if(!n)return e.status(404).json({error:"view not found"});e.json({view:Ro(t)(n),session:Wn(n.slug)})});S.post("/api/views",async(t,e)=>{let{trackId:n,name:r,cmd:o,cwd:s,port:a}=t.body||{};try{let i=n?await et(n):null;if(!i&&s&&(i=(await ut()).find(u=>u.cwd&&(s===u.cwd||s.startsWith(u.cwd+lt.sep)))||null),!i)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});let c=await ci({trackId:i.id,name:r,cmd:o,cwd:i.cwd,serveCwd:s,port:a,box:i.leadBox,branch:i.workspace?.branch??null});e.json({ok:!0,view:Ro(t)(c)})}catch(i){let c=i?.message||String(i),l=i?.status??(/cap reached/i.test(c)?409:/required|invalid|already owned|no free/i.test(c)?400:500);e.status(l).json({error:c})}});S.post("/api/views/:id/stop",async(t,e)=>{try{e.json({ok:!0,view:Ro(t)(await $d(t.params.id))})}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});S.post("/api/views/:id/restart",async(t,e)=>{try{e.json({ok:!0,view:Ro(t)(await Od(t.params.id))})}catch(n){let r=n?.message||String(n),o=/not found/i.test(r)?404:/cap reached|no known command/i.test(r)?409:500;e.status(o).json({error:r})}});S.delete("/api/views/:id",async(t,e)=>{try{e.json(await Cd(t.params.id))}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});var CE=[["linear",an,gi],["jira",cs,hi]],Hl=new Map;S.get("/api/integrations/ticketing",async(t,e)=>{try{let n=await mi(),r={};for(let[o,s,a]of CE){let i=await s();if(!i){Hl.delete(o),r[o]={configured:!1};continue}let c=Hl.get(o);if(!c||Date.now()-c.at>300*1e3){let l;try{l={configured:!0,ok:!0,...await a(i)}}catch(u){l={configured:!0,ok:!1,error:u?.message||String(u)}}c={at:Date.now(),body:l},Hl.set(o,c)}r[o]={...c.body,flow:n},o==="jira"&&i.tokenExpiresAt&&(r[o].tokenExpiresAt=i.tokenExpiresAt)}e.json(r)}catch(n){e.status(500).json({error:n?.message||String(n)})}});S.get("/api/tickets/flow",async(t,e)=>{try{e.json({flow:await mi()})}catch(n){e.status(500).json({error:n?.message||String(n)})}});S.post("/api/tickets/flow",async(t,e)=>{let{trackId:n,cwd:r,set:o,actor:s}=t.body||{};try{let a=await zd(o),i=n?await et(n):null;!i&&r&&(i=(await ut()).find(l=>l.cwd&&(r===l.cwd||r.startsWith(l.cwd+lt.sep)))||null),i&&await O({id:i.id,cwd:i.cwd},{type:"ticket.flow.changed",actor:s==="user"?"user":"agent",source:"orchestrator",payload:{ts:Date.now(),set:{...o},flow:a}}).catch(()=>{}),e.json({ok:!0,flow:a})}catch(a){let i=a?.message||String(a);e.status(/unknown|nothing to set/i.test(i)?400:500).json({error:i})}});S.post("/api/tickets/link",async(t,e)=>{let{trackId:n,cwd:r,ref:o}=t.body||{};try{let s=n?await et(n):null;if(!s&&r&&(s=(await ut()).find(c=>c.cwd&&(r===c.cwd||r.startsWith(c.cwd+lt.sep)))||null),!s)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});if(!o)return e.status(400).json({error:"ref (issue URL or key) required"});let a=await Xn(s,o,{source:n?"api":"cli"});e.json({ok:!0,trackId:s.id,ticket:a})}catch(s){let a=s?.message||String(s);e.status(/not a Linear|not found|no ticketing|required/i.test(a)?400:502).json({error:a})}});S.get("/api/tickets/status",async(t,e)=>{let{cwd:n,track:r}=t.query;try{let o=r?await et(r):null;if(!o&&n&&(o=(await ut()).find(a=>a.cwd&&(n===a.cwd||String(n).startsWith(a.cwd+lt.sep)))||null),!o)return e.status(400).json({error:"track (or a cwd inside a Track workspace) required"});e.json({trackId:o.id,configured:await ff(),ticket:await ne(o.id)})}catch(o){e.status(500).json({error:o?.message||String(o)})}});S.get("/api/tracks/:id/ticket",async(t,e)=>{e.json({ticket:await ne(t.params.id)})});S.post("/api/tracks/:id/ticket",async(t,e)=>{try{let n=await et(t.params.id);if(!n)return e.status(404).json({error:"track not found"});let r=await Xn(n,(t.body||{}).ref,{source:"api"});e.json({ok:!0,ticket:r})}catch(n){let r=n?.message||String(n);e.status(/not a Linear|not found|no ticketing|required/i.test(r)?400:502).json({error:r})}});S.delete("/api/tracks/:id/ticket",async(t,e)=>{e.json({ok:!0,removed:await Ii(t.params.id)})});var ka=t=>/no ticket linked|unknown offer|unknown state/i.test(t)?404:/already answered/i.test(t)?409:/required|no ticketing/i.test(t)?400:502;S.get("/api/tracks/:id/ticket/states",async(t,e)=>{try{e.json(await wf(t.params.id))}catch(n){let r=n?.message||String(n);e.status(ka(r)).json({error:r})}});S.post("/api/tracks/:id/ticket/state",async(t,e)=>{try{e.json({ok:!0,...await Ri(t.params.id,(t.body||{}).stateId)})}catch(n){let r=n?.message||String(n);e.status(ka(r)).json({error:r})}});S.post("/api/tracks/:id/ticket/comment",async(t,e)=>{try{e.json({ok:!0,...await gf(t.params.id,(t.body||{}).body)})}catch(n){let r=n?.message||String(n);e.status(ka(r)).json({error:r})}});S.post("/api/tracks/:id/ticket-offer",async(t,e)=>{let{offerId:n,accept:r}=t.body||{};try{let o=await et(t.params.id);if(!o)return e.status(404).json({error:"track not found"});e.json({ok:!0,...await yf(o,n,r===!0)})}catch(o){let s=o?.message||String(o);e.status(ka(s)).json({error:s})}});S.post("/api/tickets/propose",async(t,e)=>{let{trackId:n,cwd:r,plan:o}=t.body||{};try{let s=n?await et(n):null;if(!s&&r&&(s=(await ut()).find(i=>i.cwd&&(r===i.cwd||r.startsWith(i.cwd+lt.sep)))||null),!s)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});e.json({ok:!0,trackId:s.id,proposal:await kf(s,o)})}catch(s){let a=s?.message||String(s);e.status(/no ticketing|are off|needs an epic|too many|required/i.test(a)?400:502).json({error:a})}});S.get("/api/tickets/teams",async(t,e)=>{try{e.json({teams:await bf()})}catch(n){let r=n?.message||String(n);e.status(/no ticketing/i.test(r)?400:502).json({error:r})}});S.post("/api/tracks/:id/ticket-create-offer",async(t,e)=>{let{proposalId:n,accept:r,teamId:o}=t.body||{};try{let s=await et(t.params.id);if(!s)return e.status(404).json({error:"track not found"});e.json({ok:!0,...await xf(s,n,r===!0,{teamId:o||null})})}catch(s){let a=s?.message||String(s);e.status(/unknown proposal|unknown team/i.test(a)?404:/already answered/i.test(a)?409:/required|no ticketing|pick one/i.test(a)?400:502).json({error:a})}});S.get("/api/mocks",async(t,e)=>{try{e.json({mocks:await Qo({trackId:t.query.track||void 0,group:t.query.group||void 0})})}catch(n){e.status(500).json({error:n?.message||String(n)})}});S.get("/api/mocks/:id",async(t,e)=>{let n=await Ee(t.params.id);if(!n)return e.status(404).json({error:"mock not found"});e.json({mock:n})});S.get("/api/mocks/:id/png",async(t,e)=>{let n=await Ee(t.params.id);if(!n)return e.status(404).json({error:"mock not found"});let r=nn(n,t.query.v??null);if(!r)return e.status(409).json({error:`mock is ${n.state} \u2014 no captured version yet`});e.set("cache-control","private, max-age=31536000, immutable"),e.sendFile(Lt(n,r.v).png,o=>{o&&!e.headersSent&&e.status(404).json({error:"rendered artifact missing"})})});S.get("/api/mocks/:id/html",async(t,e)=>{let n=await Ee(t.params.id);if(!n)return e.status(404).json({error:"mock not found"});let r=nn(n,t.query.v??null);if(!r?.htmlRel)return e.status(404).json({error:"this version has no HTML source (image-sourced)"});e.set("content-security-policy","sandbox"),e.sendFile(Lt(n,r.v).html,o=>{o&&!e.headersSent&&e.status(404).json({error:"source artifact missing"})})});S.post("/api/mocks",async(t,e)=>{let n=Buffer.isBuffer(t.body)?t.body:null,r=n?t.query:t.body||{},{trackId:o,name:s,cwd:a,width:i,group:c}=r;try{let l=o?await et(o):null;if(!l&&a&&(l=(await ut()).find(p=>p.cwd&&(a===p.cwd||a.startsWith(p.cwd+lt.sep)))||null),!l)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});let u=(l.boxes||[]).find(f=>f.name===l.leadBox),d=await dd({trackId:l.id,name:s,html:n?null:r.html,image:n,cwd:l.cwd,width:i,group:c,box:l.leadBox,agent:u?.agent||null});e.json({ok:!0,mock:d})}catch(l){let u=l?.message||String(l);e.status(/required|invalid|must be/i.test(u)?400:500).json({error:u})}});S.post("/api/mocks/:id/update",async(t,e)=>{let n=Buffer.isBuffer(t.body)?t.body:null;try{let r=await fd(t.params.id,{html:n?null:(t.body||{}).html,image:n,width:n?t.query.width:(t.body||{}).width});e.json({ok:!0,mock:r})}catch(r){let o=r?.message||String(r),s=/not found/i.test(o)?404:/required|invalid|must be/i.test(o)?400:500;e.status(s).json({error:o})}});S.post("/api/mocks/:id/pick",async(t,e)=>{try{let{mock:n,siblings:r}=await md(t.params.id),o=r.length?` over ${r.map(s=>`"${s.name}"`).join(", ")}`:"";await ye(n.trackId,`Design decision: go with "${n.name}" (mock:${n.id})${o} \u2014 proceed with this design.`,{kind:"mock-picked"}).catch(()=>{}),e.json({ok:!0,mock:n,siblings:r})}catch(n){let r=n?.message||String(n),o=/not found/i.test(r)?404:/nothing to pick/i.test(r)?409:500;e.status(o).json({error:r})}});S.delete("/api/mocks/:id",async(t,e)=>{try{e.json(await hd(t.params.id))}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});S.get("/api/runbooks",async(t,e)=>{try{let n=t.query.track||void 0;if(!n&&t.query.cwd){let r=await ut(),o=String(t.query.cwd);if(n=r.find(s=>s.cwd&&(o===s.cwd||o.startsWith(s.cwd+lt.sep)))?.id,!n)return e.json({runbooks:[]})}e.json({runbooks:await ss({trackId:n,status:t.query.status||void 0})})}catch(n){e.status(500).json({error:n?.message||String(n)})}});S.get("/api/runbooks/:id",async(t,e)=>{let n=await fi(t.params.id);if(!n)return e.status(404).json({error:"runbook not found"});e.json({runbook:n})});S.get("/api/runbooks/:id/md",async(t,e)=>{let n=await fi(t.params.id);if(!n)return e.status(404).json({error:"runbook not found"});let r=Dd(n,t.query.v??null);if(!r)return e.status(404).json({error:"no such version"});e.set("content-security-policy","sandbox"),e.set("cache-control","private, max-age=31536000, immutable"),e.type("text/markdown; charset=utf-8"),e.sendFile(qn(n,r.v),o=>{o&&!e.headersSent&&e.status(404).json({error:"runbook artifact missing"})})});S.post("/api/runbooks",async(t,e)=>{let{trackId:n,name:r,md:o,cwd:s}=t.body||{};try{let a=n?await et(n):null;if(!a&&s&&(a=(await ut()).find(u=>u.cwd&&(s===u.cwd||s.startsWith(u.cwd+lt.sep)))||null),!a)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});let i=(a.boxes||[]).find(l=>l.name===a.leadBox),c=await Fd({trackId:a.id,trackName:a.name,name:r,md:o,cwd:a.cwd,box:a.leadBox,agent:i?.agent||null});e.json({ok:!0,runbook:c})}catch(a){let i=a?.message||String(a);e.status(/required|invalid|over the|secret VALUE/i.test(i)?400:500).json({error:i})}});S.post("/api/runbooks/:id/update",async(t,e)=>{try{let{md:n,note:r}=t.body||{};e.json({ok:!0,runbook:await Ud(t.params.id,{md:n,note:r})})}catch(n){let r=n?.message||String(n),o=/not found/i.test(r)?404:/orphaned/i.test(r)?410:/required|invalid|over the|secret VALUE/i.test(r)?400:500;e.status(o).json({error:r})}});S.post("/api/runbooks/:id/status",async(t,e)=>{try{e.json({ok:!0,runbook:await Hd(t.params.id,(t.body||{}).status)})}catch(n){let r=n?.message||String(n),o=/not found/i.test(r)?404:/orphaned/i.test(r)?410:/must be/i.test(r)?400:500;e.status(o).json({error:r})}});S.delete("/api/runbooks/:id",async(t,e)=>{try{e.json(await Wd(t.params.id))}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:500).json({error:r})}});var ME=120,Wl=400,Kl=null;S.post("/api/notify",async(t,e)=>{let{title:n,body:r,urgency:o,cwd:s,trackId:a}=t.body||{};try{let i=typeof r=="string"?r.trim():"";if(!i)return e.status(400).json({error:"body (the message) required"});if(i.length>Wl)return e.status(400).json({error:`message is ${i.length} chars \u2014 over the ${Wl} cap; a notification is a nudge, put the detail in chat`});let c=di(`${n||""}
|
|
316
|
+
${i}`);if(c.length)return e.status(400).json({error:`notification appears to contain a secret VALUE (${c.join(", ")}) \u2014 notifications ship off-box; never put a credential in one`});let l=a?await et(a):null;if(!l&&s&&(l=(await ut()).find(m=>m.cwd&&(s===m.cwd||s.startsWith(m.cwd+lt.sep)))||null),!l)return e.status(400).json({error:"trackId (or a cwd inside a Track workspace) required"});let u=(l.boxes||[]).find(p=>p.name===l.leadBox),d=(p,m)=>String(p).replace(/\s+/g," ").trim().slice(0,m),f={title:n?d(n,ME):l.name,body:d(i,Wl),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:f}),e.json({ok:!0,notification:f,delivered:Kl?Kl.status().connected:null})}catch(i){e.status(500).json({error:i?.message||String(i)})}});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 gg(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 Ur(),terms:await Ul(),problem:await wg()})});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 hg(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 ll({name:n,cwd:r,repo:o,repos:s,agent:a,skipPermissions:i!==!1}),u=null;if(c)try{await Xn(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 et(t.params.id);if(!n)return e.status(404).json({error:"track not found"});e.json({track:n,handoff:Tg(wo(t.params.id)),subtasks:br(t.params.id).map($o)})});S.delete("/api/tracks/:id",async(t,e)=>{try{let n=await qh(t.params.id,{cascadeMocks:t.query.mocks!=="0",cascadeRunbooks:t.query.runbooks==="1"});await Ii(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 yr(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 Jh(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 sl(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 et(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
317
|
data: ${JSON.stringify(i)}
|
|
298
318
|
|
|
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=
|
|
319
|
+
`),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=Yu(n.id,r),a=setInterval(()=>e.write(`: hb
|
|
300
320
|
|
|
301
|
-
`),25e3);t.on("close",()=>{clearInterval(a),s()})});S.post("/api/tracks/:id/lead/restart",async(t,e)=>{try{e.json({ok:!0,track:await
|
|
321
|
+
`),25e3);t.on("close",()=>{clearInterval(a),s()})});S.post("/api/tracks/:id/lead/restart",async(t,e)=>{try{e.json({ok:!0,track:await Xh(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})}});S.post("/api/tracks/:id/say",async(t,e)=>{let{text:n,attachments:r}=t.body||{};try{e.json(await ye(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})}});S.post("/api/tracks/:id/upload",async(t,e)=>{try{let n=await et(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 fh(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})}});S.get("/api/tracks/:id/uploads/:file",async(t,e)=>{try{let n=await et(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=lt.basename(String(t.params.file||""));if(!r||!zc(lt.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(lt.join(Jc(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 PE=/^[0-9a-fA-F-]{8,64}$/;S.get("/api/tracks/:id/chat-images/:box/:uuid/:idx",async(t,e)=>{try{let n=await et(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(!PE.test(o)||!Number.isInteger(s)||s<0)return e.status(400).json({error:"bad image ref"});let a=(n.boxes||[]).find(f=>f.name===r);if(!a||a.agent!=="claude"||!a.cwd||!a.sessionId)return e.status(404).json({error:"image source not available"});let i=ce(a.cwd,a.sessionId);if(!i)return e.status(404).json({error:"transcript not found"});let c=`"${o}"`,l=null,u=_E.createInterface({input:Ct.createReadStream(i,{encoding:"utf8"}),crlfDelay:1/0});for await(let f of u){if(!f.includes(c))continue;let p;try{p=JSON.parse(f)}catch{continue}if(p.uuid===o){l=p,u.close();break}}let d=l?Ba(l)[s]:null;if(!d||!/^image\//.test(d.mediaType))return e.status(404).json({error:"image not found"});e.set("content-type",d.mediaType),e.set("cache-control","private, max-age=31536000, immutable"),e.set("content-security-policy","sandbox"),e.send(Buffer.from(d.data,"base64"))}catch(n){e.status(500).json({error:n?.message||String(n)})}});S.post("/api/tracks/:id/interrupt",async(t,e)=>{try{e.json(await cl(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})}});S.delete("/api/tracks/:id/queue",(t,e)=>{e.json({ok:!0,removed:ta(t.params.id).filter(n=>n.kind!=="ship-notify")})});S.post("/api/tracks/:id/answer-prompt",async(t,e)=>{let{promptId:n,answers:r,text:o}=t.body||{};try{e.json(await al(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})}});S.post("/api/setup/:provider/start",async(t,e)=>{try{let n=await Hh({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})}});S.post("/api/tracks/:id/navigate-prompt",async(t,e)=>{let{promptId:n,questionIndex:r}=t.body||{};try{e.json(await Ih(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})}});S.put("/api/tracks/:id/draft",async(t,e)=>{let{text:n}=t.body||{};try{e.json(await Mh(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})}});S.get("/api/tracks/:id/usage",async(t,e)=>{let n=await et(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 Mw(r,{fresh:o}))});S.post("/api/tracks/:id/model",async(t,e)=>{let{model:n}=t.body||{};try{e.json(await Ph(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})}});S.post("/api/tracks/:id/subtask",async(t,e)=>{let{agent:n,task:r,skipPermissions:o,reason:s}=t.body||{};try{let a=await ew(t.params.id,{agent:n,task:r,skipPermissions:o!==!1,reason:s});e.json({ok:!0,subtask:$o(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})}});S.get("/api/tracks/:id/subtasks",(t,e)=>{e.json({subtasks:br(t.params.id).map($o)})});S.get("/api/tracks/:id/subtask/:sid/messages",async(t,e)=>{let n=await Eh(t.params.id,t.params.sid);if(!n)return e.status(404).json({error:"subtask not found"});e.json(n)});S.post("/api/tracks/:id/subtask/:sid/cancel",(t,e)=>{try{e.json({ok:!0,subtask:$o(hl(t.params.sid))})}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:409).json({error:r})}});S.post("/api/tracks/:id/subtask/:sid/merge-now",(t,e)=>{try{e.json({ok:!0,subtask:$o(rw(t.params.sid))})}catch(n){let r=n?.message||String(n);e.status(/not found/i.test(r)?404:409).json({error:r})}});function $o(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}S.post("/api/tracks/:id/handoff-lead",async(t,e)=>{let{toAgent:n,skipPermissions:r,reason:o}=t.body||{};try{let s=await fl(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})}});S.post("/api/tracks/:id/spin-up-view",async(t,e)=>{try{e.json(await Ch(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})}});S.get("/api/tracks/:id/handoff-lead",(t,e)=>{e.json(Tg(wo(t.params.id))??{running:!1})});function Tg(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}S.get("/api/debug/handoff",async(t,e)=>{let n=Math.min(Number(t.query.limit)||80,500);e.json({verbose:Po(),paths:eu,trace:await tu({limit:n})})});var ql=lt.resolve(Sg,"..","..","client","dist");Ct.existsSync(ql)&&(S.use(ie.static(ql)),S.get(/^(?!\/api\/).*/,(t,e)=>e.sendFile(lt.join(ql,"index.html"))));var _g=IE.createServer(S),NE=new $E({noServer:!0}),Eg=(t,e,n)=>{if(!_r.allowed(_r.clientIp(t))){e.write(`HTTP/1.1 403 Forbidden\r
|
|
302
322
|
\r
|
|
303
|
-
`),e.destroy();return}if(
|
|
323
|
+
`),e.destroy();return}if(Lw(t,e,n))return;let r=uu(t.url);if(!r.ok){e.write(`HTTP/1.1 ${r.status} ${r.status===400?"Bad Request":"Not Found"}\r
|
|
304
324
|
\r
|
|
305
325
|
`),e.destroy();return}let o=r.sessionName;if(!pt(o)){e.write(`HTTP/1.1 400 Bad Request\r
|
|
306
326
|
\r
|
|
307
|
-
`),e.destroy();return}let{cols:s,rows:a}=r;
|
|
327
|
+
`),e.destroy();return}let{cols:s,rows:a}=r;NE.handleUpgrade(t,e,n,i=>iu(i,o,{cols:s,rows:a}))};_g.on("upgrade",Eg);var Ig=process.env.BOSTRAT_CERT_DIR||lt.join(process.env.HOME||Gl.tmpdir(),".bostrat","certs"),bg=Number(process.env.BOSTRAT_TLS_PORT)||3443,kg=lt.join(Ig,"bostrat.globopi.key"),xg=lt.join(Ig,"bostrat.globopi.crt");if(Ct.existsSync(kg)&&Ct.existsSync(xg)){let t=RE.createServer({key:Ct.readFileSync(kg),cert:Ct.readFileSync(xg)},S);t.on("upgrade",Eg),t.on("error",e=>{console.error(`[bostrat-agent] https listener failed (${e?.code||e?.message}) \u2014 continuing HTTP-only`)}),t.listen(bg,ya,()=>{console.log(`[bostrat-agent] https listening on ${ya||"0.0.0.0"}:${bg}`)})}_g.listen(yg,ya,()=>{console.log(`[bostrat-agent] listening on ${ya||"0.0.0.0"}:${yg}`),console.log(`[bostrat-agent] tmux socket: ${Mt}`),console.log(`[bostrat-agent] auto-trigger default: ${vg}%`),console.log(`[bostrat-agent] ip allowlist: ${_r.open?"OPEN (BOSTRAT_ALLOW_IPS unset)":process.env.BOSTRAT_ALLOW_IPS}`),Qh().then(t=>t&&console.log("[bostrat-agent] reconciled interrupted handoff(s)")).catch(t=>console.error("[bostrat-agent] handoff reconcile failed:",t?.message||t)),tw().then(t=>t&&console.log("[bostrat-agent] reconciled interrupted subtask(s)")).catch(t=>console.error("[bostrat-agent] subtask reconcile failed:",t?.message||t)),zh().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)),Uh().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)),LE(),jE(),Kw(),Sf({resolveTrack:et}),pw({listTracks:ut,createTrack:ll,archiveTrack:yr,listRepos:me,resolveTrack:et,sayToLead:ye,answerPrompt:al,startLeadHandoff:fl,interruptLead:cl,listSubtaskJobs:br,cancelSubtask:hl,readEvents:F,appendEvent:O,knownAgents:Ys}),BE(),$r().then(t=>{t||console.warn(`[boot] no host tmux server on ${Mt} and auto-start failed \u2014 boxes cannot spawn until one runs`)}),ag(),process.env.BOSTRAT_PLANE_URL&&process.env.BOSTRAT_PLANE_TOKEN&&(Kl=bw({url:process.env.BOSTRAT_PLANE_URL,token:process.env.BOSTRAT_PLANE_TOKEN,nodeId:process.env.BOSTRAT_NODE_ID||Gl.hostname(),name:process.env.BOSTRAT_NODE_NAME||Gl.hostname(),listTracks:ut,detectAgents:ol}),console.log(`[bostrat-agent] plane link \u2192 ${process.env.BOSTRAT_PLANE_URL}`))});function BE(){DE(),rs().catch(()=>{}),setInterval(()=>rs().catch(()=>{}),1e4)}function DE(){for(let t of["bostrat-view","bostrat-mock","bostrat-ticket","bostrat-runbook","bostrat-notify","gh"])try{let e=lt.resolve(Sg,"..","bin",t),n=lt.join(process.env.HOME||"",".local","bin");if(!process.env.HOME||!Ct.existsSync(e))continue;Ct.mkdirSync(n,{recursive:!0});let r=lt.join(n,t);(Ct.existsSync(r)?Ct.readlinkSync(r):null)!==e&&(Ct.rmSync(r,{force:!0}),Ct.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 jE(){let t=!1,e=async()=>{if(!t){t=!0;try{let n=await ut(),r=await Rw({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 LE(){let t=!1;setInterval(async()=>{if(!t){t=!0;try{let e=await ut();for(let n of e)n.archivedAt||!n.boxes?.some(r=>r.state==="live")||(await sl(n.id).catch(()=>{}),await ch(n).catch(()=>{}))}catch{}finally{t=!1}}},4e3)}
|