borgmcp 1.1.7 → 1.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -23,7 +23,11 @@
23
23
  * entry surfaces, which is the part that summarizes the entry.
24
24
  *
25
25
  * Usage:
26
- * borg-inbox-monitor <inbox-file-path>
26
+ * borg-inbox-monitor --state-root <worktree-runtime-root> <inbox-file-path>
27
+ *
28
+ * The state-root form is the supported launch path. The legacy positional-only
29
+ * form remains accepted for old hand-authored Monitor commands, and keeps its
30
+ * inbox-adjacent sidecars for compatibility while fleets transition.
27
31
  */
28
32
  export declare const RECENT_EMITTED_LINE_CAP = 1024;
29
33
  export declare class RecentLineDeduper {
@@ -94,10 +98,11 @@ export interface InboxLockDeps {
94
98
  /** File contents, or null if absent/unreadable. */
95
99
  read(path: string): string | null;
96
100
  /**
97
- * COMPARE-AND-DELETE: remove the file ONLY if its current content still
98
- * equals `expected`. A no-op if the content changed (a successor reclaimed)
99
- * or the file is gone so we never delete another live holder's pidfile
100
- * (gh#795 TOCTOU windows 1 + 3).
101
+ * Verify-then-unlink the file when its content equals `expected`.
102
+ *
103
+ * This primitive is NOT an atomic filesystem compare-and-swap. Callers must
104
+ * hold the per-inbox modern mutation lock around any destructive use; it is
105
+ * never used to mutate legacy inbox-adjacent artifacts.
101
106
  */
102
107
  removeIfContent(path: string, expected: string): void;
103
108
  /** kill(pid,0) liveness: true if the process exists (alive), false if gone (ESRCH). */
@@ -132,9 +137,35 @@ export declare function parsePidfileContent(trimmed: string): {
132
137
  * pidfile all return false (a false-reap is the deafness we prevent).
133
138
  */
134
139
  export declare function isHolderWedged(pidfilePath: string, holderNonce: string | null, deps: InboxLockDeps): boolean;
135
- export declare function pidfilePathFor(inboxPath: string): string;
140
+ /**
141
+ * gh#979: a worktree is the durable local identity for a seat. Drone UUIDs
142
+ * change on re-mint, but a reused worktree must retain the same monitor
143
+ * runtime home. Keep it inside the worktree (where workspace-only sandboxes
144
+ * can write), never under TMPDIR/XDG, and make its contents self-ignored so
145
+ * runtime lock churn never dirties the repository.
146
+ */
147
+ export declare function monitorStateRootForWorktree(worktreePath: string): string;
148
+ /** Legacy sidecars written by pre-gh#979 monitors beside the config inbox. */
149
+ export declare function legacyPidfilePathFor(inboxPath: string): string;
150
+ export declare function legacyHeartbeatPathFor(inboxPath: string): string;
151
+ /**
152
+ * State paths are keyed by the absolute inbox path within the explicitly
153
+ * supplied worktree runtime root. Omitting the root intentionally preserves
154
+ * the legacy inbox-adjacent layout for old manual commands; supported launch
155
+ * and orientation paths always pass the root explicitly.
156
+ */
157
+ export declare function pidfilePathFor(inboxPath: string, stateRoot?: string | null): string;
136
158
  /** gh#822: the holder-liveness heartbeat sidecar (mtime touched each tick). */
137
- export declare function heartbeatPathFor(inboxPath: string): string;
159
+ export declare function heartbeatPathFor(inboxPath: string, stateRoot?: string | null): string;
160
+ /**
161
+ * Prepare a private, worktree-local monitor runtime root. The supplied root
162
+ * must have the exact `<worktree>/.borgmcp/inbox-monitor` shape generated by
163
+ * `monitorStateRootForWorktree()`. Before any write, resolve the saved
164
+ * worktree canonically and reject a symlinked `.borgmcp` or `inbox-monitor`
165
+ * ancestor. Its local `.gitignore` ignores itself and all descendants, so
166
+ * runtime state produces no repository dirt without mutating tracked ignores.
167
+ */
168
+ export declare function ensureMonitorStateDir(stateRoot: string): string;
138
169
  export declare const HEARTBEAT_STALE_MS: number;
139
170
  /**
140
171
  * gh#822: `tail` args — ARM (`-n 0`, skip history, matches the prior shape) vs
@@ -146,12 +177,46 @@ export declare function tailArgsFor(inboxPath: string, fromByteOffset: number |
146
177
  /**
147
178
  * Try to become the SOLE monitor for this inbox. Returns true if we claimed the
148
179
  * pidfile (caller proceeds to tail + must release it on exit); false if a LIVE
149
- * holder already owns it (caller yields/exits without tailing). Never signals a
150
- * live PID, and never deletes a pidfile that changed under us (compare-and-
151
- * delete) only reaps a still-present provably-dead (ESRCH) / unparseable
152
- * pidfile, then re-claims.
180
+ * holder already owns it (caller yields/exits without tailing). The runtime
181
+ * calls it only while holding the modern per-inbox mutation lock, so stale
182
+ * reaping and successor claims are serialized. It never mutates legacy
183
+ * inbox-adjacent artifacts.
153
184
  */
154
185
  export declare function acquireInboxLock(pidfilePath: string, ownPid: number, deps: InboxLockDeps, maxAttempts?: number, ownNonce?: string): boolean;
186
+ /** Legacy migration outcome. `blocked` includes stale or unreadable artifacts. */
187
+ export type LegacyMonitorArtifactState = 'absent' | 'live' | 'blocked';
188
+ export interface LegacyArtifactDeps {
189
+ /** Fail closed: true when the artifact exists OR cannot be inspected. */
190
+ exists(path: string): boolean;
191
+ read(path: string): string | null;
192
+ isAlive(pid: number): boolean;
193
+ }
194
+ /**
195
+ * Conservative cross-version migration boundary (gh#979): an extant legacy
196
+ * pidfile OR heartbeat is never replaced or unlinked by modern code. A proven
197
+ * live PID yields to the existing old monitor; every other artifact is a
198
+ * blocked migration requiring explicit operator cleanup. This avoids trying to
199
+ * emulate an unavailable atomic unlink-if-content primitive across binaries
200
+ * that do not share the modern mutation lock.
201
+ */
202
+ export declare function legacyMonitorArtifactState(inboxPath: string, deps: LegacyArtifactDeps): LegacyMonitorArtifactState;
203
+ export interface ModernMonitorClaimDeps {
204
+ claimMutation(): boolean;
205
+ releaseMutation(): void;
206
+ legacyState(): LegacyMonitorArtifactState;
207
+ claimModern(): boolean;
208
+ releaseModern(): void;
209
+ }
210
+ export type ModernMonitorClaimResult = 'claimed' | 'mutation-busy' | 'modern-live' | 'legacy-live' | 'legacy-blocked';
211
+ /**
212
+ * Serialize every modern startup mutation for one inbox. The mutation lock is
213
+ * acquired BEFORE the first legacy read, spans modern lock claim, and protects
214
+ * the final legacy revalidation. An old binary that creates a legacy artifact
215
+ * at the former check→claim gap therefore makes the final check yield while
216
+ * preserving that artifact untouched.
217
+ */
218
+ export declare function claimModernMonitorSafely(deps: ModernMonitorClaimDeps): ModernMonitorClaimResult;
219
+ export declare function defaultInboxLockDeps(): InboxLockDeps;
155
220
  /**
156
221
  * gh#840: read the holder heartbeat sidecar for a pidfile's inbox.
157
222
  * Freshness = file mtime; identity = file content (the holder's nonce). Returns
@@ -161,6 +226,8 @@ export declare function readHeartbeatSidecar(pidfilePath: string): {
161
226
  mtimeMs: number;
162
227
  nonce: string;
163
228
  } | null;
229
+ /** A short-lived, atomic state-root guard for all modern lock mutations. */
230
+ export declare function mutationLockPathFor(pidfilePath: string): string;
164
231
  /**
165
232
  * gh#840: write the holder heartbeat sidecar — the per-holder identity nonce as
166
233
  * content; the FILE MTIME (touched on every write) is the freshness signal the
@@ -170,18 +237,21 @@ export declare function readHeartbeatSidecar(pidfilePath: string): {
170
237
  export declare function writeHeartbeat(heartbeatPath: string, nonce: string): void;
171
238
  /**
172
239
  * 2026-07-02 incident: first-drone-in-a-new-cube arm race. The kickoff Monitor
173
- * arms `borg-inbox-monitor <inbox>` at session start, but the per-cube inbox
174
- * directory (~/.config/borgmcp/inboxes/<cubeId>/) is created by the MCP server
175
- * child only when the SSE stream first writes — so on cube #1 / drone #1 the
176
- * monitor's FIRST fs act (the pidfile-claim writeFileSync) threw an uncaught
177
- * ENOENT exit 1 the wake path died at arm time. Creating the parent chain
178
- * up front makes arming order-independent with the stream owner. Plain
179
- * recursive mkdir, NO chmod must not disturb existing config-dir perms
180
- * (same constraint as assimilate-deps' mkdirp seam). The inbox FILE itself is
181
- * NOT created here: it's the stream owner's; `tail -F` retries on a missing
182
- * file. Exported for unit testing.
240
+ * pre-gh#979 `borg-inbox-monitor <inbox>` at session start, but the per-cube
241
+ * inbox directory (~/.config/borgmcp/inboxes/<cubeId>/) is created by the MCP
242
+ * server child only when the SSE stream first writes — so the legacy monitor's
243
+ * FIRST fs act (the pidfile-claim writeFileSync) threw ENOENT. The supported
244
+ * explicit-state-root mode no longer writes beside the inbox at all; this
245
+ * helper remains only for positional legacy compatibility. The inbox FILE is
246
+ * still the stream owner's; `tail -F` retries on a missing file.
183
247
  */
184
248
  export declare function ensureInboxDir(inboxPath: string): void;
249
+ interface MonitorInvocation {
250
+ inboxPath: string;
251
+ stateRoot: string | null;
252
+ }
253
+ /** Parse the supported explicit-root command plus the legacy positional form. */
254
+ export declare function parseMonitorInvocation(argv: string[]): MonitorInvocation | null;
185
255
  /**
186
256
  * Is this module being invoked as the bin entry point?
187
257
  *
@@ -195,4 +265,5 @@ export declare function ensureInboxDir(inboxPath: string): void;
195
265
  * Exported for unit testing.
196
266
  */
197
267
  export declare function isEntryInvocation(argv1: string, importMetaUrl: string): boolean;
268
+ export {};
198
269
  //# sourceMappingURL=inbox-monitor.d.ts.map
@@ -1,2 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import{spawn as $}from"node:child_process";import{randomBytes as I}from"node:crypto";import{linkSync as O,mkdirSync as N,readFileSync as h,realpathSync as L,statSync as w,unlinkSync as x,writeFileSync as y}from"node:fs";import{dirname as M}from"node:path";import{createInterface as D}from"node:readline";import{fileURLToPath as _}from"node:url";const R=/^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\S*)\s+(\S+)\s+\(([^)]+)\):\s*(.*)$/,C=1024;class F{cap;seen=new Set;order=[];constructor(e=C){if(this.cap=e,!Number.isInteger(e)||e<1)throw new Error("cap must be a positive integer")}remember(e){if(this.seen.has(e))return!1;for(this.seen.add(e),this.order.push(e);this.order.length>this.cap;){const r=this.order.shift();r&&this.seen.delete(r)}return!0}}function T(t){const e=R.exec(t);if(!e)return null;const[,,r,n,o]=e,i=o.trim();return`${r} (${n}): ${i}`}function H(t,e){const r=T(t);return r===null?null:e.remember(t)?r:null}function A(t,e,r=512){if(!Number.isInteger(r)||r<1)throw new Error("maxLines must be a positive integer");let n;try{n=h(t,"utf-8")}catch(i){if(i?.code==="ENOENT")return;throw i}const o=n.split(/\r?\n/);o.at(-1)===""&&o.pop();for(const i of o.slice(-r))T(i)!==null&&e.remember(i)}function G(t,e,r,n){if(t<e.lastEmittedOffset)return{kind:"rotation",state:{lastEmittedOffset:t,grewSince:null}};if(t===e.lastEmittedOffset)return{kind:"ok",state:{lastEmittedOffset:e.lastEmittedOffset,grewSince:null}};const o=e.grewSince??r,i={lastEmittedOffset:e.lastEmittedOffset,grewSince:o};return r-o>=n?{kind:"respawn",state:i}:{kind:"ok",state:i}}function P(t){const e=t.indexOf(":");return e===-1?{pid:Number.parseInt(t,10),nonce:null}:{pid:Number.parseInt(t.slice(0,e),10),nonce:t.slice(e+1)||null}}function K(t,e,r){if(!e||!r.readHeartbeat)return!1;const n=r.readHeartbeat(t);if(n===null)return!1;const o=r.now?r.now():Date.now(),i=r.heartbeatStaleMs??k;return o-n.mtimeMs>=i&&n.nonce===e}function q(t){return`${t}.monitor.pid`}function v(t){return`${t}.monitor.heartbeat`}const b=3e4,B=5*b,k=5*b;function U(t,e){return e===null?["-F","-n","0",t]:["-F","-c",`+${e+1}`,t]}function E(t){try{return w(t).size}catch{return 0}}function W(t,e,r,n=3,o){const i=o?`${e}:${o}`:String(e);for(let c=0;c<n;c++){if(r.claim(t,i))return!0;const a=r.read(t);if(a===null)continue;const u=a.trim();if(u===""){r.removeIfContent(t,a);continue}const{pid:f,nonce:p}=P(u);if(!Number.isNaN(f)&&r.isAlive(f)){if(K(t,p,r)){r.removeIfContent(t,a);continue}return!1}r.removeIfContent(t,a)}return!1}function X(){return{claim:(t,e)=>{const r=`${t}.tmp.${process.pid}.${I(6).toString("hex")}`;try{y(r,e,{mode:384});try{return O(r,t),!0}catch(n){if(n?.code==="EEXIST")return!1;throw n}}finally{try{x(r)}catch{}}},read:t=>{try{return h(t,"utf8")}catch{return null}},removeIfContent:(t,e)=>{try{h(t,"utf8")===e&&x(t)}catch{}},isAlive:t=>{try{return process.kill(t,0),!0}catch(e){return e?.code==="EPERM"}},readHeartbeat:Y,now:()=>Date.now(),heartbeatStaleMs:k}}function Y(t){const e=t.replace(/\.monitor\.pid$/,""),r=v(e);try{return{mtimeMs:w(r).mtimeMs,nonce:h(r,"utf8").trim()}}catch{return null}}function j(t,e){y(t,e,{mode:384})}function z(t){N(M(t),{recursive:!0})}function J(){const t=process.argv[2];t||(console.error("borg-inbox-monitor: usage: borg-inbox-monitor <inbox-path>"),process.exit(2));try{z(t)}catch(l){const s=l instanceof Error?l.message:String(l);console.error(`borg-inbox-monitor: cannot create inbox directory for ${t}: ${s}`),process.exit(1)}const e=q(t),r=X(),n=I(16).toString("hex");W(e,process.pid,r,3,n)||process.exit(0);const o=()=>r.removeIfContent(e,`${process.pid}:${n}`),i=new F;A(t,i);let c={lastEmittedOffset:E(t),grewSince:null},a=!1,u=null;const f=l=>{const s=$("tail",U(t,l),{stdio:["ignore","pipe","inherit"]});u=s,s.stdout||(console.error("borg-inbox-monitor: tail subprocess has no stdout"),o(),process.exit(1)),D({input:s.stdout,crlfDelay:1/0}).on("line",m=>{const d=H(m,i);d!==null&&(console.log(d),c={lastEmittedOffset:E(t),grewSince:null})}),s.on("error",m=>{s===u&&(console.error(`borg-inbox-monitor: tail failed: ${m.message}`),o(),process.exit(1))}),s.on("exit",(m,d)=>{s===u&&(o(),a&&process.exit(0),d&&process.exit(0),process.exit(m??0))})};f(null);const p=v(t),S=setInterval(()=>{try{j(p,n)}catch{}const l=G(E(t),c,Date.now(),B);if(c=l.state,l.kind==="respawn"&&!a){const s=u;f(c.lastEmittedOffset);try{s?.kill("SIGKILL")}catch{}c={lastEmittedOffset:c.lastEmittedOffset,grewSince:null}}},b);S.unref();const g=l=>{if(a)return;a=!0,clearInterval(S);try{x(p)}catch{}o();const s=u;s&&!s.killed&&!s.kill(l)&&process.exit(0),setTimeout(()=>process.exit(0),1e3).unref()};process.once("SIGTERM",()=>g("SIGTERM")),process.once("SIGINT",()=>g("SIGINT"))}function Q(t,e){try{return L(t)===_(e)}catch{return!1}}Q(process.argv[1],import.meta.url)&&J();export{k as HEARTBEAT_STALE_MS,C as RECENT_EMITTED_LINE_CAP,F as RecentLineDeduper,W as acquireInboxLock,z as ensureInboxDir,G as evaluateInboxTailStall,T as formatEventLine,H as formatFreshEventLine,v as heartbeatPathFor,Q as isEntryInvocation,K as isHolderWedged,P as parsePidfileContent,q as pidfilePathFor,Y as readHeartbeatSidecar,A as seedDeduperFromInboxTail,U as tailArgsFor,j as writeHeartbeat};
2
+ import{spawn as V}from"node:child_process";import{createHash as Z,randomBytes as F}from"node:crypto";import{chmodSync as k,linkSync as tt,lstatSync as x,mkdirSync as A,readFileSync as E,realpathSync as h,statSync as H,unlinkSync as _,writeFileSync as M}from"node:fs";import{basename as B,dirname as v,isAbsolute as et,join as d,relative as rt,resolve as b,sep as ot}from"node:path";import{createInterface as nt}from"node:readline";import{fileURLToPath as it}from"node:url";const st=/^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\S*)\s+(\S+)\s+\(([^)]+)\):\s*(.*)$/,ct=1024;class at{cap;seen=new Set;order=[];constructor(e=ct){if(this.cap=e,!Number.isInteger(e)||e<1)throw new Error("cap must be a positive integer")}remember(e){if(this.seen.has(e))return!1;for(this.seen.add(e),this.order.push(e);this.order.length>this.cap;){const r=this.order.shift();r&&this.seen.delete(r)}return!0}}function G(t){const e=st.exec(t);if(!e)return null;const[,,r,o,n]=e,i=n.trim();return`${r} (${o}): ${i}`}function lt(t,e){const r=G(t);return r===null?null:e.remember(t)?r:null}function ut(t,e,r=512){if(!Number.isInteger(r)||r<1)throw new Error("maxLines must be a positive integer");let o;try{o=E(t,"utf-8")}catch(i){if(i?.code==="ENOENT")return;throw i}const n=o.split(/\r?\n/);n.at(-1)===""&&n.pop();for(const i of n.slice(-r))G(i)!==null&&e.remember(i)}function ft(t,e,r,o){if(t<e.lastEmittedOffset)return{kind:"rotation",state:{lastEmittedOffset:t,grewSince:null}};if(t===e.lastEmittedOffset)return{kind:"ok",state:{lastEmittedOffset:e.lastEmittedOffset,grewSince:null}};const n=e.grewSince??r,i={lastEmittedOffset:e.lastEmittedOffset,grewSince:n};return r-n>=o?{kind:"respawn",state:i}:{kind:"ok",state:i}}function P(t){const e=t.indexOf(":");return e===-1?{pid:Number.parseInt(t,10),nonce:null}:{pid:Number.parseInt(t.slice(0,e),10),nonce:t.slice(e+1)||null}}function mt(t,e,r){if(!e||!r.readHeartbeat)return!1;const o=r.readHeartbeat(t);if(o===null)return!1;const n=r.now?r.now():Date.now(),i=r.heartbeatStaleMs??J;return n-o.mtimeMs>=i&&o.nonce===e}function At(t){if(typeof t!="string"||t.length===0)throw new Error(`invalid monitor worktree path: ${t}`);return d(b(t),".borgmcp","inbox-monitor")}function W(t){return`${t}.monitor.pid`}function K(t){return`${t}.monitor.heartbeat`}function q(t){return Z("sha256").update(b(t)).digest("hex")}function dt(t,e){return e?d(b(e),`${q(t)}.monitor.pid`):W(t)}function pt(t,e){return e?d(b(e),`${q(t)}.monitor.heartbeat`):K(t)}function ht(t){const e=b(t);if(B(e)!=="inbox-monitor"||B(v(e))!==".borgmcp")throw new Error(`unsafe monitor state path (expected <worktree>/.borgmcp/inbox-monitor): ${e}`);const r=h(v(v(e))),o=d(r,".borgmcp"),n=d(o,"inbox-monitor");if(!z(n,r))throw new Error(`unsafe monitor state root outside worktree: ${n}`);const i=X(o);if(i&&h(o)!==o)throw new Error(`unsafe monitor state ancestor changed: ${o}`);const c=i?X(n):null;if(c&&(U(n,c),Y(d(n,".gitignore"),n)),i||j(o,448),h(o)!==o)throw new Error(`unsafe monitor state ancestor changed: ${o}`);const s=!c;s&&j(n,448);const u=h(n);if(u!==n||!z(u,r))throw new Error(`unsafe monitor state root escaped worktree: ${n}`);const m=x(u);U(u,m);const f=d(u,".gitignore"),w=`*
3
+ `;if(s||Y(f,u),k(u,448),s&&(M(f,w,{encoding:"utf8",mode:384}),k(f,384)),h(n)!==n)throw new Error(`unsafe monitor state root changed while preparing: ${n}`);return n}function X(t){try{const e=x(t);if(!e.isDirectory()||e.isSymbolicLink())throw new Error(`unsafe monitor state ancestor (not a real directory): ${t}`);return e}catch(e){if(e?.code==="ENOENT")return null;throw e}}function j(t,e){try{A(t,{mode:e})}catch(o){throw o?.code==="EEXIST"?new Error(`unsafe monitor state path appeared during preparation: ${t}`):o}const r=x(t);if(!r.isDirectory()||r.isSymbolicLink())throw new Error(`unsafe monitor state ancestor (not a real directory): ${t}`)}function U(t,e){if(!e.isDirectory()||e.isSymbolicLink())throw new Error(`unsafe monitor state path (not a real directory): ${t}`);if(typeof process.getuid=="function"&&e.uid!==process.getuid())throw new Error(`unsafe monitor state directory owner: ${t}`)}function Y(t,e){let r;try{r=x(t)}catch(o){throw o?.code==="ENOENT"?new Error(`unsafe monitor state root missing Borg ownership marker: ${e}`):o}if(!r.isFile()||r.isSymbolicLink())throw new Error(`unsafe monitor state ignore path: ${t}`);if(E(t,"utf8")!==`*
4
+ `)throw new Error(`unsafe monitor state ignore path (not Borg-owned): ${t}`);if((r.mode&511)!==384)throw new Error(`unsafe monitor state ignore path (unexpected mode): ${t}`);if(typeof process.getuid=="function"&&r.uid!==process.getuid())throw new Error(`unsafe monitor state ignore path (unexpected owner): ${t}`)}function z(t,e){const r=rt(e,t);return r!==""&&r!==".."&&!r.startsWith(`..${ot}`)&&!et(r)}const T=3e4,bt=5*T,J=5*T;function wt(t,e){return e===null?["-F","-n","0",t]:["-F","-c",`+${e+1}`,t]}function N(t){try{return H(t).size}catch{return 0}}function yt(t,e,r,o=3,n){const i=n?`${e}:${n}`:String(e);for(let c=0;c<o;c++){if(r.claim(t,i))return!0;const s=r.read(t);if(s===null)continue;const u=s.trim();if(u===""){r.removeIfContent(t,s);continue}const{pid:m,nonce:f}=P(u);if(!Number.isNaN(m)&&r.isAlive(m)){if(mt(t,f,r)){r.removeIfContent(t,s);continue}return!1}r.removeIfContent(t,s)}return!1}function gt(t,e){const r=W(t),o=K(t),n=e.exists(r),i=e.exists(o);if(!n&&!i)return"absent";if(n){const c=e.read(r);if(c!==null){const{pid:s}=P(c.trim());if(!Number.isNaN(s)&&e.isAlive(s))return"live"}}return"blocked"}function xt(t){if(!t.claimMutation())return"mutation-busy";try{const e=t.legacyState();if(e==="live")return"legacy-live";if(e==="blocked")return"legacy-blocked";if(!t.claimModern())return"modern-live";const r=t.legacyState();return r!=="absent"?(t.releaseModern(),r==="live"?"legacy-live":"legacy-blocked"):"claimed"}finally{t.releaseMutation()}}function Et(){return{claim:(t,e)=>{const r=`${t}.tmp.${process.pid}.${F(6).toString("hex")}`;try{M(r,e,{mode:384});try{return tt(r,t),!0}catch(o){if(o?.code==="EEXIST")return!1;throw o}}finally{try{_(r)}catch{}}},read:t=>{try{return E(t,"utf8")}catch{return null}},removeIfContent:(t,e)=>{try{E(t,"utf8")===e&&_(t)}catch{}},isAlive:t=>{try{return process.kill(t,0),!0}catch(e){return e?.code==="EPERM"}},readHeartbeat:St,now:()=>Date.now(),heartbeatStaleMs:J}}function St(t){const e=$t(t);try{return{mtimeMs:H(e).mtimeMs,nonce:E(e,"utf8").trim()}}catch{return null}}function $t(t){return t.replace(/\.monitor\.pid$/,".monitor.heartbeat")}function It(t){return t.replace(/\.monitor\.pid$/,".monitor.mutation")}function vt(t){try{return x(t),!0}catch(e){return e?.code!=="ENOENT"}}function Q(t,e){M(t,e,{mode:384}),k(t,384)}function kt(t){A(v(t),{recursive:!0})}function Mt(t){return t.length===1&&t[0]?{inboxPath:t[0],stateRoot:null}:t.length===3&&t[0]==="--state-root"&&t[1]&&t[2]?{inboxPath:t[2],stateRoot:b(t[1])}:null}function Tt(){const t=Mt(process.argv.slice(2));t||(console.error("borg-inbox-monitor: usage: borg-inbox-monitor --state-root <worktree-runtime-root> <inbox-path>"),process.exit(2));const{inboxPath:e}=t;let r=t.stateRoot;try{r?r=ht(r):kt(e)}catch(l){const a=l instanceof Error?l.message:String(l),C=r?"runtime state directory":`inbox directory for ${e}`;console.error(`borg-inbox-monitor: cannot create ${C}: ${a}`),process.exit(1)}const o=Et(),n=dt(e,r),i=It(n),c=F(16).toString("hex"),s=`${process.pid}:${c}`,m=xt({claimMutation:()=>o.claim(i,s),releaseMutation:()=>o.removeIfContent(i,s),legacyState:()=>r?gt(e,{exists:vt,read:o.read,isAlive:o.isAlive}):"absent",claimModern:()=>yt(n,process.pid,o,3,c),releaseModern:()=>o.removeIfContent(n,`${process.pid}:${c}`)});m==="mutation-busy"&&(console.error(`borg-inbox-monitor: another modern monitor startup is mutating this inbox state; re-arm after it finishes (if it persists after that process stops, confirm it is stopped, remove ${i}, then re-arm)`),process.exit(1)),m==="legacy-live"&&process.exit(0),m==="legacy-blocked"&&(console.error(`borg-inbox-monitor: legacy monitor artifact remains beside ${e}; stop/confirm the old monitor, remove its .monitor.pid/.monitor.heartbeat manually, then re-arm`),process.exit(1)),m==="modern-live"&&process.exit(0);const f=()=>{if(o.claim(i,s))try{o.removeIfContent(n,`${process.pid}:${c}`)}finally{o.removeIfContent(i,s)}},w=pt(e,r),S=()=>{if(o.claim(i,s))try{o.removeIfContent(w,c)}finally{o.removeIfContent(i,s)}};try{Q(w,c)}catch(l){f();const a=l instanceof Error?l.message:String(l);console.error(`borg-inbox-monitor: cannot write runtime heartbeat: ${a}`),process.exit(1)}const O=new at;ut(e,O);let p={lastEmittedOffset:N(e),grewSince:null},$=!1,y=null;const R=l=>{const a=V("tail",wt(e,l),{stdio:["ignore","pipe","inherit"]});y=a,a.stdout||(console.error("borg-inbox-monitor: tail subprocess has no stdout"),S(),f(),process.exit(1)),nt({input:a.stdout,crlfDelay:1/0}).on("line",g=>{const I=lt(g,O);I!==null&&(console.log(I),p={lastEmittedOffset:N(e),grewSince:null})}),a.on("error",g=>{a===y&&(console.error(`borg-inbox-monitor: tail failed: ${g.message}`),S(),f(),process.exit(1))}),a.on("exit",(g,I)=>{a===y&&(S(),f(),$&&process.exit(0),I&&process.exit(0),process.exit(g??0))})};R(null);const D=setInterval(()=>{try{Q(w,c)}catch{}const l=ft(N(e),p,Date.now(),bt);if(p=l.state,l.kind==="respawn"&&!$){const a=y;R(p.lastEmittedOffset);try{a?.kill("SIGKILL")}catch{}p={lastEmittedOffset:p.lastEmittedOffset,grewSince:null}}},T);D.unref();const L=l=>{if($)return;$=!0,clearInterval(D),S(),f();const a=y;a&&!a.killed&&!a.kill(l)&&process.exit(0),setTimeout(()=>process.exit(0),1e3).unref()};process.once("SIGTERM",()=>L("SIGTERM")),process.once("SIGINT",()=>L("SIGINT"))}function Nt(t,e){try{return h(t)===it(e)}catch{return!1}}Nt(process.argv[1],import.meta.url)&&Tt();export{J as HEARTBEAT_STALE_MS,ct as RECENT_EMITTED_LINE_CAP,at as RecentLineDeduper,yt as acquireInboxLock,xt as claimModernMonitorSafely,Et as defaultInboxLockDeps,kt as ensureInboxDir,ht as ensureMonitorStateDir,ft as evaluateInboxTailStall,G as formatEventLine,lt as formatFreshEventLine,pt as heartbeatPathFor,Nt as isEntryInvocation,mt as isHolderWedged,K as legacyHeartbeatPathFor,gt as legacyMonitorArtifactState,W as legacyPidfilePathFor,At as monitorStateRootForWorktree,It as mutationLockPathFor,Mt as parseMonitorInvocation,P as parsePidfileContent,dt as pidfilePathFor,St as readHeartbeatSidecar,ut as seedDeduperFromInboxTail,wt as tailArgsFor,Q as writeHeartbeat};
package/dist/index.js CHANGED
@@ -1,38 +1,38 @@
1
1
  #!/usr/bin/env node
2
- import{Server as Y}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as G}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as K,ListToolsRequestSchema as X,ListPromptsRequestSchema as Z,GetPromptRequestSchema as ee}from"@modelcontextprotocol/sdk/types.js";import{assertRoleMatches as te}from"./role-match.js";import{getCubeInfo as oe,getRoleInfo as N,getRoleInfoByName as re,getRoster as ne,readLog as se,appendLog as ie,submitReport as ae,fetchReports as ce,ackLogEntry as le,recordDecision as de,listDecisions as ue,regen as j,listCubes as pe,createCube as me,updateCube as M,deleteCube as be,createRole as fe,updateRole as ge,patchRoleSection as k,patchTaxonomyClass as A,deleteRole as he,reassignDrone as _e,evictDrone as ye,getCube as w,checkSubscriptionStatus as we,createBillingPortalSession as xe,createSubscription as $e,syncRoles as ve,applyTemplate as ke,whoami as Ee,roleRationale as Ce,getValidToken as Re}from"./remote-client.js";import{startHealthBeatTick as Se}from"./health-beat.js";import{getTemplate as E,listTemplateNames as C,resolveCubeDirectiveForCreate as Ie,resolveCubeDirectiveForApply as Te,resolveMessageTaxonomyForCreate as qe}from"./templates.js";import{activeCubeWithFreshRegenIdentity as L,getActiveCube as h,setActiveCube as O,inboxPathForDrone as R}from"./cubes.js";import{addSessionStartHook as De,addUserPromptSubmitHook as Pe}from"./config-utils.js";import{humanAgo as B,formatLogEntryMarkdown as Ue,formatRegenMarkdown as F,getDronePlaybook as Ne,getDronePlaybookChapter as je,nullTaxonomyTip as Me,regenWakePathDroneLabel as Ae}from"./regen-format.js";import{startLogStream as Le,getStreamStatus as S}from"./log-stream.js";import{isMcpReadinessProbe as Oe}from"./readiness-probe.js";import{runMcpStartupServices as Be}from"./startup-services.js";import{TOOL_MANIFEST as Fe}from"./tool-manifest.js";import{DOCS_SECTIONS as He,matchDocsSections as We,formatDocsIndex as Ve}from"./docs-sections.js";import{renderRoleList as ze}from"./list-roles-render.js";import{filterToolsForRole as Je}from"./tool-scope.js";import{getPackageVersion as x,getOnDiskVersion as Qe,handleVersionFlag as Ye}from"./version.js";import{renderStreamStatus as Ge,checkInboxMonitorHealthy as I,formatWakePathPrefix as Ke,shouldShowWakePathWarning as Xe}from"./stream-status.js";import{formatRoleAgentLabel as Ze,renderRoster as et}from"./roster-render.js";import{resolveDroneIdByLabel as tt,isUuidShape as ot}from"./evict-drone.js";import{authRecoveryMessage as rt}from"./auth-recovery.js";import{DroneEvictedError as nt,DroneFrozenError as st,formatEvictedToolResult as it,formatFrozenToolResult as at}from"./drone-lifecycle.js";import{classifyInSessionAssimilate as ct,reattachOnlyRefusal as lt,reattachFailureMessage as dt}from"./assimilate-guard.js";import{gateAllowsActivation as ut,borgSessionToolNotice as pt}from"./launch-gate.js";import{renderSyncRolesResult as mt}from"./sync-roles-render.js";import{initConsolePrefix as bt,consolePrefix as $}from"./console-prefix.js";import{isCodexRemoteWakeEnabled as T,resolveSessionAgentKind as q,probeCodexBridgeArmed as ft}from"./codex-app-wake.js";import{connectOpenCodeDrone as gt,injectOpenCodeEntry as ht,probeOpenCodeDroneArmed as _t,computeOpenCodePort as yt}from"./opencode-drone.js";import{installBorgPlugin as wt}from"./opencode-plugin.js";import{setModuleInjectOpenCode as xt}from"./log-stream.js";import{lifecycleSignalForMessage as $t,recordLifecycleLog as H,shouldSuppressLifecycleLog as vt}from"./lifecycle-log-guard.js";import{normalizeDirectLogRecipients as kt}from"./direct-log.js";import W from"open";import Et from"os";function Ct(){try{const m=Et.hostname();return m&&m.trim()?m.trim().slice(0,255):null}catch{return null}}async function V(m,y){return await ke(m,y.name)}async function _(){const m=await h();if(!m)throw new Error("Not assimilated to a cube. Use borg_assimilate <cube-name> first.");return m}async function Rt(){Ye();const m=Oe();await Be(m,{sessionStartHook:()=>{De()},auditHook:()=>{Pe()},sseStream:()=>{Le()},openCode:async()=>{wt();const d=await h();if(d&&process.env.BORG_OPENCODE==="1"){const o=`http://127.0.0.1:${yt(d.droneId)}`;await gt({serverUrl:o,directory:process.cwd(),droneLabel:d.droneLabel,cubeName:d.name}),xt(ht)}},healthBeat:()=>{Se({getActiveCube:h,getStreamConnected:()=>S().connected,getInboxPath:d=>R(d.cubeId,d.droneId),checkMonitor:I,isCodexRemoteWake:T,probeBridgeArmed:d=>ft({cubeId:d.cubeId,droneId:d.droneId}),probeOpenCodeDrone:()=>_t(),resolveAgentKind:q,resolveHostname:Ct,resolveVersion:x,getToken:Re,fetchImpl:globalThis.fetch.bind(globalThis)})}});const y=new Y({name:"borg-mcp-client",version:x()},{capabilities:{tools:{},prompts:{}}}),D=Fe;y.setRequestHandler(X,async()=>{let d=null;try{const p=await h();p&&(d={roleName:p.roleName,roleClass:p.roleClass,isHumanSeat:p.isHumanSeat})}catch{d=null}return{tools:Je(D,d)}}),y.setRequestHandler(K,async d=>{let{name:p,arguments:o}=d.params;if(p==="borg_describe-tool"){const e=typeof o?.name=="string"?o.name:"",t=D.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(!ut(`tool ${p}`))return{content:[{type:"text",text:pt(p)}],isError:!0};try{switch(p){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=await j(e.sessionToken,e.apiUrl,{since:t}),s=L(e,n);s!==e&&await O(s);const i=S(),a=R(s.cubeId,s.droneId),c=q()==="opencode"||T()?!0:I(a),u=Xe(i,c)?Ke({inboxPath:a,droneLabel:Ae(n,s.droneLabel),cubeName:s.name}):"";let b="";try{const f=x(),l=Qe();if(f!=="unknown"&&l!=="unknown"&&l!==f){const[g,P,J]=f.split(".").map(Number),[v,U,Q]=l.split(".").map(Number);(v>g||v===g&&U>P||v===g&&U===P&&Q>J)&&(b=`## \u{1F504} borgmcp ${l} installed \u2014 run /mcp and reconnect (or restart Claude Code) to apply. Currently running ${f}.
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}.
3
3
 
4
- `)}}catch{}return{content:[{type:"text",text:b+u+F(n,{mode:r})}]}}case"borg_subscribe":return{content:[{type:"text",text:`Complete your subscription at: ${await $e()}`}]};case"borg_upgrade-subscription":{const e=await xe();try{await W(e)}catch{}return{content:[{type:"text",text:`Manage your Borg MCP subscription at: ${e}`}]}}case"borg_subscription_status":{const e=await we();return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}case"borg_open_dashboard":{const e="https://borgmcp.ai/dashboard";return await W(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=ct(t,e);if(r.kind!=="reattach")return{content:[{type:"text",text:lt(r,e)}],isError:!0};try{const n=await j(t.sessionToken,t.apiUrl,{}),s=L(t,n);return s!==t&&await O(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
- `)+F(n,{mode:"full"})}]}}catch(n){const s=dt(n??{});if(!s)throw n;return{content:[{type:"text",text:s}],isError:!0}}}case"borg_version":return{content:[{type:"text",text:`borgmcp ${x()}`}]};case"borg_playbook":return{content:[{type:"text",text:je()}]};case"borg_docs":{const e=typeof o?.topic=="string"?o.topic.trim():"",t=e?We(e):[],r=t.length>0?t:He;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: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:"}
6
6
 
7
- ${Ve(r)}`}]}}case"borg_whoami":{const e=await _(),t=await Ee(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([oe(e.sessionToken,e.apiUrl),N(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=Me(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})`:"",u=i.short_description||"_(no description)_";n.push(`- **${i.name}**${c} \u2014 ${u}`)}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(Ne()),{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:s}=await re(e.sessionToken,e.apiUrl,t);return te(t,s),{content:[{type:"text",text:[`# Role: ${s.name}`,"",s.detailed_description||"_(no detailed description set)_"].join(`
9
- `)}]}}const{role:r}=await N(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 Ce(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:s}=await ne(e.sessionToken,e.apiUrl,t);return{content:[{type:"text",text:et({cubeName:e.name,drones:r,roles:n,resolvedSince:s??null,humanAgo:B})}]}}case"borg_stream-status":{const e=S(),t=await h(),r=t?R(t.cubeId,t.droneId):null,n=t&&q()==="opencode",s=t?n||T()?!0:I(r):null;let i="";e.runLoopHealth==="silent-inert"&&(i=`## \u26A0 SSE stream loop silent-inert \u2014 run /mcp and reconnect to restart
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
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 a=Ge({status:e,inboxMonitorHealthy:s,inboxPath:r,droneLabel:t?.droneLabel??null,cubeName:t?.name??null,humanAgo:B});return{content:[{type:"text",text:i+a}]}}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:s,drones:i,roles:a,behind_by:c,has_more:u}=await se(e.sessionToken,e.apiUrl,{since:t,limit:r,unreadOnly:n}),b=new Map;for(const g of i)b.set(g.id,g);const f=new Map;for(const g of a)f.set(g.id,g);const l=[];if(l.push(`# Activity log: ${e.name}`),l.push(""),!s.length)l.push("_(no entries)_");else for(const g of s)l.push(Ue(g,b,f));return u===!0?(l.push(""),l.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&&(l.push(""),l.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:l.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($t(e)){const l=await vt(t,e);if(l.suppress)return await H(t,e),{content:[{type:"text",text:`Suppressed duplicate ${l.signal?.toUpperCase()} lifecycle log for ${t.droneLabel}; recent cube log already contains this signal.`}]}}const r=Object.prototype.hasOwnProperty.call(o??{},"to"),n=r?kt(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 ie(t.sessionToken,t.apiUrl,e,a);await H(t,e);const u=c.routing?.message?`
17
- ${c.routing.message}`:"",b=c.unreachableRecipients?.length?`
18
- \u26A0 ${c.unreachableRecipients.length} directed recipient(s) currently unreachable (wake-path:deaf): ${c.unreachableRecipients.map(l=>l.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})${u}${b}`}]}}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 ae(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 ce();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=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}
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 _();return await le(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:s}=await de(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 _(),{decisions:r}=await ue(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 pe();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 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})
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=E(r),!n))throw new Error(`Unknown template "${r}". Available: ${C().join(", ")}`);const s=Ie(t,n),i=qe(void 0,n),a=await me(e,s,{message_taxonomy:i});if(n){const u=await V(a.id,n),b=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 ${u.created} role(s) created, ${u.updated} updated.${b} 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 M(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 A(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 A(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 be(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:u}=await fe(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}:{}}),b=[u.role_class==="queen"?"Queen":null,u.is_human_seat?"human-seat":null,u.is_default?"default":null].filter(Boolean).join(", "),f=b?` (${b})`:"";return{content:[{type:"text",text:`Created role **${u.name}**${f} (id: ${u.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 ge(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 k(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 k(e,{action:t,heading:r,body:i,after:a}))}else({role:n}=await k(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 he(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 _e(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(!ot(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 w(r),a=tt(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 ye(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 w(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=Ze(a?.name??"?",i.agent_kind),u=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}${u}`});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=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}):
33
33
 
34
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 w(e);return{content:[{type:"text",text:ze(t,e)}]}}case"borg_list-templates":return{content:[{type:"text",text:`Available templates:
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:
36
36
 
37
- ${C().map(r=>{const n=E(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 ve(e,t,r,n);return{content:[{type:"text",text:mt(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=E(t);if(!r)throw new Error(`Unknown template "${t}". Available: ${C().join(", ")}`);const n=await V(e,r);let s="";const i=await w(e),a=Te(i.cube_directive,r);return a!==null&&(await M(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 nt)return{content:[{type:"text",text:it(e.message)}],isError:!0};if(e instanceof st)return{content:[{type:"text",text:at(e.message)}],isError:!0};const t=rt(e??{});return t?{content:[{type:"text",text:t}],isError:!0}:{content:[{type:"text",text:`Error: ${e.message}`}],isError:!0}}}),y.setRequestHandler(Z,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(ee,async d=>{const{name:p}=d.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 z=new G;await y.connect(z),await bt(),console.error(`${$()}\u25FC Borg MCP Client started`),console.error(`${$()}\u25FC Use borg_assimilate <cube-name> to join a cube as a drone`),console.error(`${$()}\u25FC Manage your cubes at https://borgmcp.ai/dashboard`)}Rt().catch(m=>{console.error(`${$()}Fatal error:`,m),process.exit(1)});
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)});
@@ -41,10 +41,11 @@ export type AgentKind = 'claude' | 'codex' | 'opencode';
41
41
  * be wrong; the recovery is a manual `borg_regen` on return.
42
42
  *
43
43
  * `inboxPath` is the deterministic client-generated UUID path
44
- * (`~/.config/borgmcp/inboxes/<cubeId>/<droneId>.log`) no user-controlled
45
- * metacharacters, same source as today's launch monitorClause.
44
+ * (`~/.config/borgmcp/inboxes/<cubeId>/<droneId>.log`), while the optional
45
+ * explicit state root is derived from the saved worktree path. Both are
46
+ * shell-escaped before rendering the launch/orientation command.
46
47
  */
47
- export declare function wakePathArming(agentKind: AgentKind, inboxPath: string): string;
48
+ export declare function wakePathArming(agentKind: AgentKind, inboxPath: string, monitorStateRoot?: string | null): string;
48
49
  /**
49
50
  * Resolve the lean-orientation identity (gh#927), preferring the fresh
50
51
  * network `regen()` result and falling back per-field to the local
@@ -95,6 +96,8 @@ export declare function formatLeanOrientation(args: {
95
96
  droneLabel: string;
96
97
  roleName?: string | null;
97
98
  inboxPath: string;
99
+ /** Explicit worktree-local root for Claude monitor PID/heartbeat state. */
100
+ monitorStateRoot?: string | null;
98
101
  agentKind: AgentKind;
99
102
  source?: string | null;
100
103
  }): string;
@@ -1,12 +1,12 @@
1
- import{ROLE_SCOPED_SAFETY_DISCIPLINES as k,UNIVERSAL_SAFETY_DISCIPLINES as _}from"./templates.js";import{parseRoleSections as A}from"./role-section.js";import{formatRoleAgentLabel as C}from"./roster-render.js";import{formatDroneAddressToken as T}from"./drone-address.js";function M(e){if(!e||!e.trim())return null;try{const r=JSON.parse(e)?.source;return typeof r=="string"?r:null}catch{return null}}function N(e,o){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 \`borg-inbox-monitor ${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 j(e,o){return{cubeName:o?.cube?.name??e.name,droneLabel:o?.drone?.label??e.droneLabel,roleName:o?.role?.name??e.roleName??null}}function q(e){const{cubeName:o,droneLabel:r,roleName:i,inboxPath:t,agentKind:n,source:s}=e,c=s==="clear"?n==="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 ${r}`,"",`**Your role:** ${i||"_(call `borg_regen` to load)_"}`,c,"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.","",N(n,t),"","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 x(){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 H=x();function V(){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 D(e){const o=typeof e=="string"?new Date(e):e,r=Date.now()-o.getTime();if(!Number.isFinite(r)||r<0)return"just now";const i=Math.floor(r/1e3);if(i<60)return`${i}s ago`;const t=Math.floor(i/60);if(t<60)return`${t}m ago`;const n=Math.floor(t/60);return n<24?`${n}h ago`:`${Math.floor(n/24)}d ago`}function I(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 J(e,o){return e.drone?.label??o??null}let f=!1,y=null;function G(){f=!1,y=null}function L(e){const o=e??"",r=k.filter(i=>o.includes(i));return[..._,...r]}function $(e,o){return`rationale \u2192 borg_role-rationale ${JSON.stringify(e)} ${JSON.stringify(o)}`}function K(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 P=[..._,...k];function F(e,o){return A(o??"").map(t=>{if(t.kind!=="label"||t.heading==null||!t.heading.trim().toLowerCase().endsWith("rationale")||P.some(p=>t.body.includes(p)))return t.body;const s=t.body.indexOf(`
4
- `);return(s===-1?t.body+`
5
- `:t.body.slice(0,s+1))+$(e,t.heading)+`
6
- `}).join("")}function Q(e,o={}){const r=o.mode??"full",i=e.roles.map(a=>`- **${a.name}**${a.is_default?" _(default)_":""} \u2014 ${a.short_description||"_(no short description)_"}`).join(`
7
- `),t=e.drones.map(a=>{const u=e.roles.find(g=>g.id===a.role_id),h=C(u?.name??"?",a.agent_kind);return`- **${a.label}** (${h}) \u2014 last seen ${D(new Date(a.last_seen))}`}).join(`
8
- `)||"_(no drones connected)_",n=typeof e.behind_by=="number"?e.behind_by:null,s=n===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).":n>0?`You have **${n}** unread log ${n===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.",p=(n??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
- `):"",b=I(e.cube.message_taxonomy),S=12,m=Array.isArray(e.decisions)?e.decisions:[],v=(()=>{if(m.length===0)return"";const a=m.slice(0,S),u=a.map(g=>`- **${g.topic}:** ${g.decision}`),h=m.length-a.length;return h>0&&u.push(`- _+${h} more \u2014 \`borg_decisions\`_`),["## Ratified decisions","Cite these by topic \u2014 do NOT restate a ratified decision from memory.",...u].join(`
10
- `)})(),l=e.role.detailed_description_hash??null,E=e.role.detailed_description?F(e.role.name,e.role.detailed_description):"_(no detailed description set)_",O="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).",w=r==="full"||l==null||l!==y,R=r==="full"||!f,d=[p+`# Cube: ${e.cube.name} \u2014 ${e.drone.label}`,"",`**Your role:** ${e.role.name}`,""];return r==="lite"&&d.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.)_',""),d.push(r==="full"?"## Session start \u2014 required before acting":"## Session start",r==="full"?O:'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.',"",...b?[b,""]:[],`## Your role: ${e.role.name}`,w?E:["_(role playbook unchanged since your last full/lite regen; omitted in lite mode)_","",...L(e.role.detailed_description)].join(`
11
- `),"","## Roles in this cube",i,"","## Connected drones",t,"","## Cube log",s,...v?["",v]:[]),R&&(d.push("",x()),f=!0),w&&l!=null&&(y=l),d.join(`
12
- `)}function X(e,o,r){const i=o.get(e.drone_id),t=i?r.get(i.role_id):null,n=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`**[${n}]**${s}${c} ${i?.label??"?"} (${t?.name??"?"}): ${e.message}`}export{H as DRONE_PLAYBOOK,G as __resetRegenSessionState,F as compressRoleText,q as formatLeanOrientation,X as formatLogEntryMarkdown,$ as formatRationalePointer,Q as formatRegenMarkdown,x as getDronePlaybook,V as getDronePlaybookChapter,D as humanAgo,I as nullTaxonomyTip,M as parseHookSource,K as parseRationalePointer,J as regenWakePathDroneLabel,j as resolveLeanIdentity,N as wakePathArming};
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(`
4
+ `);return(s===-1?r.body+`
5
+ `:r.body.slice(0,s+1))+P(e,r.heading)+`
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(`
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(`
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};