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.
- package/dist/inbox-monitor.d.ts +14 -0
- package/dist/inbox-monitor.js +1 -1
- package/package.json +1 -1
package/dist/inbox-monitor.d.ts
CHANGED
|
@@ -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
|
*
|
package/dist/inbox-monitor.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{spawn as
|
|
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.
|
|
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",
|