borgmcp 1.0.57 → 1.0.58

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.
@@ -168,6 +168,20 @@ export declare function readHeartbeatSidecar(pidfilePath: string): {
168
168
  * read that content; mtime was always the freshness source).
169
169
  */
170
170
  export declare function writeHeartbeat(heartbeatPath: string, nonce: string): void;
171
+ /**
172
+ * 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.
183
+ */
184
+ export declare function ensureInboxDir(inboxPath: string): void;
171
185
  /**
172
186
  * Is this module being invoked as the bin entry point?
173
187
  *
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import{spawn as O}from"node:child_process";import{randomBytes as I}from"node:crypto";import{linkSync as N,readFileSync as h,realpathSync as $,statSync as g,unlinkSync as x,writeFileSync as y}from"node:fs";import{createInterface as L}from"node:readline";import{fileURLToPath as M}from"node:url";const _=/^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\S*)\s+(\S+)\s+\(([^)]+)\):\s*(.*)$/,D=1024;class R{cap;seen=new Set;order=[];constructor(e=D){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=_.exec(t);if(!e)return null;const[,,r,n,o]=e,i=o.trim();return`${r} (${n}): ${i}`}function C(t,e){const r=T(t);return r===null?null:e.remember(t)?r:null}function F(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 H(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 A(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 G(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 P(t){return`${t}.monitor.pid`}function v(t){return`${t}.monitor.heartbeat`}const b=3e4,K=5*b,k=5*b;function q(t,e){return e===null?["-F","-n","0",t]:["-F","-c",`+${e+1}`,t]}function E(t){try{return g(t).size}catch{return 0}}function B(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 l=r.read(t);if(l===null)continue;const a=l.trim();if(a===""){r.removeIfContent(t,l);continue}const{pid:f,nonce:p}=A(a);if(!Number.isNaN(f)&&r.isAlive(f)){if(G(t,p,r)){r.removeIfContent(t,l);continue}return!1}r.removeIfContent(t,l)}return!1}function U(){return{claim:(t,e)=>{const r=`${t}.tmp.${process.pid}.${I(6).toString("hex")}`;try{y(r,e,{mode:384});try{return N(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:W,now:()=>Date.now(),heartbeatStaleMs:k}}function W(t){const e=t.replace(/\.monitor\.pid$/,""),r=v(e);try{return{mtimeMs:g(r).mtimeMs,nonce:h(r,"utf8").trim()}}catch{return null}}function X(t,e){y(t,e,{mode:384})}function Y(){const t=process.argv[2];t||(console.error("borg-inbox-monitor: usage: borg-inbox-monitor <inbox-path>"),process.exit(2));const e=P(t),r=U(),n=I(16).toString("hex");B(e,process.pid,r,3,n)||process.exit(0);const o=()=>r.removeIfContent(e,`${process.pid}:${n}`),i=new R;F(t,i);let c={lastEmittedOffset:E(t),grewSince:null},l=!1,a=null;const f=u=>{const s=O("tail",q(t,u),{stdio:["ignore","pipe","inherit"]});a=s,s.stdout||(console.error("borg-inbox-monitor: tail subprocess has no stdout"),o(),process.exit(1)),L({input:s.stdout,crlfDelay:1/0}).on("line",m=>{const d=C(m,i);d!==null&&(console.log(d),c={lastEmittedOffset:E(t),grewSince:null})}),s.on("error",m=>{s===a&&(console.error(`borg-inbox-monitor: tail failed: ${m.message}`),o(),process.exit(1))}),s.on("exit",(m,d)=>{s===a&&(o(),l&&process.exit(0),d&&process.exit(0),process.exit(m??0))})};f(null);const p=v(t),S=setInterval(()=>{try{X(p,n)}catch{}const u=H(E(t),c,Date.now(),K);if(c=u.state,u.kind==="respawn"&&!l){const s=a;f(c.lastEmittedOffset);try{s?.kill("SIGKILL")}catch{}c={lastEmittedOffset:c.lastEmittedOffset,grewSince:null}}},b);S.unref();const w=u=>{if(l)return;l=!0,clearInterval(S);try{x(p)}catch{}o();const s=a;s&&!s.killed&&!s.kill(u)&&process.exit(0),setTimeout(()=>process.exit(0),1e3).unref()};process.once("SIGTERM",()=>w("SIGTERM")),process.once("SIGINT",()=>w("SIGINT"))}function j(t,e){try{return $(t)===M(e)}catch{return!1}}j(process.argv[1],import.meta.url)&&Y();export{k as HEARTBEAT_STALE_MS,D as RECENT_EMITTED_LINE_CAP,R as RecentLineDeduper,B as acquireInboxLock,H as evaluateInboxTailStall,T as formatEventLine,C as formatFreshEventLine,v as heartbeatPathFor,j as isEntryInvocation,G as isHolderWedged,A as parsePidfileContent,P as pidfilePathFor,W as readHeartbeatSidecar,F as seedDeduperFromInboxTail,q as tailArgsFor,X as writeHeartbeat};
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};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "borgmcp",
3
- "version": "1.0.57",
3
+ "version": "1.0.58",
4
4
  "description": "Coordinate AI coding agents in shared cubes. Works with Claude Code and Codex. Create projects, assign roles, and share a live activity log.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",