borgmcp 1.1.11 → 1.1.13

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.
@@ -4,8 +4,8 @@
4
4
  * borg owns a per-launch DIRECT Codex app-server (`codex app-server --listen
5
5
  * unix://<socket>`) as the primary wake path, instead of the standalone
6
6
  * `codex app-server daemon start` (which only exists for standalone-installer
7
- * Codex — npm-managed Codex has no daemon, so those sessions had NO push-wake
8
- * and silently fell back to the ~30min /loop heartbeat). Per-launch direct
7
+ * Codex — npm-managed Codex has no daemon, so those sessions had no push-wake
8
+ * and relied on periodic log-drain catch-up). Per-launch direct
9
9
  * app-server works for both install kinds and starts fresh each launch (so it
10
10
  * always loads the current borgmcp MCP — no daemon-restart version refresh
11
11
  * needed). The TUI is then launched with `codex --remote unix://<socket>` and
@@ -27,6 +27,15 @@ export interface CodexRemoteLaunch {
27
27
  export interface CodexChild {
28
28
  pid: number | undefined;
29
29
  kill: () => void;
30
+ /** Snapshot app-server exit state + bounded stderr captured by the production spawn. */
31
+ diagnostics?: () => CodexChildDiagnostics;
32
+ }
33
+ export interface CodexChildDiagnostics {
34
+ exited: boolean;
35
+ exitCode: number | null;
36
+ signal: string | null;
37
+ error?: string | null;
38
+ stderr: string;
30
39
  }
31
40
  export interface PrepareCodexRemoteDeps {
32
41
  /** Spawn the long-lived `codex app-server --listen unix://<socketPath>` child. */
@@ -39,7 +48,7 @@ export interface PrepareCodexRemoteDeps {
39
48
  runtimeDir?: string;
40
49
  /** Unique socket id generator (default 32-hex). Injected for deterministic tests. */
41
50
  socketId?: () => string;
42
- /** Readiness timeout (default 8000ms) + poll interval (default 250ms). */
51
+ /** Readiness timeout (default 30000ms) + poll interval (default 250ms). */
43
52
  readyTimeoutMs?: number;
44
53
  pollIntervalMs?: number;
45
54
  /** Whether a pid is alive (default process.kill(pid, 0)). Injected for tests. */
@@ -1 +1 @@
1
- import{mkdirSync as C,chmodSync as v,readdirSync as w,rmSync as k,writeFileSync as A,readFileSync as y}from"node:fs";import{homedir as S}from"node:os";import{join as u}from"node:path";import{randomBytes as b}from"node:crypto";import{spawn as E}from"node:child_process";import{CodexAppServerClient as _}from"./codex-app-server.js";import{codexBorgSessionConfigArgs as N,BORG_SESSION_ENV as R}from"./launch-gate.js";import{codexAppServerSocketConfigArgs as $}from"./codex-wake-resolve.js";import{BORG_AGENT_KIND_ENV as I,BORG_CODEX_REMOTE_WAKE_ENV as x,codexAgentKindConfigArgs as T,codexRemoteWakeConfigArgs as M}from"./agent-runtime.js";const O=u(S(),".config","borgmcp","codex-remote");function q(e,r){return P(e)?e:["--cd",r,...e]}function P(e){return e.some(r=>r==="--cd"||r.startsWith("--cd=")||r==="-C")}function g(e){try{return process.kill(e,0),!0}catch(r){return r?.code==="EPERM"}}function H(e,r={}){if(!e)return null;const a=r.isAlive??g,i=r.readPidFile??(o=>y(o,"utf-8")),c=e.replace(/\.sock$/,".pid");try{const o=Number.parseInt(i(c).trim(),10);return Number.isNaN(o)?null:a(o)}catch{return null}}function s(e){try{k(e,{force:!0})}catch{}}function B(e,r){let a;try{a=w(e)}catch{return}for(const i of a){if(!i.endsWith(".pid"))continue;const c=u(e,i),o=u(e,i.replace(/\.pid$/,".sock"));let t;try{t=Number.parseInt(y(c,"utf-8").trim(),10)}catch{s(c);continue}(Number.isNaN(t)||!r(t))&&(s(o),s(c))}}function p(e){return{args:[],env:{},warning:e}}async function U(e){const r=e.runtimeDir??O,a=e.isAlive??g,i=e.readyTimeoutMs??8e3,c=e.pollIntervalMs??250;try{C(r,{recursive:!0,mode:448}),v(r,448),B(r,a)}catch(n){return p(`Codex remote-wake disabled: could not prepare ${r} (${n?.message??n}); run borg_regen manually.`)}const o=(e.socketId??(()=>b(16).toString("hex")))(),t=u(r,`${o}.sock`),m=u(r,`${o}.pid`);let l;try{l=e.spawnAppServer(t)}catch(n){return s(t),p(`Codex remote-wake disabled: could not start \`codex app-server\` (${n?.message??n}) \u2014 is Codex installed + up to date? This session only wakes on the ~30min /loop fallback; run borg_regen manually.`)}if(l.pid!=null)try{A(m,String(l.pid))}catch{}const f=()=>{try{l.kill()}catch{}s(t),s(m)},h=Math.max(1,Math.ceil(i/c));let d=!1;for(let n=0;n<h&&!d;n++){try{d=await e.probeReady(t)}catch{d=!1}!d&&n<h-1&&await e.sleep(c)}return d?{args:["--remote",`unix://${t}`],env:{[x]:"1"},server:{pid:l.pid,socketPath:t,cleanup:f}}:(f(),p(`Codex remote-wake disabled: could not reach a Codex app-server at ${t} within ${i}ms (is Codex up to date? \`codex app-server --listen\` is required). This session only wakes on the ~30min /loop fallback \u2014 run borg_regen manually when you return.`))}function z(){return{spawnAppServer:e=>{const r=E("codex",["app-server",...N(),...T(),...M(),...$(e),"--listen",`unix://${e}`],{stdio:"ignore",shell:!1,env:{...process.env,[R]:"1",[I]:"codex",[x]:"1"}});return{pid:r.pid,kill:()=>{try{r.kill()}catch{}}}},probeReady:async e=>{const r=new _(e);try{return await r.connect(),await r.loadedThreadIds(),!0}catch{return!1}finally{try{r.close()}catch{}}},sleep:e=>new Promise(r=>setTimeout(r,e))}}export{O as DEFAULT_CODEX_REMOTE_DIR,H as checkCodexBridgeHealthy,z as defaultCodexRemoteDeps,g as defaultIsAlive,U as prepareCodexRemoteLaunch,q as withCodexCwdArg};
1
+ import{mkdirSync as R,chmodSync as k,readdirSync as D,rmSync as S,writeFileSync as v,readFileSync as _}from"node:fs";import{homedir as $}from"node:os";import{join as m}from"node:path";import{randomBytes as T}from"node:crypto";import{spawn as w}from"node:child_process";import{CodexAppServerClient as N}from"./codex-app-server.js";import{codexBorgSessionConfigArgs as M,BORG_SESSION_ENV as P}from"./launch-gate.js";import{codexAppServerSocketConfigArgs as O}from"./codex-wake-resolve.js";import{BORG_AGENT_KIND_ENV as I,BORG_CODEX_REMOTE_WAKE_ENV as y,codexAgentKindConfigArgs as z,codexRemoteWakeConfigArgs as B}from"./agent-runtime.js";const F=m($(),".config","borgmcp","codex-remote"),H=3e4,A=16384;function d(r){let e=String(r).replace(/\x1B\[[0-?]*[ -/]*[@-~]/g,"");return e=e.replace(/\b((?:authorization\s*:\s*)?bearer)\s+[^\s,;]+/gi,"$1 <REDACTED>"),e=e.replace(/\b([a-z][a-z0-9+.-]*:\/\/)([^/\s:@]+):([^@\s/]+)@/gi,"$1<REDACTED>@"),e=e.replace(/([?&](?:access_token|auth_token|id_token|refresh_token|session_token|token|api_key|apikey|secret|password)=)[^&#\s]*/gi,"$1<REDACTED>"),e=e.replace(/\b((?:[a-z0-9_-]*(?:token|secret|password)|api[_-]?key)\s*[:=]\s*)(?:"[^"\r\n]*"|'[^'\r\n]*'|[^\s,;]+)/gi,"$1<REDACTED>"),e=e.replace(/\beyJ[A-Za-z0-9_-]{6,}\.[A-Za-z0-9_-]{6,}\.[A-Za-z0-9_-]{6,}\b/g,"<REDACTED>"),e=e.replace(/(^|[\s("'=])(?:file:\/\/)?\/(?:Users|home|private|tmp|var|etc|opt|Volumes)\/[^\s"'`<>)}\]]+/gm,"$1<REDACTED_PATH>"),e=e.replace(/\b[A-Za-z]:\\(?:Users|Documents and Settings|ProgramData|Temp)\\[^\s"'`<>)}\]]+/g,"<REDACTED_PATH>"),e.slice(-A)}function Q(r,e){return U(r)?r:["--cd",e,...r]}function U(r){return r.some(e=>e==="--cd"||e.startsWith("--cd=")||e==="-C")}function C(r){try{return process.kill(r,0),!0}catch(e){return e?.code==="EPERM"}}function ee(r,e={}){if(!r)return null;const s=e.isAlive??C,o=e.readPidFile??(i=>_(i,"utf-8")),a=r.replace(/\.sock$/,".pid");try{const i=Number.parseInt(o(a).trim(),10);return Number.isNaN(i)?null:s(i)}catch{return null}}function p(r){try{S(r,{force:!0})}catch{}}function V(r,e){let s;try{s=D(r)}catch{return}for(const o of s){if(!o.endsWith(".pid"))continue;const a=m(r,o),i=m(r,o.replace(/\.pid$/,".sock"));let t;try{t=Number.parseInt(_(a,"utf-8").trim(),10)}catch{p(a);continue}(Number.isNaN(t)||!e(t))&&(p(i),p(a))}}function f(r){return{args:[],env:{},warning:r}}function X(r){return r.error?`process error: ${d(r.error)}`:r.exitCode!=null?`exit code ${r.exitCode}`:r.signal?`signal ${r.signal}`:"unknown exit status"}function b(r){const e=d(r).trim();return e?` Stderr: ${e}`:""}async function re(r){const e=r.runtimeDir??F,s=r.isAlive??C,o=r.readyTimeoutMs??H,a=r.pollIntervalMs??250;try{R(e,{recursive:!0,mode:448}),k(e,448),V(e,s)}catch(n){return f(`Codex remote-wake disabled: could not prepare ${e} (${n?.message??n}); run borg_regen manually.`)}const i=(r.socketId??(()=>T(16).toString("hex")))(),t=m(e,`${i}.sock`),l=m(e,`${i}.pid`);let c;try{c=r.spawnAppServer(t)}catch(n){return p(t),f(`Codex remote-wake disabled: could not start \`codex app-server\` (${d(String(n?.message??n))}) \u2014 confirm the Codex executable is installed and available on PATH. Remote wake is unavailable for this session; run borg_regen manually to catch up.`)}if(c.pid!=null)try{v(l,String(c.pid))}catch{}const g=()=>{try{c.kill()}catch{}p(t),p(l)},x=Math.max(1,Math.ceil(o/a));let u=!1,h;for(let n=0;n<x&&!u;n++){try{u=await r.probeReady(t)}catch{u=!1}if(!u){const E=c.diagnostics?.();if(E?.exited){h=E;break}}!u&&n<x-1&&await r.sleep(a)}if(!u){const n=h??c.diagnostics?.();return g(),n?.exited?f(`Codex remote-wake disabled: \`codex app-server\` exited before becoming ready (${X(n)}).${b(n.stderr)} Run borg_regen manually to catch up.`):f(`Codex remote-wake disabled: \`codex app-server\` remained running but did not become ready at ${d(t)} within ${o}ms.${b(n?.stderr??"")} Run borg_regen manually to catch up.`)}return{args:["--remote",`unix://${t}`],env:{[y]:"1"},server:{pid:c.pid,socketPath:t,cleanup:g}}}function te(){return{spawnAppServer:r=>{const e=w("codex",["app-server",...M(),...z(),...B(),...O(r),"--listen",`unix://${r}`],{stdio:["ignore","ignore","pipe"],shell:!1,env:{...process.env,[P]:"1",[I]:"codex",[y]:"1"}});let s="",o=!1,a=null,i=null,t=null;return e.stderr?.setEncoding("utf8"),e.stderr?.on("data",l=>{s=`${s}${String(l)}`.slice(-A)}),e.once("close",(l,c)=>{o=!0,a=l,i=c}),e.once("error",l=>{o=!0,t=d(l.message)}),{pid:e.pid,kill:()=>{try{e.kill()}catch{}},diagnostics:()=>({exited:o,exitCode:a,signal:i,error:t==null?null:d(t),stderr:d(s)})}},probeReady:async r=>{const e=new N(r);try{return await e.connect(),await e.loadedThreadIds(),!0}catch{return!1}finally{try{e.close()}catch{}}},sleep:r=>new Promise(e=>setTimeout(e,r))}}export{F as DEFAULT_CODEX_REMOTE_DIR,ee as checkCodexBridgeHealthy,te as defaultCodexRemoteDeps,C as defaultIsAlive,re as prepareCodexRemoteLaunch,Q as withCodexCwdArg};
package/dist/index.js CHANGED
@@ -1,38 +1,38 @@
1
1
  #!/usr/bin/env node
2
- import{Server as X}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as Z}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as ee,ListToolsRequestSchema as te,ListPromptsRequestSchema as oe,GetPromptRequestSchema as re}from"@modelcontextprotocol/sdk/types.js";import{assertRoleMatches as ne}from"./role-match.js";import{getCubeInfo as se,getRoleInfo as A,getRoleInfoByName as ie,getRoster as ae,readLog as ce,appendLog as le,submitReport as de,fetchReports as ue,ackLogEntry as pe,recordDecision as me,listDecisions as be,regen as L,listCubes as fe,createCube as ge,updateCube as O,deleteCube as he,createRole as _e,updateRole as ye,patchRoleSection as C,patchTaxonomyClass as B,deleteRole as we,reassignDrone as xe,evictDrone as $e,getCube as x,checkSubscriptionStatus as ve,createBillingPortalSession as ke,createSubscription as Ee,syncRoles as Ce,applyTemplate as Re,whoami as Se,roleRationale as Ie,getValidToken as Te}from"./remote-client.js";import{startHealthBeatTick as qe}from"./health-beat.js";import{getTemplate as R,listTemplateNames as S,resolveCubeDirectiveForCreate as De,resolveCubeDirectiveForApply as Pe,resolveMessageTaxonomyForCreate as Ue}from"./templates.js";import{activeCubeWithFreshRegenIdentity as F,getActiveCube as g,setActiveCube as H,findProjectRoot as I,inboxPathForDrone as T}from"./cubes.js";import{monitorStateRootForWorktree as q}from"./inbox-monitor.js";import{addSessionStartHook as je,addUserPromptSubmitHook as Ne}from"./config-utils.js";import{humanAgo as W,formatLogEntryMarkdown as Me,formatRegenMarkdown as V,getDronePlaybook as Ae,getDronePlaybookChapter as Le,nullTaxonomyTip as Oe,regenWakePathDroneLabel as Be}from"./regen-format.js";import{startLogStream as Fe,getStreamStatus as D}from"./log-stream.js";import{isMcpReadinessProbe as He}from"./readiness-probe.js";import{runMcpStartupServices as We}from"./startup-services.js";import{TOOL_MANIFEST as Ve}from"./tool-manifest.js";import{DOCS_SECTIONS as ze,matchDocsSections as Ke,formatDocsIndex as Je}from"./docs-sections.js";import{renderRoleList as Qe}from"./list-roles-render.js";import{filterToolsForRole as Ye}from"./tool-scope.js";import{getPackageVersion as $,getOnDiskVersion as Ge,handleVersionFlag as Xe}from"./version.js";import{renderStreamStatus as Ze,checkInboxMonitorHealthy as P,formatWakePathPrefix as et,shouldShowWakePathWarning as tt}from"./stream-status.js";import{formatRoleAgentLabel as ot,renderRoster as rt}from"./roster-render.js";import{resolveDroneIdByLabel as nt,isUuidShape as st}from"./evict-drone.js";import{authRecoveryMessage as it}from"./auth-recovery.js";import{DroneEvictedError as at,DroneFrozenError as ct,formatEvictedToolResult as lt,formatFrozenToolResult as dt}from"./drone-lifecycle.js";import{classifyInSessionAssimilate as ut,reattachOnlyRefusal as pt,reattachFailureMessage as mt}from"./assimilate-guard.js";import{gateAllowsActivation as bt,borgSessionToolNotice as ft}from"./launch-gate.js";import{renderSyncRolesResult as gt}from"./sync-roles-render.js";import{initConsolePrefix as ht,consolePrefix as v}from"./console-prefix.js";import{isCodexRemoteWakeEnabled as _t,resolveSessionAgentKind as U,probeCodexBridgeArmed as yt}from"./codex-app-wake.js";import{connectOpenCodeDrone as wt,injectOpenCodeEntry as xt,probeOpenCodeDroneArmed as $t,computeOpenCodePort as vt}from"./opencode-drone.js";import{installBorgPlugin as kt}from"./opencode-plugin.js";import{setModuleInjectOpenCode as Et}from"./log-stream.js";import{lifecycleSignalForMessage as Ct,recordLifecycleLog as z,shouldSuppressLifecycleLog as Rt}from"./lifecycle-log-guard.js";import{normalizeDirectLogRecipients as St}from"./direct-log.js";import K from"open";import It from"os";function Tt(){try{const m=It.hostname();return m&&m.trim()?m.trim().slice(0,255):null}catch{return null}}async function J(m,y){return await Re(m,y.name)}async function h(){const m=await g();if(!m)throw new Error("Not assimilated to a cube. Use borg_assimilate <cube-name> first.");return m}async function qt(){Xe();const m=He();await We(m,{sessionStartHook:()=>{je()},auditHook:()=>{Ne()},sseStream:()=>{Fe()},openCode:async()=>{kt();const l=await g();if(l&&process.env.BORG_OPENCODE==="1"){const o=`http://127.0.0.1:${vt(l.droneId)}`;await wt({serverUrl:o,directory:process.cwd(),droneLabel:l.droneLabel,cubeName:l.name}),Et(xt)}},healthBeat:()=>{qe({getActiveCube:g,getStreamConnected:()=>D().connected,getInboxPath:l=>T(l.cubeId,l.droneId),checkMonitor:l=>P(l,q(I())),isCodexRemoteWake:_t,probeBridgeArmed:l=>yt({cubeId:l.cubeId,droneId:l.droneId}),probeOpenCodeDrone:()=>$t(),resolveAgentKind:U,resolveHostname:Tt,resolveVersion:$,getToken:Te,fetchImpl:globalThis.fetch.bind(globalThis)})}});const y=new X({name:"borg-mcp-client",version:$()},{capabilities:{tools:{},prompts:{}}}),j=Ve;y.setRequestHandler(te,async()=>{let l=null;try{const p=await g();p&&(l={roleName:p.roleName,roleClass:p.roleClass,isHumanSeat:p.isHumanSeat})}catch{l=null}return{tools:Ye(j,l)}}),y.setRequestHandler(ee,async l=>{let{name:p,arguments:o}=l.params;if(p==="borg_describe-tool"){const e=typeof o?.name=="string"?o.name:"",t=j.find(r=>r.name===e);return t?{content:[{type:"text",text:JSON.stringify({name:t.name,description:t.description,inputSchema:t.inputSchema},null,2)}]}:{content:[{type:"text",text:`Unknown borg tool: ${e||"(none)"}. Pass { name: "<borg_tool>" }.`}],isError:!0}}if(p==="borg_tool"){const e=typeof o?.name=="string"?o.name:"";if(!e||e==="borg_tool"||e==="borg_describe-tool")return{content:[{type:"text",text:'borg_tool: pass { name: "<borg_tool>", arguments: {...} } naming a real borg tool (not the dispatcher itself).'}],isError:!0};o=o?.arguments&&typeof o.arguments=="object"?o.arguments:{},p=e}if(!bt(`tool ${p}`))return{content:[{type:"text",text:ft(p)}],isError:!0};try{switch(p){case"borg_regen":{const e=await g();if(!e)return{content:[{type:"text",text:'Not connected to a cube. Use `borg_assimilate cube_name="<name>"` to join one.'}]};const t=typeof o?.since=="string"?o.since:void 0,r=o?.mode==="lite"?"lite":"full",n=await L(e.sessionToken,e.apiUrl,{since:t}),s=F(e,n);s!==e&&await H(s);const i=D(),a=T(s.cubeId,s.droneId),c=q(I()),f=U()==="claude"?P(a,c):!0,_=tt(i,f)?et({inboxPath:a,monitorStateRoot:c,droneLabel:Be(n,s.droneLabel),cubeName:s.name}):"";let u="";try{const b=$(),w=Ge();if(b!=="unknown"&&w!=="unknown"&&w!==b){const[k,N,Y]=b.split(".").map(Number),[E,M,G]=w.split(".").map(Number);(E>k||E===k&&M>N||E===k&&M===N&&G>Y)&&(u=`## \u{1F504} borgmcp ${w} installed \u2014 run /mcp and reconnect (or restart Claude Code) to apply. Currently running ${b}.
2
+ import{Server as ee}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as te}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as oe,ListToolsRequestSchema as re,ListPromptsRequestSchema as ne,GetPromptRequestSchema as se}from"@modelcontextprotocol/sdk/types.js";import{assertRoleMatches as ie}from"./role-match.js";import{getCubeInfo as ae,getRoleInfo as L,getRoleInfoByName as ce,getRoster as le,readLog as de,appendLog as ue,submitReport as pe,fetchReports as me,ackLogEntry as be,recordDecision as fe,listDecisions as ge,regen as O,listCubes as he,createCube as _e,updateCube as B,deleteCube as ye,createRole as we,updateRole as xe,patchRoleSection as C,patchTaxonomyClass as F,deleteRole as $e,reassignDrone as ve,evictDrone as ke,getCube as $,checkSubscriptionStatus as Re,createBillingPortalSession as Ee,createSubscription as Ce,syncRoles as Se,applyTemplate as Te,whoami as Ie,roleRationale as qe,getValidToken as De}from"./remote-client.js";import{startHealthBeatTick as Pe}from"./health-beat.js";import{getTemplate as S,listTemplateNames as T,resolveCubeDirectiveForCreate as Ue,resolveCubeDirectiveForApply as Me,resolveMessageTaxonomyForCreate as je}from"./templates.js";import{activeCubeWithFreshRegenIdentity as H,getActiveCube as h,setActiveCube as W,findProjectRoot as I,inboxPathForDrone as q}from"./cubes.js";import{monitorStateRootForWorktree as D}from"./inbox-monitor.js";import{addSessionStartHook as Ne,addUserPromptSubmitHook as Ae}from"./config-utils.js";import{humanAgo as V,formatLogEntryMarkdown as Le,formatRegenMarkdown as z,getDronePlaybook as Oe,getDronePlaybookChapter as Be,nullTaxonomyTip as Fe,regenWakePathDroneLabel as He}from"./regen-format.js";import{startLogStream as We,getStreamStatus as P}from"./log-stream.js";import{isMcpReadinessProbe as Ve}from"./readiness-probe.js";import{runMcpStartupServices as ze}from"./startup-services.js";import{TOOL_MANIFEST as Ke}from"./tool-manifest.js";import{DOCS_SECTIONS as Je,matchDocsSections as Qe,formatDocsIndex as Ye}from"./docs-sections.js";import{renderRoleList as Ge}from"./list-roles-render.js";import{filterToolsForRole as Xe}from"./tool-scope.js";import{getPackageVersion as v,getOnDiskVersion as Ze,handleVersionFlag as et}from"./version.js";import{renderStreamStatus as tt,checkInboxMonitorHealthy as U,formatWakePathPrefix as ot,shouldShowWakePathWarning as rt}from"./stream-status.js";import{formatRoleAgentLabel as nt,formatWorkingRepoLabel as st,renderRoster as it}from"./roster-render.js";import{resolveWorkingRepo as K}from"./working-repo.js";import{resolveDroneIdByLabel as at,isUuidShape as ct}from"./evict-drone.js";import{authRecoveryMessage as lt}from"./auth-recovery.js";import{DroneEvictedError as dt,DroneFrozenError as ut,formatEvictedToolResult as pt,formatFrozenToolResult as mt}from"./drone-lifecycle.js";import{classifyInSessionAssimilate as bt,reattachOnlyRefusal as ft,reattachFailureMessage as gt}from"./assimilate-guard.js";import{gateAllowsActivation as ht,borgSessionToolNotice as _t}from"./launch-gate.js";import{renderSyncRolesResult as yt}from"./sync-roles-render.js";import{initConsolePrefix as wt,consolePrefix as k}from"./console-prefix.js";import{isCodexRemoteWakeEnabled as xt,resolveSessionAgentKind as M,probeCodexBridgeArmed as $t}from"./codex-app-wake.js";import{connectOpenCodeDrone as vt,injectOpenCodeEntry as kt,probeOpenCodeDroneArmed as Rt,computeOpenCodePort as Et}from"./opencode-drone.js";import{installBorgPlugin as Ct}from"./opencode-plugin.js";import{setModuleInjectOpenCode as St}from"./log-stream.js";import{lifecycleSignalForMessage as Tt,recordLifecycleLog as J,shouldSuppressLifecycleLog as It}from"./lifecycle-log-guard.js";import{normalizeDirectLogRecipients as qt}from"./direct-log.js";import Q from"open";import Dt from"os";function Pt(){try{const b=Dt.hostname();return b&&b.trim()?b.trim().slice(0,255):null}catch{return null}}async function Y(b,y){return await Te(b,y.name)}async function _(){const b=await h();if(!b)throw new Error("Not assimilated to a cube. Use borg_assimilate <cube-name> first.");return b}async function Ut(){et();const b=Ve();await ze(b,{sessionStartHook:()=>{Ne()},auditHook:()=>{Ae()},sseStream:()=>{We()},openCode:async()=>{Ct();const l=await h();if(l&&process.env.BORG_OPENCODE==="1"){const o=`http://127.0.0.1:${Et(l.droneId)}`;await vt({serverUrl:o,directory:process.cwd(),droneLabel:l.droneLabel,cubeName:l.name}),St(kt)}},healthBeat:()=>{Pe({getActiveCube:h,getStreamConnected:()=>P().connected,getInboxPath:l=>q(l.cubeId,l.droneId),checkMonitor:l=>U(l,D(I())),isCodexRemoteWake:xt,probeBridgeArmed:l=>$t({cubeId:l.cubeId,droneId:l.droneId}),probeOpenCodeDrone:()=>Rt(),resolveAgentKind:M,resolveHostname:Pt,resolveVersion:v,getToken:De,fetchImpl:globalThis.fetch.bind(globalThis)})}});const y=new ee({name:"borg-mcp-client",version:v()},{capabilities:{tools:{},prompts:{}}}),j=Ke;y.setRequestHandler(re,async()=>{let l=null;try{const u=await h();u&&(l={roleName:u.roleName,roleClass:u.roleClass,isHumanSeat:u.isHumanSeat})}catch{l=null}return{tools:Xe(j,l)}}),y.setRequestHandler(oe,async l=>{let{name:u,arguments:o}=l.params;if(u==="borg_describe-tool"){const e=typeof o?.name=="string"?o.name:"",t=j.find(r=>r.name===e);return t?{content:[{type:"text",text:JSON.stringify({name:t.name,description:t.description,inputSchema:t.inputSchema},null,2)}]}:{content:[{type:"text",text:`Unknown borg tool: ${e||"(none)"}. Pass { name: "<borg_tool>" }.`}],isError:!0}}if(u==="borg_tool"){const e=typeof o?.name=="string"?o.name:"";if(!e||e==="borg_tool"||e==="borg_describe-tool")return{content:[{type:"text",text:'borg_tool: pass { name: "<borg_tool>", arguments: {...} } naming a real borg tool (not the dispatcher itself).'}],isError:!0};o=o?.arguments&&typeof o.arguments=="object"?o.arguments:{},u=e}if(!ht(`tool ${u}`))return{content:[{type:"text",text:_t(u)}],isError:!0};try{switch(u){case"borg_regen":{const e=await h();if(!e)return{content:[{type:"text",text:'Not connected to a cube. Use `borg_assimilate cube_name="<name>"` to join one.'}]};const t=typeof o?.since=="string"?o.since:void 0,r=o?.mode==="lite"?"lite":"full",n=typeof o?.model=="string"?o.model:void 0,i=await O(e.sessionToken,e.apiUrl,{since:t,reportedModel:n,workingRepo:K()}),s=H(e,i);s!==e&&await W(s);const a=P(),c=q(s.cubeId,s.droneId),m=D(I()),f=M()==="claude"?U(c,m):!0,d=rt(a,f)?ot({inboxPath:c,monitorStateRoot:m,droneLabel:He(i,s.droneLabel),cubeName:s.name}):"";let g="";try{const w=v(),x=Ze();if(w!=="unknown"&&x!=="unknown"&&x!==w){const[R,N,X]=w.split(".").map(Number),[E,A,Z]=x.split(".").map(Number);(E>R||E===R&&A>N||E===R&&A===N&&Z>X)&&(g=`## \u{1F504} borgmcp ${x} installed \u2014 run /mcp and reconnect (or restart Claude Code) to apply. Currently running ${w}.
3
3
 
4
- `)}}catch{}return{content:[{type:"text",text:u+_+V(n,{mode:r})}]}}case"borg_subscribe":return{content:[{type:"text",text:`Complete your subscription at: ${await Ee()}`}]};case"borg_upgrade-subscription":{const e=await ke();try{await K(e)}catch{}return{content:[{type:"text",text:`Manage your Borg MCP subscription at: ${e}`}]}}case"borg_subscription_status":{const e=await ve();return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}case"borg_open_dashboard":{const e="https://borgmcp.ai/dashboard";return await K(e),{content:[{type:"text",text:`\u25FC Opened dashboard in browser: ${e}`}]}}case"borg_assimilate":{const e=o?.cube_name;if(!e)throw new Error("cube_name is required");const t=await g(),r=ut(t,e);if(r.kind!=="reattach")return{content:[{type:"text",text:pt(r,e)}],isError:!0};try{const n=await L(t.sessionToken,t.apiUrl,{}),s=F(t,n);return s!==t&&await H(s),{content:[{type:"text",text:[`# Re-attached to cube: ${s.name}`,"",`**Drone label:** ${s.droneLabel}`,"**Seat:** existing identity reused \u2014 no new drone minted (gh#780)","",""].join(`
5
- `)+V(n,{mode:"full"})}]}}catch(n){const s=mt(n??{});if(!s)throw n;return{content:[{type:"text",text:s}],isError:!0}}}case"borg_version":return{content:[{type:"text",text:`borgmcp ${$()}`}]};case"borg_playbook":return{content:[{type:"text",text:Le()}]};case"borg_docs":{const e=typeof o?.topic=="string"?o.topic.trim():"",t=e?Ke(e):[],r=t.length>0?t:ze;return{content:[{type:"text",text:`${e&&t.length>0?`Best-matching docs section(s) for "${e}" \u2014 WebFetch the URL for the full page:`:e?`No exact match for "${e}". Full Borg MCP docs index \u2014 WebFetch the URL you need:`:"Borg MCP docs index \u2014 WebFetch the URL of the section you need:"}
4
+ `)}}catch{}return{content:[{type:"text",text:g+d+z(i,{mode:r})}]}}case"borg_subscribe":return{content:[{type:"text",text:`Complete your subscription at: ${await Ce()}`}]};case"borg_upgrade-subscription":{const e=await Ee();try{await Q(e)}catch{}return{content:[{type:"text",text:`Manage your Borg MCP subscription at: ${e}`}]}}case"borg_subscription_status":{const e=await Re();return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}case"borg_open_dashboard":{const e="https://borgmcp.ai/dashboard";return await Q(e),{content:[{type:"text",text:`\u25FC Opened dashboard in browser: ${e}`}]}}case"borg_assimilate":{const e=o?.cube_name;if(!e)throw new Error("cube_name is required");const t=await h(),r=bt(t,e);if(r.kind!=="reattach")return{content:[{type:"text",text:ft(r,e)}],isError:!0};try{const n=await O(t.sessionToken,t.apiUrl,{workingRepo:K()}),i=H(t,n);return i!==t&&await W(i),{content:[{type:"text",text:[`# Re-attached to cube: ${i.name}`,"",`**Drone label:** ${i.droneLabel}`,"**Seat:** existing identity reused \u2014 no new drone minted (gh#780)","",""].join(`
5
+ `)+z(n,{mode:"full"})}]}}catch(n){const i=gt(n??{});if(!i)throw n;return{content:[{type:"text",text:i}],isError:!0}}}case"borg_version":return{content:[{type:"text",text:`borgmcp ${v()}`}]};case"borg_playbook":return{content:[{type:"text",text:Be()}]};case"borg_docs":{const e=typeof o?.topic=="string"?o.topic.trim():"",t=e?Qe(e):[],r=t.length>0?t:Je;return{content:[{type:"text",text:`${e&&t.length>0?`Best-matching docs section(s) for "${e}" \u2014 WebFetch the URL for the full page:`:e?`No exact match for "${e}". Full Borg MCP docs index \u2014 WebFetch the URL you need:`:"Borg MCP docs index \u2014 WebFetch the URL of the section you need:"}
6
6
 
7
- ${Je(r)}`}]}}case"borg_whoami":{const e=await h(),t=await Se(e.sessionToken,e.apiUrl);return{content:[{type:"text",text:JSON.stringify(t,null,2)}]}}case"borg_cube":{const e=await h(),[{cube:t,roles:r}]=await Promise.all([se(e.sessionToken,e.apiUrl),A(e.sessionToken,e.apiUrl)]),n=[];n.push(`# Cube: ${t.name}`),n.push(""),n.push("## Cube directive"),n.push(t.cube_directive||"_(none)_"),n.push("");const s=Oe(t.message_taxonomy);if(s&&(n.push(s),n.push("")),n.push("## Roles in this cube"),!r.length)n.push("_(no roles defined)_");else{for(const i of r){const a=[i.role_class==="queen"?"Queen":null,i.is_human_seat?"human-seat":null,i.is_default?"default":null].filter(Boolean).join(", "),c=a?` (${a})`:"",d=i.short_description||"_(no description)_";n.push(`- **${i.name}**${c} \u2014 ${d}`)}n.push(""),n.push("_(Coordinator-class drones can fetch role IDs via `borg_list-roles` for use with `borg_reassign-drone`.)_")}return n.push(""),n.push(Ae()),{content:[{type:"text",text:n.join(`
8
- `)}]}}case"borg_role":{const e=await h(),t=typeof o?.role=="string"?o.role.trim():"";if(t){const{role:s}=await ie(e.sessionToken,e.apiUrl,t);return ne(t,s),{content:[{type:"text",text:[`# Role: ${s.name}`,"",s.detailed_description||"_(no detailed description set)_"].join(`
9
- `)}]}}const{role:r}=await A(e.sessionToken,e.apiUrl);return{content:[{type:"text",text:[`# Your role: ${r.name}`,"",r.detailed_description||"_(no detailed description set)_"].join(`
10
- `)}]}}case"borg_role-rationale":{const e=await h(),t=typeof o?.role=="string"?o.role:"",r=typeof o?.section=="string"?o.section:"",n=await Ie(e.sessionToken,e.apiUrl,t,r);return{content:[{type:"text",text:[`# Role rationale: ${n.role} \u2014 ${n.section}`,"",n.body||"_(empty)_"].join(`
11
- `)}]}}case"borg_roster":{const e=await h(),t=typeof o?.since=="string"?o.since:void 0,{drones:r,roles:n,since:s}=await ae(e.sessionToken,e.apiUrl,t);return{content:[{type:"text",text:rt({cubeName:e.name,drones:r,roles:n,resolvedSince:s??null,humanAgo:W})}]}}case"borg_stream-status":{const e=D(),t=await g(),r=t?T(t.cubeId,t.droneId):null,n=t?q(I()):null,s=t&&U()!=="claude",i=t?s?!0:P(r,n):null;let a="";e.runLoopHealth==="silent-inert"&&(a=`## \u26A0 SSE stream loop silent-inert \u2014 run /mcp and reconnect to restart
7
+ ${Ye(r)}`}]}}case"borg_whoami":{const e=await _(),t=await Ie(e.sessionToken,e.apiUrl);return{content:[{type:"text",text:JSON.stringify(t,null,2)}]}}case"borg_cube":{const e=await _(),[{cube:t,roles:r}]=await Promise.all([ae(e.sessionToken,e.apiUrl),L(e.sessionToken,e.apiUrl)]),n=[];n.push(`# Cube: ${t.name}`),n.push(""),n.push("## Cube directive"),n.push(t.cube_directive||"_(none)_"),n.push("");const i=Fe(t.message_taxonomy);if(i&&(n.push(i),n.push("")),n.push("## Roles in this cube"),!r.length)n.push("_(no roles defined)_");else{for(const s of r){const a=[s.role_class==="queen"?"Queen":null,s.is_human_seat?"human-seat":null,s.is_default?"default":null,s.is_mandatory?"mandatory":null].filter(Boolean).join(", "),c=a?` (${a})`:"",m=s.short_description||"_(no description)_";n.push(`- **${s.name}**${c} \u2014 ${m}`)}n.push(""),n.push("_(Coordinator-class drones can fetch role IDs via `borg_list-roles` for use with `borg_reassign-drone`.)_")}return n.push(""),n.push(Oe()),{content:[{type:"text",text:n.join(`
8
+ `)}]}}case"borg_role":{const e=await _(),t=typeof o?.role=="string"?o.role.trim():"";if(t){const{role:i}=await ce(e.sessionToken,e.apiUrl,t);return ie(t,i),{content:[{type:"text",text:[`# Role: ${i.name}`,"",i.detailed_description||"_(no detailed description set)_"].join(`
9
+ `)}]}}const{role:r}=await L(e.sessionToken,e.apiUrl);return{content:[{type:"text",text:[`# Your role: ${r.name}`,"",r.detailed_description||"_(no detailed description set)_"].join(`
10
+ `)}]}}case"borg_role-rationale":{const e=await _(),t=typeof o?.role=="string"?o.role:"",r=typeof o?.section=="string"?o.section:"",n=await qe(e.sessionToken,e.apiUrl,t,r);return{content:[{type:"text",text:[`# Role rationale: ${n.role} \u2014 ${n.section}`,"",n.body||"_(empty)_"].join(`
11
+ `)}]}}case"borg_roster":{const e=await _(),t=typeof o?.since=="string"?o.since:void 0,{drones:r,roles:n,since:i}=await le(e.sessionToken,e.apiUrl,t);return{content:[{type:"text",text:it({cubeName:e.name,drones:r,roles:n,resolvedSince:i??null,humanAgo:V})}]}}case"borg_stream-status":{const e=P(),t=await h(),r=t?q(t.cubeId,t.droneId):null,n=t?D(I()):null,i=t&&M()!=="claude",s=t?i?!0:U(r,n):null;let a="";e.runLoopHealth==="silent-inert"&&(a=`## \u26A0 SSE stream loop silent-inert \u2014 run /mcp and reconnect to restart
12
12
 
13
13
  The log-stream consumer started but never connected. This drone will not receive real-time cube events.
14
14
 
15
- `);const c=Ze({status:e,inboxMonitorHealthy:i,inboxPath:r,monitorStateRoot:n,droneLabel:t?.droneLabel??null,cubeName:t?.name??null,humanAgo:W});return{content:[{type:"text",text:a+c}]}}case"borg_read-log":{const e=await h(),t=typeof o?.since=="string"?o.since:void 0,r=typeof o?.limit=="number"?o.limit:void 0,n=o?.unread_only===!0||o?.unread_only==="true",{entries:s,drones:i,roles:a,behind_by:c,has_more:d}=await ce(e.sessionToken,e.apiUrl,{since:t,limit:r,unreadOnly:n}),f=new Map;for(const b of i)f.set(b.id,b);const _=new Map;for(const b of a)_.set(b.id,b);const u=[];if(u.push(`# Activity log: ${e.name}`),u.push(""),!s.length)u.push("_(no entries)_");else for(const b of s)u.push(Me(b,f,_));return d===!0?(u.push(""),u.push("\u26A0 has_more: true \u2014 call `borg_read-log unread_only=true` again until has_more=false so you finish draining unread entries.")):typeof c=="number"&&c>0&&(u.push(""),u.push(`\u26A0 behind_by: ${c} more unread ${c===1?"entry":"entries"} addressed to you \u2014 call \`borg_read-log unread_only=true\` again until behind_by=0 so you don't skip messages.`)),{content:[{type:"text",text:u.join(`
16
- `)}]}}case"borg_log":{const e=o?.message;if(!e||typeof e!="string")throw new Error("message is required");const t=await g();if(!t)throw new Error("Not assimilated to a cube. Use borg_assimilate <cube-name> first.");if(Ct(e)){const u=await Rt(t,e);if(u.suppress)return await z(t,e),{content:[{type:"text",text:`Suppressed duplicate ${u.signal?.toUpperCase()} lifecycle log for ${t.droneLabel}; recent cube log already contains this signal.`}]}}const r=Object.prototype.hasOwnProperty.call(o??{},"to"),n=r?St(o?.to):void 0,s=typeof o?.class=="string"?o.class:void 0,i=o?.visibility==="broadcast"||o?.visibility==="direct"?o.visibility:void 0,a={...s?{class:s}:{},...r?{to:n??[]}:{},...i?{visibility:i}:{}},c=await le(t.sessionToken,t.apiUrl,e,a);await z(t,e);const d=c.routing?.message?`
17
- ${c.routing.message}`:"",f=c.unreachableRecipients?.length?`
18
- \u26A0 ${c.unreachableRecipients.length} directed recipient(s) currently unreachable (wake-path:deaf): ${c.unreachableRecipients.map(u=>u.label).join(", ")}. Message delivered \u2014 they'll read it when they return.`:"";return{content:[{type:"text",text:`Logged to cube "${t.name}" as ${t.droneLabel}. (entry id: ${c.entry.id})${d}${f}`}]}}case"borg_report-friction":{const e=o?.message;if(!e||typeof e!="string")throw new Error("message is required");const t=await g();if(!t)throw new Error("Not assimilated to a cube. Use borg_assimilate <cube-name> first.");const r=o?.kind==="bug"?"bug":"friction",n=o?.metadata&&typeof o.metadata=="object"&&!Array.isArray(o.metadata)?o.metadata:void 0;return{content:[{type:"text",text:(await de(t.sessionToken,t.apiUrl,{kind:r,message:e,metadata:n})).ok?"Report submitted \u2014 thank you. The borgmcp team will see it. (Write-only: you cannot read reports back.)":"Report did not submit. Try again, or raise it in the cube log."}]}}case"borg_reports":{const e=await ue();if(e.forbidden)return{content:[{type:"text",text:"Reports triage is builder/dogfooder-tier only. Your account is not on the dogfooder (builder) tier, so the friction-reports store is not readable. (Server-enforced gate.)"}]};if(!e.reports.length)return{content:[{type:"text",text:"No reports yet. Submissions via borg_report-friction will appear here, newest first."}]};const t=e.reports.map(r=>{const n=r.metadata&&Object.keys(r.metadata).length?" \xB7 "+Object.entries(r.metadata).map(([i,a])=>`${i}=${a}`).join(", "):"",s=r.redacted?" \xB7 [secrets-scrubbed]":"";return`**[${r.kind}]** ${r.created_at} \xB7 ${r.reporter_email}${n}${s}
15
+ `);const c=tt({status:e,inboxMonitorHealthy:s,inboxPath:r,monitorStateRoot:n,droneLabel:t?.droneLabel??null,cubeName:t?.name??null,humanAgo:V});return{content:[{type:"text",text:a+c}]}}case"borg_read-log":{const e=await _(),t=typeof o?.since=="string"?o.since:void 0,r=typeof o?.limit=="number"?o.limit:void 0,n=o?.unread_only===!0||o?.unread_only==="true",{entries:i,drones:s,roles:a,behind_by:c,has_more:m}=await de(e.sessionToken,e.apiUrl,{since:t,limit:r,unreadOnly:n}),p=new Map;for(const g of s)p.set(g.id,g);const f=new Map;for(const g of a)f.set(g.id,g);const d=[];if(d.push(`# Activity log: ${e.name}`),d.push(""),!i.length)d.push("_(no entries)_");else for(const g of i)d.push(Le(g,p,f));return m===!0?(d.push(""),d.push("\u26A0 has_more: true \u2014 call `borg_read-log unread_only=true` again until has_more=false so you finish draining unread entries.")):typeof c=="number"&&c>0&&(d.push(""),d.push(`\u26A0 behind_by: ${c} more unread ${c===1?"entry":"entries"} addressed to you \u2014 call \`borg_read-log unread_only=true\` again until behind_by=0 so you don't skip messages.`)),{content:[{type:"text",text:d.join(`
16
+ `)}]}}case"borg_log":{const e=o?.message;if(!e||typeof e!="string")throw new Error("message is required");const t=await h();if(!t)throw new Error("Not assimilated to a cube. Use borg_assimilate <cube-name> first.");if(Tt(e)){const d=await It(t,e);if(d.suppress)return await J(t,e),{content:[{type:"text",text:`Suppressed duplicate ${d.signal?.toUpperCase()} lifecycle log for ${t.droneLabel}; recent cube log already contains this signal.`}]}}const r=Object.prototype.hasOwnProperty.call(o??{},"to"),n=r?qt(o?.to):void 0,i=typeof o?.class=="string"?o.class:void 0,s=o?.visibility==="broadcast"||o?.visibility==="direct"?o.visibility:void 0,a={...i?{class:i}:{},...r?{to:n??[]}:{},...s?{visibility:s}:{}},c=await ue(t.sessionToken,t.apiUrl,e,a);await J(t,e);const m=c.routing?.message?`
17
+ ${c.routing.message}`:"",p=c.unreachableRecipients?.length?`
18
+ \u26A0 ${c.unreachableRecipients.length} directed recipient(s) currently unreachable (wake-path:deaf): ${c.unreachableRecipients.map(d=>d.label).join(", ")}. Message delivered \u2014 they'll read it when they return.`:"";return{content:[{type:"text",text:`Logged to cube "${t.name}" as ${t.droneLabel}. (entry id: ${c.entry.id})${m}${p}`}]}}case"borg_report-friction":{const e=o?.message;if(!e||typeof e!="string")throw new Error("message is required");const t=await h();if(!t)throw new Error("Not assimilated to a cube. Use borg_assimilate <cube-name> first.");const r=o?.kind==="bug"?"bug":"friction",n=o?.metadata&&typeof o.metadata=="object"&&!Array.isArray(o.metadata)?o.metadata:void 0;return{content:[{type:"text",text:(await pe(t.sessionToken,t.apiUrl,{kind:r,message:e,metadata:n})).ok?"Report submitted \u2014 thank you. The borgmcp team will see it. (Write-only: you cannot read reports back.)":"Report did not submit. Try again, or raise it in the cube log."}]}}case"borg_reports":{const e=await me();if(e.forbidden)return{content:[{type:"text",text:"Reports triage is builder/dogfooder-tier only. Your account is not on the dogfooder (builder) tier, so the friction-reports store is not readable. (Server-enforced gate.)"}]};if(!e.reports.length)return{content:[{type:"text",text:"No reports yet. Submissions via borg_report-friction will appear here, newest first."}]};const t=e.reports.map(r=>{const n=r.metadata&&Object.keys(r.metadata).length?" \xB7 "+Object.entries(r.metadata).map(([s,a])=>`${s}=${a}`).join(", "):"",i=r.redacted?" \xB7 [secrets-scrubbed]":"";return`**[${r.kind}]** ${r.created_at} \xB7 ${r.reporter_email}${n}${i}
19
19
  ${r.message}`});return{content:[{type:"text",text:`Reports (${e.reports.length}, newest first):
20
20
 
21
21
  ${t.join(`
22
22
 
23
23
  ---
24
24
 
25
- `)}`}]}}case"borg_ack":{const e=o?.entry_id;if(!e||typeof e!="string")throw new Error("entry_id is required");const t=o?.kind==="claim"?"claim":"ack",r=await h();return await pe(r.sessionToken,r.apiUrl,e,t),{content:[{type:"text",text:t==="claim"?`Claimed entry ${e} in cube "${r.name}" (advisory \u2014 merge stays keyed on REVIEW-APPROVED).`:`Acked entry ${e} in cube "${r.name}".`}]}}case"borg_decide":{const e=o?.topic,t=o?.decision;if(!e||typeof e!="string")throw new Error("topic is required");if(!t||typeof t!="string")throw new Error("decision is required");const r=typeof o?.rationale=="string"?o.rationale:void 0,n=await h(),{decision:s}=await me(n.sessionToken,n.apiUrl,{topic:e,decision:t,...r!==void 0?{rationale:r}:{}}),i=s?.supersedes?" (superseded the prior decision on this topic)":"";return{content:[{type:"text",text:`Recorded ratified decision on "${e}" in cube "${n.name}"${i}. Cite it via borg_decisions; it surfaces in borg_regen.`}]}}case"borg_decisions":{const e=typeof o?.topic=="string"?o.topic:void 0,t=await h(),{decisions:r}=await be(t.sessionToken,t.apiUrl,e);return{content:[{type:"text",text:r.length===0?e?`No active ratified decision on "${e}" in cube "${t.name}".`:`No active ratified decisions in cube "${t.name}".`:r.map(s=>`**${s.topic}:** ${s.decision}${s.rationale?` \u2014 ${s.rationale}`:""}`).join(`
26
- `)}]}}case"borg_list-cubes":{const{cubes:e}=await fe();if(!e.length)return{content:[{type:"text",text:"No cubes yet. Use borg_create-cube to make your first one."}]};const t=e.map(r=>`- **${r.name}** (id: ${r.id})
25
+ `)}`}]}}case"borg_ack":{const e=o?.entry_id;if(!e||typeof e!="string")throw new Error("entry_id is required");const t=o?.kind==="claim"?"claim":"ack",r=await _();return await be(r.sessionToken,r.apiUrl,e,t),{content:[{type:"text",text:t==="claim"?`Claimed entry ${e} in cube "${r.name}" (advisory \u2014 merge stays keyed on REVIEW-APPROVED).`:`Acked entry ${e} in cube "${r.name}".`}]}}case"borg_decide":{const e=o?.topic,t=o?.decision;if(!e||typeof e!="string")throw new Error("topic is required");if(!t||typeof t!="string")throw new Error("decision is required");const r=typeof o?.rationale=="string"?o.rationale:void 0,n=await _(),{decision:i}=await fe(n.sessionToken,n.apiUrl,{topic:e,decision:t,...r!==void 0?{rationale:r}:{}}),s=i?.supersedes?" (superseded the prior decision on this topic)":"";return{content:[{type:"text",text:`Recorded ratified decision on "${e}" in cube "${n.name}"${s}. Cite it via borg_decisions; it surfaces in borg_regen.`}]}}case"borg_decisions":{const e=typeof o?.topic=="string"?o.topic:void 0,t=await _(),{decisions:r}=await ge(t.sessionToken,t.apiUrl,e);return{content:[{type:"text",text:r.length===0?e?`No active ratified decision on "${e}" in cube "${t.name}".`:`No active ratified decisions in cube "${t.name}".`:r.map(i=>`**${i.topic}:** ${i.decision}${i.rationale?` \u2014 ${i.rationale}`:""}`).join(`
26
+ `)}]}}case"borg_list-cubes":{const{cubes:e}=await he();if(!e.length)return{content:[{type:"text",text:"No cubes yet. Use borg_create-cube to make your first one."}]};const t=e.map(r=>`- **${r.name}** (id: ${r.id})
27
27
  ${(r.cube_directive||"_(no directive set)_").split(`
28
28
  `)[0].slice(0,120)}`);return{content:[{type:"text",text:`Your cubes (${e.length}):
29
29
 
30
30
  ${t.join(`
31
31
 
32
- `)}`}]}}case"borg_create-cube":{const e=o?.name,t=o?.cube_directive,r=o?.template;if(!e)throw new Error("name is required");if(t===void 0)throw new Error("cube_directive is required (pass empty string if none)");let n=null;if(r&&(n=R(r),!n))throw new Error(`Unknown template "${r}". Available: ${S().join(", ")}`);const s=De(t,n),i=Ue(void 0,n),a=await ge(e,s,{message_taxonomy:i});if(n){const d=await J(a.id,n),f=s!==t?" Template cube directive applied (operator passed empty).":"";return{content:[{type:"text",text:`Created cube **${a.name}** (id: ${a.id}) with template **${r}** applied \u2014 ${d.created} role(s) created, ${d.updated} updated.${f} Use borg_assimilate ${a.name} to join as a drone.`}]}}return{content:[{type:"text",text:`Created cube **${a.name}** (id: ${a.id}). A default "Drone" role was seeded \u2014 rename or replace it via borg_update-role / borg_create-role / borg_delete-role. Use borg_assimilate ${a.name} to join as a drone.`}]}}case"borg_update-cube":{const e=o?.cube_id;if(!e)throw new Error("cube_id is required");const t={};if(typeof o?.name=="string"&&(t.name=o.name),typeof o?.cube_directive=="string"&&(t.cube_directive=o.cube_directive),Array.isArray(o?.message_taxonomy)&&(t.message_taxonomy=o.message_taxonomy),Object.keys(t).length===0)throw new Error("Pass at least one of: name, cube_directive, message_taxonomy.");const{cube:r}=await O(e,t);return{content:[{type:"text",text:`Updated cube **${r.name}** (id: ${r.id}).`}]}}case"borg_patch-taxonomy-class":{const e=o?.cube_id;if(!e)throw new Error("cube_id is required");const t=o?.action;if(t!=="add"&&t!=="replace"&&t!=="remove")throw new Error("action must be one of: add, replace, remove.");let r,n;if(t==="remove"){const i=o?.class;if(!i)throw new Error("class is required for remove.");({cube:r}=await B(e,{action:t,class:i})),n=i}else{const i=o?.class_def;if(i==null||typeof i!="object"||Array.isArray(i))throw new Error("class_def (object) is required for add/replace.");({cube:r}=await B(e,{action:t,class_def:i})),n=String(i.class??"")}return{content:[{type:"text",text:`${t==="add"?"Added":t==="replace"?"Replaced":"Removed"} taxonomy class **${n}** in cube **${r.name}** (id: ${r.id}).`}]}}case"borg_delete-cube":{const e=o?.cube_id;if(!e)throw new Error("cube_id is required");return await he(e),{content:[{type:"text",text:`Deleted cube ${e} (and all its roles, drones, log entries).`}]}}case"borg_create-role":{const e=o?.cube_id,t=o?.name,r=o?.short_description,n=o?.detailed_description;if(!e)throw new Error("cube_id is required");if(!t)throw new Error("name is required");if(r===void 0)throw new Error("short_description is required (pass empty string if none)");if(n===void 0)throw new Error("detailed_description is required (pass empty string if none)");const s=o?.is_default===!0,i=o?.is_human_seat===!0,a=o?.can_broadcast===!0,c=o?.receives_all_direct===!0,{role:d}=await _e(e,{name:t,short_description:r,detailed_description:n,is_default:s,is_human_seat:i,can_broadcast:a,receives_all_direct:c,...typeof o?.default_model=="string"?{default_model:o.default_model}:{}}),f=[d.role_class==="queen"?"Queen":null,d.is_human_seat?"human-seat":null,d.is_default?"default":null].filter(Boolean).join(", "),_=f?` (${f})`:"";return{content:[{type:"text",text:`Created role **${d.name}**${_} (id: ${d.id}) in cube ${e}.`}]}}case"borg_update-role":{const e=o?.role_id;if(!e)throw new Error("role_id is required");const t={};if(typeof o?.name=="string"&&(t.name=o.name),typeof o?.short_description=="string"&&(t.short_description=o.short_description),typeof o?.detailed_description=="string"&&(t.detailed_description=o.detailed_description),typeof o?.is_default=="boolean"&&(t.is_default=o.is_default),typeof o?.is_human_seat=="boolean"&&(t.is_human_seat=o.is_human_seat),typeof o?.can_broadcast=="boolean"&&(t.can_broadcast=o.can_broadcast),typeof o?.receives_all_direct=="boolean"&&(t.receives_all_direct=o.receives_all_direct),typeof o?.default_model=="string"&&(t.default_model=o.default_model),Object.keys(t).length===0)throw new Error("Pass at least one of: name, short_description, detailed_description, is_default, is_human_seat, can_broadcast, receives_all_direct, default_model.");const{role:r}=await ye(e,t),n=[r.role_class==="queen"?"Queen":null,r.is_human_seat?"human-seat":null,r.is_default?"default":null].filter(Boolean).join(", "),s=n?` (${n})`:"";return{content:[{type:"text",text:`Updated role **${r.name}**${s} (id: ${r.id}).`}]}}case"borg_patch-role-section":{const e=o?.role_id;if(!e)throw new Error("role_id is required");const t=o?.action;if(t!=="replace"&&t!=="insert"&&t!=="delete")throw new Error("action must be one of: replace, insert, delete.");const r=o?.heading;if(!r)throw new Error("heading is required");let n;if(t==="delete")({role:n}=await C(e,{action:t,heading:r}));else{const i=o?.body;if(typeof i!="string")throw new Error("body is required for replace/insert (pass empty string for an empty section).");if(t==="insert"){const a=typeof o?.after=="string"?o.after:null;({role:n}=await C(e,{action:t,heading:r,body:i,after:a}))}else({role:n}=await C(e,{action:t,heading:r,body:i}))}return{content:[{type:"text",text:`${t==="replace"?"Replaced":t==="insert"?"Inserted":"Deleted"} section **${r}** in role **${n.name}** (id: ${n.id}).`}]}}case"borg_delete-role":{const e=o?.role_id;if(!e)throw new Error("role_id is required");return await we(e),{content:[{type:"text",text:`Deleted role ${e}.`}]}}case"borg_reassign-drone":{const e=o?.drone_id,t=o?.role_id;if(!e)throw new Error("drone_id is required");if(!t)throw new Error("role_id is required");const{drone:r}=await xe(e,t);return{content:[{type:"text",text:`Reassigned drone ${r.label} (${r.id}) to role ${r.role_id}.`}]}}case"borg_evict-drone":{const e=o?.drone_id?.trim(),t=o?.label?.trim(),r=o?.cube_id?.trim();let n,s;if(e){if(!st(e))throw new Error(`drone_id "${e}" is not a UUID \u2014 if that's a drone label, pass it as label + cube_id instead.`);n=e,s=e}else if(t){if(!r)throw new Error("cube_id is required when evicting by label");const{drones:i}=await x(r),a=nt(i,t);if(!a)throw new Error(`No active drone labelled "${t}" in cube ${r} (it may already be evicted; check borg_list-drones).`);n=a.id,s=a.label}else throw new Error("Provide drone_id, or label + cube_id, to identify the drone to evict");return await $e(n),{content:[{type:"text",text:`Evicted drone ${s} (${n}). Soft-deleted: removed from the roster and freed its seat; log history preserved with anonymized attribution.`}]}}case"borg_list-drones":{const e=o?.cube_id;if(!e)throw new Error("cube_id is required");const{drones:t,roles:r}=await x(e);if(!t.length)return{content:[{type:"text",text:"No drones in this cube yet."}]};const n=new Map(r.map(i=>[i.id,i])),s=t.map(i=>{const a=n.get(i.role_id),c=ot(a?.name??"?",i.agent_kind),d=i.wake_path_alert_class&&i.wake_path_alert_class!=="independent"?` \u2014 wake-path-class: ${i.wake_path_alert_class}`:"";return`- **${i.label}** (id: ${i.id}) \u2014 role: ${c} (${i.role_id}) \u2014 last seen ${i.last_seen}${d}`});return{content:[{type:"text",text:`Drones in cube ${e} (${t.length}):
32
+ `)}`}]}}case"borg_create-cube":{const e=o?.name,t=o?.cube_directive,r=o?.template;if(!e)throw new Error("name is required");if(t===void 0)throw new Error("cube_directive is required (pass empty string if none)");let n=null;if(r&&(n=S(r),!n))throw new Error(`Unknown template "${r}". Available: ${T().join(", ")}`);const i=Ue(t,n),s=je(void 0,n),a=await _e(e,i,{message_taxonomy:s});if(n){const m=await Y(a.id,n),p=i!==t?" Template cube directive applied (operator passed empty).":"";return{content:[{type:"text",text:`Created cube **${a.name}** (id: ${a.id}) with template **${r}** applied \u2014 ${m.created} role(s) created, ${m.updated} updated.${p} Use borg_assimilate ${a.name} to join as a drone.`}]}}return{content:[{type:"text",text:`Created cube **${a.name}** (id: ${a.id}). A default "Drone" role was seeded \u2014 rename or replace it via borg_update-role / borg_create-role / borg_delete-role. Use borg_assimilate ${a.name} to join as a drone.`}]}}case"borg_update-cube":{const e=o?.cube_id;if(!e)throw new Error("cube_id is required");const t={};if(typeof o?.name=="string"&&(t.name=o.name),typeof o?.cube_directive=="string"&&(t.cube_directive=o.cube_directive),Array.isArray(o?.message_taxonomy)&&(t.message_taxonomy=o.message_taxonomy),Object.keys(t).length===0)throw new Error("Pass at least one of: name, cube_directive, message_taxonomy.");const{cube:r}=await B(e,t);return{content:[{type:"text",text:`Updated cube **${r.name}** (id: ${r.id}).`}]}}case"borg_patch-taxonomy-class":{const e=o?.cube_id;if(!e)throw new Error("cube_id is required");const t=o?.action;if(t!=="add"&&t!=="replace"&&t!=="remove")throw new Error("action must be one of: add, replace, remove.");let r,n;if(t==="remove"){const s=o?.class;if(!s)throw new Error("class is required for remove.");({cube:r}=await F(e,{action:t,class:s})),n=s}else{const s=o?.class_def;if(s==null||typeof s!="object"||Array.isArray(s))throw new Error("class_def (object) is required for add/replace.");({cube:r}=await F(e,{action:t,class_def:s})),n=String(s.class??"")}return{content:[{type:"text",text:`${t==="add"?"Added":t==="replace"?"Replaced":"Removed"} taxonomy class **${n}** in cube **${r.name}** (id: ${r.id}).`}]}}case"borg_delete-cube":{const e=o?.cube_id;if(!e)throw new Error("cube_id is required");return await ye(e),{content:[{type:"text",text:`Deleted cube ${e} (and all its roles, drones, log entries).`}]}}case"borg_create-role":{const e=o?.cube_id,t=o?.name,r=o?.short_description,n=o?.detailed_description;if(!e)throw new Error("cube_id is required");if(!t)throw new Error("name is required");if(r===void 0)throw new Error("short_description is required (pass empty string if none)");if(n===void 0)throw new Error("detailed_description is required (pass empty string if none)");const i=o?.is_default===!0,s=o?.is_mandatory===!0,a=o?.is_human_seat===!0,c=o?.can_broadcast===!0,m=o?.receives_all_direct===!0,{role:p}=await we(e,{name:t,short_description:r,detailed_description:n,is_default:i,is_mandatory:s,is_human_seat:a,can_broadcast:c,receives_all_direct:m,...typeof o?.default_model=="string"?{default_model:o.default_model}:{}}),f=[p.role_class==="queen"?"Queen":null,p.is_human_seat?"human-seat":null,p.is_default?"default":null,p.is_mandatory?"mandatory":null].filter(Boolean).join(", "),d=f?` (${f})`:"";return{content:[{type:"text",text:`Created role **${p.name}**${d} (id: ${p.id}) in cube ${e}.`}]}}case"borg_update-role":{const e=o?.role_id;if(!e)throw new Error("role_id is required");const t={};if(typeof o?.name=="string"&&(t.name=o.name),typeof o?.short_description=="string"&&(t.short_description=o.short_description),typeof o?.detailed_description=="string"&&(t.detailed_description=o.detailed_description),typeof o?.is_default=="boolean"&&(t.is_default=o.is_default),typeof o?.is_mandatory=="boolean"&&(t.is_mandatory=o.is_mandatory),typeof o?.is_human_seat=="boolean"&&(t.is_human_seat=o.is_human_seat),typeof o?.can_broadcast=="boolean"&&(t.can_broadcast=o.can_broadcast),typeof o?.receives_all_direct=="boolean"&&(t.receives_all_direct=o.receives_all_direct),typeof o?.default_model=="string"&&(t.default_model=o.default_model),Object.keys(t).length===0)throw new Error("Pass at least one of: name, short_description, detailed_description, is_default, is_mandatory, is_human_seat, can_broadcast, receives_all_direct, default_model.");const{role:r}=await xe(e,t),n=[r.role_class==="queen"?"Queen":null,r.is_human_seat?"human-seat":null,r.is_default?"default":null,r.is_mandatory?"mandatory":null].filter(Boolean).join(", "),i=n?` (${n})`:"";return{content:[{type:"text",text:`Updated role **${r.name}**${i} (id: ${r.id}).`}]}}case"borg_patch-role-section":{const e=o?.role_id;if(!e)throw new Error("role_id is required");const t=o?.action;if(t!=="replace"&&t!=="insert"&&t!=="delete")throw new Error("action must be one of: replace, insert, delete.");const r=o?.heading;if(!r)throw new Error("heading is required");let n;if(t==="delete")({role:n}=await C(e,{action:t,heading:r}));else{const s=o?.body;if(typeof s!="string")throw new Error("body is required for replace/insert (pass empty string for an empty section).");if(t==="insert"){const a=typeof o?.after=="string"?o.after:null;({role:n}=await C(e,{action:t,heading:r,body:s,after:a}))}else({role:n}=await C(e,{action:t,heading:r,body:s}))}return{content:[{type:"text",text:`${t==="replace"?"Replaced":t==="insert"?"Inserted":"Deleted"} section **${r}** in role **${n.name}** (id: ${n.id}).`}]}}case"borg_delete-role":{const e=o?.role_id;if(!e)throw new Error("role_id is required");return await $e(e),{content:[{type:"text",text:`Deleted role ${e}.`}]}}case"borg_reassign-drone":{const e=o?.drone_id,t=o?.role_id;if(!e)throw new Error("drone_id is required");if(!t)throw new Error("role_id is required");const{drone:r}=await ve(e,t);return{content:[{type:"text",text:`Reassigned drone ${r.label} (${r.id}) to role ${r.role_id}.`}]}}case"borg_evict-drone":{const e=o?.drone_id?.trim(),t=o?.label?.trim(),r=o?.cube_id?.trim();let n,i;if(e){if(!ct(e))throw new Error(`drone_id "${e}" is not a UUID \u2014 if that's a drone label, pass it as label + cube_id instead.`);n=e,i=e}else if(t){if(!r)throw new Error("cube_id is required when evicting by label");const{drones:s}=await $(r),a=at(s,t);if(!a)throw new Error(`No active drone labelled "${t}" in cube ${r} (it may already be evicted; check borg_list-drones).`);n=a.id,i=a.label}else throw new Error("Provide drone_id, or label + cube_id, to identify the drone to evict");return await ke(n),{content:[{type:"text",text:`Evicted drone ${i} (${n}). Soft-deleted: removed from the roster and freed its seat; log history preserved with anonymized attribution.`}]}}case"borg_list-drones":{const e=o?.cube_id;if(!e)throw new Error("cube_id is required");const{drones:t,roles:r}=await $(e);if(!t.length)return{content:[{type:"text",text:"No drones in this cube yet."}]};const n=new Map(r.map(s=>[s.id,s])),i=t.map(s=>{const a=n.get(s.role_id),c=nt(a?.name??"?",s.agent_kind),m=s.wake_path_alert_class&&s.wake_path_alert_class!=="independent"?` \u2014 wake-path-class: ${s.wake_path_alert_class}`:"",p=s.reported_model?` \u2014 Reported model: ${s.reported_model}`:" \u2014 Reported model: not reported",f=st(s),d=f?` \u2014 ${f}`:"";return`- **${s.label}** (id: ${s.id}) \u2014 role: ${c} (${s.role_id}) \u2014 last seen ${s.last_seen}${m}${p}${d}`});return{content:[{type:"text",text:`Drones in cube ${e} (${t.length}):
33
33
 
34
- ${s.join(`
35
- `)}`}]}}case"borg_list-roles":{const e=o?.cube_id;if(!e)throw new Error("cube_id is required");const{roles:t}=await x(e);return{content:[{type:"text",text:Qe(t,e)}]}}case"borg_list-templates":return{content:[{type:"text",text:`Available templates:
34
+ ${i.join(`
35
+ `)}`}]}}case"borg_list-roles":{const e=o?.cube_id;if(!e)throw new Error("cube_id is required");const{roles:t}=await $(e);return{content:[{type:"text",text:Ge(t,e)}]}}case"borg_list-templates":return{content:[{type:"text",text:`Available templates:
36
36
 
37
- ${S().map(r=>{const n=R(r);return`- **${r}**: ${n.description}`}).join(`
38
- `)}`}]};case"borg_sync-roles":{const e=o?.cube_id,t=o?.template_name||"software-dev",r=o?.apply===!0,n=o?.decisions&&typeof o.decisions=="object"?o.decisions:void 0;if(!e)throw new Error("cube_id is required");const s=await Ce(e,t,r,n);return{content:[{type:"text",text:gt(s,t)}]}}case"borg_apply-template":{const e=o?.cube_id,t=o?.template_name;if(!e)throw new Error("cube_id is required");if(!t)throw new Error("template_name is required");const r=R(t);if(!r)throw new Error(`Unknown template "${t}". Available: ${S().join(", ")}`);const n=await J(e,r);let s="";const i=await x(e),a=Pe(i.cube_directive,r);return a!==null&&(await O(e,{cube_directive:a}),s=" Template cube directive applied (cube directive was empty)."),{content:[{type:"text",text:`Applied template **${t}** to cube ${e} \u2014 ${n.created} role(s) created, ${n.updated} updated.${s}`}]}}default:throw new Error(`Unknown tool: ${p}`)}}catch(e){if(e instanceof at)return{content:[{type:"text",text:lt(e.message)}],isError:!0};if(e instanceof ct)return{content:[{type:"text",text:dt(e.message)}],isError:!0};const t=it(e??{});return t?{content:[{type:"text",text:t}],isError:!0}:{content:[{type:"text",text:`Error: ${e.message}`}],isError:!0}}}),y.setRequestHandler(oe,async()=>({prompts:[{name:"borg_subscribe",description:"Set up Borg MCP Cube tier subscription ($1/month per cube; each cube adds 8 pooled agent sessions + 1000 req/hr). Free tier is permanent (1 cube + 3 agent sessions + 100 req/hr); no trial."},{name:"dashboard",description:"Open Borg MCP dashboard to manage cubes"}]})),y.setRequestHandler(re,async l=>{const{name:p}=l.params;switch(p){case"borg_subscribe":return{description:"Set up Borg MCP Cube tier subscription ($1/month per cube; each cube adds 8 pooled agent sessions + 1000 req/hr). Free tier is permanent (1 cube + 3 agent sessions + 100 req/hr); no trial.",messages:[{role:"user",content:{type:"text",text:"Please help me set up a Borg MCP subscription using the subscribe tool."}}]};case"dashboard":return{description:"Open Borg MCP dashboard to manage cubes",messages:[{role:"user",content:{type:"text",text:"Please open the Borg MCP dashboard using the borg_open_dashboard tool."}}]};default:throw new Error(`Unknown prompt: ${p}`)}});const Q=new Z;await y.connect(Q),await ht(),console.error(`${v()}\u25FC Borg MCP Client started`),console.error(`${v()}\u25FC Use borg_assimilate <cube-name> to join a cube as a drone`),console.error(`${v()}\u25FC Manage your cubes at https://borgmcp.ai/dashboard`)}qt().catch(m=>{console.error(`${v()}Fatal error:`,m),process.exit(1)});
37
+ ${T().map(r=>{const n=S(r);return`- **${r}**: ${n.description}`}).join(`
38
+ `)}`}]};case"borg_sync-roles":{const e=o?.cube_id,t=o?.template_name||"software-dev",r=o?.apply===!0,n=o?.decisions&&typeof o.decisions=="object"?o.decisions:void 0;if(!e)throw new Error("cube_id is required");const i=await Se(e,t,r,n);return{content:[{type:"text",text:yt(i,t)}]}}case"borg_apply-template":{const e=o?.cube_id,t=o?.template_name;if(!e)throw new Error("cube_id is required");if(!t)throw new Error("template_name is required");const r=S(t);if(!r)throw new Error(`Unknown template "${t}". Available: ${T().join(", ")}`);const n=await Y(e,r);let i="";const s=await $(e),a=Me(s.cube_directive,r);return a!==null&&(await B(e,{cube_directive:a}),i=" Template cube directive applied (cube directive was empty)."),{content:[{type:"text",text:`Applied template **${t}** to cube ${e} \u2014 ${n.created} role(s) created, ${n.updated} updated.${i}`}]}}default:throw new Error(`Unknown tool: ${u}`)}}catch(e){if(e instanceof dt)return{content:[{type:"text",text:pt(e.message)}],isError:!0};if(e instanceof ut)return{content:[{type:"text",text:mt(e.message)}],isError:!0};const t=lt(e??{});return t?{content:[{type:"text",text:t}],isError:!0}:{content:[{type:"text",text:`Error: ${e.message}`}],isError:!0}}}),y.setRequestHandler(ne,async()=>({prompts:[{name:"borg_subscribe",description:"Set up Borg MCP Cube tier subscription ($1/month per cube; each cube adds 8 pooled agent sessions + 1000 req/hr). Free tier is permanent (1 cube + 3 agent sessions + 100 req/hr); no trial."},{name:"dashboard",description:"Open Borg MCP dashboard to manage cubes"}]})),y.setRequestHandler(se,async l=>{const{name:u}=l.params;switch(u){case"borg_subscribe":return{description:"Set up Borg MCP Cube tier subscription ($1/month per cube; each cube adds 8 pooled agent sessions + 1000 req/hr). Free tier is permanent (1 cube + 3 agent sessions + 100 req/hr); no trial.",messages:[{role:"user",content:{type:"text",text:"Please help me set up a Borg MCP subscription using the subscribe tool."}}]};case"dashboard":return{description:"Open Borg MCP dashboard to manage cubes",messages:[{role:"user",content:{type:"text",text:"Please open the Borg MCP dashboard using the borg_open_dashboard tool."}}]};default:throw new Error(`Unknown prompt: ${u}`)}});const G=new te;await y.connect(G),await wt(),console.error(`${k()}\u25FC Borg MCP Client started`),console.error(`${k()}\u25FC Use borg_assimilate <cube-name> to join a cube as a drone`),console.error(`${k()}\u25FC Manage your cubes at https://borgmcp.ai/dashboard`)}Ut().catch(b=>{console.error(`${k()}Fatal error:`,b),process.exit(1)});
@@ -12,6 +12,7 @@ export interface RoleForRender {
12
12
  name: string;
13
13
  short_description?: string;
14
14
  is_default?: boolean;
15
+ is_mandatory?: boolean;
15
16
  is_human_seat?: boolean;
16
17
  can_broadcast?: boolean;
17
18
  receives_all_direct?: boolean;
@@ -25,7 +26,7 @@ export interface RoleForRender {
25
26
  * Each role line shape:
26
27
  * - **<name>**(<tags>) `<uuid>` — <description>
27
28
  *
28
- * Tags collected in order: Queen, human-seat, default, can-broadcast,
29
+ * Tags collected in order: Queen, human-seat, default, mandatory, can-broadcast,
29
30
  * receives-all-direct. Joined with `, `; suffix omitted entirely when no tags apply.
30
31
  */
31
32
  export declare function renderRoleList(roles: RoleForRender[], cubeId: string): string;
@@ -1,4 +1,4 @@
1
- function i(n,s){if(!n.length)return"No roles in this cube yet.";const l=n.map(e=>{const t=[e.role_class==="queen"?"Queen":null,e.is_human_seat?"human-seat":null,e.is_default?"default":null,e.can_broadcast?"can-broadcast":null,e.receives_all_direct?"receives-all-direct":null].filter(Boolean).join(", "),o=t?` (${t})`:"",a=e.short_description||"_(no description)_";return`- **${e.name}**${o} \`${e.id}\` \u2014 ${a}`});return`Roles in cube ${s} (${n.length}):
1
+ function i(n,a){if(!n.length)return"No roles in this cube yet.";const l=n.map(e=>{const t=[e.role_class==="queen"?"Queen":null,e.is_human_seat?"human-seat":null,e.is_default?"default":null,e.is_mandatory?"mandatory":null,e.can_broadcast?"can-broadcast":null,e.receives_all_direct?"receives-all-direct":null].filter(Boolean).join(", "),o=t?` (${t})`:"",s=e.short_description||"_(no description)_";return`- **${e.name}**${o} \`${e.id}\` \u2014 ${s}`});return`Roles in cube ${a} (${n.length}):
2
2
 
3
3
  ${l.join(`
4
4
  `)}
@@ -36,9 +36,9 @@ export type AgentKind = 'claude' | 'codex' | 'opencode';
36
36
  * `isCodexRemoteWakeEnabled`), NOT on a mutable server-recorded field:
37
37
  * - claude: arm the inbox-file tail Monitor + engage `/loop` + set a ~3600s
38
38
  * `ScheduleWakeup` fallback heartbeat (three independent wake mechanisms).
39
- * - codex: wakes via the app-server remote-wake injection — it has NO
40
- * tail-Monitor and NO `/loop` heartbeat to arm, so instructing either would
41
- * be wrong; the recovery is a manual `borg_regen` on return.
39
+ * - codex: Borg's activity stream reaches the app-server remote-control inbox
40
+ * channel; each wake is followed by an unread-log drain. Manual full regen
41
+ * + drain is a degraded fallback when remote control is unavailable.
42
42
  *
43
43
  * `inboxPath` is the deterministic client-generated UUID path
44
44
  * (`~/.config/borgmcp/inboxes/<cubeId>/<droneId>.log`), while the optional
@@ -80,9 +80,9 @@ export declare function resolveLeanIdentity(active: {
80
80
  * - IDENTITY: cube + drone label + role, so a weak model knows who it is.
81
81
  * - WAKE-PATH ARMING: the shared `wakePathArming` block (liveness — correct
82
82
  * to carry pre-`borg_regen`).
83
- * - `borg_regen` POINTER: the path to the FULL context AND the safety floor
84
- * (NEVER-TaskStop / 410-EVICTED / 423-FROZEN rules live in role-text,
85
- * reachable only via this pointer). Kept in EVERY render.
83
+ * - `borg_regen` POINTER: the path to the full operating context and safety
84
+ * floor. Role-specific lifecycle guidance remains reachable through the
85
+ * role-text pointer. Kept in EVERY render.
86
86
  *
87
87
  * Template-agnostic (#921): the escalation target is "your cube's coordinating
88
88
  * role" — NEVER a hardcoded `coordinator` / `drone-1` (this is the
@@ -1,12 +1,12 @@
1
- import{ROLE_SCOPED_SAFETY_DISCIPLINES as _,UNIVERSAL_SAFETY_DISCIPLINES as x}from"./templates.js";import{parseRoleSections as C}from"./role-section.js";import{formatRoleAgentLabel as T}from"./roster-render.js";import{formatDroneAddressToken as N}from"./drone-address.js";import{shellEscape as f}from"./shell-escape.js";function q(e){if(!e||!e.trim())return null;try{const t=JSON.parse(e)?.source;return typeof t=="string"?t:null}catch{return null}}function D(e,o,t){return e==="codex"?"Wake path: Codex wakes via the app-server remote-wake injection \u2014 there is no tail-Monitor or `/loop` heartbeat to arm. If no wake arrives when you return to the session, run `borg_regen` manually.":e==="opencode"?"Wake path: OpenCode wakes via SDK-driven entry injection into the TUI session \u2014 there is no tail-Monitor or `/loop` heartbeat to arm. Cube posts are delivered directly into your active session. Use `borg_regen` at any time for fresh context.":["Arm your wake path before working:",`1. **Inbox Monitor** (wake path) \u2014 run a persistent Monitor on \`${t?`borg-inbox-monitor --state-root ${f(t)} ${f(o)}`:`borg-inbox-monitor ${f(o)}`}\` so cube posts wake you in real time.`,"2. **Engage `/loop`** (self-paced) so you keep waking to triage the cube.","3. **Fallback heartbeat** \u2014 set a ~3600s (60-min) `ScheduleWakeup` so you never go fully silent."].join(`
2
- `)}function H(e,o){return{cubeName:o?.cube?.name??e.name,droneLabel:o?.drone?.label??e.droneLabel,roleName:o?.role?.name??e.roleName??null}}function V(e){const{cubeName:o,droneLabel:t,roleName:n,inboxPath:r,monitorStateRoot:i,agentKind:s,source:c}=e,l=c==="clear"?s==="codex"?"\n_(`/clear` cleared your conversation; Codex remote-wake remains app-server-driven. If no wake arrives, run `borg_regen` manually when returning.)_\n":"\n_(`/clear` cleared your conversation + session-scoped `/loop` and `ScheduleWakeup` heartbeat \u2014 re-establish them now.)_\n":"";return[`# Cube: ${o} \u2014 ${t}`,"",`**Your role:** ${n||"_(call `borg_regen` to load)_"}`,l,"You are a Borg drone \u2014 coordinate through the cube log, and never pause for the user. Blocked \u2192 escalate to your cube's coordinating role.","",D(s,r,i),"","This orientation is intentionally lean. Before acting, call `borg_regen`, load the cube directive and conventions with `borg_cube`, and load your own role playbook/details with `borg_role` when not already present or after compaction. Use `borg_playbook` once per session for the complete operating disciplines.",""].join(`
3
- `)}function S(){return"## How to operate as a Drone\n\nYou're a Drone in a Cube. Coordinate with other drones through the activity log.\n\n**User asks how Borg MCP works** \u2014 a feature, setup, pricing, or concept question? Call `borg_docs {topic}` for the documentation index, then WebFetch the matching section URL and answer from the page. Don't guess borgmcp's own behavior from memory.\n\n**Tools:**\n- `borg_regen` \u2014 refresh full state (your role, roster, unread-log COUNT, and fetch-on-demand pointers) in one call; the cube directive (\u2192 `borg_cube`), the operating-playbook detail (\u2192 `borg_playbook`), and the recent-log payload (\u2192 `borg_read-log` when count >0) are NOT inlined \u2014 fetch them on demand\n- `borg_cube` \u2014 re-read the cube directive and the role overview\n- `borg_role` \u2014 re-read your role's detailed playbook\n- `borg_roster` \u2014 see who else is connected\n- `borg_read-log unread_only=true [limit]` \u2014 drain unread log entries from your server-side cursor\n- `borg_log <message>` \u2014 append to the log\n- `borg_assimilate <cube>` \u2014 switch to a different cube\n\n**How coordination works:** the Cube gives primitives, not workflows. Your role's `detailed_description` (above) is your playbook \u2014 its conventions + signals come from there, not the system. The log is the coordination channel. Different cubes, different conventions.\n\n**Default: act autonomously, coordinate through the log.** Don't wait for user input. Need input \u2192 post the question, continue other work, other drones respond. The human supervisor is reachable through your cube's coordinating / human-seat role (the role your cube designates for direction + integration), or the Queen role when the seat is delegated to a drone \u2014 one continuous seat. Your role's `detailed_description` says when to escalate + which decisions need human input; follow it.\n\n**Operating loop \u2014 each wake, in order:**\n1. Drain unread: `borg_read-log unread_only=true` (oldest-first, repeat until `behind_by=0`) before acting. The \"Cube log\" section gives your UNREAD COUNT.\n2. Apply your role's conventions to each entry. Act on: questions you can answer; blocked peers you can unblock; unowned work you can claim; decisions affecting you.\n3. Actionable signal \u2192 act + post the convention. Don't wait to be asked.\n4. User prompt waiting \u2192 respond, informed by cube context; log substantive units (shipped changes, blockers, findings) regardless of who initiated.\n5. Nothing actionable + no prompt \u2192 done; wait for next wake.\n\n**On a `<task-notification>` wake:** the payload is a truncatable preview; the full entry is in the DB. Drain: `borg_read-log unread_only=true limit=20`, repeat until `behind_by=0`. Do NOT triage with `since=<notification timestamp>` (strict-after \u2014 skips the boundary entry) or a bare window (skips older-unread during bursts).\n\n**On first wake this session:** post one `ARRIVAL: <your-label> (<your-role>) online on <hostname> at <project-path>` (run `hostname`; use cwd for the path). One-time per session \u2014 don't repeat on later wakes; skip if already posted this session (e.g. after a `/mcp` reconnect).\n\n**When a log entry routes work to you** (a routing/assignment-class entry per your cube's conventions that names your label + asks for action, or a direct `<your-label>:` mention): call `borg_ack entry_id=<id>` within ~60s. Use the `borg_ack` TOOL, not an in-band `ACK:` post (it records a queryable flag + wakes the author's Monitor + keeps the log clean). Ack = receipt, not completion (`STARTING` / `DONE` still apply). Ack only routing-class signals \u2014 not every mention.\n\n**Claim a work item before you start it (`borg_ack ... kind=claim`):** `borg_ack` has two kinds \u2014 `ack` (receipt, the default) and `claim` (advisory ownership of a routed work item you are about to take). When a routed entry could be picked up by more than one drone, `borg_ack entry_id=<id> kind=claim` BEFORE starting \u2014 it announces you are taking it so peers skip the duplicate work, and wakes the rest of the entry's audience. If a live peer already holds the claim, skip it; if the claim is STALE (the claimant went silent past the wake-path SLA), re-claim and proceed. A claim is ADVISORY only \u2014 it NEVER substitutes for the completion or approval signal your role's conventions require; a bogus or abandoned claim can at most delay a work item, never bypass its real gate.\n\n**When stuck:** post your blocker per your role's conventions, continue other work. Escalation is per your role detail, not by stalling.\n\n**Anti-passive (lane idle = no work routed to you, no actionable signal in the log):**\n- If your work arrives via dispatch / a work queue: when your lane goes idle, post your role's availability signal (capacity clean, awaiting next assignment from your coordinating role) \u2014 once per idle period, don't spam. No assignment in ~15 min \u2192 ping your coordinating role (capacity available since <time>; any queue item to pick up?).\n- If your work is SELF-DIRECTED (not dispatch-driven): do NOT post an availability signal \u2014 proactively surface lane-substantive work per your role (reviews, audits, proposals, coherence / quality sweeps on relevant in-flight work).\n- Route work-asks through your cube's coordinating role, never directly to the human Queen.\n\n**Verify factual claims:** verify any verifiable claim \u2014 versions, code-state, prod behavior, npm state \u2014 against the SOURCE-OF-TRUTH surface (`git tag` / `git show <ref>:<path>` / grep, `curl` / `wrangler tail`, `npm view`, the live DB) BEFORE writing it; never a derivative artifact (another post, summary, or your own prior framing). The full discipline \u2014 the v1/v2/v3 sharpening levels, the per-claim-type concrete surfaces, and four-surface propagation (brainstorm / comment / review / issue-filing) \u2014 is in the operating-playbook chapter (`borg_playbook`; loaded via the session-start block in your regen).\n\n**Posting to the log:** post per your role's conventions whenever you start/finish a task, get stuck, answer a drone, or learn something others need \u2014 regardless of who initiated (a log signal, your own scan, or a user prompt). Conventions live in your role detail; the system is vocabulary-agnostic.\n\n**Routing posts \u2014 widen the directed default:** the taxonomy routes most prefixes DIRECTED to your cube's coordinating role; your `to:` / `visibility:` overrides it. Widen when a post must reach more than the coordinating role:\n- Posting a verdict / decision / result a specific drone is waiting on: add `to:[that drone]` so they're WOKEN \u2014 without it they can be left UNAWARE of their own merge or feedback. Directed governs the WAKE; it is NOT read-confidentiality: every member can read every entry \u2014 the cube is the trust boundary \u2014 so never post secrets relying on `to:[x]`.\n- Any drone posting a multi-seat DELIVERABLE (spec / security classification / review artifact 3+ seats build or gate against): pass `visibility:broadcast` (or `to:[the seats]`) EVEN IF your prefix (`DONE` etc.) is a directed status class \u2014 else only your coordinating role wakes (taxonomy routes by prefix, not payload) and the building/gating seats miss it.\n\n**Pre-commit git hygiene (universal):**\n\nAny drone that commits code: run `git diff --staged --stat` before `git commit` to verify file count + LOC direction + paths match your intent. Catches deleted files / anomalous -LOC / wrong paths pre-push. Your role may layer more git rules (code-implementing + coordinating roles typically carry the full set)."}const J=S();function G(){return'## Operating playbook \u2014 full disciplines (borg_playbook chapter)\n\nThis is the on-demand detail behind the rule-spine in your regen. Load it ONCE per session; it is static \u2014 do not re-fetch on every wake.\n\n**Verifying factual claims:**\n\nAny time you make a factual claim that could be verified \u2014 "this shipped as version Y", "function Z does W", "endpoint A returns B in prod", "package P is at version Q on npm" \u2014 verify the claim against a SOURCE-OF-TRUTH surface BEFORE writing it, not against a derivative artifact (another post, doc, summary, or your own prior framing). Three sharpening levels:\n\n- **v1 (verify against the actual surface):** check the claim against the surface it describes (e.g. a code-state claim \u2192 grep the file). Apply when the claim is about code-state.\n- **v2 (source-of-truth vs derivative artifacts):** when the verification surface itself could carry the original error chain (another post citing the same wrong claim, a doc copy-mirrored from the post you\'re checking), verify against the canonical source-of-truth: `git tag` for version-attribution, code-by-grep / direct file read for code-state, live `curl` or `wrangler tail` for prod-state, `npm view` for npm-state. Apply when version numbers, deploy timestamps, or other discrete facts are in scope.\n- **v3 (end-to-end execution path vs originating mechanism):** when verifying a live-mechanism claim ("the watchdog wakes silent drones"), verify the END-TO-END execution path, not just each isolated component \u2014 each isolated mechanism can be correct while the path between them silently breaks. Apply when live-mechanism correctness is being claimed; trace the path the wake/value/state actually takes from origin to terminal observer.\n\n**Concrete verification surfaces by claim type:**\n- Version attribution \u2192 `git tag --contains <sha>` or `git log --oneline <tag>`\n- Code state \u2192 match the grep surface to the claim surface:\n - Local uncommitted claim \u2192 `grep -n "<symbol>" <file>` or direct file read in the working tree\n - `origin/main`, PR head, branch, merge-SHA, or tag claim \u2192 `git show <ref>:<path> | grep -n "<symbol>"` (examples: `git show origin/main:workers/heartbeat.ts | grep -n "last_log_post"`; `git show origin/feat/foo:client/src/log-stream.ts | grep -n "ownDrone"`; `git show abc1234:workers/cubes.ts | grep -n "visibility"`)\n- Prod state \u2192 `curl https://<endpoint>` or `wrangler tail --env production`\n- npm registry state \u2192 `npm view <package>@<version>` or `npm view <package>@latest`\n- DB state \u2192 query through the existing `db` interface; never trust a doc claim about row counts / column values\n- Cube log state \u2192 `borg_read-log unread_only=true` for wake triage, draining until `behind_by=0`; don\'t cite from memory or from another drone\'s summary\n- Ratified cube decision \u2192 `borg_decisions {topic}` \u2014 cite the registry\'s active decision by topic; NEVER restate a ratified decision from memory (a memory restatement drifts on the axis). A ratified decision is a first-class verifiable claim type with its own source of truth: the active registry entry. Recording one is `borg_decide` (seat-holder only \u2014 recording IS the ratification act).\n\n**The discipline is universal to reviewer-class actions** (Code Reviewer formal gates + Security Auditor SR gates + PM-courtesy verifications + UX-courtesy reviews + any drone making a verification-worthy factual claim in their cube-log post). It lives in this universal playbook rather than any one role\'s text because it applies to ALL reviewers.\n\n**Four-surface propagation:**\n\nThe discipline applies at FOUR surfaces. Catches at the surface closest to origin are cheapest; catches at later surfaces have already propagated through earlier consumers:\n\n- **Surface 1 (brainstorm-proposal time)**: when a brainstorm contribution names specific code identifiers / API field names / enum values / column names / function signatures, the PROPOSING drone source-grep\'s the referenced file BEFORE composing the proposal. If the proposal cites current `origin/main` or a branch/SHA, grep that ref via `git show <ref>:<path> | grep`; working-tree grep is only for explicitly local/uncommitted claims. Cheapest catch surface; one drone catches one error.\n- **Surface 2 (comment/JSDoc/docstring writing time)**: when an implementation comment cites cross-file invariants (other modules\' thresholds, schema columns, enum values, semantic contracts), the WRITING drone source-grep\'s the referenced file BEFORE writing the comment. If the comment describes a merged/base/PR-head state, grep the named ref via `git show <ref>:<path> | grep`; don\'t let a stale local checkout stand in for the ref being described. Mid-cost catch; one drone catches one error but downstream reviewers may inherit the wrong mental model from the comment.\n- **Surface 3 (review-time verification)**: the existing review-class discipline (Code Reviewer formal gates + Security Auditor SR gates + PM/UX/QA courtesy reviews). Late catch opportunity; if the error propagated through Surfaces 1 + 2, multiple reviewers may have already trusted the framing instead of source-grepping themselves.\n- **Surface 4 (durable-tracking-artifact-writing time)**: when filing a deferred-tracking issue from a cube event payload, the FILING drone fetches the originating entry\'s full body from the cube log BEFORE composing the issue body. For routine wake triage, use `borg_read-log unread_only=true` and drain until caught up; do not rely on a truncated event preview or a `since=<same timestamp>` read, which can skip the boundary entry. Cube event previews can truncate substantive content (mid-paragraph cuts on long entries); filing from the truncated preview trusts a derivative artifact instead of the source-of-truth full entry. Most expensive surface \u2014 the filed issue becomes the cube\'s durable cross-cycle memory; correcting it requires a follow-up correction post, and later pickup drones inherit the incomplete framing if the correction is missed.\n\n**Ratified-decision drift is a four-surface drift-class.** A ratified cube decision restated from memory drifts exactly like a code-identifier claim \u2014 it propagates dispatch (Surface 1, brainstorm) \u2192 copy (Surface 2, comment) \u2192 gate (Surface 3, review), and the cheapest catch is at the brainstorm surface. At each surface, a drone restating a ratified decision source-reads `borg_decisions {topic}` FIRST: the active registry entry is the source of truth; your memory is a derivative artifact. Core rule \u2014 **cite ratified decisions by topic; never restate one from memory.**'}function I(e){const o=typeof e=="string"?new Date(e):e,t=Date.now()-o.getTime();if(!Number.isFinite(t)||t<0)return"just now";const n=Math.floor(t/1e3);if(n<60)return`${n}s ago`;const r=Math.floor(n/60);if(r<60)return`${r}m ago`;const i=Math.floor(r/60);return i<24?`${i}h ago`:`${Math.floor(i/24)}d ago`}function L(e){return e==null||Array.isArray(e)&&e.length===0?"Tip: no message taxonomy declared \u2014 set one to enable intent-based smart routing (#468). Use borg_update-cube with a taxonomy array, or add classes with borg_patch-taxonomy-class.":""}function K(e,o){return e.drone?.label??o??null}let y=!1,b=null;function Q(){y=!1,b=null}function $(e){const o=e??"",t=_.filter(n=>o.includes(n));return[...x,...t]}function P(e,o){return`rationale \u2192 borg_role-rationale ${JSON.stringify(e)} ${JSON.stringify(o)}`}function X(e){const o=e.match(/borg_role-rationale\s+("(?:(?:\\.)|[^"\\])*")\s+("(?:(?:\\.)|[^"\\])*")/);if(!o)return null;try{return{role:JSON.parse(o[1]),section:JSON.parse(o[2])}}catch{return null}}const F=[...x,..._];function U(e,o){return C(o??"").map(r=>{if(r.kind!=="label"||r.heading==null||!r.heading.trim().toLowerCase().endsWith("rationale")||F.some(l=>r.body.includes(l)))return r.body;const s=r.body.indexOf(`
1
+ import{ROLE_SCOPED_SAFETY_DISCIPLINES as _,UNIVERSAL_SAFETY_DISCIPLINES as x}from"./templates.js";import{parseRoleSections as C}from"./role-section.js";import{formatRoleAgentLabel as N}from"./roster-render.js";import{formatDroneAddressToken as T}from"./drone-address.js";import{shellEscape as f}from"./shell-escape.js";function q(e){if(!e||!e.trim())return null;try{const t=JSON.parse(e)?.source;return typeof t=="string"?t:null}catch{return null}}function D(e,o,t){return e==="codex"?["Required Codex wake path: Borg activity stream \u2192 inbox wake channel via app-server remote control.","On every wake, run `borg_read-log unread_only=true` and drain until caught up.","No additional scheduler setup is required.",'Degraded fallback (only if remote control is unavailable): on return, call `borg_regen mode="full"` and drain unread log.'].join(" "):e==="opencode"?"Wake path: OpenCode wakes via SDK-driven entry injection into the TUI session \u2014 there is no tail-Monitor or `/loop` heartbeat to arm. Cube posts are delivered directly into your active session. Use `borg_regen` at any time for fresh context.":["Arm your wake path before working:",`1. **Inbox Monitor** (wake path) \u2014 run a persistent Monitor on \`${t?`borg-inbox-monitor --state-root ${f(t)} ${f(o)}`:`borg-inbox-monitor ${f(o)}`}\` so cube posts wake you in real time.`,"2. **Engage `/loop`** (self-paced) so you keep waking to triage the cube.","3. **Fallback heartbeat** \u2014 set a ~3600s (60-min) `ScheduleWakeup` so you never go fully silent."].join(`
2
+ `)}function H(e,o){return{cubeName:o?.cube?.name??e.name,droneLabel:o?.drone?.label??e.droneLabel,roleName:o?.role?.name??e.roleName??null}}function V(e){const{cubeName:o,droneLabel:t,roleName:n,inboxPath:r,monitorStateRoot:i,agentKind:s,source:c}=e,l=c==="clear"?s==="codex"?"\n_(`/clear` cleared your conversation; Codex remote-control wake remains active. Follow the required Codex wake path below.)_\n":"\n_(`/clear` cleared your conversation + session-scoped `/loop` and `ScheduleWakeup` heartbeat \u2014 re-establish them now.)_\n":"";return[`# Cube: ${o} \u2014 ${t}`,"",`**Your role:** ${n||"_(call `borg_regen` to load)_"}`,l,"You are a Borg drone \u2014 coordinate through the cube log, and never pause for the user. Blocked \u2192 escalate to your cube's coordinating role.","",D(s,r,i),"",'REQUIRED BEFORE ACTING OR POSTING: (1) call `borg_regen mode="full"`; (2) load the cube directive and conventions with `borg_cube`; (3) ensure your own role playbook/details are loaded \u2014 full regen normally supplies them, but call `borg_role` if missing or after compaction; (4) load `borg_playbook` once per session for the complete operating disciplines. Do not proceed until all four are in context. This orientation stays lean and does not inline them. If you know this session\'s model, include `model="<model-id>"` in the initial full regen for advisory roster metadata.',""].join(`
3
+ `)}function E(){return"## How to operate as a Drone\n\nYou're a Drone in a Cube. Coordinate with other drones through the activity log.\n\n**User asks how Borg MCP works** \u2014 a feature, setup, pricing, or concept question? Call `borg_docs {topic}` for the documentation index, then WebFetch the matching section URL and answer from the page. Don't guess borgmcp's own behavior from memory.\n\n**Tools:**\n- `borg_regen` \u2014 refresh full state (your role, roster, unread-log COUNT, and fetch-on-demand pointers) in one call; the cube directive (\u2192 `borg_cube`), the operating-playbook detail (\u2192 `borg_playbook`), and the recent-log payload (\u2192 `borg_read-log` when count >0) are NOT inlined \u2014 fetch them on demand\n- `borg_cube` \u2014 re-read the cube directive and the role overview\n- `borg_role` \u2014 re-read your role's detailed playbook\n- `borg_roster` \u2014 see who else is connected\n- `borg_read-log unread_only=true [limit]` \u2014 drain unread log entries from your server-side cursor\n- `borg_log <message>` \u2014 append to the log\n- `borg_assimilate <cube>` \u2014 switch to a different cube\n\n**How coordination works:** the Cube gives primitives, not workflows. Your role's `detailed_description` (above) is your playbook \u2014 its conventions + signals come from there, not the system. The log is the coordination channel. Different cubes, different conventions.\n\n**Default: act autonomously, coordinate through the log.** Don't wait for user input. Need input \u2192 post the question, continue other work, other drones respond. The human supervisor is reachable through your cube's coordinating / human-seat role (the role your cube designates for direction + integration), or the Queen role when the seat is delegated to a drone \u2014 one continuous seat. Your role's `detailed_description` says when to escalate + which decisions need human input; follow it.\n\n**Operating loop \u2014 each wake, in order:**\n1. Drain unread: `borg_read-log unread_only=true` (oldest-first, repeat until `behind_by=0`) before acting. The \"Cube log\" section gives your UNREAD COUNT.\n2. Apply your role's conventions to each entry. Act on: questions you can answer; blocked peers you can unblock; unowned work you can claim; decisions affecting you.\n3. Actionable signal \u2192 act + post the convention. Don't wait to be asked.\n4. User prompt waiting \u2192 respond, informed by cube context; log substantive units (shipped changes, blockers, findings) regardless of who initiated.\n5. Nothing actionable + no prompt \u2192 done; wait for next wake.\n\n**On a `<task-notification>` wake:** the payload is a truncatable preview; the full entry is in the DB. Drain: `borg_read-log unread_only=true limit=20`, repeat until `behind_by=0`. Do NOT triage with `since=<notification timestamp>` (strict-after \u2014 skips the boundary entry) or a bare window (skips older-unread during bursts).\n\n**On first wake this session:** post one `ARRIVAL: <your-label> (<your-role>) online on <hostname> at <project-path>` (run `hostname`; use cwd for the path). One-time per session \u2014 don't repeat on later wakes; skip if already posted this session (e.g. after a `/mcp` reconnect).\n\n**When a log entry routes work to you** (a routing/assignment-class entry per your cube's conventions that names your label + asks for action, or a direct `<your-label>:` mention): call `borg_ack entry_id=<id>` within ~60s. Use the `borg_ack` TOOL, not an in-band `ACK:` post (it records a queryable flag + wakes the author's Monitor + keeps the log clean). Ack = receipt, not completion (`STARTING` / `DONE` still apply). Ack only routing-class signals \u2014 not every mention.\n\n**Claim a work item before you start it (`borg_ack ... kind=claim`):** `borg_ack` has two kinds \u2014 `ack` (receipt, the default) and `claim` (advisory ownership of a routed work item you are about to take). When a routed entry could be picked up by more than one drone, `borg_ack entry_id=<id> kind=claim` BEFORE starting \u2014 it announces you are taking it so peers skip the duplicate work, and wakes the rest of the entry's audience. If a live peer already holds the claim, skip it; if the claim is STALE (the claimant went silent past the wake-path SLA), re-claim and proceed. A claim is ADVISORY only \u2014 it NEVER substitutes for the completion or approval signal your role's conventions require; a bogus or abandoned claim can at most delay a work item, never bypass its real gate.\n\n**When stuck:** post your blocker per your role's conventions, continue other work. Escalation is per your role detail, not by stalling.\n\n**Anti-passive (lane idle = no work routed to you, no actionable signal in the log):**\n- If your work arrives via dispatch / a work queue: when your lane goes idle, post your role's availability signal (capacity clean, awaiting next assignment from your coordinating role) \u2014 once per idle period, don't spam. No assignment in ~15 min \u2192 ping your coordinating role (capacity available since <time>; any queue item to pick up?).\n- If your work is SELF-DIRECTED (not dispatch-driven): do NOT post an availability signal \u2014 proactively surface lane-substantive work per your role (reviews, audits, proposals, coherence / quality sweeps on relevant in-flight work).\n- Route work-asks through your cube's coordinating role, never directly to the human Queen.\n\n**Verify factual claims:** verify any verifiable claim \u2014 versions, code-state, prod behavior, npm state \u2014 against the SOURCE-OF-TRUTH surface (`git tag` / `git show <ref>:<path>` / grep, `curl` / `wrangler tail`, `npm view`, the live DB) BEFORE writing it; never a derivative artifact (another post, summary, or your own prior framing). The full discipline \u2014 the v1/v2/v3 sharpening levels, the per-claim-type concrete surfaces, and four-surface propagation (brainstorm / comment / review / issue-filing) \u2014 is in the operating-playbook chapter (`borg_playbook`; loaded via the session-start block in your regen).\n\n**Posting to the log:** post per your role's conventions whenever you start/finish a task, get stuck, answer a drone, or learn something others need \u2014 regardless of who initiated (a log signal, your own scan, or a user prompt). Conventions live in your role detail; the system is vocabulary-agnostic.\n\n**Routing posts \u2014 widen the directed default:** the taxonomy routes most prefixes DIRECTED to your cube's coordinating role; your `to:` / `visibility:` overrides it. Widen when a post must reach more than the coordinating role:\n- Posting a verdict / decision / result a specific drone is waiting on: add `to:[that drone]` so they're WOKEN \u2014 without it they can be left UNAWARE of their own merge or feedback. Directed governs the WAKE; it is NOT read-confidentiality: every member can read every entry \u2014 the cube is the trust boundary \u2014 so never post secrets relying on `to:[x]`.\n- Any drone posting a multi-seat DELIVERABLE (spec / security classification / review artifact 3+ seats build or gate against): pass `visibility:broadcast` (or `to:[the seats]`) EVEN IF your prefix (`DONE` etc.) is a directed status class \u2014 else only your coordinating role wakes (taxonomy routes by prefix, not payload) and the building/gating seats miss it.\n\n**Pre-commit git hygiene (universal):**\n\nAny drone that commits code: run `git diff --staged --stat` before `git commit` to verify file count + LOC direction + paths match your intent. Catches deleted files / anomalous -LOC / wrong paths pre-push. Your role may layer more git rules (code-implementing + coordinating roles typically carry the full set)."}const G=E();function J(){return'## Operating playbook \u2014 full disciplines (borg_playbook chapter)\n\nThis is the on-demand detail behind the rule-spine in your regen. Load it ONCE per session; it is static \u2014 do not re-fetch on every wake.\n\n**Verifying factual claims:**\n\nAny time you make a factual claim that could be verified \u2014 "this shipped as version Y", "function Z does W", "endpoint A returns B in prod", "package P is at version Q on npm" \u2014 verify the claim against a SOURCE-OF-TRUTH surface BEFORE writing it, not against a derivative artifact (another post, doc, summary, or your own prior framing). Three sharpening levels:\n\n- **v1 (verify against the actual surface):** check the claim against the surface it describes (e.g. a code-state claim \u2192 grep the file). Apply when the claim is about code-state.\n- **v2 (source-of-truth vs derivative artifacts):** when the verification surface itself could carry the original error chain (another post citing the same wrong claim, a doc copy-mirrored from the post you\'re checking), verify against the canonical source-of-truth: `git tag` for version-attribution, code-by-grep / direct file read for code-state, live `curl` or `wrangler tail` for prod-state, `npm view` for npm-state. Apply when version numbers, deploy timestamps, or other discrete facts are in scope.\n- **v3 (end-to-end execution path vs originating mechanism):** when verifying a live-mechanism claim ("the watchdog wakes silent drones"), verify the END-TO-END execution path, not just each isolated component \u2014 each isolated mechanism can be correct while the path between them silently breaks. Apply when live-mechanism correctness is being claimed; trace the path the wake/value/state actually takes from origin to terminal observer.\n\n**Concrete verification surfaces by claim type:**\n- Version attribution \u2192 `git tag --contains <sha>` or `git log --oneline <tag>`\n- Code state \u2192 match the grep surface to the claim surface:\n - Local uncommitted claim \u2192 `grep -n "<symbol>" <file>` or direct file read in the working tree\n - `origin/main`, PR head, branch, merge-SHA, or tag claim \u2192 `git show <ref>:<path> | grep -n "<symbol>"` (examples: `git show origin/main:workers/heartbeat.ts | grep -n "last_log_post"`; `git show origin/feat/foo:client/src/log-stream.ts | grep -n "ownDrone"`; `git show abc1234:workers/cubes.ts | grep -n "visibility"`)\n- Prod state \u2192 `curl https://<endpoint>` or `wrangler tail --env production`\n- npm registry state \u2192 `npm view <package>@<version>` or `npm view <package>@latest`\n- DB state \u2192 query through the existing `db` interface; never trust a doc claim about row counts / column values\n- Cube log state \u2192 `borg_read-log unread_only=true` for wake triage, draining until `behind_by=0`; don\'t cite from memory or from another drone\'s summary\n- Ratified cube decision \u2192 `borg_decisions {topic}` \u2014 cite the registry\'s active decision by topic; NEVER restate a ratified decision from memory (a memory restatement drifts on the axis). A ratified decision is a first-class verifiable claim type with its own source of truth: the active registry entry. Recording one is `borg_decide` (seat-holder only \u2014 recording IS the ratification act).\n\n**The discipline is universal to reviewer-class actions** (Code Reviewer formal gates + Security Auditor SR gates + PM-courtesy verifications + UX-courtesy reviews + any drone making a verification-worthy factual claim in their cube-log post). It lives in this universal playbook rather than any one role\'s text because it applies to ALL reviewers.\n\n**Four-surface propagation:**\n\nThe discipline applies at FOUR surfaces. Catches at the surface closest to origin are cheapest; catches at later surfaces have already propagated through earlier consumers:\n\n- **Surface 1 (brainstorm-proposal time)**: when a brainstorm contribution names specific code identifiers / API field names / enum values / column names / function signatures, the PROPOSING drone source-grep\'s the referenced file BEFORE composing the proposal. If the proposal cites current `origin/main` or a branch/SHA, grep that ref via `git show <ref>:<path> | grep`; working-tree grep is only for explicitly local/uncommitted claims. Cheapest catch surface; one drone catches one error.\n- **Surface 2 (comment/JSDoc/docstring writing time)**: when an implementation comment cites cross-file invariants (other modules\' thresholds, schema columns, enum values, semantic contracts), the WRITING drone source-grep\'s the referenced file BEFORE writing the comment. If the comment describes a merged/base/PR-head state, grep the named ref via `git show <ref>:<path> | grep`; don\'t let a stale local checkout stand in for the ref being described. Mid-cost catch; one drone catches one error but downstream reviewers may inherit the wrong mental model from the comment.\n- **Surface 3 (review-time verification)**: the existing review-class discipline (Code Reviewer formal gates + Security Auditor SR gates + PM/UX/QA courtesy reviews). Late catch opportunity; if the error propagated through Surfaces 1 + 2, multiple reviewers may have already trusted the framing instead of source-grepping themselves.\n- **Surface 4 (durable-tracking-artifact-writing time)**: when filing a deferred-tracking issue from a cube event payload, the FILING drone fetches the originating entry\'s full body from the cube log BEFORE composing the issue body. For routine wake triage, use `borg_read-log unread_only=true` and drain until caught up; do not rely on a truncated event preview or a `since=<same timestamp>` read, which can skip the boundary entry. Cube event previews can truncate substantive content (mid-paragraph cuts on long entries); filing from the truncated preview trusts a derivative artifact instead of the source-of-truth full entry. Most expensive surface \u2014 the filed issue becomes the cube\'s durable cross-cycle memory; correcting it requires a follow-up correction post, and later pickup drones inherit the incomplete framing if the correction is missed.\n\n**Ratified-decision drift is a four-surface drift-class.** A ratified cube decision restated from memory drifts exactly like a code-identifier claim \u2014 it propagates dispatch (Surface 1, brainstorm) \u2192 copy (Surface 2, comment) \u2192 gate (Surface 3, review), and the cheapest catch is at the brainstorm surface. At each surface, a drone restating a ratified decision source-reads `borg_decisions {topic}` FIRST: the active registry entry is the source of truth; your memory is a derivative artifact. Core rule \u2014 **cite ratified decisions by topic; never restate one from memory.**'}function I(e){const o=typeof e=="string"?new Date(e):e,t=Date.now()-o.getTime();if(!Number.isFinite(t)||t<0)return"just now";const n=Math.floor(t/1e3);if(n<60)return`${n}s ago`;const r=Math.floor(n/60);if(r<60)return`${r}m ago`;const i=Math.floor(r/60);return i<24?`${i}h ago`:`${Math.floor(i/24)}d ago`}function L(e){return e==null||Array.isArray(e)&&e.length===0?"Tip: no message taxonomy declared \u2014 set one to enable intent-based smart routing (#468). Use borg_update-cube with a taxonomy array, or add classes with borg_patch-taxonomy-class.":""}function K(e,o){return e.drone?.label??o??null}let y=!1,b=null;function Q(){y=!1,b=null}function $(e){const o=e??"",t=_.filter(n=>o.includes(n));return[...x,...t]}function P(e,o){return`rationale \u2192 borg_role-rationale ${JSON.stringify(e)} ${JSON.stringify(o)}`}function X(e){const o=e.match(/borg_role-rationale\s+("(?:(?:\\.)|[^"\\])*")\s+("(?:(?:\\.)|[^"\\])*")/);if(!o)return null;try{return{role:JSON.parse(o[1]),section:JSON.parse(o[2])}}catch{return null}}const F=[...x,..._];function U(e,o){return C(o??"").map(r=>{if(r.kind!=="label"||r.heading==null||!r.heading.trim().toLowerCase().endsWith("rationale")||F.some(l=>r.body.includes(l)))return r.body;const s=r.body.indexOf(`
4
4
  `);return(s===-1?r.body+`
5
5
  `:r.body.slice(0,s+1))+P(e,r.heading)+`
6
6
  `}).join("")}function Z(e,o={}){const t=o.mode??"full",n=e.roles.map(a=>`- **${a.name}**${a.is_default?" _(default)_":""} \u2014 ${a.short_description||"_(no short description)_"}`).join(`
7
- `),r=e.drones.map(a=>{const h=e.roles.find(p=>p.id===a.role_id),g=T(h?.name??"?",a.agent_kind);return`- **${a.label}** (${g}) \u2014 last seen ${I(new Date(a.last_seen))}`}).join(`
7
+ `),r=e.drones.map(a=>{const h=e.roles.find(p=>p.id===a.role_id),g=N(h?.name??"?",a.agent_kind);return`- **${a.label}** (${g}) \u2014 last seen ${I(new Date(a.last_seen))}`}).join(`
8
8
  `)||"_(no drones connected)_",i=typeof e.behind_by=="number"?e.behind_by:null,s=i===null?"Call `borg_read-log unread_only=true` to check for and drain any unread log entries (the log payload is not inlined in regen).":i>0?`You have **${i}** unread log ${i===1?"entry":"entries"}. Drain them with \`borg_read-log unread_only=true\` (oldest-unread first; repeat until \`behind_by=0\`). The log payload is not inlined here \u2014 fetch on demand.`:"You're caught up \u2014 **0** unread log entries. No need to read the log right now.",l=(i??0)===0&&e.drones.length<=1?["## Getting started","","Welcome to your first cube. Here's how to get going:","",'1. Post your first activity: `borg_log message="Starting work on <your task>"`',"2. Invite another agent session: open a new terminal and run `borg assimilate --worktree <name>`","3. Check who's here: `borg_roster`","","---",""].join(`
9
- `):"",v=L(e.cube.message_taxonomy),E=12,m=Array.isArray(e.decisions)?e.decisions:[],w=(()=>{if(m.length===0)return"";const a=m.slice(0,E),h=a.map(p=>`- **${p.topic}:** ${p.decision}`),g=m.length-a.length;return g>0&&h.push(`- _+${g} more \u2014 \`borg_decisions\`_`),["## Ratified decisions","Cite these by topic \u2014 do NOT restate a ratified decision from memory.",...h].join(`
9
+ `):"",v=L(e.cube.message_taxonomy),S=12,m=Array.isArray(e.decisions)?e.decisions:[],w=(()=>{if(m.length===0)return"";const a=m.slice(0,S),h=a.map(p=>`- **${p.topic}:** ${p.decision}`),g=m.length-a.length;return g>0&&h.push(`- _+${g} more \u2014 \`borg_decisions\`_`),["## Ratified decisions","Cite these by topic \u2014 do NOT restate a ratified decision from memory.",...h].join(`
10
10
  `)})(),d=e.role.detailed_description_hash??null,O=e.role.detailed_description?U(e.role.name,e.role.detailed_description):"_(no detailed description set)_",R="Before you post or act, load your full operating context \u2014 once per session; static, do NOT re-fetch on every wake:\n- `borg_playbook` \u2014 your full operating disciplines (verification, four-surface propagation, ack / routing / idle detail).\n- `borg_cube` \u2014 the cube directive + conventions (log vocabulary, project / git / dispatch conventions).",k=t==="full"||d==null||d!==b,A=t==="full"||!y,u=[l+`# Cube: ${e.cube.name} \u2014 ${e.drone.label}`,"",`**Your role:** ${e.role.name}`,""];return t==="lite"&&u.push('_(lite regen \u2014 the role playbook may be omitted when unchanged; your operating context (playbook + cube directive) loads via the Session-start block (borg_playbook + borg_cube). If the playbook is NOT in your current context (e.g. after a context-compaction), call `borg_regen mode="full"` to re-orient.)_',""),u.push(t==="full"?"## Session start \u2014 required before acting":"## Session start",t==="full"?R:'Operating context (playbook + cube directive) was loaded at session start \u2014 re-fetch `borg_playbook` / `borg_cube` ONLY after a context-compaction (a `mode="full"` regen), not on every wake.',"",...v?[v,""]:[],`## Your role: ${e.role.name}`,k?O:["_(role playbook unchanged since your last full/lite regen; omitted in lite mode)_","",...$(e.role.detailed_description)].join(`
11
- `),"","## Roles in this cube",n,"","## Connected drones",r,"","## Cube log",s,...w?["",w]:[]),A&&(u.push("",S()),y=!0),k&&d!=null&&(b=d),u.join(`
12
- `)}function z(e,o,t){const n=o.get(e.drone_id),r=n?t.get(n.role_id):null,i=new Date(e.created_at).toISOString(),s=typeof e.id=="string"&&e.id.length>0?` [entry_id: ${e.id}]`:"",c=typeof e.drone_id=="string"&&e.drone_id.length>0?` ${N(e.drone_id)}`:"";return`**[${i}]**${s}${c} ${n?.label??"?"} (${r?.name??"?"}): ${e.message}`}export{J as DRONE_PLAYBOOK,Q as __resetRegenSessionState,U as compressRoleText,V as formatLeanOrientation,z as formatLogEntryMarkdown,P as formatRationalePointer,Z as formatRegenMarkdown,S as getDronePlaybook,G as getDronePlaybookChapter,I as humanAgo,L as nullTaxonomyTip,q as parseHookSource,X as parseRationalePointer,K as regenWakePathDroneLabel,H as resolveLeanIdentity,D as wakePathArming};
11
+ `),"","## Roles in this cube",n,"","## Connected drones",r,"","## Cube log",s,...w?["",w]:[]),A&&(u.push("",E()),y=!0),k&&d!=null&&(b=d),u.join(`
12
+ `)}function z(e,o,t){const n=o.get(e.drone_id),r=n?t.get(n.role_id):null,i=new Date(e.created_at).toISOString(),s=typeof e.id=="string"&&e.id.length>0?` [entry_id: ${e.id}]`:"",c=typeof e.drone_id=="string"&&e.drone_id.length>0?` ${T(e.drone_id)}`:"";return`**[${i}]**${s}${c} ${n?.label??"?"} (${r?.name??"?"}): ${e.message}`}export{G as DRONE_PLAYBOOK,Q as __resetRegenSessionState,U as compressRoleText,V as formatLeanOrientation,z as formatLogEntryMarkdown,P as formatRationalePointer,Z as formatRegenMarkdown,E as getDronePlaybook,J as getDronePlaybookChapter,I as humanAgo,L as nullTaxonomyTip,q as parseHookSource,X as parseRationalePointer,K as regenWakePathDroneLabel,H as resolveLeanIdentity,D as wakePathArming};
package/dist/regen.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import{regen as a,listCubes as c}from"./remote-client.js";import{findProjectRoot as u,getActiveCube as h,inboxPathForDrone as l}from"./cubes.js";import{monitorStateRootForWorktree as p}from"./inbox-monitor.js";import{parseHookSource as b,formatLeanOrientation as m,resolveLeanIdentity as g}from"./regen-format.js";import{resolveSessionAgentKind as d}from"./codex-app-wake.js";import{handleVersionFlag as f}from"./version.js";import{gateAllowsActivation as w}from"./launch-gate.js";async function y(){if(process.stdin.isTTY)return"";try{const e=[];for await(const t of process.stdin)e.push(t);return Buffer.concat(e).toString("utf-8")}catch{return""}}async function v(){if(f(),!w("borg-regen SessionStart hook"))return;const e=b(await y()),t=await h();if(!t){await _();return}const o=l(t.cubeId,t.droneId),n=p(u()),r=d();let s=null;try{s=await a(t.sessionToken,t.apiUrl)}catch{s=null}const i=g(t,s);process.stdout.write(m({...i,inboxPath:o,monitorStateRoot:n,agentKind:r,source:e})+`
3
- `)}async function _(){const e=[];e.push("# Borg MCP \u2014 not connected to a cube"),e.push(""),e.push("The borg MCP server is installed in this project but no cube has been joined here yet."),e.push(""),e.push("**Drone instructions:**"),e.push("1. **Tell the user** which cubes exist on their account (see the list below) and ask them which one to join, or whether to create a new cube. This is the first thing you should surface in this session."),e.push("2. **Do not** call `borg_assimilate`, `borg_create-cube`, or any other state-changing borg tool until the user has chosen."),e.push("3. The user may also decline to use borg in this project at all \u2014 that's a valid choice; just stop suggesting it."),e.push("");try{const{cubes:t}=await c();if(t.length>0){e.push("## Cubes on your account");for(const o of t)e.push(`- **${o.name}** (id: ${o.id})`)}else e.push("## Cubes on your account"),e.push("_(none yet \u2014 offer to create one via `borg_create-cube` once the user confirms)_")}catch(t){e.push("## Cubes on your account"),e.push("_(could not list \u2014 "+(t?.message??String(t))+")_")}e.push(""),e.push("## Tools you can call once the user has chosen"),e.push('- Join an existing cube: `borg_assimilate cube_name="<their choice>"`'),e.push('- Create a new cube: `borg_create-cube name="<name>" cube_directive="<markdown>"` (optionally `template="software-dev"`)'),e.push("- See available templates: `borg_list-templates`"),process.stdout.write(e.join(`
2
+ import{regen as a,listCubes as c}from"./remote-client.js";import{findProjectRoot as u,getActiveCube as h,inboxPathForDrone as l}from"./cubes.js";import{monitorStateRootForWorktree as p}from"./inbox-monitor.js";import{parseHookSource as m,formatLeanOrientation as b,resolveLeanIdentity as g}from"./regen-format.js";import{resolveSessionAgentKind as d}from"./codex-app-wake.js";import{handleVersionFlag as f}from"./version.js";import{gateAllowsActivation as w}from"./launch-gate.js";import{resolveWorkingRepo as y}from"./working-repo.js";async function v(){if(process.stdin.isTTY)return"";try{const e=[];for await(const t of process.stdin)e.push(t);return Buffer.concat(e).toString("utf-8")}catch{return""}}async function _(){if(f(),!w("borg-regen SessionStart hook"))return;const e=m(await v()),t=await h();if(!t){await S();return}const o=l(t.cubeId,t.droneId),n=p(u()),r=d();let s=null;try{s=await a(t.sessionToken,t.apiUrl,{workingRepo:y()})}catch{s=null}const i=g(t,s);process.stdout.write(b({...i,inboxPath:o,monitorStateRoot:n,agentKind:r,source:e})+`
3
+ `)}async function S(){const e=[];e.push("# Borg MCP \u2014 not connected to a cube"),e.push(""),e.push("The borg MCP server is installed in this project but no cube has been joined here yet."),e.push(""),e.push("**Drone instructions:**"),e.push("1. **Tell the user** which cubes exist on their account (see the list below) and ask them which one to join, or whether to create a new cube. This is the first thing you should surface in this session."),e.push("2. **Do not** call `borg_assimilate`, `borg_create-cube`, or any other state-changing borg tool until the user has chosen."),e.push("3. The user may also decline to use borg in this project at all \u2014 that's a valid choice; just stop suggesting it."),e.push("");try{const{cubes:t}=await c();if(t.length>0){e.push("## Cubes on your account");for(const o of t)e.push(`- **${o.name}** (id: ${o.id})`)}else e.push("## Cubes on your account"),e.push("_(none yet \u2014 offer to create one via `borg_create-cube` once the user confirms)_")}catch(t){e.push("## Cubes on your account"),e.push("_(could not list \u2014 "+(t?.message??String(t))+")_")}e.push(""),e.push("## Tools you can call once the user has chosen"),e.push('- Join an existing cube: `borg_assimilate cube_name="<their choice>"`'),e.push('- Create a new cube: `borg_create-cube name="<name>" cube_directive="<markdown>"` (optionally `template="software-dev"`)'),e.push("- See available templates: `borg_list-templates`"),process.stdout.write(e.join(`
4
4
  `)+`
5
- `)}v().catch(e=>{const t=e?.message??String(e);process.stderr.write(`borg-regen: ${t}
5
+ `)}_().catch(e=>{const t=e?.message??String(e);process.stderr.write(`borg-regen: ${t}
6
6
  `),process.exit(1)});
@@ -8,6 +8,7 @@
8
8
  * - Offline queue for pending operations
9
9
  */
10
10
  import type { MessageTaxonomy, MessageTaxonomyClass } from './templates.js';
11
+ import { type WorkingRepo } from './working-repo.js';
11
12
  export declare const API_URL: string;
12
13
  /**
13
14
  * Parse a `Retry-After` header (delta-seconds form, which the worker
@@ -103,6 +104,7 @@ export declare function assimilate(cubeNameOrSelector: string | {
103
104
  short_description: string;
104
105
  detailed_description: string;
105
106
  is_default: boolean;
107
+ is_mandatory: boolean;
106
108
  is_human_seat: boolean;
107
109
  role_class: 'queen' | 'worker';
108
110
  created_at: string;
@@ -224,6 +226,10 @@ export declare function listDecisions(sessionToken: string, apiUrl: string, topi
224
226
  */
225
227
  export declare function regen(sessionToken: string, apiUrl: string, opts?: {
226
228
  since?: string;
229
+ /** Advisory self-report from the running agent; never model-routing config. */
230
+ reportedModel?: string;
231
+ /** Current cwd-derived identity; refreshed each regen to avoid stale routing data. */
232
+ workingRepo?: WorkingRepo;
227
233
  }): Promise<{
228
234
  cube: any;
229
235
  role: any;
@@ -383,6 +389,7 @@ export declare function createRole(cubeId: string, data: {
383
389
  short_description: string;
384
390
  detailed_description: string;
385
391
  is_default?: boolean;
392
+ is_mandatory?: boolean;
386
393
  is_human_seat?: boolean;
387
394
  can_broadcast?: boolean;
388
395
  receives_all_direct?: boolean;
@@ -399,6 +406,7 @@ export declare function updateRole(roleId: string, updates: {
399
406
  short_description?: string;
400
407
  detailed_description?: string;
401
408
  is_default?: boolean;
409
+ is_mandatory?: boolean;
402
410
  is_human_seat?: boolean;
403
411
  can_broadcast?: boolean;
404
412
  receives_all_direct?: boolean;
@@ -1 +1 @@
1
- import{getIdToken as f,getRefreshToken as h,clearTokens as m}from"./config.js";import{refreshIdToken as j,RefreshTokenInvalidError as T,RefreshTransientError as g}from"./auth.js";import{consolePrefix as R}from"./console-prefix.js";import{debugLog as b}from"./debug.js";import{assertUuidShape as $}from"./evict-drone.js";import{DroneEvictedError as C,DroneFrozenError as k,DRONE_EVICTED_CODE as P,DRONE_FROZEN_CODE as O,errorCodeFromBody as I}from"./drone-lifecycle.js";import{MODEL_DESCRIPTOR_REGEX as D}from"./model-presets.js";const L=process.env.BORG_API_URL||"https://api.borgmcp.ai",A=3,G=6e4;let l=null;function x(e){return l||(l=j(e).finally(()=>{l=null}),l)}function J(e){if(e==null)return null;const n=e.trim();return/^\d+$/.test(n)?parseInt(n,10)*1e3:null}function v(e,n,t=G,o=()=>Math.random()*500){const s=e??1e3*(n+1);return Math.min(s,t)+o()}function M(e){const n=(t,o)=>`${t}${/[.:!?]$/.test(t)?"":":"} ${o}`;try{const t=JSON.parse(e);if(typeof t?.error=="string")return typeof t.details=="string"?n(t.error,t.details):t.error;if(t?.error&&typeof t.error=="object"){const o=t.error.message,s=t.error.details??t.details;if(typeof o=="string"&&typeof s=="string")return n(o,s);if(typeof o=="string")return o}if(typeof t?.message=="string"&&typeof t?.details=="string")return n(t.message,t.details);if(typeof t?.message=="string")return t.message}catch{}return e}async function U(e,n,t){const o=t.maxRetries??A;let s=e,a=0;for(;s.status===429&&a<o;){const d=v(J(s.headers.get("Retry-After")),a,t.capMs,t.jitter);t.log?.(`rate limited (429); retrying in ${Math.round(d)}ms (attempt ${a+1}/${o})`),await t.sleep(d),a++,s=await n()}return s}function q(e){return new Promise(n=>setTimeout(n,e))}async function B(){let e=await f();if(!e){const n=await h(),t=n!=null;if(n)try{await x(n),e=await f()}catch(o){if(o instanceof T)await m();else throw o instanceof g?o:new g(`Token refresh failed unexpectedly (your saved login was NOT cleared \u2014 retry; if it persists, restart the borg session): ${o?.message??"unknown"}`)}if(!e)throw new Error(t?"Authentication expired \u2014 your saved login has expired. Run: borg setup":"Authentication required \u2014 you are not signed in. Run: borg setup")}return e}async function F(){const e=await h();if(!e)return null;try{return await x(e),await f()}catch(n){if(n instanceof T&&await m(),n instanceof g)throw n;return null}}async function Q(){if(await f())return"valid";const n=await h();if(!n)return"dead";try{return await x(n),await f()?"valid":"transient"}catch(t){return t instanceof T?(await m(),"dead"):"transient"}}async function r(e,n={}){let t=await B();const{droneSession:o,apiUrl:s,headers:a,...d}=n,_=s??L,y=(d.method??"GET").toUpperCase(),w=async c=>{const p={Authorization:`Bearer ${c}`,...a};o&&(p["X-Drone-Session"]=o),b(`\u2192 ${y} ${e}`);const u=await fetch(`${_}${e}`,{...d,headers:p});return b(`\u2190 ${u.status} ${y} ${e}`),u};let i=await w(t);if(i.status===401){const c=await F();c&&(t=c,i=await w(t))}if(i.status===401)throw new Error("Authentication required. Run: borg setup");if(i.status===429&&(i=await U(i,()=>w(t),{sleep:q,log:c=>console.error(`${R()}${c}`)})),!i.ok){const c=await i.text();b(`\u2717 ${i.status} ${y} ${e}: ${c}`);const p=M(c),u=I(c);if(i.status===410&&u===P)throw new C(p);if(i.status===423&&u===O)throw new k(p);if(i.status===429){const E=i.headers.get("Retry-After"),S=E?` (retry after ${E}s)`:"";throw new Error(`HTTP 429: rate limited${S}: ${p}`)}throw new Error(`HTTP ${i.status}: ${p}`)}return i}async function Y(e,n,t,o){const s={hostname:t??null};if((o==="claude"||o==="codex"||o==="opencode")&&(s.agent_kind=o),typeof e=="string")s.cube_name=e;else if(e.cube_id&&(s.cube_id=e.cube_id),e.cube_name&&(s.cube_name=e.cube_name),e.role_id&&(s.role_id=e.role_id),e.role_name&&(s.role_name=e.role_name),e.prior_drone_id&&(s.prior_drone_id=e.prior_drone_id),e.model!=null){if(!D.test(e.model))throw new Error(`Invalid model descriptor: "${e.model}" (expected "<claude|ollama>:<model>").`);s.model=e.model}return await(await r("/api/assimilate",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s),apiUrl:n})).json()}async function K(e,n){return await(await r("/api/drone/cube",{method:"GET",droneSession:e,apiUrl:n})).json()}async function ee(e,n){return await(await r("/api/drone/role",{method:"GET",droneSession:e,apiUrl:n})).json()}async function te(e,n,t){const o=new URLSearchParams({role:t});return await(await r(`/api/drone/role?${o.toString()}`,{method:"GET",droneSession:e,apiUrl:n})).json()}async function ne(e,n){return await(await r("/api/drone/whoami",{method:"GET",droneSession:e,apiUrl:n})).json()}async function oe(e,n,t){const o=t?`?since=${encodeURIComponent(t)}`:"";return await(await r(`/api/drone/roster${o}`,{method:"GET",droneSession:e,apiUrl:n})).json()}async function se(e,n,t={}){const o=new URLSearchParams;t.since&&o.set("since",t.since),t.limit!==void 0&&o.set("limit",String(t.limit)),t.unreadOnly&&o.set("unread_only","true");const s=o.toString();return await(await r(`/api/drone/log${s?`?${s}`:""}`,{method:"GET",droneSession:e,apiUrl:n})).json()}async function re(e,n,t,o="ack"){await r(`/api/drone/log/${t}/ack`,{method:"POST",body:JSON.stringify({kind:o}),droneSession:e,apiUrl:n})}async function ae(e,n,t){return await(await r("/api/drone/decide",{method:"POST",body:JSON.stringify(t),droneSession:e,apiUrl:n})).json()}async function ie(e,n,t){const o=t?`?topic=${encodeURIComponent(t)}`:"";return await(await r(`/api/drone/decisions${o}`,{method:"GET",droneSession:e,apiUrl:n})).json()}async function ce(e,n,t={}){const o=new URLSearchParams;t.since&&o.set("since",t.since);const s=o.toString();return await(await r(`/api/drone/regen${s?`?${s}`:""}`,{method:"GET",droneSession:e,apiUrl:n})).json()}async function pe(e,n,t,o){const s=new URLSearchParams({role:t,section:o});return await(await r(`/api/drone/role-rationale?${s.toString()}`,{method:"GET",droneSession:e,apiUrl:n})).json()}async function de(e,n,t,o={}){const s={message:t,...o.visibility?{visibility:o.visibility}:{},...o.recipientDroneIds?{recipientDroneIds:o.recipientDroneIds}:{},...o.class?{class:o.class}:{},...o.to?{to:o.to}:{}};return await(await r("/api/drone/log",{method:"POST",headers:{"Content-Type":"application/json"},droneSession:e,apiUrl:n,body:JSON.stringify(s)})).json()}async function ue(e,n,t){const o={kind:t.kind??"friction",message:t.message,...t.metadata?{metadata:t.metadata}:{}};return await(await r("/api/drone/report",{method:"POST",headers:{"Content-Type":"application/json"},droneSession:e,apiUrl:n,body:JSON.stringify(o)})).json()}async function fe(){const e=await r("/api/reports",{method:"GET"});if(e.status===403)return{forbidden:!0};if(!e.ok)throw new Error(`Failed to fetch reports: ${e.status}`);return{forbidden:!1,reports:(await e.json()).reports}}async function le(){return await(await r("/api/cubes",{method:"GET"})).json()}async function ye(){return await(await r("/api/templates",{method:"GET"})).json()}async function we(e,n,t){const o={cube_directive:n};e&&(o.name=e),t?.template&&(o.template=t.template),t&&Object.prototype.hasOwnProperty.call(t,"message_taxonomy")&&(o.message_taxonomy=t.message_taxonomy??null);const a=await(await r("/api/cubes",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(o)})).json();return a.cube?{...a.cube,roles:a.roles??[],drones:a.drones??[]}:a}async function he(e,n){return await(await r(`/api/cubes/${e}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).json()}async function me(e,n){return await(await r(`/api/cubes/${e}/taxonomy-patch`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).json()}async function Te(e){await r(`/api/cubes/${e}`,{method:"DELETE"})}async function ge(e,n){return await(await r(`/api/cubes/${e}/roles`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).json()}async function be(e,n){return await(await r(`/api/roles/${e}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).json()}async function xe(e,n){return await(await r(`/api/roles/${e}/section-patch`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).json()}async function Ee(e){await r(`/api/roles/${e}`,{method:"DELETE"})}async function $e(e,n){return $(e,"drone_id"),await(await r(`/api/drones/${e}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({role_id:n})})).json()}async function _e(e){$(e,"drone_id"),await r(`/api/drones/${e}`,{method:"DELETE"})}async function Se(e){const t=await(await r(`/api/cubes/${e}`,{method:"GET"})).json();return t.cube?{...t.cube,roles:t.roles??[],drones:t.drones??[]}:t}async function je(e,n){return await(await r(`/api/cubes/${e}/apply-template`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({template_name:n})})).json()}async function Re(){return await(await r("/api/subscription/status",{method:"GET"})).json()}async function Ce(e,n="software-dev",t=!1,o){return await(await r(`/api/cubes/${e}/sync-roles`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({template_name:n,apply:t,...o?{decisions:o}:{}})})).json()}async function ke(){const n=await(await r("/api/subscribe",{method:"POST",headers:{"Content-Type":"application/json"}})).json();if(!n.checkout_url)throw new Error("No checkout URL in response");return n.checkout_url}async function Pe(){const n=await(await r("/api/subscription/portal",{method:"POST",headers:{"Content-Type":"application/json"}})).json();if(!n.portal_url)throw new Error(n.message||"No portal URL in response");return n.portal_url}export{L as API_URL,re as ackLogEntry,de as appendLog,je as applyTemplate,Y as assimilate,Re as checkSubscriptionStatus,Pe as createBillingPortalSession,we as createCube,ge as createRole,ke as createSubscription,Te as deleteCube,Ee as deleteRole,_e as evictDrone,M as extractHttpErrorMessage,fe as fetchReports,Se as getCube,K as getCubeInfo,ee as getRoleInfo,te as getRoleInfoByName,oe as getRoster,B as getValidToken,le as listCubes,ie as listDecisions,ye as listTemplates,J as parseRetryAfterMs,xe as patchRoleSection,me as patchTaxonomyClass,Q as probeSession,v as rateLimitWaitMs,se as readLog,$e as reassignDrone,ae as recordDecision,ce as regen,U as retryOn429,pe as roleRationale,ue as submitReport,Ce as syncRoles,he as updateCube,be as updateRole,ne as whoami};
1
+ import{getIdToken as f,getRefreshToken as m,clearTokens as h}from"./config.js";import{refreshIdToken as S,RefreshTokenInvalidError as g,RefreshTransientError as T}from"./auth.js";import{consolePrefix as j}from"./console-prefix.js";import{debugLog as _}from"./debug.js";import{assertUuidShape as E}from"./evict-drone.js";import{DroneEvictedError as k,DroneFrozenError as C,DRONE_EVICTED_CODE as P,DRONE_FROZEN_CODE as O,errorCodeFromBody as I}from"./drone-lifecycle.js";import{MODEL_DESCRIPTOR_REGEX as D}from"./model-presets.js";import{canonicalizeWorkingRepoIdentity as L}from"./working-repo.js";const A=process.env.BORG_API_URL||"https://api.borgmcp.ai",G=3,M=6e4;let l=null;function b(e){return l||(l=S(e).finally(()=>{l=null}),l)}function J(e){if(e==null)return null;const t=e.trim();return/^\d+$/.test(t)?parseInt(t,10)*1e3:null}function v(e,t,n=M,o=()=>Math.random()*500){const r=e??1e3*(t+1);return Math.min(r,n)+o()}function U(e){const t=(n,o)=>`${n}${/[.:!?]$/.test(n)?"":":"} ${o}`;try{const n=JSON.parse(e);if(typeof n?.error=="string")return typeof n.details=="string"?t(n.error,n.details):n.error;if(n?.error&&typeof n.error=="object"){const o=n.error.message,r=n.error.details??n.details;if(typeof o=="string"&&typeof r=="string")return t(o,r);if(typeof o=="string")return o}if(typeof n?.message=="string"&&typeof n?.details=="string")return t(n.message,n.details);if(typeof n?.message=="string")return n.message}catch{}return e}async function q(e,t,n){const o=n.maxRetries??G;let r=e,i=0;for(;r.status===429&&i<o;){const p=v(J(r.headers.get("Retry-After")),i,n.capMs,n.jitter);n.log?.(`rate limited (429); retrying in ${Math.round(p)}ms (attempt ${i+1}/${o})`),await n.sleep(p),i++,r=await t()}return r}function B(e){return new Promise(t=>setTimeout(t,e))}async function F(){let e=await f();if(!e){const t=await m(),n=t!=null;if(t)try{await b(t),e=await f()}catch(o){if(o instanceof g)await h();else throw o instanceof T?o:new T(`Token refresh failed unexpectedly (your saved login was NOT cleared \u2014 retry; if it persists, restart the borg session): ${o?.message??"unknown"}`)}if(!e)throw new Error(n?"Authentication expired \u2014 your saved login has expired. Run: borg setup":"Authentication required \u2014 you are not signed in. Run: borg setup")}return e}async function H(){const e=await m();if(!e)return null;try{return await b(e),await f()}catch(t){if(t instanceof g&&await h(),t instanceof T)throw t;return null}}async function K(){if(await f())return"valid";const t=await m();if(!t)return"dead";try{return await b(t),await f()?"valid":"transient"}catch(n){return n instanceof g?(await h(),"dead"):"transient"}}async function s(e,t={}){let n=await F();const{droneSession:o,apiUrl:r,headers:i,...p}=t,$=r??A,y=(p.method??"GET").toUpperCase(),w=async c=>{const d={Authorization:`Bearer ${c}`,...i};o&&(d["X-Drone-Session"]=o),_(`\u2192 ${y} ${e}`);const u=await fetch(`${$}${e}`,{...p,headers:d});return _(`\u2190 ${u.status} ${y} ${e}`),u};let a=await w(n);if(a.status===401){const c=await H();c&&(n=c,a=await w(n))}if(a.status===401)throw new Error("Authentication required. Run: borg setup");if(a.status===429&&(a=await q(a,()=>w(n),{sleep:B,log:c=>console.error(`${j()}${c}`)})),!a.ok){const c=await a.text();_(`\u2717 ${a.status} ${y} ${e}: ${c}`);const d=U(c),u=I(c);if(a.status===410&&u===P)throw new k(d);if(a.status===423&&u===O)throw new C(d);if(a.status===429){const x=a.headers.get("Retry-After"),R=x?` (retry after ${x}s)`:"";throw new Error(`HTTP 429: rate limited${R}: ${d}`)}throw new Error(`HTTP ${a.status}: ${d}`)}return a}async function ee(e,t,n,o){const r={hostname:n??null};if((o==="claude"||o==="codex"||o==="opencode")&&(r.agent_kind=o),typeof e=="string")r.cube_name=e;else if(e.cube_id&&(r.cube_id=e.cube_id),e.cube_name&&(r.cube_name=e.cube_name),e.role_id&&(r.role_id=e.role_id),e.role_name&&(r.role_name=e.role_name),e.prior_drone_id&&(r.prior_drone_id=e.prior_drone_id),e.model!=null){if(!D.test(e.model))throw new Error(`Invalid model descriptor: "${e.model}" (expected "<claude|ollama>:<model>").`);r.model=e.model}return await(await s("/api/assimilate",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(r),apiUrl:t})).json()}async function ne(e,t){return await(await s("/api/drone/cube",{method:"GET",droneSession:e,apiUrl:t})).json()}async function te(e,t){return await(await s("/api/drone/role",{method:"GET",droneSession:e,apiUrl:t})).json()}async function oe(e,t,n){const o=new URLSearchParams({role:n});return await(await s(`/api/drone/role?${o.toString()}`,{method:"GET",droneSession:e,apiUrl:t})).json()}async function re(e,t){return await(await s("/api/drone/whoami",{method:"GET",droneSession:e,apiUrl:t})).json()}async function se(e,t,n){const o=n?`?since=${encodeURIComponent(n)}`:"";return await(await s(`/api/drone/roster${o}`,{method:"GET",droneSession:e,apiUrl:t})).json()}async function ie(e,t,n={}){const o=new URLSearchParams;n.since&&o.set("since",n.since),n.limit!==void 0&&o.set("limit",String(n.limit)),n.unreadOnly&&o.set("unread_only","true");const r=o.toString();return await(await s(`/api/drone/log${r?`?${r}`:""}`,{method:"GET",droneSession:e,apiUrl:t})).json()}async function ae(e,t,n,o="ack"){await s(`/api/drone/log/${n}/ack`,{method:"POST",body:JSON.stringify({kind:o}),droneSession:e,apiUrl:t})}async function ce(e,t,n){return await(await s("/api/drone/decide",{method:"POST",body:JSON.stringify(n),droneSession:e,apiUrl:t})).json()}async function pe(e,t,n){const o=n?`?topic=${encodeURIComponent(n)}`:"";return await(await s(`/api/drone/decisions${o}`,{method:"GET",droneSession:e,apiUrl:t})).json()}async function de(e,t,n={}){const o=new URLSearchParams;if(n.since&&o.set("since",n.since),n.reportedModel&&o.set("reported_model",n.reportedModel),n.workingRepo){o.set("working_repo_reported","1");const p=n.workingRepo.origin?L(n.workingRepo.origin):null;p&&o.set("working_repo_origin",p)}const r=o.toString();return await(await s(`/api/drone/regen${r?`?${r}`:""}`,{method:"GET",droneSession:e,apiUrl:t})).json()}async function ue(e,t,n,o){const r=new URLSearchParams({role:n,section:o});return await(await s(`/api/drone/role-rationale?${r.toString()}`,{method:"GET",droneSession:e,apiUrl:t})).json()}async function fe(e,t,n,o={}){const r={message:n,...o.visibility?{visibility:o.visibility}:{},...o.recipientDroneIds?{recipientDroneIds:o.recipientDroneIds}:{},...o.class?{class:o.class}:{},...o.to?{to:o.to}:{}};return await(await s("/api/drone/log",{method:"POST",headers:{"Content-Type":"application/json"},droneSession:e,apiUrl:t,body:JSON.stringify(r)})).json()}async function le(e,t,n){const o={kind:n.kind??"friction",message:n.message,...n.metadata?{metadata:n.metadata}:{}};return await(await s("/api/drone/report",{method:"POST",headers:{"Content-Type":"application/json"},droneSession:e,apiUrl:t,body:JSON.stringify(o)})).json()}async function ye(){const e=await s("/api/reports",{method:"GET"});if(e.status===403)return{forbidden:!0};if(!e.ok)throw new Error(`Failed to fetch reports: ${e.status}`);return{forbidden:!1,reports:(await e.json()).reports}}async function we(){return await(await s("/api/cubes",{method:"GET"})).json()}async function me(){return await(await s("/api/templates",{method:"GET"})).json()}async function he(e,t,n){const o={cube_directive:t};e&&(o.name=e),n?.template&&(o.template=n.template),n&&Object.prototype.hasOwnProperty.call(n,"message_taxonomy")&&(o.message_taxonomy=n.message_taxonomy??null);const i=await(await s("/api/cubes",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(o)})).json();return i.cube?{...i.cube,roles:i.roles??[],drones:i.drones??[]}:i}async function ge(e,t){return await(await s(`/api/cubes/${e}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})).json()}async function Te(e,t){return await(await s(`/api/cubes/${e}/taxonomy-patch`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})).json()}async function _e(e){await s(`/api/cubes/${e}`,{method:"DELETE"})}async function be(e,t){return await(await s(`/api/cubes/${e}/roles`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})).json()}async function xe(e,t){return await(await s(`/api/roles/${e}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})).json()}async function Ee(e,t){return await(await s(`/api/roles/${e}/section-patch`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})).json()}async function $e(e){await s(`/api/roles/${e}`,{method:"DELETE"})}async function Re(e,t){return E(e,"drone_id"),await(await s(`/api/drones/${e}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({role_id:t})})).json()}async function Se(e){E(e,"drone_id"),await s(`/api/drones/${e}`,{method:"DELETE"})}async function je(e){const n=await(await s(`/api/cubes/${e}`,{method:"GET"})).json();return n.cube?{...n.cube,roles:n.roles??[],drones:n.drones??[]}:n}async function ke(e,t){return await(await s(`/api/cubes/${e}/apply-template`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({template_name:t})})).json()}async function Ce(){return await(await s("/api/subscription/status",{method:"GET"})).json()}async function Pe(e,t="software-dev",n=!1,o){return await(await s(`/api/cubes/${e}/sync-roles`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({template_name:t,apply:n,...o?{decisions:o}:{}})})).json()}async function Oe(){const t=await(await s("/api/subscribe",{method:"POST",headers:{"Content-Type":"application/json"}})).json();if(!t.checkout_url)throw new Error("No checkout URL in response");return t.checkout_url}async function Ie(){const t=await(await s("/api/subscription/portal",{method:"POST",headers:{"Content-Type":"application/json"}})).json();if(!t.portal_url)throw new Error(t.message||"No portal URL in response");return t.portal_url}export{A as API_URL,ae as ackLogEntry,fe as appendLog,ke as applyTemplate,ee as assimilate,Ce as checkSubscriptionStatus,Ie as createBillingPortalSession,he as createCube,be as createRole,Oe as createSubscription,_e as deleteCube,$e as deleteRole,Se as evictDrone,U as extractHttpErrorMessage,ye as fetchReports,je as getCube,ne as getCubeInfo,te as getRoleInfo,oe as getRoleInfoByName,se as getRoster,F as getValidToken,we as listCubes,pe as listDecisions,me as listTemplates,J as parseRetryAfterMs,Ee as patchRoleSection,Te as patchTaxonomyClass,K as probeSession,v as rateLimitWaitMs,ie as readLog,Re as reassignDrone,ce as recordDecision,de as regen,q as retryOn429,ue as roleRationale,le as submitReport,Pe as syncRoles,ge as updateCube,xe as updateRole,re as whoami};