borgmcp 1.1.6 → 1.1.7
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/assimilate-deps.js +2 -2
- package/dist/index.js +20 -20
- package/dist/readiness-probe.d.ts +5 -0
- package/dist/readiness-probe.js +1 -0
- package/dist/startup-services.d.ts +17 -0
- package/dist/startup-services.js +1 -0
- package/dist/stream-owner.d.ts +7 -1
- package/dist/stream-owner.js +2 -2
- package/dist/stream-status.js +3 -3
- package/package.json +1 -1
package/dist/assimilate-deps.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{spawnSync as p,spawn as l}from"node:child_process";import{existsSync as h,mkdirSync as f}from"node:fs";import{hostname as b,homedir as _}from"node:os";import{createInterface as d}from"node:readline/promises";import{API_URL as u,getValidToken as m,listCubes as
|
|
1
|
+
import{spawnSync as p,spawn as l}from"node:child_process";import{existsSync as h,mkdirSync as f}from"node:fs";import{hostname as b,homedir as _}from"node:os";import{createInterface as d}from"node:readline/promises";import{readinessProbeEnv as C}from"./readiness-probe.js";import{API_URL as u,getValidToken as m,listCubes as T,getCube as y,createCube as k,assimilate as w,listTemplates as g}from"./remote-client.js";import{findProjectRoot as L,getActiveCube as v,setActiveCube as x,inboxPathForDrone as M,setCodexWakeTarget as S,getLaunchModel as R,setLaunchModel as P,clearLaunchModel as A}from"./cubes.js";import{authenticateWithGoogle as U}from"./auth.js";import{addProjectSessionStartHook as j}from"./config-utils.js";import{setTerminalTitle as D}from"./terminal-title.js";import{defaultCliChoiceDeps as G,resolveCliChoice as H}from"./cli-platform.js";import{prepareCodexRemoteLaunch as I,defaultCodexRemoteDeps as q}from"./codex-remote.js";import{findLoadedCodexThread as E}from"./codex-app-server.js";import{checkModelReachable as V}from"./model-presets.js";function te(){return{runSync:(e,r,t)=>{const o=p(e,r,{cwd:t,encoding:"utf-8"});return{status:o.status,stdout:o.stdout??"",stderr:o.stderr??""}},pathExists:e=>h(e),cwd:()=>process.cwd(),chdir:e=>process.chdir(e),homedir:()=>_(),mkdirp:e=>f(e,{recursive:!0}),exec:(e,r,t,o)=>new Promise((s,i)=>{const a=l(e,r,{cwd:t,stdio:"inherit",shell:!1,env:o??process.env});a.on("error",i),a.on("exit",n=>s(n??0))}),stderr:e=>process.stderr.write(e),stdout:e=>process.stdout.write(e),prompt:async e=>{const r=d({input:process.stdin,output:process.stdout});try{return await r.question(e)}finally{r.close()}},isTTY:()=>process.stdin.isTTY===!0,getHostname:()=>b(),setTerminalTitle:(e,r)=>{D({label:e,cubeName:r},r)},getActiveCube:()=>v(),setActiveCube:e=>x(e),findProjectRoot:e=>L(e),installProjectSessionHook:e=>{j(e)},getCachedAuth:async()=>{try{return{token:await m(),apiUrl:u}}catch{return null}},runSetup:async()=>(await U(),{token:await m(),apiUrl:u}),listCubes:async(e,r)=>{const{cubes:t}=await T();return t.map(o=>({id:o.id,name:o.name}))},getCube:async(e,r,t)=>{const o=await y(t);return{id:o.id,name:o.name,roles:o.roles,drones:(o.drones??[]).map(s=>({role_id:s.role_id}))}},createCube:async(e,r,t)=>{const o=await k(t.name,"",t.template?{template:t.template}:void 0);return{id:o.id,name:o.name,roles:o.roles}},assimilate:async(e,r,t)=>{const o=await w({cube_id:t.cube_id,role_id:t.role_id,...t.prior_drone_id?{prior_drone_id:t.prior_drone_id}:{},...t.model!=null?{model:t.model}:{}},void 0,t.hostname??null,t.agent_kind??null);return{cube_id:o.cube.id,drone_id:o.drone.id,drone_label:o.drone.label,session_token:o.sessionToken,role_id:o.role.id,reattached:o.reattached===!0}},listTemplates:async(e,r)=>{const{templates:t}=await g();return t.map(o=>({name:o.name,description:o.description}))},getInboxPath:(e,r)=>M(e,r),probeMcpReady:()=>new Promise(e=>{const r=l("borg-mcp",[],{stdio:["pipe","pipe","pipe"],shell:!1,env:C()});let t="",o=!1;const s=n=>{if(!o){o=!0;try{r.kill("SIGTERM")}catch{}e(n)}},i=setTimeout(()=>s(!1),2e3);r.on("error",()=>{clearTimeout(i),s(!1)}),r.on("exit",()=>{clearTimeout(i),s(o)}),r.stdout?.on("data",n=>{t+=n.toString("utf-8");for(const c of t.split(`
|
|
2
2
|
`))if(c.includes('"protocolVersion"')&&c.includes('"result"')){clearTimeout(i),s(!0);return}});const a=JSON.stringify({jsonrpc:"2.0",id:1,method:"initialize",params:{protocolVersion:"2024-11-05",capabilities:{},clientInfo:{name:"borg-assimilate-probe",version:"0.9.3"}}});try{r.stdin?.write(a+`
|
|
3
|
-
`)}catch{clearTimeout(i),s(!1)}}),resolveCli:e=>
|
|
3
|
+
`)}catch{clearTimeout(i),s(!1)}}),resolveCli:e=>H(e,G(async r=>{const t=d({input:process.stdin,output:process.stdout});try{return await t.question(r)}finally{t.close()}},()=>process.stdin.isTTY===!0)),prepareCodexRemoteLaunch:()=>I(q()),setCodexWakeTarget:S,findLoadedCodexThread:E,fetch,checkModelReachable:V,getLaunchModel:(e,r,t)=>R(e,r,t),setLaunchModel:(e,r,t)=>P(e,r,t),clearLaunchModel:(e,r)=>A(e,r)}}export{te as buildDefaultAssimilateDeps};
|
package/dist/index.js
CHANGED
|
@@ -1,38 +1,38 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{Server as
|
|
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}.
|
|
3
3
|
|
|
4
|
-
`)}}catch{}return{content:[{type:"text",text:b+u+
|
|
5
|
-
`)+
|
|
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:"}
|
|
6
6
|
|
|
7
|
-
${
|
|
8
|
-
`)}]}}case"borg_role":{const e=await _(),t=typeof
|
|
9
|
-
`)}]}}const{role:
|
|
10
|
-
`)}]}}case"borg_role-rationale":{const e=await _(),t=typeof
|
|
11
|
-
`)}]}}case"borg_roster":{const e=await _(),t=typeof
|
|
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
|
|
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=
|
|
16
|
-
`)}]}}case"borg_log":{const e=
|
|
17
|
-
${
|
|
18
|
-
\u26A0 ${
|
|
19
|
-
${
|
|
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}
|
|
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=
|
|
26
|
-
`)}]}}case"borg_list-cubes":{const{cubes:e}=await
|
|
27
|
-
${(
|
|
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})
|
|
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=
|
|
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}):
|
|
33
33
|
|
|
34
34
|
${s.join(`
|
|
35
|
-
`)}`}]}}case"borg_list-roles":{const e=
|
|
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:
|
|
36
36
|
|
|
37
|
-
${C().map(
|
|
38
|
-
`)}`}]};case"borg_sync-roles":{const e=
|
|
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)});
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Environment contract for the short-lived `borg assimilate` MCP readiness child. */
|
|
2
|
+
export declare const MCP_READINESS_PROBE_ENV = "BORG_MCP_READINESS_PROBE";
|
|
3
|
+
export declare function isMcpReadinessProbe(env?: NodeJS.ProcessEnv): boolean;
|
|
4
|
+
export declare function readinessProbeEnv(env?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|
|
5
|
+
//# sourceMappingURL=readiness-probe.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const n="BORG_MCP_READINESS_PROBE";function r(e=process.env){return e[n]==="1"}function o(e=process.env){return{...e,[n]:"1"}}export{n as MCP_READINESS_PROBE_ENV,r as isMcpReadinessProbe,o as readinessProbeEnv};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type McpStartupTask = () => void | Promise<void>;
|
|
2
|
+
/** Required named services: omission from index wiring is a type error. */
|
|
3
|
+
export interface McpStartupServices {
|
|
4
|
+
sessionStartHook: McpStartupTask;
|
|
5
|
+
auditHook: McpStartupTask;
|
|
6
|
+
sseStream: McpStartupTask;
|
|
7
|
+
openCode: McpStartupTask;
|
|
8
|
+
healthBeat: McpStartupTask;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Run normal long-lived MCP child startup work. The assimilation readiness
|
|
12
|
+
* child must reach the initialize response without acquiring leases, fetching
|
|
13
|
+
* SSE, mutating hooks, or starting timers because it is intentionally killed
|
|
14
|
+
* immediately after that response.
|
|
15
|
+
*/
|
|
16
|
+
export declare function runMcpStartupServices(readinessProbe: boolean, services: McpStartupServices): Promise<void>;
|
|
17
|
+
//# sourceMappingURL=startup-services.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
async function r(o,t){if(o)return;const a=[t.sessionStartHook,t.auditHook,t.sseStream,t.openCode,t.healthBeat];for(const n of a)try{await n()}catch{}}export{r as runMcpStartupServices};
|
package/dist/stream-owner.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export declare const STREAM_OWNER_STALE_MS = 70000;
|
|
2
|
+
/** Grace window for mkdir→owner.json initialization before an empty lock is reclaimable. */
|
|
3
|
+
export declare const STREAM_OWNER_INIT_STALE_MS = 5000;
|
|
2
4
|
export interface StreamOwnerRecord {
|
|
3
5
|
schemaVersion: number;
|
|
4
6
|
pid: number;
|
|
@@ -8,7 +10,7 @@ export interface StreamOwnerRecord {
|
|
|
8
10
|
heartbeatAt: string;
|
|
9
11
|
}
|
|
10
12
|
export interface StreamOwnershipSnapshot {
|
|
11
|
-
state: 'owner' | 'owned-by-other-process' | 'unowned';
|
|
13
|
+
state: 'owner' | 'owned-by-other-process' | 'initializing' | 'orphaned-initialization' | 'unowned';
|
|
12
14
|
pid?: number;
|
|
13
15
|
processNonce?: string;
|
|
14
16
|
cwd?: string;
|
|
@@ -16,6 +18,8 @@ export interface StreamOwnershipSnapshot {
|
|
|
16
18
|
heartbeatAt?: string;
|
|
17
19
|
ageMs?: number;
|
|
18
20
|
lockPath?: string;
|
|
21
|
+
/** Directory mtime used to compare-before-reap an orphaned initialization. */
|
|
22
|
+
lockMtimeMs?: number;
|
|
19
23
|
}
|
|
20
24
|
export interface StreamLease {
|
|
21
25
|
lockPath: string;
|
|
@@ -32,6 +36,8 @@ export interface StreamOwnerDeps {
|
|
|
32
36
|
processStartedAt?: string;
|
|
33
37
|
isPidAlive?: (pid: number) => boolean;
|
|
34
38
|
beforeTakeoverVerify?: (takeoverPath: string) => Promise<void>;
|
|
39
|
+
/** Initialization/refresh writer seam for failure-path regression tests. */
|
|
40
|
+
writeRecord?: (lockPath: string, record: StreamOwnerRecord) => Promise<void>;
|
|
35
41
|
}
|
|
36
42
|
export declare function streamLockPath(cubeId: string, droneId: string, locksDir?: string): string;
|
|
37
43
|
export declare function acquireStreamLease(cubeId: string, droneId: string, staleMs?: number, deps?: StreamOwnerDeps): Promise<StreamLease | null>;
|
package/dist/stream-owner.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{randomUUID as
|
|
2
|
-
`,{mode:384}),await a.rename(
|
|
1
|
+
import{randomUUID as O}from"node:crypto";import{promises as a}from"node:fs";import{homedir as _}from"node:os";import u from"node:path";const D=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,R=u.join(_(),".config","borgmcp","stream-locks"),m="owner.json",S=1,F=7e4,$=5e3,l=O(),L=new Date().toISOString();function y(t,r,n=R){return A("cubeId",t),A("droneId",r),u.join(n,t,`${r}.lock`)}async function J(t,r,n=F,e={}){const i=y(t,r,e.locksDir);await a.mkdir(u.dirname(i),{recursive:!0,mode:448});const o=await d(i,e);if(o)return o;const c=await V(t,r,e);if(c.state==="unowned")return d(i,e);if(c.state==="initializing")return null;if(c.state==="orphaned-initialization")return await E(i,c,n,e)?d(i,e):null;if(c.state!=="owned-by-other-process")return null;const s=(c.ageMs??0)>n,w=typeof c.pid=="number"&&e.isPidAlive!==void 0&&!e.isPidAlive(c.pid);return!s&&!w||!await E(i,c,n,e)?null:d(i,e)}async function V(t,r,n={}){const e=y(t,r,n.locksDir);let i;try{i=await a.readFile(u.join(e,m),"utf8")}catch(h){if(h?.code==="ENOENT")try{const f=await a.stat(e),M=(n.now??(()=>new Date))(),I=Math.max(0,M.getTime()-f.mtimeMs);return{state:I>=$?"orphaned-initialization":"initializing",ageMs:I,lockPath:e,lockMtimeMs:f.mtimeMs}}catch(f){if(f?.code==="ENOENT")return{state:"unowned",lockPath:e};throw f}throw h}let o;try{o=JSON.parse(i)}catch{return{state:"owned-by-other-process",lockPath:e,ageMs:Number.POSITIVE_INFINITY}}if(!T(o))return{state:"owned-by-other-process",lockPath:e,ageMs:Number.POSITIVE_INFINITY};const c=(n.now??(()=>new Date))(),s=Date.parse(o.heartbeatAt),w=Number.isFinite(s)?c.getTime()-s:Number.POSITIVE_INFINITY,N=n.pid??process.pid,g=n.processNonce??l;return{state:o.pid===N&&o.processNonce===g?"owner":"owned-by-other-process",pid:o.pid,processNonce:o.processNonce,cwd:o.cwd,startedAt:o.startedAt,heartbeatAt:o.heartbeatAt,ageMs:w,lockPath:e}}async function d(t,r){try{await a.mkdir(t,{mode:448})}catch(e){if(e?.code==="EEXIST")return null;throw e}const n=x(r);try{await(r.writeRecord??b)(t,n)}catch(e){throw await j(t,n).catch(()=>{}),e}return k(t,n,r)}async function j(t,r){const n=`${t}.failed-${r.processNonce}-${Date.now()}`;try{await a.rename(t,n)}catch(i){if(i?.code==="ENOENT")return;throw i}const e=await p(n);if(e&&(e.pid!==r.pid||e.processNonce!==r.processNonce)){try{await a.rename(n,t)}catch(i){if(i?.code!=="EEXIST")throw i;await a.rm(n,{recursive:!0,force:!0})}return}await a.rm(n,{recursive:!0,force:!0})}async function E(t,r,n,e){const i=`${t}.takeover-${e.processNonce??l}-${Date.now()}`;try{await a.rename(t,i)}catch(s){if(s?.code==="ENOENT")return!1;throw s}await e.beforeTakeoverVerify?.(i);const o=await p(i),c=await a.stat(i).catch(()=>null);if(!v(r,o,c?.mtimeMs,n,e)){try{await a.rename(i,t)}catch(s){if(s?.code!=="EEXIST")throw s;await a.rm(i,{recursive:!0,force:!0})}return!1}return await a.rm(i,{recursive:!0,force:!0}),!0}function v(t,r,n,e,i){if(!r)return t.state==="orphaned-initialization"?t.lockMtimeMs!==void 0&&n===t.lockMtimeMs:t.ageMs===Number.POSITIVE_INFINITY;if(t.pid!==r.pid||t.processNonce!==r.processNonce||t.heartbeatAt!==r.heartbeatAt)return!1;const o=(i.now??(()=>new Date))(),c=Date.parse(r.heartbeatAt),w=(Number.isFinite(c)?o.getTime()-c:Number.POSITIVE_INFINITY)>e,N=i.isPidAlive!==void 0&&!i.isPidAlive(r.pid);return w||N}function k(t,r,n){return{lockPath:t,record:r,async refresh(){const e=await p(t);if(!e||e.pid!==r.pid||e.processNonce!==r.processNonce)return!1;const i={...r,heartbeatAt:(n.now??(()=>new Date))().toISOString()};return await(n.writeRecord??b)(t,i),this.record=i,!0},async release(){const e=await p(t);e?.pid===r.pid&&e.processNonce===r.processNonce&&await a.rm(t,{recursive:!0,force:!0})}}}async function p(t){try{const r=await a.readFile(u.join(t,m),"utf8"),n=JSON.parse(r);return T(n)?n:null}catch{return null}}async function b(t,r){const n=u.join(t,m),e=u.join(t,`${m}.${r.processNonce}.tmp`);await a.writeFile(e,JSON.stringify(r,null,2)+`
|
|
2
|
+
`,{mode:384}),await a.rename(e,n)}function x(t){const r=t.now??(()=>new Date);return{schemaVersion:S,pid:t.pid??process.pid,processNonce:t.processNonce??l,cwd:t.cwd??process.cwd(),startedAt:t.processStartedAt??L,heartbeatAt:r().toISOString()}}function T(t){return t!==null&&typeof t=="object"&&t.schemaVersion===S&&typeof t.pid=="number"&&Number.isInteger(t.pid)&&typeof t.processNonce=="string"&&typeof t.cwd=="string"&&typeof t.startedAt=="string"&&typeof t.heartbeatAt=="string"}function A(t,r){if(!D.test(r))throw new Error(`Invalid ${t}: ${r}`)}export{$ as STREAM_OWNER_INIT_STALE_MS,F as STREAM_OWNER_STALE_MS,J as acquireStreamLease,V as readOwnershipSnapshot,y as streamLockPath};
|
package/dist/stream-status.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{spawnSync as
|
|
2
|
-
`)}function
|
|
3
|
-
`)}export{
|
|
1
|
+
import{spawnSync as d}from"node:child_process";import{statSync as f}from"node:fs";import{heartbeatPathFor as w,HEARTBEAT_STALE_MS as b}from"./inbox-monitor.js";function A(n){if(!n)return null;try{const t=d("pgrep",["-f",n],{encoding:"utf-8",timeout:2e3});return t.error?null:t.status===0&&t.stdout.trim().length>0?!_(n):t.status===1?!1:null}catch{return null}}function _(n){try{const t=f(w(n)).mtimeMs;return Date.now()-t>b}catch{return!1}}function g(n){const{status:t,inboxMonitorHealthy:s,inboxPath:i,droneLabel:c,cubeName:h,humanAgo:a}=n,u=t.reconnectAttempts===0&&t.lastWireActivityAt===null&&!t.connected,l=t.ownership?.state==="owned-by-other-process",p=t.ownership?.state==="orphaned-initialization",m=t.ownership?.state==="initializing";let o;p?o="**Stream blocked by an orphaned initialization lock.**":l?o="**Stream owned by another Borg MCP process.**":u?o="**Stream not started.**":t.connected?s===!1?o="**Stream connected (no inbox-Monitor \u2014 wake path broken).**":t.lastContentEventAt===null?o="**Stream connected, awaiting first content event.**":o=`**Stream connected, last content ${a(new Date(t.lastContentEventAt))}.**`:o=`**Stream disconnected (reconnect attempt ${t.reconnectAttempts}).**`;const e=[];if(e.push(o),e.push(""),e.push("# Log-stream status"),e.push(""),p?e.push("- **state**: _(orphaned stream-owner initialization)_"):m?e.push("- **state**: _(stream-owner initialization in progress)_"):l?e.push("- **state**: _(stream owner is another local process)_"):u?e.push("- **state**: _(stream not started)_"):e.push(`- **connected**: ${t.connected}`),e.push(`- **last content event**: ${t.lastContentEventAt?`${t.lastContentEventAt} (${a(new Date(t.lastContentEventAt))})`:"_(none yet)_"}`),e.push(`- **last heartbeat at**: ${t.lastHeartbeatAt?`${t.lastHeartbeatAt} (${a(new Date(t.lastHeartbeatAt))})`:"_(none)_"}`),e.push(`- **last wire activity**: ${t.lastWireActivityAt?`${t.lastWireActivityAt} (${a(new Date(t.lastWireActivityAt))})`:"_(none)_"}`),e.push(`- **last persisted event id**: ${t.lastPersistedEventId??"_(none)_"}`),e.push(`- **reconnect attempts**: ${t.reconnectAttempts}`),e.push(`- **run-loop restarts**: ${t.runLoopRestartCount}`),e.push(`- **stream ownership**: ${t.ownership?.state??"_(unknown)_"}`),t.ownership?.lockPath&&e.push(`- **ownership lock path**: ${t.ownership.lockPath}`),l){const r=t.ownership;e.push(`- **stream owner pid**: ${r.pid??"_(unknown)_"}`),e.push(`- **stream owner cwd**: ${r.cwd??"_(unknown)_"}`),e.push(`- **stream owner heartbeat age**: ${typeof r.ageMs=="number"&&Number.isFinite(r.ageMs)?`${Math.max(0,Math.round(r.ageMs/1e3))}s`:"_(unknown)_"}`)}return t.connected&&s===!1&&(e.push("- **inbox-monitor**: _(no watcher detected \u2014 wake path broken)_"),i&&c&&h&&(e.push(""),e.push("## Real-time wake-up (do this now)"),e.push("No process is tailing this drone's inbox file \u2014 this session won't wake on real-time cube activity, only on its fallback timer, and will miss live coordination from other drones. Arm an inbox Monitor:"),e.push(""),e.push(`> Monitor command: \`tail -n 0 -F ${i}\` \u2014 persistent, 1h timeout, description "borg inbox for ${c} on cube ${h}".`))),e.join(`
|
|
2
|
+
`)}function v(n,t){return n.connected&&t===!1}function S(n){const{inboxPath:t,droneLabel:s,cubeName:i}=n;return!t||!s||!i?"":["## \u26A0 Wake path broken \u2014 arm Monitor NOW","","No process is tailing this drone's inbox file. SSE delivery is healthy (entries reach disk), but Claude Code has no event source to wake on. Until you arm a Monitor, this session only wakes on the /loop fallback heartbeat and will miss live coordination from other drones:","",`> Monitor command: \`tail -n 0 -F ${t}\` \u2014 persistent, 1h timeout, description "borg inbox for ${s} on cube ${i}".`,"","---",""].join(`
|
|
3
|
+
`)}export{A as checkInboxMonitorHealthy,S as formatWakePathPrefix,_ as isHeartbeatStale,g as renderStreamStatus,v as shouldShowWakePathWarning};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "borgmcp",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.7",
|
|
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",
|