borgmcp 1.0.14 → 1.0.15
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/codex-app-wake.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { CodexAppServerClient } from './codex-app-server.js';
|
|
|
3
3
|
import { checkCodexBridgeHealthy } from './codex-remote.js';
|
|
4
4
|
export declare const CODEX_WAKE_PROMPT = "New Borg cube-log activity arrived.";
|
|
5
5
|
export declare function formatCodexWakePrompt(inboxLine: string): string;
|
|
6
|
+
export declare const CODEX_CATCHUP_PROMPT = "Borg cube activity arrived while you were busy. Run `borg:read-log unread_only=true` and DRAIN \u2014 repeat until the returned page is under the limit and behind_by is 0 \u2014 so no entries are skipped.";
|
|
6
7
|
export declare function isCodexRemoteWakeEnabled(env?: NodeJS.ProcessEnv): boolean;
|
|
7
8
|
export declare function resolveSessionAgentKind(env?: NodeJS.ProcessEnv): 'claude' | 'codex';
|
|
8
9
|
export interface CodexWakeTarget {
|
|
@@ -35,6 +36,8 @@ export interface CodexWakeDeps {
|
|
|
35
36
|
getActiveCube?: typeof getActiveCube;
|
|
36
37
|
getCodexWakeTarget?: typeof getCodexWakeTarget;
|
|
37
38
|
createClient?: (socketPath: string) => Pick<CodexAppServerClient, 'connect' | 'readThread' | 'startTurn' | 'close'>;
|
|
39
|
+
sleep?: (ms: number) => Promise<void>;
|
|
40
|
+
now?: () => number;
|
|
38
41
|
}
|
|
39
42
|
export declare function wakeCodexViaAppServer(reason?: string, env?: NodeJS.ProcessEnv, deps?: CodexWakeDeps): void;
|
|
40
43
|
export declare function resetCodexWakeForTests(): void;
|
package/dist/codex-app-wake.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{getActiveCube as
|
|
2
|
-
${e}`}function
|
|
1
|
+
import{getActiveCube as h,getCodexWakeTarget as f}from"./cubes.js";import{CodexAppServerClient as g}from"./codex-app-server.js";import{checkCodexBridgeHealthy as p}from"./codex-remote.js";import{recordEventReceipt as C}from"./health-beat.js";const v="New Borg cube-log activity arrived.";function B(e){return`New Borg cube-log activity arrived:
|
|
2
|
+
${e}`}const k="Borg cube activity arrived while you were busy. Run `borg:read-log unread_only=true` and DRAIN \u2014 repeat until the returned page is under the limit and behind_by is 0 \u2014 so no entries are skipped.",y=5e3,x=15*6e4;function w(e=process.env){return e.BORG_CODEX_REMOTE_WAKE==="1"}function S(e=process.env){return w(e)?"codex":"claude"}function _(e=process.env){return w(e)?{enabled:!0}:{enabled:!1}}async function U(e,t={}){try{const r=await(t.getCodexWakeTarget??f)(e.cubeId,e.droneId);return r?(t.checkBridge??p)(r.socketPath):!1}catch{return null}}let s=!1;const d=[],c=new Set,u=[],b=100;let l=!1;function T(e){return new Promise(t=>setTimeout(t,e))}function M(e=v,t=process.env,a={}){_(t).enabled&&(d.push({reason:e,deps:a}),!s&&(s=!0,P().finally(()=>{s=!1})))}async function P(){for(;d.length>0;){const e=d.shift();await m(e.reason,e.deps)}}async function m(e,t){try{const a=await(t.getActiveCube??h)();if(!a)return;const r=await(t.getCodexWakeTarget??f)(a.cubeId,a.droneId);if(!r)return;const o=`${r.threadId}\0${e}`;if(c.has(o))return;const n=t.createClient?t.createClient(r.socketPath):new g(r.socketPath);await n.connect();try{if((await n.readThread(r.threadId))?.status?.type==="active"){I(t);return}await n.startTurn(r.threadId,e),C(),E(o)}finally{n.close()}}catch{}}function I(e){l||(l=!0,A(e).finally(()=>{l=!1}))}async function A(e){const t=e.sleep??T,a=e.now??Date.now,r=a()+x;for(;a()<r;){await t(y);try{const o=await(e.getActiveCube??h)();if(!o)continue;const n=await(e.getCodexWakeTarget??f)(o.cubeId,o.droneId);if(!n)continue;const i=e.createClient?e.createClient(n.socketPath):new g(n.socketPath);await i.connect();try{if((await i.readThread(n.threadId))?.status?.type==="active")continue;await i.startTurn(n.threadId,k),C();return}finally{i.close()}}catch{}}}function H(){s=!1,d.length=0,c.clear(),u.length=0,l=!1}function E(e){if(!c.has(e))for(c.add(e),u.push(e);u.length>b;){const t=u.shift();t&&c.delete(t)}}export{k as CODEX_CATCHUP_PROMPT,v as CODEX_WAKE_PROMPT,B as formatCodexWakePrompt,w as isCodexRemoteWakeEnabled,U as probeCodexBridgeArmed,H as resetCodexWakeForTests,_ as resolveCodexWakeTarget,S as resolveSessionAgentKind,M as wakeCodexViaAppServer};
|
package/dist/inbox-monitor.d.ts
CHANGED
|
@@ -49,6 +49,36 @@ export declare class RecentLineDeduper {
|
|
|
49
49
|
export declare function formatEventLine(inboxLine: string): string | null;
|
|
50
50
|
export declare function formatFreshEventLine(inboxLine: string, deduper: RecentLineDeduper): string | null;
|
|
51
51
|
export declare function seedDeduperFromInboxTail(inboxPath: string, deduper: RecentLineDeduper, maxLines?: number): void;
|
|
52
|
+
export interface InboxLockDeps {
|
|
53
|
+
/**
|
|
54
|
+
* ATOMICALLY create the pidfile WITH `content` iff it does not exist. True =
|
|
55
|
+
* claimed, false = already exists. Atomic-with-content (no create-then-write
|
|
56
|
+
* gap) so a concurrent reader never sees an empty pidfile (gh#795 TOCTOU
|
|
57
|
+
* window 2).
|
|
58
|
+
*/
|
|
59
|
+
claim(path: string, content: string): boolean;
|
|
60
|
+
/** File contents, or null if absent/unreadable. */
|
|
61
|
+
read(path: string): string | null;
|
|
62
|
+
/**
|
|
63
|
+
* COMPARE-AND-DELETE: remove the file ONLY if its current content still
|
|
64
|
+
* equals `expected`. A no-op if the content changed (a successor reclaimed)
|
|
65
|
+
* or the file is gone — so we never delete another live holder's pidfile
|
|
66
|
+
* (gh#795 TOCTOU windows 1 + 3).
|
|
67
|
+
*/
|
|
68
|
+
removeIfContent(path: string, expected: string): void;
|
|
69
|
+
/** kill(pid,0) liveness: true if the process exists (alive), false if gone (ESRCH). */
|
|
70
|
+
isAlive(pid: number): boolean;
|
|
71
|
+
}
|
|
72
|
+
export declare function pidfilePathFor(inboxPath: string): string;
|
|
73
|
+
/**
|
|
74
|
+
* Try to become the SOLE monitor for this inbox. Returns true if we claimed the
|
|
75
|
+
* pidfile (caller proceeds to tail + must release it on exit); false if a LIVE
|
|
76
|
+
* holder already owns it (caller yields/exits without tailing). Never signals a
|
|
77
|
+
* live PID, and never deletes a pidfile that changed under us (compare-and-
|
|
78
|
+
* delete) — only reaps a still-present provably-dead (ESRCH) / unparseable
|
|
79
|
+
* pidfile, then re-claims.
|
|
80
|
+
*/
|
|
81
|
+
export declare function acquireInboxLock(pidfilePath: string, ownPid: number, deps: InboxLockDeps, maxAttempts?: number): boolean;
|
|
52
82
|
/**
|
|
53
83
|
* Is this module being invoked as the bin entry point?
|
|
54
84
|
*
|
package/dist/inbox-monitor.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{spawn as
|
|
2
|
+
import{spawn as d}from"node:child_process";import{randomBytes as h}from"node:crypto";import{linkSync as x,readFileSync as a,realpathSync as b,unlinkSync as p,writeFileSync as y}from"node:fs";import{createInterface as I}from"node:readline";import{fileURLToPath as E}from"node:url";const g=/^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\S*)\s+(\S+)\s+\(([^)]+)\):\s*(.*)$/,S=1024;class w{cap;seen=new Set;order=[];constructor(r=S){if(this.cap=r,!Number.isInteger(r)||r<1)throw new Error("cap must be a positive integer")}remember(r){if(this.seen.has(r))return!1;for(this.seen.add(r),this.order.push(r);this.order.length>this.cap;){const t=this.order.shift();t&&this.seen.delete(t)}return!0}}function m(e){const r=g.exec(e);if(!r)return null;const[,,t,n,i]=r,o=i.trim();return`${t} (${n}): ${o}`}function v(e,r){const t=m(e);return t===null?null:r.remember(e)?t:null}function N(e,r,t=512){if(!Number.isInteger(t)||t<1)throw new Error("maxLines must be a positive integer");let n;try{n=a(e,"utf-8")}catch(o){if(o?.code==="ENOENT")return;throw o}const i=n.split(/\r?\n/);i.at(-1)===""&&i.pop();for(const o of i.slice(-t))m(o)!==null&&r.remember(o)}function T(e){return`${e}.monitor.pid`}function $(e,r,t,n=3){const i=String(r);for(let o=0;o<n;o++){if(t.claim(e,i))return!0;const s=t.read(e);if(s===null)continue;const u=s.trim();if(u===""){t.removeIfContent(e,s);continue}const l=Number.parseInt(u,10);if(!Number.isNaN(l)&&t.isAlive(l))return!1;t.removeIfContent(e,s)}return!1}function k(){return{claim:(e,r)=>{const t=`${e}.tmp.${process.pid}.${h(6).toString("hex")}`;try{y(t,r,{mode:384});try{return x(t,e),!0}catch(n){if(n?.code==="EEXIST")return!1;throw n}}finally{try{p(t)}catch{}}},read:e=>{try{return a(e,"utf8")}catch{return null}},removeIfContent:(e,r)=>{try{a(e,"utf8")===r&&p(e)}catch{}},isAlive:e=>{try{return process.kill(e,0),!0}catch(r){return r?.code==="EPERM"}}}}function R(){const e=process.argv[2];e||(console.error("borg-inbox-monitor: usage: borg-inbox-monitor <inbox-path>"),process.exit(2));const r=T(e),t=k();$(r,process.pid,t)||process.exit(0);const n=()=>t.removeIfContent(r,String(process.pid)),i=new w;N(e,i);const o=d("tail",["-F","-n","0",e],{stdio:["ignore","pipe","inherit"]});o.stdout||(console.error("borg-inbox-monitor: tail subprocess has no stdout"),n(),process.exit(1));const s=I({input:o.stdout,crlfDelay:1/0});let u=!1;s.on("line",c=>{const f=v(c,i);f!==null&&console.log(f)}),o.on("error",c=>{console.error(`borg-inbox-monitor: tail failed: ${c.message}`),n(),process.exit(1)}),o.on("exit",(c,f)=>{n(),u&&process.exit(0),f&&process.exit(0),process.exit(c??0)});const l=c=>{u||(u=!0,n(),s.close(),!o.killed&&!o.kill(c)&&process.exit(0),setTimeout(()=>process.exit(0),1e3).unref())};process.once("SIGTERM",()=>l("SIGTERM")),process.once("SIGINT",()=>l("SIGINT"))}function D(e,r){try{return b(e)===E(r)}catch{return!1}}D(process.argv[1],import.meta.url)&&R();export{S as RECENT_EMITTED_LINE_CAP,w as RecentLineDeduper,$ as acquireInboxLock,m as formatEventLine,v as formatFreshEventLine,D as isEntryInvocation,T as pidfilePathFor,N as seedDeduperFromInboxTail};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "borgmcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.15",
|
|
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",
|