borgmcp 1.0.59 → 1.1.1

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/index.js CHANGED
@@ -1,38 +1,38 @@
1
1
  #!/usr/bin/env node
2
- import{Server as J}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as Q}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as Y,ListToolsRequestSchema as K,ListPromptsRequestSchema as G,GetPromptRequestSchema as X}from"@modelcontextprotocol/sdk/types.js";import{assertRoleMatches as Z}from"./role-match.js";import{getCubeInfo as ee,getRoleInfo as D,getRoleInfoByName as te,getRoster as re,readLog as oe,appendLog as ne,submitReport as se,fetchReports as ie,ackLogEntry as ae,recordDecision as ce,listDecisions as le,regen as P,listCubes as de,createCube as ue,updateCube as N,deleteCube as pe,createRole as me,updateRole as be,patchRoleSection as k,patchTaxonomyClass as j,deleteRole as fe,reassignDrone as ge,evictDrone as he,getCube as w,checkSubscriptionStatus as _e,createBillingPortalSession as ye,createSubscription as we,syncRoles as xe,applyTemplate as $e,whoami as ve,roleRationale as ke,getValidToken as Ee}from"./remote-client.js";import{startHealthBeatTick as Re}from"./health-beat.js";import{getTemplate as E,listTemplateNames as R,resolveCubeDirectiveForCreate as Ce,resolveCubeDirectiveForApply as Se,resolveMessageTaxonomyForCreate as Te}from"./templates.js";import{activeCubeWithFreshRegenIdentity as A,getActiveCube as _,setActiveCube as M,inboxPathForDrone as C}from"./cubes.js";import{addSessionStartHook as qe,addUserPromptSubmitHook as Ie}from"./config-utils.js";import{humanAgo as L,formatLogEntryMarkdown as Ue,formatRegenMarkdown as B,getDronePlaybook as De,getDronePlaybookChapter as Pe,nullTaxonomyTip as Ne,regenWakePathDroneLabel as je}from"./regen-format.js";import{startLogStream as Ae,getStreamStatus as S}from"./log-stream.js";import{TOOL_MANIFEST as Me}from"./tool-manifest.js";import{DOCS_SECTIONS as Le,matchDocsSections as Be,formatDocsIndex as Oe}from"./docs-sections.js";import{renderRoleList as Fe}from"./list-roles-render.js";import{filterToolsForRole as He}from"./tool-scope.js";import{getPackageVersion as x,getOnDiskVersion as We,handleVersionFlag as Ve}from"./version.js";import{renderStreamStatus as ze,checkInboxMonitorHealthy as T,formatWakePathPrefix as Je,shouldShowWakePathWarning as Qe}from"./stream-status.js";import{formatRoleAgentLabel as Ye,renderRoster as Ke}from"./roster-render.js";import{resolveDroneIdByLabel as Ge,isUuidShape as Xe}from"./evict-drone.js";import{authRecoveryMessage as Ze}from"./auth-recovery.js";import{DroneEvictedError as et,DroneFrozenError as tt,formatEvictedToolResult as rt,formatFrozenToolResult as ot}from"./drone-lifecycle.js";import{classifyInSessionAssimilate as nt,reattachOnlyRefusal as st,reattachFailureMessage as it}from"./assimilate-guard.js";import{gateAllowsActivation as at,borgSessionToolNotice as ct}from"./launch-gate.js";import{renderSyncRolesResult as lt}from"./sync-roles-render.js";import{initConsolePrefix as dt,consolePrefix as $}from"./console-prefix.js";import{isCodexRemoteWakeEnabled as q,resolveSessionAgentKind as ut,probeCodexBridgeArmed as pt}from"./codex-app-wake.js";import{lifecycleSignalForMessage as mt,recordLifecycleLog as O,shouldSuppressLifecycleLog as bt}from"./lifecycle-log-guard.js";import{normalizeDirectLogRecipients as ft}from"./direct-log.js";import F from"open";import gt from"os";function ht(){try{const u=gt.hostname();return u&&u.trim()?u.trim().slice(0,255):null}catch{return null}}async function H(u,y){return await $e(u,y.name)}async function h(){const u=await _();if(!u)throw new Error("Not assimilated to a cube. Use borg_assimilate <cube-name> first.");return u}async function _t(){Ve();try{qe()}catch{}try{Ie()}catch{}try{Ae()}catch{}try{Re({getActiveCube:_,getStreamConnected:()=>S().connected,getInboxPath:p=>C(p.cubeId,p.droneId),checkMonitor:T,isCodexRemoteWake:q,probeBridgeArmed:p=>pt({cubeId:p.cubeId,droneId:p.droneId}),resolveAgentKind:ut,resolveHostname:ht,resolveVersion:x,getToken:Ee,fetchImpl:globalThis.fetch.bind(globalThis)})}catch{}const u=new J({name:"borg-mcp-client",version:x()},{capabilities:{tools:{},prompts:{}}}),y=Me;u.setRequestHandler(K,async()=>{let p=null;try{const m=await _();m&&(p={roleName:m.roleName,roleClass:m.roleClass,isHumanSeat:m.isHumanSeat})}catch{p=null}return{tools:He(y,p)}}),u.setRequestHandler(Y,async p=>{let{name:m,arguments:r}=p.params;if(m==="borg_describe-tool"){const e=typeof r?.name=="string"?r.name:"",t=y.find(o=>o.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(m==="borg_tool"){const e=typeof r?.name=="string"?r.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};r=r?.arguments&&typeof r.arguments=="object"?r.arguments:{},m=e}if(!at(`tool ${m}`))return{content:[{type:"text",text:ct(m)}],isError:!0};try{switch(m){case"borg_regen":{const e=await _();if(!e)return{content:[{type:"text",text:'Not connected to a cube. Use `borg_assimilate cube_name="<name>"` to join one.'}]};const t=typeof r?.since=="string"?r.since:void 0,o=r?.mode==="lite"?"lite":"full",n=await P(e.sessionToken,e.apiUrl,{since:t}),s=A(e,n);s!==e&&await M(s);const i=S(),a=C(s.cubeId,s.droneId),c=q()?!0:T(a),d=Qe(i,c)?Je({inboxPath:a,droneLabel:je(n,s.droneLabel),cubeName:s.name}):"";let b="";try{const f=x(),l=We();if(f!=="unknown"&&l!=="unknown"&&l!==f){const[g,I,V]=f.split(".").map(Number),[v,U,z]=l.split(".").map(Number);(v>g||v===g&&U>I||v===g&&U===I&&z>V)&&(b=`## \u{1F504} borgmcp ${l} installed \u2014 run /mcp and reconnect (or restart Claude Code) to apply. Currently running ${f}.
2
+ import{Server as Q}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as Y}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as G,ListToolsRequestSchema as K,ListPromptsRequestSchema as X,GetPromptRequestSchema as Z}from"@modelcontextprotocol/sdk/types.js";import{assertRoleMatches as ee}from"./role-match.js";import{getCubeInfo as te,getRoleInfo as P,getRoleInfoByName as re,getRoster as oe,readLog as ne,appendLog as se,submitReport as ie,fetchReports as ae,ackLogEntry as ce,recordDecision as le,listDecisions as de,regen as N,listCubes as ue,createCube as pe,updateCube as j,deleteCube as me,createRole as be,updateRole as fe,patchRoleSection as k,patchTaxonomyClass as A,deleteRole as ge,reassignDrone as he,evictDrone as _e,getCube as w,checkSubscriptionStatus as ye,createBillingPortalSession as we,createSubscription as xe,syncRoles as $e,applyTemplate as ve,whoami as ke,roleRationale as Ee,getValidToken as Ce}from"./remote-client.js";import{startHealthBeatTick as Re}from"./health-beat.js";import{getTemplate as E,listTemplateNames as C,resolveCubeDirectiveForCreate as Se,resolveCubeDirectiveForApply as Ie,resolveMessageTaxonomyForCreate as Te}from"./templates.js";import{activeCubeWithFreshRegenIdentity as M,getActiveCube as h,setActiveCube as L,inboxPathForDrone as R}from"./cubes.js";import{addSessionStartHook as qe,addUserPromptSubmitHook as De}from"./config-utils.js";import{humanAgo as O,formatLogEntryMarkdown as Ue,formatRegenMarkdown as B,getDronePlaybook as Pe,getDronePlaybookChapter as Ne,nullTaxonomyTip as je,regenWakePathDroneLabel as Ae}from"./regen-format.js";import{startLogStream as Me,getStreamStatus as S}from"./log-stream.js";import{TOOL_MANIFEST as Le}from"./tool-manifest.js";import{DOCS_SECTIONS as Oe,matchDocsSections as Be,formatDocsIndex as Fe}from"./docs-sections.js";import{renderRoleList as He}from"./list-roles-render.js";import{filterToolsForRole as We}from"./tool-scope.js";import{getPackageVersion as x,getOnDiskVersion as Ve,handleVersionFlag as ze}from"./version.js";import{renderStreamStatus as Je,checkInboxMonitorHealthy as I,formatWakePathPrefix as Qe,shouldShowWakePathWarning as Ye}from"./stream-status.js";import{formatRoleAgentLabel as Ge,renderRoster as Ke}from"./roster-render.js";import{resolveDroneIdByLabel as Xe,isUuidShape as Ze}from"./evict-drone.js";import{authRecoveryMessage as et}from"./auth-recovery.js";import{DroneEvictedError as tt,DroneFrozenError as rt,formatEvictedToolResult as ot,formatFrozenToolResult as nt}from"./drone-lifecycle.js";import{classifyInSessionAssimilate as st,reattachOnlyRefusal as it,reattachFailureMessage as at}from"./assimilate-guard.js";import{gateAllowsActivation as ct,borgSessionToolNotice as lt}from"./launch-gate.js";import{renderSyncRolesResult as dt}from"./sync-roles-render.js";import{initConsolePrefix as ut,consolePrefix as $}from"./console-prefix.js";import{isCodexRemoteWakeEnabled as T,resolveSessionAgentKind as q,probeCodexBridgeArmed as pt}from"./codex-app-wake.js";import{connectOpenCodeDrone as mt,injectOpenCodeEntry as bt,probeOpenCodeDroneArmed as ft,computeOpenCodePort as gt}from"./opencode-drone.js";import{installBorgPlugin as ht}from"./opencode-plugin.js";import{setModuleInjectOpenCode as _t}from"./log-stream.js";import{lifecycleSignalForMessage as yt,recordLifecycleLog as F,shouldSuppressLifecycleLog as wt}from"./lifecycle-log-guard.js";import{normalizeDirectLogRecipients as xt}from"./direct-log.js";import H from"open";import $t from"os";function vt(){try{const p=$t.hostname();return p&&p.trim()?p.trim().slice(0,255):null}catch{return null}}async function W(p,y){return await ve(p,y.name)}async function _(){const p=await h();if(!p)throw new Error("Not assimilated to a cube. Use borg_assimilate <cube-name> first.");return p}async function kt(){ze();try{qe()}catch{}try{De()}catch{}try{Me()}catch{}try{ht();const c=await h();if(c&&process.env.BORG_OPENCODE==="1"){const r=`http://127.0.0.1:${gt(c.droneId)}`;await mt({serverUrl:r,directory:process.cwd(),droneLabel:c.droneLabel,cubeName:c.name}),_t(bt)}}catch{}try{Re({getActiveCube:h,getStreamConnected:()=>S().connected,getInboxPath:c=>R(c.cubeId,c.droneId),checkMonitor:I,isCodexRemoteWake:T,probeBridgeArmed:c=>pt({cubeId:c.cubeId,droneId:c.droneId}),probeOpenCodeDrone:()=>ft(),resolveAgentKind:q,resolveHostname:vt,resolveVersion:x,getToken:Ce,fetchImpl:globalThis.fetch.bind(globalThis)})}catch{}const p=new Q({name:"borg-mcp-client",version:x()},{capabilities:{tools:{},prompts:{}}}),y=Le;p.setRequestHandler(K,async()=>{let c=null;try{const m=await h();m&&(c={roleName:m.roleName,roleClass:m.roleClass,isHumanSeat:m.isHumanSeat})}catch{c=null}return{tools:We(y,c)}}),p.setRequestHandler(G,async c=>{let{name:m,arguments:r}=c.params;if(m==="borg_describe-tool"){const e=typeof r?.name=="string"?r.name:"",t=y.find(o=>o.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(m==="borg_tool"){const e=typeof r?.name=="string"?r.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};r=r?.arguments&&typeof r.arguments=="object"?r.arguments:{},m=e}if(!ct(`tool ${m}`))return{content:[{type:"text",text:lt(m)}],isError:!0};try{switch(m){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 r?.since=="string"?r.since:void 0,o=r?.mode==="lite"?"lite":"full",n=await N(e.sessionToken,e.apiUrl,{since:t}),s=M(e,n);s!==e&&await L(s);const i=S(),a=R(s.cubeId,s.droneId),l=q()==="opencode"||T()?!0:I(a),u=Ye(i,l)?Qe({inboxPath:a,droneLabel:Ae(n,s.droneLabel),cubeName:s.name}):"";let b="";try{const f=x(),d=Ve();if(f!=="unknown"&&d!=="unknown"&&d!==f){const[g,D,z]=f.split(".").map(Number),[v,U,J]=d.split(".").map(Number);(v>g||v===g&&U>D||v===g&&U===D&&J>z)&&(b=`## \u{1F504} borgmcp ${d} 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+d+B(n,{mode:o})}]}}case"borg_subscribe":return{content:[{type:"text",text:`Complete your subscription at: ${await we()}`}]};case"borg_upgrade-subscription":{const e=await ye();try{await F(e)}catch{}return{content:[{type:"text",text:`Manage your Borg MCP subscription at: ${e}`}]}}case"borg_subscription_status":{const e=await _e();return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}case"borg_open_dashboard":{const e="https://borgmcp.ai/dashboard";return await F(e),{content:[{type:"text",text:`\u25FC Opened dashboard in browser: ${e}`}]}}case"borg_assimilate":{const e=r?.cube_name;if(!e)throw new Error("cube_name is required");const t=await _(),o=nt(t,e);if(o.kind!=="reattach")return{content:[{type:"text",text:st(o,e)}],isError:!0};try{const n=await P(t.sessionToken,t.apiUrl,{}),s=A(t,n);return s!==t&&await M(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
- `)+B(n,{mode:"full"})}]}}catch(n){const s=it(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:Pe()}]};case"borg_docs":{const e=typeof r?.topic=="string"?r.topic.trim():"",t=e?Be(e):[],o=t.length>0?t:Le;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:b+u+B(n,{mode:o})}]}}case"borg_subscribe":return{content:[{type:"text",text:`Complete your subscription at: ${await xe()}`}]};case"borg_upgrade-subscription":{const e=await we();try{await H(e)}catch{}return{content:[{type:"text",text:`Manage your Borg MCP subscription at: ${e}`}]}}case"borg_subscription_status":{const e=await ye();return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}case"borg_open_dashboard":{const e="https://borgmcp.ai/dashboard";return await H(e),{content:[{type:"text",text:`\u25FC Opened dashboard in browser: ${e}`}]}}case"borg_assimilate":{const e=r?.cube_name;if(!e)throw new Error("cube_name is required");const t=await h(),o=st(t,e);if(o.kind!=="reattach")return{content:[{type:"text",text:it(o,e)}],isError:!0};try{const n=await N(t.sessionToken,t.apiUrl,{}),s=M(t,n);return s!==t&&await L(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
+ `)+B(n,{mode:"full"})}]}}catch(n){const s=at(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:Ne()}]};case"borg_docs":{const e=typeof r?.topic=="string"?r.topic.trim():"",t=e?Be(e):[],o=t.length>0?t:Oe;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
- ${Oe(o)}`}]}}case"borg_whoami":{const e=await h(),t=await ve(e.sessionToken,e.apiUrl);return{content:[{type:"text",text:JSON.stringify(t,null,2)}]}}case"borg_cube":{const e=await h(),[{cube:t,roles:o}]=await Promise.all([ee(e.sessionToken,e.apiUrl),D(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=Ne(t.message_taxonomy);if(s&&(n.push(s),n.push("")),n.push("## Roles in this cube"),!o.length)n.push("_(no roles defined)_");else{for(const i of o){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(De()),{content:[{type:"text",text:n.join(`
8
- `)}]}}case"borg_role":{const e=await h(),t=typeof r?.role=="string"?r.role.trim():"";if(t){const{role:s}=await te(e.sessionToken,e.apiUrl,t);return Z(t,s),{content:[{type:"text",text:[`# Role: ${s.name}`,"",s.detailed_description||"_(no detailed description set)_"].join(`
9
- `)}]}}const{role:o}=await D(e.sessionToken,e.apiUrl);return{content:[{type:"text",text:[`# Your role: ${o.name}`,"",o.detailed_description||"_(no detailed description set)_"].join(`
10
- `)}]}}case"borg_role-rationale":{const e=await h(),t=typeof r?.role=="string"?r.role:"",o=typeof r?.section=="string"?r.section:"",n=await ke(e.sessionToken,e.apiUrl,t,o);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 r?.since=="string"?r.since:void 0,{drones:o,roles:n,since:s}=await re(e.sessionToken,e.apiUrl,t);return{content:[{type:"text",text:Ke({cubeName:e.name,drones:o,roles:n,resolvedSince:s??null,humanAgo:L})}]}}case"borg_stream-status":{const e=S(),t=await _(),o=t?C(t.cubeId,t.droneId):null,n=t?q()?!0:T(o):null;let s="";e.runLoopHealth==="silent-inert"&&(s=`## \u26A0 SSE stream loop silent-inert \u2014 run /mcp and reconnect to restart
7
+ ${Fe(o)}`}]}}case"borg_whoami":{const e=await _(),t=await ke(e.sessionToken,e.apiUrl);return{content:[{type:"text",text:JSON.stringify(t,null,2)}]}}case"borg_cube":{const e=await _(),[{cube:t,roles:o}]=await Promise.all([te(e.sessionToken,e.apiUrl),P(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=je(t.message_taxonomy);if(s&&(n.push(s),n.push("")),n.push("## Roles in this cube"),!o.length)n.push("_(no roles defined)_");else{for(const i of o){const a=[i.role_class==="queen"?"Queen":null,i.is_human_seat?"human-seat":null,i.is_default?"default":null].filter(Boolean).join(", "),l=a?` (${a})`:"",u=i.short_description||"_(no description)_";n.push(`- **${i.name}**${l} \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(Pe()),{content:[{type:"text",text:n.join(`
8
+ `)}]}}case"borg_role":{const e=await _(),t=typeof r?.role=="string"?r.role.trim():"";if(t){const{role:s}=await re(e.sessionToken,e.apiUrl,t);return ee(t,s),{content:[{type:"text",text:[`# Role: ${s.name}`,"",s.detailed_description||"_(no detailed description set)_"].join(`
9
+ `)}]}}const{role:o}=await P(e.sessionToken,e.apiUrl);return{content:[{type:"text",text:[`# Your role: ${o.name}`,"",o.detailed_description||"_(no detailed description set)_"].join(`
10
+ `)}]}}case"borg_role-rationale":{const e=await _(),t=typeof r?.role=="string"?r.role:"",o=typeof r?.section=="string"?r.section:"",n=await Ee(e.sessionToken,e.apiUrl,t,o);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 r?.since=="string"?r.since:void 0,{drones:o,roles:n,since:s}=await oe(e.sessionToken,e.apiUrl,t);return{content:[{type:"text",text:Ke({cubeName:e.name,drones:o,roles:n,resolvedSince:s??null,humanAgo:O})}]}}case"borg_stream-status":{const e=S(),t=await h(),o=t?R(t.cubeId,t.droneId):null,n=t&&q()==="opencode",s=t?n||T()?!0:I(o):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 i=ze({status:e,inboxMonitorHealthy:n,inboxPath:o,droneLabel:t?.droneLabel??null,cubeName:t?.name??null,humanAgo:L});return{content:[{type:"text",text:s+i}]}}case"borg_read-log":{const e=await h(),t=typeof r?.since=="string"?r.since:void 0,o=typeof r?.limit=="number"?r.limit:void 0,n=r?.unread_only===!0||r?.unread_only==="true",{entries:s,drones:i,roles:a,behind_by:c,has_more:d}=await oe(e.sessionToken,e.apiUrl,{since:t,limit:o,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 d===!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=r?.message;if(!e||typeof e!="string")throw new Error("message is required");const t=await _();if(!t)throw new Error("Not assimilated to a cube. Use borg_assimilate <cube-name> first.");if(mt(e)){const l=await bt(t,e);if(l.suppress)return await O(t,e),{content:[{type:"text",text:`Suppressed duplicate ${l.signal?.toUpperCase()} lifecycle log for ${t.droneLabel}; recent cube log already contains this signal.`}]}}const o=Object.prototype.hasOwnProperty.call(r??{},"to"),n=o?ft(r?.to):void 0,s=typeof r?.class=="string"?r.class:void 0,i=r?.visibility==="broadcast"||r?.visibility==="direct"?r.visibility:void 0,a={...s?{class:s}:{},...o?{to:n??[]}:{},...i?{visibility:i}:{}},c=await ne(t.sessionToken,t.apiUrl,e,a);await O(t,e);const d=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})${d}${b}`}]}}case"borg_report-friction":{const e=r?.message;if(!e||typeof e!="string")throw new Error("message is required");const t=await _();if(!t)throw new Error("Not assimilated to a cube. Use borg_assimilate <cube-name> first.");const o=r?.kind==="bug"?"bug":"friction",n=r?.metadata&&typeof r.metadata=="object"&&!Array.isArray(r.metadata)?r.metadata:void 0;return{content:[{type:"text",text:(await se(t.sessionToken,t.apiUrl,{kind:o,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 ie();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(o=>{const n=o.metadata&&Object.keys(o.metadata).length?" \xB7 "+Object.entries(o.metadata).map(([i,a])=>`${i}=${a}`).join(", "):"",s=o.redacted?" \xB7 [secrets-scrubbed]":"";return`**[${o.kind}]** ${o.created_at} \xB7 ${o.reporter_email}${n}${s}
15
+ `);const a=Je({status:e,inboxMonitorHealthy:s,inboxPath:o,droneLabel:t?.droneLabel??null,cubeName:t?.name??null,humanAgo:O});return{content:[{type:"text",text:i+a}]}}case"borg_read-log":{const e=await _(),t=typeof r?.since=="string"?r.since:void 0,o=typeof r?.limit=="number"?r.limit:void 0,n=r?.unread_only===!0||r?.unread_only==="true",{entries:s,drones:i,roles:a,behind_by:l,has_more:u}=await ne(e.sessionToken,e.apiUrl,{since:t,limit:o,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 d=[];if(d.push(`# Activity log: ${e.name}`),d.push(""),!s.length)d.push("_(no entries)_");else for(const g of s)d.push(Ue(g,b,f));return u===!0?(d.push(""),d.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 l=="number"&&l>0&&(d.push(""),d.push(`\u26A0 behind_by: ${l} more unread ${l===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:d.join(`
16
+ `)}]}}case"borg_log":{const e=r?.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(yt(e)){const d=await wt(t,e);if(d.suppress)return await F(t,e),{content:[{type:"text",text:`Suppressed duplicate ${d.signal?.toUpperCase()} lifecycle log for ${t.droneLabel}; recent cube log already contains this signal.`}]}}const o=Object.prototype.hasOwnProperty.call(r??{},"to"),n=o?xt(r?.to):void 0,s=typeof r?.class=="string"?r.class:void 0,i=r?.visibility==="broadcast"||r?.visibility==="direct"?r.visibility:void 0,a={...s?{class:s}:{},...o?{to:n??[]}:{},...i?{visibility:i}:{}},l=await se(t.sessionToken,t.apiUrl,e,a);await F(t,e);const u=l.routing?.message?`
17
+ ${l.routing.message}`:"",b=l.unreachableRecipients?.length?`
18
+ \u26A0 ${l.unreachableRecipients.length} directed recipient(s) currently unreachable (wake-path:deaf): ${l.unreachableRecipients.map(d=>d.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: ${l.entry.id})${u}${b}`}]}}case"borg_report-friction":{const e=r?.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 o=r?.kind==="bug"?"bug":"friction",n=r?.metadata&&typeof r.metadata=="object"&&!Array.isArray(r.metadata)?r.metadata:void 0;return{content:[{type:"text",text:(await ie(t.sessionToken,t.apiUrl,{kind:o,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 ae();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(o=>{const n=o.metadata&&Object.keys(o.metadata).length?" \xB7 "+Object.entries(o.metadata).map(([i,a])=>`${i}=${a}`).join(", "):"",s=o.redacted?" \xB7 [secrets-scrubbed]":"";return`**[${o.kind}]** ${o.created_at} \xB7 ${o.reporter_email}${n}${s}
19
19
  ${o.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=r?.entry_id;if(!e||typeof e!="string")throw new Error("entry_id is required");const t=r?.kind==="claim"?"claim":"ack",o=await h();return await ae(o.sessionToken,o.apiUrl,e,t),{content:[{type:"text",text:t==="claim"?`Claimed entry ${e} in cube "${o.name}" (advisory \u2014 merge stays keyed on REVIEW-APPROVED).`:`Acked entry ${e} in cube "${o.name}".`}]}}case"borg_decide":{const e=r?.topic,t=r?.decision;if(!e||typeof e!="string")throw new Error("topic is required");if(!t||typeof t!="string")throw new Error("decision is required");const o=typeof r?.rationale=="string"?r.rationale:void 0,n=await h(),{decision:s}=await ce(n.sessionToken,n.apiUrl,{topic:e,decision:t,...o!==void 0?{rationale:o}:{}}),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 r?.topic=="string"?r.topic:void 0,t=await h(),{decisions:o}=await le(t.sessionToken,t.apiUrl,e);return{content:[{type:"text",text:o.length===0?e?`No active ratified decision on "${e}" in cube "${t.name}".`:`No active ratified decisions in cube "${t.name}".`:o.map(s=>`**${s.topic}:** ${s.decision}${s.rationale?` \u2014 ${s.rationale}`:""}`).join(`
26
- `)}]}}case"borg_list-cubes":{const{cubes:e}=await de();if(!e.length)return{content:[{type:"text",text:"No cubes yet. Use borg_create-cube to make your first one."}]};const t=e.map(o=>`- **${o.name}** (id: ${o.id})
25
+ `)}`}]}}case"borg_ack":{const e=r?.entry_id;if(!e||typeof e!="string")throw new Error("entry_id is required");const t=r?.kind==="claim"?"claim":"ack",o=await _();return await ce(o.sessionToken,o.apiUrl,e,t),{content:[{type:"text",text:t==="claim"?`Claimed entry ${e} in cube "${o.name}" (advisory \u2014 merge stays keyed on REVIEW-APPROVED).`:`Acked entry ${e} in cube "${o.name}".`}]}}case"borg_decide":{const e=r?.topic,t=r?.decision;if(!e||typeof e!="string")throw new Error("topic is required");if(!t||typeof t!="string")throw new Error("decision is required");const o=typeof r?.rationale=="string"?r.rationale:void 0,n=await _(),{decision:s}=await le(n.sessionToken,n.apiUrl,{topic:e,decision:t,...o!==void 0?{rationale:o}:{}}),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 r?.topic=="string"?r.topic:void 0,t=await _(),{decisions:o}=await de(t.sessionToken,t.apiUrl,e);return{content:[{type:"text",text:o.length===0?e?`No active ratified decision on "${e}" in cube "${t.name}".`:`No active ratified decisions in cube "${t.name}".`:o.map(s=>`**${s.topic}:** ${s.decision}${s.rationale?` \u2014 ${s.rationale}`:""}`).join(`
26
+ `)}]}}case"borg_list-cubes":{const{cubes:e}=await ue();if(!e.length)return{content:[{type:"text",text:"No cubes yet. Use borg_create-cube to make your first one."}]};const t=e.map(o=>`- **${o.name}** (id: ${o.id})
27
27
  ${(o.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=r?.name,t=r?.cube_directive,o=r?.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(o&&(n=E(o),!n))throw new Error(`Unknown template "${o}". Available: ${R().join(", ")}`);const s=Ce(t,n),i=Te(void 0,n),a=await ue(e,s,{message_taxonomy:i});if(n){const d=await H(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 **${o}** applied \u2014 ${d.created} role(s) created, ${d.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=r?.cube_id;if(!e)throw new Error("cube_id is required");const t={};if(typeof r?.name=="string"&&(t.name=r.name),typeof r?.cube_directive=="string"&&(t.cube_directive=r.cube_directive),Array.isArray(r?.message_taxonomy)&&(t.message_taxonomy=r.message_taxonomy),Object.keys(t).length===0)throw new Error("Pass at least one of: name, cube_directive, message_taxonomy.");const{cube:o}=await N(e,t);return{content:[{type:"text",text:`Updated cube **${o.name}** (id: ${o.id}).`}]}}case"borg_patch-taxonomy-class":{const e=r?.cube_id;if(!e)throw new Error("cube_id is required");const t=r?.action;if(t!=="add"&&t!=="replace"&&t!=="remove")throw new Error("action must be one of: add, replace, remove.");let o,n;if(t==="remove"){const i=r?.class;if(!i)throw new Error("class is required for remove.");({cube:o}=await j(e,{action:t,class:i})),n=i}else{const i=r?.class_def;if(i==null||typeof i!="object"||Array.isArray(i))throw new Error("class_def (object) is required for add/replace.");({cube:o}=await j(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 **${o.name}** (id: ${o.id}).`}]}}case"borg_delete-cube":{const e=r?.cube_id;if(!e)throw new Error("cube_id is required");return await pe(e),{content:[{type:"text",text:`Deleted cube ${e} (and all its roles, drones, log entries).`}]}}case"borg_create-role":{const e=r?.cube_id,t=r?.name,o=r?.short_description,n=r?.detailed_description;if(!e)throw new Error("cube_id is required");if(!t)throw new Error("name is required");if(o===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=r?.is_default===!0,i=r?.is_human_seat===!0,a=r?.can_broadcast===!0,c=r?.receives_all_direct===!0,{role:d}=await me(e,{name:t,short_description:o,detailed_description:n,is_default:s,is_human_seat:i,can_broadcast:a,receives_all_direct:c,...typeof r?.default_model=="string"?{default_model:r.default_model}:{}}),b=[d.role_class==="queen"?"Queen":null,d.is_human_seat?"human-seat":null,d.is_default?"default":null].filter(Boolean).join(", "),f=b?` (${b})`:"";return{content:[{type:"text",text:`Created role **${d.name}**${f} (id: ${d.id}) in cube ${e}.`}]}}case"borg_update-role":{const e=r?.role_id;if(!e)throw new Error("role_id is required");const t={};if(typeof r?.name=="string"&&(t.name=r.name),typeof r?.short_description=="string"&&(t.short_description=r.short_description),typeof r?.detailed_description=="string"&&(t.detailed_description=r.detailed_description),typeof r?.is_default=="boolean"&&(t.is_default=r.is_default),typeof r?.is_human_seat=="boolean"&&(t.is_human_seat=r.is_human_seat),typeof r?.can_broadcast=="boolean"&&(t.can_broadcast=r.can_broadcast),typeof r?.receives_all_direct=="boolean"&&(t.receives_all_direct=r.receives_all_direct),typeof r?.default_model=="string"&&(t.default_model=r.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:o}=await be(e,t),n=[o.role_class==="queen"?"Queen":null,o.is_human_seat?"human-seat":null,o.is_default?"default":null].filter(Boolean).join(", "),s=n?` (${n})`:"";return{content:[{type:"text",text:`Updated role **${o.name}**${s} (id: ${o.id}).`}]}}case"borg_patch-role-section":{const e=r?.role_id;if(!e)throw new Error("role_id is required");const t=r?.action;if(t!=="replace"&&t!=="insert"&&t!=="delete")throw new Error("action must be one of: replace, insert, delete.");const o=r?.heading;if(!o)throw new Error("heading is required");let n;if(t==="delete")({role:n}=await k(e,{action:t,heading:o}));else{const i=r?.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 r?.after=="string"?r.after:null;({role:n}=await k(e,{action:t,heading:o,body:i,after:a}))}else({role:n}=await k(e,{action:t,heading:o,body:i}))}return{content:[{type:"text",text:`${t==="replace"?"Replaced":t==="insert"?"Inserted":"Deleted"} section **${o}** in role **${n.name}** (id: ${n.id}).`}]}}case"borg_delete-role":{const e=r?.role_id;if(!e)throw new Error("role_id is required");return await fe(e),{content:[{type:"text",text:`Deleted role ${e}.`}]}}case"borg_reassign-drone":{const e=r?.drone_id,t=r?.role_id;if(!e)throw new Error("drone_id is required");if(!t)throw new Error("role_id is required");const{drone:o}=await ge(e,t);return{content:[{type:"text",text:`Reassigned drone ${o.label} (${o.id}) to role ${o.role_id}.`}]}}case"borg_evict-drone":{const e=r?.drone_id?.trim(),t=r?.label?.trim(),o=r?.cube_id?.trim();let n,s;if(e){if(!Xe(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(!o)throw new Error("cube_id is required when evicting by label");const{drones:i}=await w(o),a=Ge(i,t);if(!a)throw new Error(`No active drone labelled "${t}" in cube ${o} (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 he(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=r?.cube_id;if(!e)throw new Error("cube_id is required");const{drones:t,roles:o}=await w(e);if(!t.length)return{content:[{type:"text",text:"No drones in this cube yet."}]};const n=new Map(o.map(i=>[i.id,i])),s=t.map(i=>{const a=n.get(i.role_id),c=Ye(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}):
32
+ `)}`}]}}case"borg_create-cube":{const e=r?.name,t=r?.cube_directive,o=r?.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(o&&(n=E(o),!n))throw new Error(`Unknown template "${o}". Available: ${C().join(", ")}`);const s=Se(t,n),i=Te(void 0,n),a=await pe(e,s,{message_taxonomy:i});if(n){const u=await W(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 **${o}** 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=r?.cube_id;if(!e)throw new Error("cube_id is required");const t={};if(typeof r?.name=="string"&&(t.name=r.name),typeof r?.cube_directive=="string"&&(t.cube_directive=r.cube_directive),Array.isArray(r?.message_taxonomy)&&(t.message_taxonomy=r.message_taxonomy),Object.keys(t).length===0)throw new Error("Pass at least one of: name, cube_directive, message_taxonomy.");const{cube:o}=await j(e,t);return{content:[{type:"text",text:`Updated cube **${o.name}** (id: ${o.id}).`}]}}case"borg_patch-taxonomy-class":{const e=r?.cube_id;if(!e)throw new Error("cube_id is required");const t=r?.action;if(t!=="add"&&t!=="replace"&&t!=="remove")throw new Error("action must be one of: add, replace, remove.");let o,n;if(t==="remove"){const i=r?.class;if(!i)throw new Error("class is required for remove.");({cube:o}=await A(e,{action:t,class:i})),n=i}else{const i=r?.class_def;if(i==null||typeof i!="object"||Array.isArray(i))throw new Error("class_def (object) is required for add/replace.");({cube:o}=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 **${o.name}** (id: ${o.id}).`}]}}case"borg_delete-cube":{const e=r?.cube_id;if(!e)throw new Error("cube_id is required");return await me(e),{content:[{type:"text",text:`Deleted cube ${e} (and all its roles, drones, log entries).`}]}}case"borg_create-role":{const e=r?.cube_id,t=r?.name,o=r?.short_description,n=r?.detailed_description;if(!e)throw new Error("cube_id is required");if(!t)throw new Error("name is required");if(o===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=r?.is_default===!0,i=r?.is_human_seat===!0,a=r?.can_broadcast===!0,l=r?.receives_all_direct===!0,{role:u}=await be(e,{name:t,short_description:o,detailed_description:n,is_default:s,is_human_seat:i,can_broadcast:a,receives_all_direct:l,...typeof r?.default_model=="string"?{default_model:r.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=r?.role_id;if(!e)throw new Error("role_id is required");const t={};if(typeof r?.name=="string"&&(t.name=r.name),typeof r?.short_description=="string"&&(t.short_description=r.short_description),typeof r?.detailed_description=="string"&&(t.detailed_description=r.detailed_description),typeof r?.is_default=="boolean"&&(t.is_default=r.is_default),typeof r?.is_human_seat=="boolean"&&(t.is_human_seat=r.is_human_seat),typeof r?.can_broadcast=="boolean"&&(t.can_broadcast=r.can_broadcast),typeof r?.receives_all_direct=="boolean"&&(t.receives_all_direct=r.receives_all_direct),typeof r?.default_model=="string"&&(t.default_model=r.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:o}=await fe(e,t),n=[o.role_class==="queen"?"Queen":null,o.is_human_seat?"human-seat":null,o.is_default?"default":null].filter(Boolean).join(", "),s=n?` (${n})`:"";return{content:[{type:"text",text:`Updated role **${o.name}**${s} (id: ${o.id}).`}]}}case"borg_patch-role-section":{const e=r?.role_id;if(!e)throw new Error("role_id is required");const t=r?.action;if(t!=="replace"&&t!=="insert"&&t!=="delete")throw new Error("action must be one of: replace, insert, delete.");const o=r?.heading;if(!o)throw new Error("heading is required");let n;if(t==="delete")({role:n}=await k(e,{action:t,heading:o}));else{const i=r?.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 r?.after=="string"?r.after:null;({role:n}=await k(e,{action:t,heading:o,body:i,after:a}))}else({role:n}=await k(e,{action:t,heading:o,body:i}))}return{content:[{type:"text",text:`${t==="replace"?"Replaced":t==="insert"?"Inserted":"Deleted"} section **${o}** in role **${n.name}** (id: ${n.id}).`}]}}case"borg_delete-role":{const e=r?.role_id;if(!e)throw new Error("role_id is required");return await ge(e),{content:[{type:"text",text:`Deleted role ${e}.`}]}}case"borg_reassign-drone":{const e=r?.drone_id,t=r?.role_id;if(!e)throw new Error("drone_id is required");if(!t)throw new Error("role_id is required");const{drone:o}=await he(e,t);return{content:[{type:"text",text:`Reassigned drone ${o.label} (${o.id}) to role ${o.role_id}.`}]}}case"borg_evict-drone":{const e=r?.drone_id?.trim(),t=r?.label?.trim(),o=r?.cube_id?.trim();let n,s;if(e){if(!Ze(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(!o)throw new Error("cube_id is required when evicting by label");const{drones:i}=await w(o),a=Xe(i,t);if(!a)throw new Error(`No active drone labelled "${t}" in cube ${o} (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=r?.cube_id;if(!e)throw new Error("cube_id is required");const{drones:t,roles:o}=await w(e);if(!t.length)return{content:[{type:"text",text:"No drones in this cube yet."}]};const n=new Map(o.map(i=>[i.id,i])),s=t.map(i=>{const a=n.get(i.role_id),l=Ge(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: ${l} (${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=r?.cube_id;if(!e)throw new Error("cube_id is required");const{roles:t}=await w(e);return{content:[{type:"text",text:Fe(t,e)}]}}case"borg_list-templates":return{content:[{type:"text",text:`Available templates:
35
+ `)}`}]}}case"borg_list-roles":{const e=r?.cube_id;if(!e)throw new Error("cube_id is required");const{roles:t}=await w(e);return{content:[{type:"text",text:He(t,e)}]}}case"borg_list-templates":return{content:[{type:"text",text:`Available templates:
36
36
 
37
- ${R().map(o=>{const n=E(o);return`- **${o}**: ${n.description}`}).join(`
38
- `)}`}]};case"borg_sync-roles":{const e=r?.cube_id,t=r?.template_name||"software-dev",o=r?.apply===!0,n=r?.decisions&&typeof r.decisions=="object"?r.decisions:void 0;if(!e)throw new Error("cube_id is required");const s=await xe(e,t,o,n);return{content:[{type:"text",text:lt(s,t)}]}}case"borg_apply-template":{const e=r?.cube_id,t=r?.template_name;if(!e)throw new Error("cube_id is required");if(!t)throw new Error("template_name is required");const o=E(t);if(!o)throw new Error(`Unknown template "${t}". Available: ${R().join(", ")}`);const n=await H(e,o);let s="";const i=await w(e),a=Se(i.cube_directive,o);return a!==null&&(await N(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: ${m}`)}}catch(e){if(e instanceof et)return{content:[{type:"text",text:rt(e.message)}],isError:!0};if(e instanceof tt)return{content:[{type:"text",text:ot(e.message)}],isError:!0};const t=Ze(e??{});return t?{content:[{type:"text",text:t}],isError:!0}:{content:[{type:"text",text:`Error: ${e.message}`}],isError:!0}}}),u.setRequestHandler(G,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"}]})),u.setRequestHandler(X,async p=>{const{name:m}=p.params;switch(m){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: ${m}`)}});const W=new Q;await u.connect(W),await dt(),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`)}_t().catch(u=>{console.error(`${$()}Fatal error:`,u),process.exit(1)});
37
+ ${C().map(o=>{const n=E(o);return`- **${o}**: ${n.description}`}).join(`
38
+ `)}`}]};case"borg_sync-roles":{const e=r?.cube_id,t=r?.template_name||"software-dev",o=r?.apply===!0,n=r?.decisions&&typeof r.decisions=="object"?r.decisions:void 0;if(!e)throw new Error("cube_id is required");const s=await $e(e,t,o,n);return{content:[{type:"text",text:dt(s,t)}]}}case"borg_apply-template":{const e=r?.cube_id,t=r?.template_name;if(!e)throw new Error("cube_id is required");if(!t)throw new Error("template_name is required");const o=E(t);if(!o)throw new Error(`Unknown template "${t}". Available: ${C().join(", ")}`);const n=await W(e,o);let s="";const i=await w(e),a=Ie(i.cube_directive,o);return a!==null&&(await j(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: ${m}`)}}catch(e){if(e instanceof tt)return{content:[{type:"text",text:ot(e.message)}],isError:!0};if(e instanceof rt)return{content:[{type:"text",text:nt(e.message)}],isError:!0};const t=et(e??{});return t?{content:[{type:"text",text:t}],isError:!0}:{content:[{type:"text",text:`Error: ${e.message}`}],isError:!0}}}),p.setRequestHandler(X,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"}]})),p.setRequestHandler(Z,async c=>{const{name:m}=c.params;switch(m){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: ${m}`)}});const V=new Y;await p.connect(V),await ut(),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`)}kt().catch(p=>{console.error(`${$()}Fatal error:`,p),process.exit(1)});
@@ -62,7 +62,7 @@ export interface LaunchAllDeps {
62
62
  */
63
63
  probeSeat: (sessionToken: string, apiUrl: string) => Promise<SeatStatus>;
64
64
  /** Saved CLI preference for a worktree path (launch.json). */
65
- getCliPreferenceForPath: (projectPath: string) => Promise<'claude' | 'codex' | null>;
65
+ getCliPreferenceForPath: (projectPath: string) => Promise<'claude' | 'codex' | 'opencode' | null>;
66
66
  /** All persisted project identities from cubes.json. */
67
67
  readAllProjectIdentities: () => Promise<Array<{
68
68
  projectPath: string;
@@ -25,6 +25,7 @@
25
25
  */
26
26
  import { getActiveCube } from './cubes.js';
27
27
  import { acquireStreamLease, type StreamOwnershipSnapshot } from './stream-owner.js';
28
+ export declare function setModuleInjectOpenCode(fn: (text: string) => Promise<boolean>): void;
28
29
  export declare const INBOX_TAIL_LINES_CAP = 512;
29
30
  export declare const INBOX_TAIL_TRIM_THRESHOLD_LINES: number;
30
31
  export type RunLoopHealth = 'connected' | 'reconnecting' | 'silent-inert' | 'never-started';
@@ -119,6 +120,13 @@ export interface StreamDeps {
119
120
  * SSE stream's fetchImpl — below the agent classifier). Tests inject a spy.
120
121
  */
121
122
  onInboxReceipt?: (active: ActiveCube, token: string) => void;
123
+ /**
124
+ * Optional opencode entry injector for autonomous drone processing.
125
+ * When provided AND the injection succeeds, the inbox file write is skipped
126
+ * (the drone processes the entry autonomously). On failure, falls through
127
+ * to the inbox write for backup.
128
+ */
129
+ injectOpenCode?: (text: string) => Promise<boolean>;
122
130
  }
123
131
  /**
124
132
  * Test-only injection seam for runLoop (gh#866 item 2). Production calls
@@ -1,12 +1,12 @@
1
- import{promises as E}from"node:fs";import oe from"node:os";import L from"node:path";import{getActiveCube as ae,inboxPathForDrone as X}from"./cubes.js";import{DroneEvictedError as J,DRONE_EVICTED_CODE as ie,EVICTED_RESULT_MARKER as se,errorCodeFromBody as ce}from"./drone-lifecycle.js";import{CODEX_HEARTBEAT_CADENCE_MS as U,fireCodexHeartbeatTick as le,formatCodexWakePrompt as de,resolveSessionAgentKind as ue,startCodexHeartbeat as fe,wakeCodexViaAppServer as pe}from"./codex-app-wake.js";import{getValidToken as me}from"./remote-client.js";import{recordEventReceipt as we,emitHealthBeat as he,getCachedMonitorHealthy as be,getCachedWakeArmed as ge}from"./health-beat.js";import{getPackageVersion as ye}from"./version.js";import{acquireStreamLease as q,readOwnershipSnapshot as S}from"./stream-owner.js";const z=9e4,Ie=2e3,_e=500,Ee=3e4,Se=50,F=512,Ze=F*2;function Te(){try{const e=oe.hostname();return e&&e.trim()?e.trim().slice(0,255):null}catch{return null}}const Ae=Date.now(),a={connected:!1,lastWireActivityAt:null,lastContentEventAt:null,lastHeartbeatAt:null,lastPersistedEventId:null,reconnectAttempts:0,runLoopRestartCount:0,ownership:{state:"unowned"}};function xe(e,t=Date.now()-Ae){return e.connected&&e.lastWireActivityAt?"connected":!e.connected&&e.reconnectAttempts>0?"reconnecting":!e.connected&&!e.lastWireActivityAt&&e.reconnectAttempts===0&&t>1e4?"silent-inert":"never-started"}function et(){return{...a,runLoopHealth:xe(a)}}function tt(){a.connected=!1,a.lastWireActivityAt=null,a.lastContentEventAt=null,a.lastHeartbeatAt=null,a.lastPersistedEventId=null,a.reconnectAttempts=0,a.runLoopRestartCount=0,a.ownership={state:"unowned"}}let k=null,T=null,P=U;function W(e=Ce){k||(k=e())}function Ce(){return fe({tick:()=>{le({isStreamOwner:()=>a.ownership?.state==="owner",onAppServerSocketDead:ke})}})}function ke(){B(),He()}function He(e=P){T||(T=setTimeout(()=>{T=null,W()},e),T.unref?.())}function B(){k&&clearInterval(k),k=null,T&&clearTimeout(T),T=null}function nt(){B(),P=U}function rt(e){P=e}function De(){(async()=>{for(;;){try{await Q(),process.stderr.write(`[borg-mcp log stream] runLoop returned unexpectedly; restarting in 5s
1
+ import{promises as E}from"node:fs";import ie from"node:os";import L from"node:path";import{getActiveCube as se,inboxPathForDrone as U}from"./cubes.js";import{DroneEvictedError as q,DRONE_EVICTED_CODE as ce,EVICTED_RESULT_MARKER as le,errorCodeFromBody as de}from"./drone-lifecycle.js";import{CODEX_HEARTBEAT_CADENCE_MS as z,fireCodexHeartbeatTick as ue,formatCodexWakePrompt as fe,resolveSessionAgentKind as pe,startCodexHeartbeat as me,wakeCodexViaAppServer as we}from"./codex-app-wake.js";import{getValidToken as he}from"./remote-client.js";import{recordEventReceipt as be,emitHealthBeat as ge,getCachedMonitorHealthy as Ie,getCachedWakeArmed as _e}from"./health-beat.js";import{getPackageVersion as ye}from"./version.js";import{acquireStreamLease as Y,readOwnershipSnapshot as S}from"./stream-owner.js";const Q=9e4,Ee=2e3,Se=500,Te=3e4;let P;function tt(e){P=e}const xe=50,W=512,nt=W*2;function Ae(){try{const e=ie.hostname();return e&&e.trim()?e.trim().slice(0,255):null}catch{return null}}const Ce=Date.now(),a={connected:!1,lastWireActivityAt:null,lastContentEventAt:null,lastHeartbeatAt:null,lastPersistedEventId:null,reconnectAttempts:0,runLoopRestartCount:0,ownership:{state:"unowned"}};function ke(e,t=Date.now()-Ce){return e.connected&&e.lastWireActivityAt?"connected":!e.connected&&e.reconnectAttempts>0?"reconnecting":!e.connected&&!e.lastWireActivityAt&&e.reconnectAttempts===0&&t>1e4?"silent-inert":"never-started"}function rt(){return{...a,runLoopHealth:ke(a)}}function ot(){a.connected=!1,a.lastWireActivityAt=null,a.lastContentEventAt=null,a.lastHeartbeatAt=null,a.lastPersistedEventId=null,a.reconnectAttempts=0,a.runLoopRestartCount=0,a.ownership={state:"unowned"}}let D=null,T=null,B=z;function j(e=He){D||(D=e())}function He(){return me({tick:()=>{ue({isStreamOwner:()=>a.ownership?.state==="owner",onAppServerSocketDead:De})}})}function De(){V(),ve()}function ve(e=B){T||(T=setTimeout(()=>{T=null,j()},e),T.unref?.())}function V(){D&&clearInterval(D),D=null,T&&clearTimeout(T),T=null}function at(){V(),B=z}function it(e){B=e}function Oe(){(async()=>{for(;;){try{await ee(),process.stderr.write(`[borg-mcp log stream] runLoop returned unexpectedly; restarting in 5s
2
2
  `)}catch(e){process.stderr.write(`[borg-mcp log stream] runLoop threw: ${e?.message??e}; restarting in 5s
3
- `)}a.runLoopRestartCount+=1,await ne(5e3)}})()}function ot(e={}){W(),(e.runForever??De)()}const Y={fetchImpl:globalThis.fetch.bind(globalThis),appendLine:Fe,hasInboxEntryId:Ve,getToken:me,wakeCodex:pe,heartbeatTimeoutMs:z,hwmDivergenceGraceMs:Ie,abortSignal:new AbortController().signal,ownerDeps:{},ownerStaleMs:7e4,onInboxReceipt:ve};function ve(e,t){we(),he(e,{sseConnected:!0,inboxMonitorHealthy:be(),wakeArmed:ge(),agentKind:ue(),hostname:Te(),version:ye(),getToken:async()=>t,fetchImpl:globalThis.fetch.bind(globalThis)})}async function Q(e={}){const t=e.getActiveCube??ae,i=e.acquireStreamLease??q,r=e.sleep??ne,c=e.maxIterations??1/0;let o=0,s=0,l=null,g=null,u=null,h=null;for(;o<c;){o+=1;const d=await t();if(!d){u&&(await u.release(),u=null,h=null),a.connected=!1,a.ownership={state:"unowned"},B(),await r(5e3);continue}W(),d.cubeId!==g&&(g=d.cubeId,l=null);const H=`${d.cubeId}:${d.droneId}`;if(u&&h!==H&&(await u.release(),u=null,h=null),u||(u=await i(d.cubeId,d.droneId),h=u?H:null),!u){a.connected=!1,a.ownership=await S(d.cubeId,d.droneId),await r(5e3);continue}a.ownership=await S(d.cubeId,d.droneId);let y=!1;try{const w=new AbortController,b=async()=>{try{await u.refresh()||(y=!0,w.abort(new Error("stream ownership lost")))}catch(p){y=!0,w.abort(p instanceof Error?p:new Error(String(p)))}},x=setInterval(()=>{b()},Math.max(1e3,Math.floor(z/2)));try{await Z(d,l,p=>{l=p},{abortSignal:w.signal})}finally{clearInterval(x)}if(y){u=null,h=null,a.connected=!1,a.ownership=await S(d.cubeId,d.droneId),await r(5e3);continue}s=0,a.reconnectAttempts=0}catch(w){if(y){u=null,h=null,a.connected=!1,a.ownership=await S(d.cubeId,d.droneId),await r(5e3);continue}if(w instanceof J){u&&await u.release().catch(()=>{}),u=null,h=null,a.connected=!1,a.ownership=await S(d.cubeId,d.droneId),process.stderr.write(`[borg-mcp log stream] drone evicted \u2014 stream terminated (no reconnect).
4
- `);return}a.connected=!1;const b=Math.min(_e*2**s,Ee)+Math.random()*500;process.stderr.write(`[borg-mcp log stream] reconnect in ${Math.round(b)}ms: ${w?.message??w}
5
- `),s+=1,a.reconnectAttempts=s,await r(b)}}}function at(e){return Q(e)}async function Z(e,t,i,r={}){const{fetchImpl:c,appendLine:o,hasInboxEntryId:s,getToken:l,wakeCodex:g,heartbeatTimeoutMs:u,hwmDivergenceGraceMs:h,abortSignal:d,onInboxReceipt:H}={...Y,...r},y=await l(),w={Authorization:`Bearer ${y}`,"X-Drone-Session":e.sessionToken,Accept:"text/event-stream"};t&&(w["Last-Event-ID"]=t);const b=new AbortController,x=()=>{try{b.abort(d.reason??new Error("external abort"))}catch{}};d.aborted&&x(),d.addEventListener("abort",x,{once:!0});let p=null;const V=()=>{p&&clearTimeout(p),p=setTimeout(()=>{try{b.abort(new Error("heartbeat watchdog timeout"))}catch{}},u)};V();let G=t,m=null,I=null;const C=()=>{I&&(clearTimeout(I.timer),I=null)};let D=null;const v=(n,f)=>{const R={id:n,created_at:f};D&&f&&D.created_at&&A(R,D)<=0||(D=R,G=n,a.lastPersistedEventId=n,i(n))},M=n=>{n&&(m=!m||A(n,m)>0?n:m,I&&A(m,I.hwm)>=0&&C())},re=n=>{if(I?.hwm.id===n.id)return;C();const f=setTimeout(()=>{if(m&&A(m,n)>=0){C();return}try{b.abort(new Error("hwm divergence \u2014 reconnect for catchup"))}catch{}},h);I={hwm:n,timer:f}},N=new Set,O=[];let j=t!==null;const K=async n=>{const f=Oe(Me(n.data,n.id));return j&&await s(e.cubeId,e.droneId,n.id,f)?(v(n.id,n.data?.created_at??""),"persisted-skip"):(await o(e.cubeId,e.droneId,f),g(de(f)),H(e,y),"written")},$=n=>{for(N.add(n.id),O.push(n.id);O.length>Se;){const f=O.shift();f&&N.delete(f)}v(n.id,n.data?.created_at??""),M(te(n))};let _;try{_=await c(`${e.apiUrl}/api/drone/stream`,{method:"GET",headers:w,signal:b.signal})}catch(n){throw p&&clearTimeout(p),n}if(!_.ok||!_.body){if(p&&clearTimeout(p),_.status===410){const n=await _.text().catch(()=>"");if(ce(n)===ie)throw new J}throw new Error(`stream HTTP ${_.status}`)}a.connected=!0;try{for await(const n of Re(_.body)){V();const f=new Date().toISOString();if(a.lastWireActivityAt=f,(n.type==="log"||n.type==="bookmark")&&(a.lastContentEventAt=f),n.type==="eviction"){a.lastContentEventAt=f;try{await o(e.cubeId,e.droneId,$e(n.reason))}catch{}break}if(n.type==="heartbeat"){if(a.lastHeartbeatAt=f,n.hwm&&m===null){M(n.hwm),G===null&&v(n.hwm.id,n.hwm.created_at);continue}if(n.hwm&&m&&A(n.hwm,m)<=0){C();continue}n.hwm&&m&&A(n.hwm,m)>0&&re(n.hwm);continue}if(n.type==="bookmark"){j=!1;continue}if(n.type==="log"){if(N.has(n.id)){v(n.id,n.data?.created_at??""),M(te(n));continue}const R=typeof n.data?.message=="string"&&n.data.message.startsWith("[HEARTBEAT-PING]");if(n.data?.kind==="ack"){if(n.data?.author_drone_id===e.droneId&&await K(n)==="persisted-skip")continue;$(n);continue}if(n.data?.drone_id===e.droneId&&!R){$(n);continue}if(await K(n)==="persisted-skip")continue;$(n)}}}finally{d.removeEventListener("abort",x),p&&clearTimeout(p),C(),a.connected=!1}}async function it(e,t,i,r={}){const{ownerDeps:c,ownerStaleMs:o}={...Y,...r},s=await q(e.cubeId,e.droneId,o,c);if(!s)return a.connected=!1,a.ownership=await S(e.cubeId,e.droneId,c),"skipped";a.ownership=await S(e.cubeId,e.droneId,c);try{return await Z(e,t,i,r),"streamed"}finally{await s.release()}}async function*Re(e){const t=e.getReader(),i=new TextDecoder;let r="";try{for(;;){const{value:c,done:o}=await t.read();if(o){if(r.trim()){const l=ee(r);l&&(yield l)}return}r+=i.decode(c,{stream:!0});let s;for(;(s=r.indexOf(`
3
+ `)}a.runLoopRestartCount+=1,await oe(5e3)}})()}function st(e={}){j(),(e.runForever??Oe)()}const Z={fetchImpl:globalThis.fetch.bind(globalThis),appendLine:We,hasInboxEntryId:Ge,getToken:he,wakeCodex:we,heartbeatTimeoutMs:Q,hwmDivergenceGraceMs:Ee,abortSignal:new AbortController().signal,ownerDeps:{},ownerStaleMs:7e4,onInboxReceipt:Re,injectOpenCode:e=>P?P(e):Promise.resolve(!1)};function Re(e,t){be(),ge(e,{sseConnected:!0,inboxMonitorHealthy:Ie(),wakeArmed:_e(),agentKind:pe(),hostname:Ae(),version:ye(),getToken:async()=>t,fetchImpl:globalThis.fetch.bind(globalThis)})}async function ee(e={}){const t=e.getActiveCube??se,i=e.acquireStreamLease??Y,r=e.sleep??oe,c=e.maxIterations??1/0;let o=0,s=0,l=null,g=null,u=null,w=null;for(;o<c;){o+=1;const d=await t();if(!d){u&&(await u.release(),u=null,w=null),a.connected=!1,a.ownership={state:"unowned"},V(),await r(5e3);continue}j(),d.cubeId!==g&&(g=d.cubeId,l=null);const k=`${d.cubeId}:${d.droneId}`;if(u&&w!==k&&(await u.release(),u=null,w=null),u||(u=await i(d.cubeId,d.droneId),w=u?k:null),!u){a.connected=!1,a.ownership=await S(d.cubeId,d.droneId),await r(5e3);continue}a.ownership=await S(d.cubeId,d.droneId);let x=!1;try{const m=new AbortController,I=async()=>{try{await u.refresh()||(x=!0,m.abort(new Error("stream ownership lost")))}catch(h){x=!0,m.abort(h instanceof Error?h:new Error(String(h)))}},A=setInterval(()=>{I()},Math.max(1e3,Math.floor(Q/2)));try{await te(d,l,h=>{l=h},{abortSignal:m.signal})}finally{clearInterval(A)}if(x){u=null,w=null,a.connected=!1,a.ownership=await S(d.cubeId,d.droneId),await r(5e3);continue}s=0,a.reconnectAttempts=0}catch(m){if(x){u=null,w=null,a.connected=!1,a.ownership=await S(d.cubeId,d.droneId),await r(5e3);continue}if(m instanceof q){u&&await u.release().catch(()=>{}),u=null,w=null,a.connected=!1,a.ownership=await S(d.cubeId,d.droneId),process.stderr.write(`[borg-mcp log stream] drone evicted \u2014 stream terminated (no reconnect).
4
+ `);return}a.connected=!1;const I=Math.min(Se*2**s,Te)+Math.random()*500;process.stderr.write(`[borg-mcp log stream] reconnect in ${Math.round(I)}ms: ${m?.message??m}
5
+ `),s+=1,a.reconnectAttempts=s,await r(I)}}}function ct(e){return ee(e)}async function te(e,t,i,r={}){const{fetchImpl:c,appendLine:o,hasInboxEntryId:s,getToken:l,wakeCodex:g,heartbeatTimeoutMs:u,hwmDivergenceGraceMs:w,abortSignal:d,onInboxReceipt:k,injectOpenCode:x}={...Z,...r},m=await l(),I={Authorization:`Bearer ${m}`,"X-Drone-Session":e.sessionToken,Accept:"text/event-stream"};t&&(I["Last-Event-ID"]=t);const A=new AbortController,h=()=>{try{A.abort(d.reason??new Error("external abort"))}catch{}};d.aborted&&h(),d.addEventListener("abort",h,{once:!0});let b=null;const G=()=>{b&&clearTimeout(b),b=setTimeout(()=>{try{A.abort(new Error("heartbeat watchdog timeout"))}catch{}},u)};G();let K=t,p=null,_=null;const H=()=>{_&&(clearTimeout(_.timer),_=null)};let v=null;const O=(n,f)=>{const R={id:n,created_at:f};v&&f&&v.created_at&&C(R,v)<=0||(v=R,K=n,a.lastPersistedEventId=n,i(n))},M=n=>{n&&(p=!p||C(n,p)>0?n:p,_&&C(p,_.hwm)>=0&&H())},ae=n=>{if(_?.hwm.id===n.id)return;H();const f=setTimeout(()=>{if(p&&C(p,n)>=0){H();return}try{A.abort(new Error("hwm divergence \u2014 reconnect for catchup"))}catch{}},w);_={hwm:n,timer:f}},N=new Set,$=[];let X=t!==null;const J=async n=>{const f=Fe(Ne(n.data,n.id));return X&&await s(e.cubeId,e.droneId,n.id,f)?(O(n.id,n.data?.created_at??""),"persisted-skip"):await x(f)?(k(e,m),"written"):(await o(e.cubeId,e.droneId,f),g(fe(f)),k(e,m),"written")},F=n=>{for(N.add(n.id),$.push(n.id);$.length>xe;){const f=$.shift();f&&N.delete(f)}O(n.id,n.data?.created_at??""),M(re(n))};let y;try{y=await c(`${e.apiUrl}/api/drone/stream`,{method:"GET",headers:I,signal:A.signal})}catch(n){throw b&&clearTimeout(b),n}if(!y.ok||!y.body){if(b&&clearTimeout(b),y.status===410){const n=await y.text().catch(()=>"");if(de(n)===ce)throw new q}throw new Error(`stream HTTP ${y.status}`)}a.connected=!0;try{for await(const n of Le(y.body)){G();const f=new Date().toISOString();if(a.lastWireActivityAt=f,(n.type==="log"||n.type==="bookmark")&&(a.lastContentEventAt=f),n.type==="eviction"){a.lastContentEventAt=f;try{await o(e.cubeId,e.droneId,Pe(n.reason))}catch{}break}if(n.type==="heartbeat"){if(a.lastHeartbeatAt=f,n.hwm&&p===null){M(n.hwm),K===null&&O(n.hwm.id,n.hwm.created_at);continue}if(n.hwm&&p&&C(n.hwm,p)<=0){H();continue}n.hwm&&p&&C(n.hwm,p)>0&&ae(n.hwm);continue}if(n.type==="bookmark"){X=!1;continue}if(n.type==="log"){if(N.has(n.id)){O(n.id,n.data?.created_at??""),M(re(n));continue}const R=typeof n.data?.message=="string"&&n.data.message.startsWith("[HEARTBEAT-PING]");if(n.data?.kind==="ack"){if(n.data?.author_drone_id===e.droneId&&await J(n)==="persisted-skip")continue;F(n);continue}if(n.data?.drone_id===e.droneId&&!R){F(n);continue}if(await J(n)==="persisted-skip")continue;F(n)}}}finally{d.removeEventListener("abort",h),b&&clearTimeout(b),H(),a.connected=!1}}async function lt(e,t,i,r={}){const{ownerDeps:c,ownerStaleMs:o}={...Z,...r},s=await Y(e.cubeId,e.droneId,o,c);if(!s)return a.connected=!1,a.ownership=await S(e.cubeId,e.droneId,c),"skipped";a.ownership=await S(e.cubeId,e.droneId,c);try{return await te(e,t,i,r),"streamed"}finally{await s.release()}}async function*Le(e){const t=e.getReader(),i=new TextDecoder;let r="";try{for(;;){const{value:c,done:o}=await t.read();if(o){if(r.trim()){const l=ne(r);l&&(yield l)}return}r+=i.decode(c,{stream:!0});let s;for(;(s=r.indexOf(`
6
6
 
7
- `))!==-1;){const l=r.slice(0,s);r=r.slice(s+2);const g=ee(l);g&&(yield g)}}}finally{try{t.releaseLock()}catch{}}}function ee(e){let t=null,i=null,r=[];for(const o of e.split(`
7
+ `))!==-1;){const l=r.slice(0,s);r=r.slice(s+2);const g=ne(l);g&&(yield g)}}}finally{try{t.releaseLock()}catch{}}}function ne(e){let t=null,i=null,r=[];for(const o of e.split(`
8
8
  `))o.startsWith("event:")?t=o.slice(6).trim():o.startsWith("id:")?i=o.slice(3).trim():o.startsWith("data:")&&r.push(o.slice(5).trim());const c=r.join(`
9
- `);if(!t)return null;if(t==="log"){if(!i)return null;let o;try{o=JSON.parse(c)}catch{return null}return{type:"log",id:i,data:o}}if(t==="heartbeat"){let o=null,s=null;try{const l=JSON.parse(c);o=typeof l.ts=="string"?l.ts:null,s=Le(l.hwm)}catch{}return{type:"heartbeat",ts:o,hwm:s}}if(t==="bookmark"){let o=null;try{const s=JSON.parse(c);o=typeof s.as_of=="string"?s.as_of:null}catch{}return{type:"bookmark",as_of:o}}if(t==="eviction"){let o=null,s=null;try{const l=JSON.parse(c);o=typeof l.cube_id=="string"?l.cube_id:null,s=typeof l.reason=="string"?l.reason:null}catch{}return{type:"eviction",cube_id:o,reason:s}}return{type:"unknown",raw:e}}function Le(e){if(!e||typeof e!="object")return null;const t=e;return typeof t.id=="string"&&t.id.length>0&&typeof t.created_at=="string"&&t.created_at.length>0?{id:t.id,created_at:t.created_at}:null}function A(e,t){const i=Date.parse(e.created_at),r=Date.parse(t.created_at);return Number.isFinite(i)&&Number.isFinite(r)&&i!==r?i-r:e.created_at!==t.created_at?e.created_at<t.created_at?-1:1:e.id.localeCompare(t.id)}function te(e){if(e.data?.visibility==="direct"||e.data?.kind==="ack")return null;const t=e.data?.created_at;return typeof t=="string"&&t.length>0?{id:e.id,created_at:t}:null}function Me(e,t){return!e||typeof e!="object"?{id:t}:typeof e.id=="string"&&e.id.length>0?e:{...e,id:t}}function Ne(...e){for(const t of e)if(typeof t=="string"&&t.length>0)return t;return""}function Oe(e){const t=typeof e.created_at=="string"?new Date(e.created_at).toISOString():new Date().toISOString(),i=e.drone_label??"?",r=e.role_name??"?",c=typeof e.message=="string"?e.message:"",o=Ne(e.id,e.entry_id),s=o?`[entry_id: ${o}] `:"",l=c.replace(/\r\n|\r|\n/g," \u23CE ");return`${t} ${i} (${r}): ${s}${l}`}function $e(e){const t=new Date().toISOString(),i=e&&e.trim().length>0?e:"evicted from cube";return`${t} SYSTEM (eviction): ${se} ${i} \u2014 confirm with any borg_* call; on DRONE_EVICTED (410) shut down: print the terminal message, TaskStop the inbox Monitor, do NOT reschedule /loop.`}async function Fe(e,t,i){const r=X(e,t);await Pe(r,i,F)}async function Pe(e,t,i=F,r=i*2){await E.mkdir(L.dirname(e),{recursive:!0}),await E.appendFile(e,t+`
10
- `,"utf-8"),await We(e,i,r)}async function We(e,t,i=t){if(!Number.isInteger(t)||t<1)throw new Error("maxLines must be a positive integer");if(!Number.isInteger(i)||i<t)throw new Error("trimThresholdLines must be an integer >= maxLines");const c=(await E.readFile(e,"utf-8")).split(/\r?\n/);if(c.at(-1)===""&&c.pop(),c.length<=i)return;const o=c.slice(-t),s=L.join(L.dirname(e),`.${L.basename(e)}.${process.pid}.${Date.now()}.tmp`);try{await E.writeFile(s,o.join(`
9
+ `);if(!t)return null;if(t==="log"){if(!i)return null;let o;try{o=JSON.parse(c)}catch{return null}return{type:"log",id:i,data:o}}if(t==="heartbeat"){let o=null,s=null;try{const l=JSON.parse(c);o=typeof l.ts=="string"?l.ts:null,s=Me(l.hwm)}catch{}return{type:"heartbeat",ts:o,hwm:s}}if(t==="bookmark"){let o=null;try{const s=JSON.parse(c);o=typeof s.as_of=="string"?s.as_of:null}catch{}return{type:"bookmark",as_of:o}}if(t==="eviction"){let o=null,s=null;try{const l=JSON.parse(c);o=typeof l.cube_id=="string"?l.cube_id:null,s=typeof l.reason=="string"?l.reason:null}catch{}return{type:"eviction",cube_id:o,reason:s}}return{type:"unknown",raw:e}}function Me(e){if(!e||typeof e!="object")return null;const t=e;return typeof t.id=="string"&&t.id.length>0&&typeof t.created_at=="string"&&t.created_at.length>0?{id:t.id,created_at:t.created_at}:null}function C(e,t){const i=Date.parse(e.created_at),r=Date.parse(t.created_at);return Number.isFinite(i)&&Number.isFinite(r)&&i!==r?i-r:e.created_at!==t.created_at?e.created_at<t.created_at?-1:1:e.id.localeCompare(t.id)}function re(e){if(e.data?.visibility==="direct"||e.data?.kind==="ack")return null;const t=e.data?.created_at;return typeof t=="string"&&t.length>0?{id:e.id,created_at:t}:null}function Ne(e,t){return!e||typeof e!="object"?{id:t}:typeof e.id=="string"&&e.id.length>0?e:{...e,id:t}}function $e(...e){for(const t of e)if(typeof t=="string"&&t.length>0)return t;return""}function Fe(e){const t=typeof e.created_at=="string"?new Date(e.created_at).toISOString():new Date().toISOString(),i=e.drone_label??"?",r=e.role_name??"?",c=typeof e.message=="string"?e.message:"",o=$e(e.id,e.entry_id),s=o?`[entry_id: ${o}] `:"",l=c.replace(/\r\n|\r|\n/g," \u23CE ");return`${t} ${i} (${r}): ${s}${l}`}function Pe(e){const t=new Date().toISOString(),i=e&&e.trim().length>0?e:"evicted from cube";return`${t} SYSTEM (eviction): ${le} ${i} \u2014 confirm with any borg_* call; on DRONE_EVICTED (410) shut down: print the terminal message, TaskStop the inbox Monitor, do NOT reschedule /loop.`}async function We(e,t,i){const r=U(e,t);await Be(r,i,W)}async function Be(e,t,i=W,r=i*2){await E.mkdir(L.dirname(e),{recursive:!0}),await E.appendFile(e,t+`
10
+ `,"utf-8"),await je(e,i,r)}async function je(e,t,i=t){if(!Number.isInteger(t)||t<1)throw new Error("maxLines must be a positive integer");if(!Number.isInteger(i)||i<t)throw new Error("trimThresholdLines must be an integer >= maxLines");const c=(await E.readFile(e,"utf-8")).split(/\r?\n/);if(c.at(-1)===""&&c.pop(),c.length<=i)return;const o=c.slice(-t),s=L.join(L.dirname(e),`.${L.basename(e)}.${process.pid}.${Date.now()}.tmp`);try{await E.writeFile(s,o.join(`
11
11
  `)+`
12
- `,"utf-8"),await E.rename(s,e)}catch(l){throw await E.rm(s,{force:!0}),l}}function Be(e,t,i){if(t&&e.includes(`[entry_id: ${t}]`))return!0;const r=t?i.replace(`[entry_id: ${t}] `,""):i;return!!(r&&e.split(/\r?\n/).includes(r))}async function Ve(e,t,i,r){const c=X(e,t);let o;try{o=await E.readFile(c,"utf-8")}catch(s){if(s?.code==="ENOENT")return!1;throw s}return Be(o,i,r)}function ne(e){return new Promise(t=>setTimeout(t,e))}export{F as INBOX_TAIL_LINES_CAP,Ze as INBOX_TAIL_TRIM_THRESHOLD_LINES,nt as __resetCodexHeartbeatForTest,tt as __resetStreamStateForTest,at as __runLoopForTest,rt as __setCodexReArmDelayForTest,Pe as appendCappedInboxLine,xe as classifyRunLoopHealth,A as compareBroadcastHwm,W as ensureCodexHeartbeatStarted,$e as formatEvictionSentinelLine,Oe as formatInboxLine,et as getStreamStatus,Be as inboxRawHasEntry,Re as parseSSE,ot as startLogStream,B as stopCodexHeartbeat,Z as streamOnce,it as streamOnceIfOwner,We as trimInboxFileToRecentLines};
12
+ `,"utf-8"),await E.rename(s,e)}catch(l){throw await E.rm(s,{force:!0}),l}}function Ve(e,t,i){if(t&&e.includes(`[entry_id: ${t}]`))return!0;const r=t?i.replace(`[entry_id: ${t}] `,""):i;return!!(r&&e.split(/\r?\n/).includes(r))}async function Ge(e,t,i,r){const c=U(e,t);let o;try{o=await E.readFile(c,"utf-8")}catch(s){if(s?.code==="ENOENT")return!1;throw s}return Ve(o,i,r)}function oe(e){return new Promise(t=>setTimeout(t,e))}export{W as INBOX_TAIL_LINES_CAP,nt as INBOX_TAIL_TRIM_THRESHOLD_LINES,at as __resetCodexHeartbeatForTest,ot as __resetStreamStateForTest,ct as __runLoopForTest,it as __setCodexReArmDelayForTest,Be as appendCappedInboxLine,ke as classifyRunLoopHealth,C as compareBroadcastHwm,j as ensureCodexHeartbeatStarted,Pe as formatEvictionSentinelLine,Fe as formatInboxLine,rt as getStreamStatus,Ve as inboxRawHasEntry,Le as parseSSE,tt as setModuleInjectOpenCode,st as startLogStream,V as stopCodexHeartbeat,te as streamOnce,lt as streamOnceIfOwner,je as trimInboxFileToRecentLines};
@@ -0,0 +1,18 @@
1
+ interface ConnectDeps {
2
+ serverUrl: string;
3
+ directory: string;
4
+ droneLabel: string;
5
+ cubeName: string;
6
+ }
7
+ export declare function connectOpenCodeDrone(deps: ConnectDeps): Promise<void>;
8
+ export declare function injectOpenCodeEntry(text: string): Promise<boolean>;
9
+ export declare function probeOpenCodeDroneArmed(): Promise<boolean | null>;
10
+ export declare function disconnectOpenCodeDrone(): void;
11
+ export declare function getOpenCodeConnectionState(): {
12
+ connected: boolean;
13
+ sessionId: string | null;
14
+ totalEntriesInjected: number;
15
+ };
16
+ export declare function computeOpenCodePort(droneId: string, base?: number): number;
17
+ export {};
18
+ //# sourceMappingURL=opencode-drone.d.ts.map
@@ -0,0 +1 @@
1
+ import{createOpencodeClient as s}from"@opencode-ai/sdk";let e=null;async function i(t){try{const r=s({baseUrl:t.serverUrl}),n=await r.session.create({body:{title:`borg-drone: ${t.droneLabel} on ${t.cubeName}`}});if(!n.data){e=null;return}e={client:r,sessionId:n.data.id,directory:t.directory,connected:!0,totalEntriesInjected:0}}catch{e=null}}async function l(t){if(!e?.connected)return!1;try{return await e.client.session.prompt({path:{id:e.sessionId},body:{parts:[{type:"text",text:t}]}}),e.totalEntriesInjected++,!0}catch{return e.connected=!1,!1}}async function d(){if(!e?.connected)return null;try{return(await e.client.path.get()).response.status===200}catch{return!1}}function u(){e=null}function p(){return{connected:e?.connected??!1,sessionId:e?.sessionId??null,totalEntriesInjected:e?.totalEntriesInjected??0}}function f(t,r=14096){let n=0;for(let o=0;o<t.length;o++){const c=t.charCodeAt(o);n=(n<<5)-n+c,n|=0}return r+Math.abs(n)%1024}export{f as computeOpenCodePort,i as connectOpenCodeDrone,u as disconnectOpenCodeDrone,p as getOpenCodeConnectionState,l as injectOpenCodeEntry,d as probeOpenCodeDroneArmed};
@@ -0,0 +1,3 @@
1
+ export declare const BORG_PLUGIN_SOURCE = "\n// Borg MCP orientation plugin - installed by borg assimilate\nexport default async function (ctx) {\n const serverUrl = ctx.serverUrl?.toString() ?? '';\n\n if (serverUrl) {\n try {\n const homeDir = process.env.HOME || process.env.USERPROFILE || '';\n const markerDir = homeDir + '/.config/borgmcp';\n const marker = markerDir + '/opencode-server';\n const encoder = new TextEncoder();\n ctx.$.write(marker, encoder.encode(serverUrl));\n } catch {}\n }\n\n return {\n event: async ({ event }) => {\n if (event.type === 'session.created') {\n const sessionId = event.properties.info?.id;\n if (!sessionId) return;\n try {\n await ctx.client.session.prompt({\n path: { id: sessionId },\n body: {\n noReply: true,\n parts: [{ type: 'text', text: '## Borg Cube Context\\nYou are operating in a borgmcp.ai multi-agent cube. Use borg_regen to get full context and recent activity.' }]\n }\n });\n } catch {}\n }\n },\n 'experimental.session.compacting': async (_input, output) => {\n output.context.push('## Borg Cube\\nYou are in a borgmcp.ai multi-agent cube. Use borg_regen to refresh context and see recent activity.');\n },\n };\n}\n";
2
+ export declare function installBorgPlugin(): void;
3
+ //# sourceMappingURL=opencode-plugin.d.ts.map
@@ -0,0 +1,37 @@
1
+ import e from"node:fs";import i from"node:os";import n from"node:path";const o=`
2
+ // Borg MCP orientation plugin - installed by borg assimilate
3
+ export default async function (ctx) {
4
+ const serverUrl = ctx.serverUrl?.toString() ?? '';
5
+
6
+ if (serverUrl) {
7
+ try {
8
+ const homeDir = process.env.HOME || process.env.USERPROFILE || '';
9
+ const markerDir = homeDir + '/.config/borgmcp';
10
+ const marker = markerDir + '/opencode-server';
11
+ const encoder = new TextEncoder();
12
+ ctx.$.write(marker, encoder.encode(serverUrl));
13
+ } catch {}
14
+ }
15
+
16
+ return {
17
+ event: async ({ event }) => {
18
+ if (event.type === 'session.created') {
19
+ const sessionId = event.properties.info?.id;
20
+ if (!sessionId) return;
21
+ try {
22
+ await ctx.client.session.prompt({
23
+ path: { id: sessionId },
24
+ body: {
25
+ noReply: true,
26
+ parts: [{ type: 'text', text: '## Borg Cube Context\\nYou are operating in a borgmcp.ai multi-agent cube. Use borg_regen to get full context and recent activity.' }]
27
+ }
28
+ });
29
+ } catch {}
30
+ }
31
+ },
32
+ 'experimental.session.compacting': async (_input, output) => {
33
+ output.context.push('## Borg Cube\\nYou are in a borgmcp.ai multi-agent cube. Use borg_regen to refresh context and see recent activity.');
34
+ },
35
+ };
36
+ }
37
+ `;function u(){const r=n.join(i.homedir(),".config","opencode","plugins"),t=n.join(r,"borg-orient.js");try{if(e.existsSync(t)&&e.readFileSync(t,"utf-8")===o)return;e.mkdirSync(r,{recursive:!0}),e.writeFileSync(t,o,"utf-8")}catch{}}export{o as BORG_PLUGIN_SOURCE,u as installBorgPlugin};
@@ -2,7 +2,7 @@ export interface LaunchAllFlags {
2
2
  mode?: 'tmux' | 'windows' | 'pastelist';
3
3
  only?: string;
4
4
  dryRun?: boolean;
5
- cli?: 'claude' | 'codex';
5
+ cli?: 'claude' | 'codex' | 'opencode';
6
6
  noAttach?: boolean;
7
7
  yes?: boolean;
8
8
  force?: boolean;
@@ -23,7 +23,7 @@
23
23
  */
24
24
  export declare function parseHookSource(raw: string): string | null;
25
25
  /** The agent runtime a session runs under — drives the wake-path branch. */
26
- export type AgentKind = 'claude' | 'codex';
26
+ export type AgentKind = 'claude' | 'codex' | 'opencode';
27
27
  /**
28
28
  * The agent-branched WAKE-PATH ARMING sub-block (gh#929/gh#927) — the single
29
29
  * shared "re-establish your wake path" instruction reused by the launch
@@ -34,7 +34,7 @@ export type AgentKind = 'claude' | 'codex';
34
34
  *
35
35
  * Agent-branched on the existing env-agnostic signal (BORG_SESSION-style
36
36
  * `isCodexRemoteWakeEnabled`), NOT on a mutable server-recorded field:
37
- * - claude: arm the inbox-file tail Monitor + engage `/loop` + set a ~1800s
37
+ * - claude: arm the inbox-file tail Monitor + engage `/loop` + set a ~3600s
38
38
  * `ScheduleWakeup` fallback heartbeat (three independent wake mechanisms).
39
39
  * - codex: wakes via the app-server remote-wake injection — it has NO
40
40
  * tail-Monitor and NO `/loop` heartbeat to arm, so instructing either would
@@ -1,4 +1,4 @@
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 N}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 T(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.":["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 ~1800s (30-min) `ScheduleWakeup` so you never go fully silent."].join(`
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 N}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 T(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.":["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
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.","",T(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
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
4
  `);return(s===-1?t.body+`
package/dist/regen.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import{regen as i,listCubes as a}from"./remote-client.js";import{getActiveCube as c,inboxPathForDrone as u}from"./cubes.js";import{parseHookSource as h,formatLeanOrientation as l,resolveLeanIdentity as p}from"./regen-format.js";import{isCodexRemoteWakeEnabled as b}from"./codex-app-wake.js";import{handleVersionFlag as d}from"./version.js";import{gateAllowsActivation as g}from"./launch-gate.js";async function m(){if(process.stdin.isTTY)return"";try{const e=[];for await(const t of process.stdin)e.push(t);return Buffer.concat(e).toString("utf-8")}catch{return""}}async function f(){if(d(),!g("borg-regen SessionStart hook"))return;const e=h(await m()),t=await c();if(!t){await w();return}const o=u(t.cubeId,t.droneId),n=b()?"codex":"claude";let s=null;try{s=await i(t.sessionToken,t.apiUrl)}catch{s=null}const r=p(t,s);process.stdout.write(l({...r,inboxPath:o,agentKind:n,source:e})+`
2
+ import{regen as i,listCubes as a}from"./remote-client.js";import{getActiveCube as c,inboxPathForDrone as u}from"./cubes.js";import{parseHookSource as h,formatLeanOrientation as l,resolveLeanIdentity as p}from"./regen-format.js";import{resolveSessionAgentKind as b}from"./codex-app-wake.js";import{handleVersionFlag as g}from"./version.js";import{gateAllowsActivation as m}from"./launch-gate.js";async function d(){if(process.stdin.isTTY)return"";try{const e=[];for await(const t of process.stdin)e.push(t);return Buffer.concat(e).toString("utf-8")}catch{return""}}async function f(){if(g(),!m("borg-regen SessionStart hook"))return;const e=h(await d()),t=await c();if(!t){await w();return}const o=u(t.cubeId,t.droneId),n=b();let s=null;try{s=await i(t.sessionToken,t.apiUrl)}catch{s=null}const r=p(t,s);process.stdout.write(l({...r,inboxPath:o,agentKind:n,source:e})+`
3
3
  `)}async function w(){const e=[];e.push("# Borg MCP \u2014 not connected to a cube"),e.push(""),e.push("The borg MCP server is installed in this project but no cube has been joined here yet."),e.push(""),e.push("**Drone instructions:**"),e.push("1. **Tell the user** which cubes exist on their account (see the list below) and ask them which one to join, or whether to create a new cube. This is the first thing you should surface in this session."),e.push("2. **Do not** call `borg_assimilate`, `borg_create-cube`, or any other state-changing borg tool until the user has chosen."),e.push("3. The user may also decline to use borg in this project at all \u2014 that's a valid choice; just stop suggesting it."),e.push("");try{const{cubes:t}=await a();if(t.length>0){e.push("## Cubes on your account");for(const o of t)e.push(`- **${o.name}** (id: ${o.id})`)}else e.push("## Cubes on your account"),e.push("_(none yet \u2014 offer to create one via `borg_create-cube` once the user confirms)_")}catch(t){e.push("## Cubes on your account"),e.push("_(could not list \u2014 "+(t?.message??String(t))+")_")}e.push(""),e.push("## Tools you can call once the user has chosen"),e.push('- Join an existing cube: `borg_assimilate cube_name="<their choice>"`'),e.push('- Create a new cube: `borg_create-cube name="<name>" cube_directive="<markdown>"` (optionally `template="software-dev"`)'),e.push("- See available templates: `borg_list-templates`"),process.stdout.write(e.join(`
4
4
  `)+`
5
5
  `)}f().catch(e=>{const t=e?.message??String(e);process.stderr.write(`borg-regen: ${t}
@@ -87,7 +87,7 @@ export declare function assimilate(cubeNameOrSelector: string | {
87
87
  role_name?: string;
88
88
  prior_drone_id?: string;
89
89
  model?: string | null;
90
- }, apiUrl?: string, hostname?: string | null, agentKind?: 'claude' | 'codex' | null): Promise<{
90
+ }, apiUrl?: string, hostname?: string | null, agentKind?: 'claude' | 'codex' | 'opencode' | null): Promise<{
91
91
  cube: {
92
92
  id: string;
93
93
  owner_id: string;
@@ -26,7 +26,7 @@ export interface RosterDrone {
26
26
  * joined before the column existed or via a launcher path that didn't
27
27
  * forward the kind. Rendered as `(Role, kind)` when present.
28
28
  */
29
- agent_kind?: 'claude' | 'codex' | null;
29
+ agent_kind?: 'claude' | 'codex' | 'opencode' | null;
30
30
  regen_count?: number | null;
31
31
  wake_path?: 'live' | 'degraded' | 'deaf' | null;
32
32
  wake_path_alert_class?: 'dead' | 'post-blocked' | 'presumed-dead' | 'systemic-post-block' | 'wake-path-deaf' | 'independent' | null;
@@ -26,10 +26,12 @@ export interface ConfigMutationTarget {
26
26
  * agent CLIs. Paths mirror `config-utils.ts`:
27
27
  * Claude Code: ~/.claude.json (MCP server) + ~/.claude/settings.json (hook)
28
28
  * Codex: ~/.codex/config.toml (MCP server) + ~/.codex/hooks.json (hooks)
29
+ * OpenCode: ~/.config/opencode/opencode.json (MCP server)
29
30
  */
30
31
  export declare function configMutationTargets(deps: {
31
32
  claude: boolean;
32
33
  codex: boolean;
34
+ opencode?: boolean;
33
35
  }): ConfigMutationTarget[];
34
36
  /**
35
37
  * Disclosure text: the files Step-1 will write + an undo note. Pure so SR
@@ -96,8 +98,10 @@ export declare function parseYesFlag(argv: string[]): boolean;
96
98
  export declare function setupMutationPending(deps: {
97
99
  claude: boolean;
98
100
  codex: boolean;
101
+ opencode: boolean;
99
102
  claudeMcpConfigured: boolean;
100
103
  codexMcpConfigured: boolean;
104
+ opencodeMcpConfigured: boolean;
101
105
  claudeHookPending: boolean;
102
106
  codexHookPending: boolean;
103
107
  }): boolean;
@@ -1,2 +1,2 @@
1
- function r(e){const o=[];return e.claude&&(o.push({file:"~/.claude.json",change:"registers the borg MCP server"}),o.push({file:"~/.claude/settings.json",change:"adds a UserPromptSubmit hook"})),e.codex&&(o.push({file:"~/.codex/config.toml",change:"registers the borg MCP server"}),o.push({file:"~/.codex/hooks.json",change:"adds SessionStart + UserPromptSubmit hooks"})),o}function t(e){const o=[];o.push("borg setup will register the borg MCP server in your agent config:");for(const n of e)o.push(` \u2022 ${n.file} (${n.change})`);return o.push('These changes are additive and reversible \u2014 remove the "borg" entries to undo.'),o.join(`
2
- `)}async function i(e){return!e.isTTY||e.yes||await e.confirm()?"proceed":"abort"}function s(e){return e.includes("--yes")||e.includes("-y")}function c(e){return e.claude&&(!e.claudeMcpConfigured||e.claudeHookPending)||e.codex&&(!e.codexMcpConfigured||e.codexHookPending)}export{r as configMutationTargets,i as confirmConfigMutation,t as formatConfigMutationDisclosure,s as parseYesFlag,c as setupMutationPending};
1
+ function r(e){const o=[];return e.claude&&(o.push({file:"~/.claude.json",change:"registers the borg MCP server"}),o.push({file:"~/.claude/settings.json",change:"adds a UserPromptSubmit hook"})),e.codex&&(o.push({file:"~/.codex/config.toml",change:"registers the borg MCP server"}),o.push({file:"~/.codex/hooks.json",change:"adds SessionStart + UserPromptSubmit hooks"})),e.opencode&&o.push({file:"~/.config/opencode/opencode.json",change:"registers the borg MCP server (with BORG_SESSION activation)"}),o}function t(e){const o=[];o.push("borg setup will register the borg MCP server in your agent config:");for(const n of e)o.push(` \u2022 ${n.file} (${n.change})`);return o.push('These changes are additive and reversible \u2014 remove the "borg" entries to undo.'),o.join(`
2
+ `)}async function i(e){return!e.isTTY||e.yes||await e.confirm()?"proceed":"abort"}function c(e){return e.includes("--yes")||e.includes("-y")}function s(e){return e.claude&&(!e.claudeMcpConfigured||e.claudeHookPending)||e.codex&&(!e.codexMcpConfigured||e.codexHookPending)||e.opencode&&!e.opencodeMcpConfigured}export{r as configMutationTargets,i as confirmConfigMutation,t as formatConfigMutationDisclosure,c as parseYesFlag,s as setupMutationPending};