borgmcp 1.0.58 → 1.0.59
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assimilate-cmd.d.ts +3 -0
- package/dist/assimilate-cmd.js +10 -10
- package/dist/assimilate-deps.js +3 -3
- package/dist/index.js +17 -16
- package/dist/remote-client.d.ts +8 -0
- package/dist/remote-client.js +1 -1
- package/dist/role-match.d.ts +17 -0
- package/dist/role-match.js +1 -0
- package/dist/role-resolver.d.ts +13 -4
- package/dist/role-resolver.js +1 -1
- package/dist/tool-manifest.js +1 -1
- package/package.json +1 -1
package/dist/assimilate-cmd.d.ts
CHANGED
package/dist/assimilate-cmd.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import{dirname as K,basename as C}from"node:path";import{randomUUID as oe}from"node:crypto";import{roleSlug as ie,matchRoleByName as ae,pickDefaultRole as le}from"./role-resolver.js";import{deriveCubeName as se,parseGitRemote as ce,sanitizeRemoteUrl as ue}from"./cube-name.js";import{validateName as q}from"./name-validator.js";import{renderAssimilationWelcome as me}from"./assimilate-welcome.js";import{shellEscape as de}from"./shell-escape.js";import{withCodexCwdArg as fe}from"./codex-remote.js";import{buildAgentKickoffPrompt as he,buildKickoffWakePathClause as ge,recordCodexWakeTarget as we,socketPathFromRemoteArgs as be}from"./codex-launch.js";import{perWorktreeBranchName as W,adoptWorktree as ke,computeWorktreePath as z,localBranchExists as J,isMerged as ye}from"./worktree-lifecycle.js";import{DroneEvictedError as ve}from"./drone-lifecycle.js";import{codexBorgSessionConfigArgs as
|
|
1
|
+
import{dirname as K,basename as C}from"node:path";import{randomUUID as oe}from"node:crypto";import{roleSlug as ie,matchRoleByName as ae,pickDefaultRole as le}from"./role-resolver.js";import{deriveCubeName as se,parseGitRemote as ce,sanitizeRemoteUrl as ue}from"./cube-name.js";import{validateName as q}from"./name-validator.js";import{renderAssimilationWelcome as me}from"./assimilate-welcome.js";import{shellEscape as de}from"./shell-escape.js";import{withCodexCwdArg as fe}from"./codex-remote.js";import{buildAgentKickoffPrompt as he,buildKickoffWakePathClause as ge,recordCodexWakeTarget as we,socketPathFromRemoteArgs as be}from"./codex-launch.js";import{perWorktreeBranchName as W,adoptWorktree as ke,computeWorktreePath as z,localBranchExists as J,isMerged as ye}from"./worktree-lifecycle.js";import{DroneEvictedError as ve}from"./drone-lifecycle.js";import{codexBorgSessionConfigArgs as pe}from"./launch-gate.js";import{inboxPathForDrone as $e}from"./cubes.js";import{resolveLaunchEnv as xe,resolveOllamaBaseUrl as Se,parseModel as Ce}from"./model-presets.js";import{unlinkSync as Re}from"node:fs";import{gcOrphanInboxesForCube as Ee,defaultListInboxLogs as _e,defaultInboxLivenessDeps as Ie,isInboxLive as Ae,ORPHAN_INBOX_STALE_MS as Ne}from"./gc-orphan-inboxes.js";async function rt(r,e){if(r.role!==void 0){const t=q(r.role);if(!t.ok)return e.stderr(t.error+`
|
|
2
2
|
`),1}if(r.flags.worktree!==void 0){const t=q(r.flags.worktree);if(!t.ok)return e.stderr(t.error+`
|
|
3
3
|
`),1}let a=await e.getCachedAuth();if(!a){if(!e.isTTY()&&!r.flags.yes)return e.stderr("borg setup required and stdin is non-interactive. Run `borg setup` first in an interactive terminal, then `borg assimilate`.\n"),1;a=await e.runSetup()}const o=e.findProjectRoot(e.cwd());let i;if(r.flags.cubeName)i=r.flags.cubeName;else{const t=e.runSync("git",["remote","get-url","origin"],o),n=t.status===0?t.stdout:null;if(i=se(o,n),n){const c=ue(n),m=c?ce(c):null;c&&!m&&i&&e.stderr(`couldn't parse git remote '${c}' \u2014 using directory name '${i}' as cube name
|
|
4
4
|
`)}}let l=null;if(i&&i.includes("@")&&i.includes(":")){const t=i.lastIndexOf(":");l={ownerEmail:i.substring(0,t),cubeName:i.substring(t+1)},i=l.cubeName}const R=e.cwd();e.stderr(`Checking your cubes\u2026
|
|
5
|
-
`);let
|
|
6
|
-
`),a=await e.runSetup(),
|
|
7
|
-
`),1;let u,N;if(
|
|
5
|
+
`);let I;try{I=await e.listCubes(a.apiUrl,a.token)}catch(t){const n=t instanceof Error?t.message:String(t);if(n.includes("Authentication required")||n.includes("Authentication expired"))e.stderr(`Re-authenticating...
|
|
6
|
+
`),a=await e.runSetup(),I=await e.listCubes(a.apiUrl,a.token);else throw t}const A=I.find(t=>t.name===i);if(!A&&l)return e.stderr(`No cube named '${l.cubeName}' accessible to you owned by '${l.ownerEmail}'. Did you accept their invite? See borgmcp.ai/dashboard.
|
|
7
|
+
`),1;let u,N;if(A)u=await e.getCube(a.apiUrl,a.token,A.id),N=!1;else{let t;if(r.flags.template)t=r.flags.template;else if(r.flags.noTemplate)t=void 0;else if(e.isTTY())if(r.flags.yes)t="starter";else{const n=await e.listTemplates(a.apiUrl,a.token),c=["First drone joining a new cube. Apply a template?"];n.forEach((p,$)=>{const _=$===0?" (default)":"";c.push(` ${$+1}) ${p.name}${_} \u2014 ${p.description}`)}),c.push(` ${n.length+1}) skip \u2014 no template`);const m=(await e.prompt(c.join(`
|
|
8
8
|
`)+`
|
|
9
9
|
[1]: `)).trim(),y=m===""?1:parseInt(m,10);if(Number.isNaN(y)||y<1||y>n.length+1)return e.stderr(`invalid choice "${m}"
|
|
10
10
|
`),1;t=y<=n.length?n[y-1].name:void 0}else{if(!r.flags.yes)return e.stderr(`cube creation needs a template choice but stdin is non-interactive.
|
|
@@ -13,8 +13,8 @@ Pass --template <name>, --no-template, or --yes (defaults to starter).
|
|
|
13
13
|
`:`Creating your cube\u2026
|
|
14
14
|
`),u=await e.createCube(a.apiUrl,a.token,t?{name:i??void 0,template:t}:{name:i??void 0}),N=!0}let f;if(r.role!==void 0){if(f=ae(u.roles,r.role),!f){const t=u.roles.map(m=>m.name).join(", "),n=De(r.role,u.roles.map(m=>m.name)),c=n?` Did you mean "${n}"?`:"";return e.stderr(`no role matching "${r.role}" in cube "${u.name}". Available: ${t}.${c}
|
|
15
15
|
(Use --template <name> on first-drone setup or run \`borg_create-role\` from inside Claude.)
|
|
16
|
-
`),1}}else if(f=le(u.roles,{isFirstDrone:N}),!f)return e.stderr(`cube "${u.name}" has no default or human-seat role; cannot infer a role. Either pass a role argument explicitly (e.g. \`borg assimilate builder\`) or run \`borg_create-role\` from inside Claude to set up roles.
|
|
17
|
-
`),1
|
|
16
|
+
`),1}}else{const t=new Set((u.drones??[]).map(n=>n.role_id));if(f=le(u.roles,{isFirstDrone:N,occupiedRoleIds:t}),!f)return e.stderr(`cube "${u.name}" has no default or human-seat role; cannot infer a role. Either pass a role argument explicitly (e.g. \`borg assimilate builder\`) or run \`borg_create-role\` from inside Claude to set up roles.
|
|
17
|
+
`),1}const x=await e.getActiveCube();let S;if(x&&r.flags.here)if(x.cubeId===u.id)S=x.droneId;else return e.stderr(`this directory already hosts an active drone; remove --here or run from a fresh worktree
|
|
18
18
|
`),1;const M=r.flags.worktree!==void 0||x!==null&&!r.flags.here,X=S??x?.droneId??null,P=M?null:await e.getLaunchModel(u.id,o,X),b=r.flags.model??P?.model??f.default_model??null,L=Se(process.env,b!=null&&b===P?.model?P?.ollamaBaseUrl:void 0);if(b){const t=await e.checkModelReachable(b,e.fetch,L);if(!t.ok)return e.stderr(`${t.message}
|
|
19
19
|
`),1}const h=await e.resolveCli(r.flags.cli);e.stderr(`Joining cube '${u.name}' as ${f.name}\u2026
|
|
20
20
|
`);let s;try{s=await e.assimilate(a.apiUrl,a.token,{cube_id:u.id,role_id:f.id,hostname:e.getHostname(),agent_kind:h,model:b,...S?{prior_drone_id:S}:{}})}catch(t){if(t instanceof ve&&S!=null)return e.stderr(`seat evicted \u2014 this worktree's saved seat was evicted from the cube. Re-assimilate fresh from a terminal, or remove this worktree.
|
|
@@ -25,18 +25,18 @@ Pass --template <name>, --no-template, or --yes (defaults to starter).
|
|
|
25
25
|
Fix: create at least one commit (\`git commit --allow-empty -m "initial"\`)
|
|
26
26
|
OR: pass --here to skip the sibling spawn and use the current directory
|
|
27
27
|
`),1;e.runSync("git",["fetch","origin"],o);let n="origin/main";e.runSync("git",["rev-parse","--verify","origin/main"],o).status!==0&&e.runSync("git",["rev-parse","--verify","origin/master"],o).status===0&&(n="origin/master");const m=t.stdout.trim(),y=e.runSync("git",["rev-parse",n],o).stdout.trim();m!==y&&e.stderr(`note: local HEAD (${m.slice(0,7)}) differs from ${n} (${y.slice(0,7)}); new worktree will start on ${n}
|
|
28
|
-
`);const
|
|
29
|
-
`),1;const _=e.homedir();let d=z(_
|
|
28
|
+
`);const p=C(o),$=r.flags.worktree??ie(k.name);if($.length===0)return e.stderr(`cannot derive a worktree name from role "${k.name}"; pass an explicit --worktree <name>
|
|
29
|
+
`),1;const _=e.homedir();let d=z(_,p,$),v=W(C(d),p),Y=2;for(;e.pathExists(d)||Te(e,o,d)||J(e.runSync,o,v)&&!ye(e.runSync,o,v,n);)d=z(_,p,$,Y),v=W(C(d),p),Y++;e.mkdirp(K(d));const G=J(e.runSync,o,v)?e.runSync("git",["worktree","add",d,v],o):e.runSync("git",["worktree","add","-b",v,d,n],o);if(G.status!==0)return e.stderr(`git worktree add failed: ${V(G.stderr)}
|
|
30
30
|
`),1;e.stderr(`spawned sibling worktree at ${d} on branch ${v} (${n}); original dir is registered as active (edit ~/.config/borgmcp/cubes.json if stale).
|
|
31
31
|
`),e.chdir(d),e.stderr(Pe(d,v,o)),g=e.cwd()}try{await e.setActiveCube({cubeId:s.cube_id,droneId:s.drone_id,name:u.name,sessionToken:s.session_token,droneLabel:s.drone_label,apiUrl:a.apiUrl,roleName:k.name,isHumanSeat:k.is_human_seat,...k.role_class?{roleClass:k.role_class}:{}})}catch(t){const n=t instanceof Error?t.message:String(t);if(e.stderr(`setActiveCube failed: ${n}
|
|
32
32
|
`),g){const c=e.runSync("git",["worktree","remove","--force",g],o);c.status===0?e.stderr(`rolled back spawned worktree at ${g}
|
|
33
33
|
`):e.stderr(`manual cleanup needed: \`git worktree remove --force ${g}\` (rollback attempt failed: ${V(c.stderr).trim()||"unknown"})
|
|
34
|
-
`)}return 1}try{const t=
|
|
34
|
+
`)}return 1}try{const t=Ie(),n=K($e(s.cube_id,s.drone_id));Ee({cubeInboxDir:n,selfDroneId:s.drone_id,deps:{listInboxLogs:_e,isLive:c=>Ae(c,t),droneState:()=>"absent",unlink:c=>Re(c),now:t.now,staleMs:Ne}})}catch{}e.setTerminalTitle(s.drone_label,u.name);const Z=e.isTTY()&&!process.env.NO_COLOR&&!process.env.CI;e.stdout(me(k.name,u.name,Z));const w=e.cwd();try{e.installProjectSessionHook(w)}catch{e.stderr(`warning: could not install the project-local SessionStart hook in ${w}; it will be re-attempted on the next borg launch
|
|
35
35
|
`)}if(!g){e.runSync("git",["fetch","origin","--prune"],w);const t=W(C(w),C(o)),n=ke(e.runSync,w,t,"origin/main");n.action==="adopted"?(e.stderr(`worktree: adopted branch ${t} at origin/main
|
|
36
36
|
`),e.stderr(Le(w,t))):n.message&&e.stderr(`worktree sync: ${n.message}
|
|
37
37
|
`)}await e.probeMcpReady()||e.stderr(`warning: borg-mcp readiness probe did not complete within the timeout; launching ${h} anyway \u2014 the kickoff prompt's ToolSearch fallback will recover if the MCP server takes longer to start.
|
|
38
38
|
`);const ee=e.getInboxPath(s.cube_id,s.drone_id),T=h==="codex"?`borg-wake-${oe()}`:null,te=ge(h==="codex"?"codex":"claude",h==="claude"?ee:null);let D,H=[],E,U=null,O=null;const B=e.findProjectRoot(w);b?await e.setLaunchModel(s.cube_id,B,{model:b,ollamaBaseUrl:Ce(b).kind==="ollama"?L:null}):await e.clearLaunchModel(s.cube_id,B);const F=xe(b,L),j={...process.env,...F.set,BORG_SESSION:"1"};for(const t of F.unset)delete j[t];if(h==="codex"){const t=await e.prepareCodexRemoteLaunch();t.warning?(e.stderr(`warning: ${t.warning}
|
|
39
|
-
`),D="\u26A0 Codex wake-path capability check failed: remote-control is unavailable for this session. Run borg_regen manually whenever you return, and expect only fallback wakeups until relaunch."):D="Codex wake-path capability check passed: remote-control socket established for this session.",H=t.args,Object.keys(t.env).length>0&&Object.assign(j,t.env),U=be(t.args),O=t.server?.cleanup??null}E=[he({cli:h,codexWakeNonce:T,monitorClause:te,codexWakePathClause:D})],h==="codex"&&(E=[
|
|
39
|
+
`),D="\u26A0 Codex wake-path capability check failed: remote-control is unavailable for this session. Run borg_regen manually whenever you return, and expect only fallback wakeups until relaunch."):D="Codex wake-path capability check passed: remote-control socket established for this session.",H=t.args,Object.keys(t.env).length>0&&Object.assign(j,t.env),U=be(t.args),O=t.server?.cleanup??null}E=[he({cli:h,codexWakeNonce:T,monitorClause:te,codexWakePathClause:D})],h==="codex"&&(E=[...pe(),...H,...fe(E,w)]);const re=e.exec(h,E,w,j);h==="codex"&&U&&T&&we({deps:e,cubeId:s.cube_id,droneId:s.drone_id,socketPath:U,cwd:w,previewNeedle:T,launchedAtSeconds:Math.floor(Date.now()/1e3)});const ne=await re;if(O)try{O()}catch{}return g&&R!==g&&e.stderr(`
|
|
40
40
|
Agent exited. You were working in ${g}; your shell is back in ${R}.
|
|
41
41
|
To return:
|
|
42
42
|
cd ${de(g)}
|
package/dist/assimilate-deps.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{spawnSync as p,spawn as l}from"node:child_process";import{existsSync as h,mkdirSync as f}from"node:fs";import{hostname as b,homedir as
|
|
2
|
-
`))if(c.includes('"protocolVersion"')&&c.includes('"result"')){clearTimeout(i),s(!0);return}});const a=JSON.stringify({jsonrpc:"2.0",id:1,method:"initialize",params:{protocolVersion:"2024-11-05",capabilities:{},clientInfo:{name:"borg-assimilate-probe",version:"0.9.3"}}});try{
|
|
3
|
-
`)}catch{clearTimeout(i),s(!1)}}),resolveCli:e=>G(e,D(async
|
|
1
|
+
import{spawnSync as p,spawn as l}from"node:child_process";import{existsSync as h,mkdirSync as f}from"node:fs";import{hostname as b,homedir as _}from"node:os";import{createInterface as d}from"node:readline/promises";import{API_URL as u,getValidToken as m,listCubes as C,getCube as T,createCube as y,assimilate as k,listTemplates as w}from"./remote-client.js";import{findProjectRoot as g,getActiveCube as L,setActiveCube as x,inboxPathForDrone as M,setCodexWakeTarget as S,getLaunchModel as v,setLaunchModel as R,clearLaunchModel as A}from"./cubes.js";import{authenticateWithGoogle as P}from"./auth.js";import{addProjectSessionStartHook as U}from"./config-utils.js";import{setTerminalTitle as j}from"./terminal-title.js";import{defaultCliChoiceDeps as D,resolveCliChoice as G}from"./cli-platform.js";import{prepareCodexRemoteLaunch as H,defaultCodexRemoteDeps as I}from"./codex-remote.js";import{findLoadedCodexThread as q}from"./codex-app-server.js";import{checkModelReachable as V}from"./model-presets.js";function $(){return{runSync:(e,r,t)=>{const o=p(e,r,{cwd:t,encoding:"utf-8"});return{status:o.status,stdout:o.stdout??"",stderr:o.stderr??""}},pathExists:e=>h(e),cwd:()=>process.cwd(),chdir:e=>process.chdir(e),homedir:()=>_(),mkdirp:e=>f(e,{recursive:!0}),exec:(e,r,t,o)=>new Promise((s,i)=>{const a=l(e,r,{cwd:t,stdio:"inherit",shell:!1,env:o??process.env});a.on("error",i),a.on("exit",n=>s(n??0))}),stderr:e=>process.stderr.write(e),stdout:e=>process.stdout.write(e),prompt:async e=>{const r=d({input:process.stdin,output:process.stdout});try{return await r.question(e)}finally{r.close()}},isTTY:()=>process.stdin.isTTY===!0,getHostname:()=>b(),setTerminalTitle:(e,r)=>{j({label:e,cubeName:r},r)},getActiveCube:()=>L(),setActiveCube:e=>x(e),findProjectRoot:e=>g(e),installProjectSessionHook:e=>{U(e)},getCachedAuth:async()=>{try{return{token:await m(),apiUrl:u}}catch{return null}},runSetup:async()=>(await P(),{token:await m(),apiUrl:u}),listCubes:async(e,r)=>{const{cubes:t}=await C();return t.map(o=>({id:o.id,name:o.name}))},getCube:async(e,r,t)=>{const o=await T(t);return{id:o.id,name:o.name,roles:o.roles,drones:(o.drones??[]).map(s=>({role_id:s.role_id}))}},createCube:async(e,r,t)=>{const o=await y(t.name,"",t.template?{template:t.template}:void 0);return{id:o.id,name:o.name,roles:o.roles}},assimilate:async(e,r,t)=>{const o=await k({cube_id:t.cube_id,role_id:t.role_id,...t.prior_drone_id?{prior_drone_id:t.prior_drone_id}:{},...t.model!=null?{model:t.model}:{}},void 0,t.hostname??null,t.agent_kind??null);return{cube_id:o.cube.id,drone_id:o.drone.id,drone_label:o.drone.label,session_token:o.sessionToken,role_id:o.role.id,reattached:o.reattached===!0}},listTemplates:async(e,r)=>{const{templates:t}=await w();return t.map(o=>({name:o.name,description:o.description}))},getInboxPath:(e,r)=>M(e,r),probeMcpReady:()=>new Promise(e=>{const r=l("borg-mcp",[],{stdio:["pipe","pipe","pipe"],shell:!1});let t="",o=!1;const s=n=>{if(!o){o=!0;try{r.kill("SIGTERM")}catch{}e(n)}},i=setTimeout(()=>s(!1),2e3);r.on("error",()=>{clearTimeout(i),s(!1)}),r.on("exit",()=>{clearTimeout(i),s(o)}),r.stdout?.on("data",n=>{t+=n.toString("utf-8");for(const c of t.split(`
|
|
2
|
+
`))if(c.includes('"protocolVersion"')&&c.includes('"result"')){clearTimeout(i),s(!0);return}});const a=JSON.stringify({jsonrpc:"2.0",id:1,method:"initialize",params:{protocolVersion:"2024-11-05",capabilities:{},clientInfo:{name:"borg-assimilate-probe",version:"0.9.3"}}});try{r.stdin?.write(a+`
|
|
3
|
+
`)}catch{clearTimeout(i),s(!1)}}),resolveCli:e=>G(e,D(async r=>{const t=d({input:process.stdin,output:process.stdout});try{return await t.question(r)}finally{t.close()}},()=>process.stdin.isTTY===!0)),prepareCodexRemoteLaunch:()=>H(I()),setCodexWakeTarget:S,findLoadedCodexThread:q,fetch,checkModelReachable:V,getLaunchModel:(e,r,t)=>v(e,r,t),setLaunchModel:(e,r,t)=>R(e,r,t),clearLaunchModel:(e,r)=>A(e,r)}}export{$ as buildDefaultAssimilateDeps};
|
package/dist/index.js
CHANGED
|
@@ -1,37 +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{getCubeInfo as
|
|
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}.
|
|
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
|
|
5
|
-
`)+B(n,{mode:"full"})}]}}catch(n){const s=
|
|
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:"}
|
|
6
6
|
|
|
7
|
-
${
|
|
8
|
-
`)}]}}case"borg_role":{const e=await h(),{role:
|
|
9
|
-
`)}]}}
|
|
10
|
-
`)}]}}case"
|
|
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
|
|
11
12
|
|
|
12
13
|
The log-stream consumer started but never connected. This drone will not receive real-time cube events.
|
|
13
14
|
|
|
14
|
-
`);const i=
|
|
15
|
-
`)}]}}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(
|
|
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?`
|
|
16
17
|
${c.routing.message}`:"",b=c.unreachableRecipients?.length?`
|
|
17
|
-
\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
|
|
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}
|
|
18
19
|
${o.message}`});return{content:[{type:"text",text:`Reports (${e.reports.length}, newest first):
|
|
19
20
|
|
|
20
21
|
${t.join(`
|
|
21
22
|
|
|
22
23
|
---
|
|
23
24
|
|
|
24
|
-
`)}`}]}}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
|
|
25
|
-
`)}]}}case"borg_list-cubes":{const{cubes:e}=await
|
|
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})
|
|
26
27
|
${(o.cube_directive||"_(no directive set)_").split(`
|
|
27
28
|
`)[0].slice(0,120)}`);return{content:[{type:"text",text:`Your cubes (${e.length}):
|
|
28
29
|
|
|
29
30
|
${t.join(`
|
|
30
31
|
|
|
31
|
-
`)}`}]}}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: ${
|
|
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
33
|
|
|
33
34
|
${s.join(`
|
|
34
|
-
`)}`}]}}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:
|
|
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
36
|
|
|
36
|
-
${
|
|
37
|
-
`)}`}]};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
|
|
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)});
|
package/dist/remote-client.d.ts
CHANGED
|
@@ -132,6 +132,14 @@ export declare function getCubeInfo(sessionToken: string, apiUrl: string): Promi
|
|
|
132
132
|
export declare function getRoleInfo(sessionToken: string, apiUrl: string): Promise<{
|
|
133
133
|
role: any;
|
|
134
134
|
}>;
|
|
135
|
+
/**
|
|
136
|
+
* Get a named role's full playbook (detailed_description). Any drone in
|
|
137
|
+
* the cube may read any role. `role` is a role name (case-insensitive)
|
|
138
|
+
* or role id.
|
|
139
|
+
*/
|
|
140
|
+
export declare function getRoleInfoByName(sessionToken: string, apiUrl: string, role: string): Promise<{
|
|
141
|
+
role: any;
|
|
142
|
+
}>;
|
|
135
143
|
export declare function whoami(sessionToken: string, apiUrl: string): Promise<{
|
|
136
144
|
cube_id: string;
|
|
137
145
|
cube_name: string;
|
package/dist/remote-client.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{getIdToken as f,getRefreshToken as h,clearTokens as m}from"./config.js";import{refreshIdToken as S,RefreshTokenInvalidError as T,RefreshTransientError as g}from"./auth.js";import{consolePrefix as R}from"./console-prefix.js";import{debugLog as b}from"./debug.js";import{assertUuidShape as $}from"./evict-drone.js";import{DroneEvictedError as C,DroneFrozenError as k,DRONE_EVICTED_CODE as P,DRONE_FROZEN_CODE as O,errorCodeFromBody as I}from"./drone-lifecycle.js";import{MODEL_DESCRIPTOR_REGEX as D}from"./model-presets.js";const L=process.env.BORG_API_URL||"https://api.borgmcp.ai",A=3,G=6e4;let l=null;function x(e){return l||(l=S(e).finally(()=>{l=null}),l)}function J(e){if(e==null)return null;const n=e.trim();return/^\d+$/.test(n)?parseInt(n,10)*1e3:null}function v(e,n,t=G,o=()=>Math.random()*500){const s=e??1e3*(n+1);return Math.min(s,t)+o()}function M(e){const n=(t,o)=>`${t}${/[.:!?]$/.test(t)?"":":"} ${o}`;try{const t=JSON.parse(e);if(typeof t?.error=="string")return typeof t.details=="string"?n(t.error,t.details):t.error;if(t?.error&&typeof t.error=="object"){const o=t.error.message,s=t.error.details??t.details;if(typeof o=="string"&&typeof s=="string")return n(o,s);if(typeof o=="string")return o}if(typeof t?.message=="string"&&typeof t?.details=="string")return n(t.message,t.details);if(typeof t?.message=="string")return t.message}catch{}return e}async function U(e,n,t){const o=t.maxRetries??A;let s=e,a=0;for(;s.status===429&&a<o;){const d=v(J(s.headers.get("Retry-After")),a,t.capMs,t.jitter);t.log?.(`rate limited (429); retrying in ${Math.round(d)}ms (attempt ${a+1}/${o})`),await t.sleep(d),a++,s=await n()}return s}function q(e){return new Promise(n=>setTimeout(n,e))}async function F(){let e=await f();if(!e){const n=await h(),t=n!=null;if(n)try{await x(n),e=await f()}catch(o){if(o instanceof T)await m();else throw o instanceof g?o:new g(`Token refresh failed unexpectedly (your saved login was NOT cleared \u2014 retry; if it persists, restart the borg session): ${o?.message??"unknown"}`)}if(!e)throw new Error(t?"Authentication expired \u2014 your saved login has expired. Run: borg setup":"Authentication required \u2014 you are not signed in. Run: borg setup")}return e}async function H(){const e=await h();if(!e)return null;try{return await x(e),await f()}catch(n){if(n instanceof T&&await m(),n instanceof g)throw n;return null}}async function Q(){if(await f())return"valid";const n=await h();if(!n)return"dead";try{return await x(n),await f()?"valid":"transient"}catch(t){return t instanceof T?(await m(),"dead"):"transient"}}async function r(e,n={}){let t=await F();const{droneSession:o,apiUrl:s,headers:a,...d}=n,_=s??L,y=(d.method??"GET").toUpperCase(),w=async c=>{const p={Authorization:`Bearer ${c}`,...a};o&&(p["X-Drone-Session"]=o),b(`\u2192 ${y} ${e}`);const u=await fetch(`${_}${e}`,{...d,headers:p});return b(`\u2190 ${u.status} ${y} ${e}`),u};let i=await w(t);if(i.status===401){const c=await H();c&&(t=c,i=await w(t))}if(i.status===401)throw new Error("Authentication required. Run: borg setup");if(i.status===429&&(i=await U(i,()=>w(t),{sleep:q,log:c=>console.error(`${R()}${c}`)})),!i.ok){const c=await i.text();b(`\u2717 ${i.status} ${y} ${e}: ${c}`);const p=M(c),u=I(c);if(i.status===410&&u===P)throw new C(p);if(i.status===423&&u===O)throw new k(p);if(i.status===429){const E=i.headers.get("Retry-After"),j=E?` (retry after ${E}s)`:"";throw new Error(`HTTP 429: rate limited${j}: ${p}`)}throw new Error(`HTTP ${i.status}: ${p}`)}return i}async function Y(e,n,t,o){const s={hostname:t??null};if((o==="claude"||o==="codex")&&(s.agent_kind=o),typeof e=="string")s.cube_name=e;else if(e.cube_id&&(s.cube_id=e.cube_id),e.cube_name&&(s.cube_name=e.cube_name),e.role_id&&(s.role_id=e.role_id),e.role_name&&(s.role_name=e.role_name),e.prior_drone_id&&(s.prior_drone_id=e.prior_drone_id),e.model!=null){if(!D.test(e.model))throw new Error(`Invalid model descriptor: "${e.model}" (expected "<claude|ollama>:<model>").`);s.model=e.model}return await(await r("/api/assimilate",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s),apiUrl:n})).json()}async function K(e,n){return await(await r("/api/drone/cube",{method:"GET",droneSession:e,apiUrl:n})).json()}async function ee(e,n){return await(await r("/api/drone/role",{method:"GET",droneSession:e,apiUrl:n})).json()}async function te(e,n){return await(await r("/api/drone/whoami",{method:"GET",droneSession:e,apiUrl:n})).json()}async function ne(e,n,t){const o=t?`?since=${encodeURIComponent(t)}`:"";return await(await r(`/api/drone/roster${o}`,{method:"GET",droneSession:e,apiUrl:n})).json()}async function oe(e,n,t={}){const o=new URLSearchParams;t.since&&o.set("since",t.since),t.limit!==void 0&&o.set("limit",String(t.limit)),t.unreadOnly&&o.set("unread_only","true");const s=o.toString();return await(await r(`/api/drone/log${s?`?${s}`:""}`,{method:"GET",droneSession:e,apiUrl:n})).json()}async function se(e,n,t,o="ack"){await r(`/api/drone/log/${t}/ack`,{method:"POST",body:JSON.stringify({kind:o}),droneSession:e,apiUrl:n})}async function re(e,n,t){return await(await r("/api/drone/decide",{method:"POST",body:JSON.stringify(t),droneSession:e,apiUrl:n})).json()}async function ae(e,n,t){const o=t?`?topic=${encodeURIComponent(t)}`:"";return await(await r(`/api/drone/decisions${o}`,{method:"GET",droneSession:e,apiUrl:n})).json()}async function ie(e,n,t={}){const o=new URLSearchParams;t.since&&o.set("since",t.since);const s=o.toString();return await(await r(`/api/drone/regen${s?`?${s}`:""}`,{method:"GET",droneSession:e,apiUrl:n})).json()}async function ce(e,n,t,o){const s=new URLSearchParams({role:t,section:o});return await(await r(`/api/drone/role-rationale?${s.toString()}`,{method:"GET",droneSession:e,apiUrl:n})).json()}async function pe(e,n,t,o={}){const s={message:t,...o.visibility?{visibility:o.visibility}:{},...o.recipientDroneIds?{recipientDroneIds:o.recipientDroneIds}:{},...o.class?{class:o.class}:{},...o.to?{to:o.to}:{}};return await(await r("/api/drone/log",{method:"POST",headers:{"Content-Type":"application/json"},droneSession:e,apiUrl:n,body:JSON.stringify(s)})).json()}async function de(e,n,t){const o={kind:t.kind??"friction",message:t.message,...t.metadata?{metadata:t.metadata}:{}};return await(await r("/api/drone/report",{method:"POST",headers:{"Content-Type":"application/json"},droneSession:e,apiUrl:n,body:JSON.stringify(o)})).json()}async function ue(){const e=await r("/api/reports",{method:"GET"});if(e.status===403)return{forbidden:!0};if(!e.ok)throw new Error(`Failed to fetch reports: ${e.status}`);return{forbidden:!1,reports:(await e.json()).reports}}async function fe(){return await(await r("/api/cubes",{method:"GET"})).json()}async function le(){return await(await r("/api/templates",{method:"GET"})).json()}async function ye(e,n,t){const o={cube_directive:n};e&&(o.name=e),t?.template&&(o.template=t.template),t&&Object.prototype.hasOwnProperty.call(t,"message_taxonomy")&&(o.message_taxonomy=t.message_taxonomy??null);const a=await(await r("/api/cubes",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(o)})).json();return a.cube?{...a.cube,roles:a.roles??[],drones:a.drones??[]}:a}async function we(e,n){return await(await r(`/api/cubes/${e}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).json()}async function he(e,n){return await(await r(`/api/cubes/${e}/taxonomy-patch`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).json()}async function me(e){await r(`/api/cubes/${e}`,{method:"DELETE"})}async function Te(e,n){return await(await r(`/api/cubes/${e}/roles`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).json()}async function ge(e,n){return await(await r(`/api/roles/${e}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).json()}async function be(e,n){return await(await r(`/api/roles/${e}/section-patch`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).json()}async function xe(e){await r(`/api/roles/${e}`,{method:"DELETE"})}async function Ee(e,n){return $(e,"drone_id"),await(await r(`/api/drones/${e}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({role_id:n})})).json()}async function $e(e){$(e,"drone_id"),await r(`/api/drones/${e}`,{method:"DELETE"})}async function _e(e){const t=await(await r(`/api/cubes/${e}`,{method:"GET"})).json();return t.cube?{...t.cube,roles:t.roles??[],drones:t.drones??[]}:t}async function je(e,n){return await(await r(`/api/cubes/${e}/apply-template`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({template_name:n})})).json()}async function Se(){return await(await r("/api/subscription/status",{method:"GET"})).json()}async function Re(e,n="software-dev",t=!1,o){return await(await r(`/api/cubes/${e}/sync-roles`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({template_name:n,apply:t,...o?{decisions:o}:{}})})).json()}async function Ce(){const n=await(await r("/api/subscribe",{method:"POST",headers:{"Content-Type":"application/json"}})).json();if(!n.checkout_url)throw new Error("No checkout URL in response");return n.checkout_url}async function ke(){const n=await(await r("/api/subscription/portal",{method:"POST",headers:{"Content-Type":"application/json"}})).json();if(!n.portal_url)throw new Error(n.message||"No portal URL in response");return n.portal_url}export{L as API_URL,se as ackLogEntry,pe as appendLog,je as applyTemplate,Y as assimilate,Se as checkSubscriptionStatus,ke as createBillingPortalSession,ye as createCube,Te as createRole,Ce as createSubscription,me as deleteCube,xe as deleteRole,$e as evictDrone,M as extractHttpErrorMessage,ue as fetchReports,_e as getCube,K as getCubeInfo,ee as getRoleInfo,ne as getRoster,F as getValidToken,fe as listCubes,ae as listDecisions,le as listTemplates,J as parseRetryAfterMs,be as patchRoleSection,he as patchTaxonomyClass,Q as probeSession,v as rateLimitWaitMs,oe as readLog,Ee as reassignDrone,re as recordDecision,ie as regen,U as retryOn429,ce as roleRationale,de as submitReport,Re as syncRoles,we as updateCube,ge as updateRole,te as whoami};
|
|
1
|
+
import{getIdToken as f,getRefreshToken as h,clearTokens as m}from"./config.js";import{refreshIdToken as j,RefreshTokenInvalidError as T,RefreshTransientError as g}from"./auth.js";import{consolePrefix as R}from"./console-prefix.js";import{debugLog as b}from"./debug.js";import{assertUuidShape as $}from"./evict-drone.js";import{DroneEvictedError as C,DroneFrozenError as k,DRONE_EVICTED_CODE as P,DRONE_FROZEN_CODE as O,errorCodeFromBody as I}from"./drone-lifecycle.js";import{MODEL_DESCRIPTOR_REGEX as D}from"./model-presets.js";const L=process.env.BORG_API_URL||"https://api.borgmcp.ai",A=3,G=6e4;let l=null;function x(e){return l||(l=j(e).finally(()=>{l=null}),l)}function J(e){if(e==null)return null;const n=e.trim();return/^\d+$/.test(n)?parseInt(n,10)*1e3:null}function v(e,n,t=G,o=()=>Math.random()*500){const s=e??1e3*(n+1);return Math.min(s,t)+o()}function M(e){const n=(t,o)=>`${t}${/[.:!?]$/.test(t)?"":":"} ${o}`;try{const t=JSON.parse(e);if(typeof t?.error=="string")return typeof t.details=="string"?n(t.error,t.details):t.error;if(t?.error&&typeof t.error=="object"){const o=t.error.message,s=t.error.details??t.details;if(typeof o=="string"&&typeof s=="string")return n(o,s);if(typeof o=="string")return o}if(typeof t?.message=="string"&&typeof t?.details=="string")return n(t.message,t.details);if(typeof t?.message=="string")return t.message}catch{}return e}async function U(e,n,t){const o=t.maxRetries??A;let s=e,a=0;for(;s.status===429&&a<o;){const d=v(J(s.headers.get("Retry-After")),a,t.capMs,t.jitter);t.log?.(`rate limited (429); retrying in ${Math.round(d)}ms (attempt ${a+1}/${o})`),await t.sleep(d),a++,s=await n()}return s}function q(e){return new Promise(n=>setTimeout(n,e))}async function B(){let e=await f();if(!e){const n=await h(),t=n!=null;if(n)try{await x(n),e=await f()}catch(o){if(o instanceof T)await m();else throw o instanceof g?o:new g(`Token refresh failed unexpectedly (your saved login was NOT cleared \u2014 retry; if it persists, restart the borg session): ${o?.message??"unknown"}`)}if(!e)throw new Error(t?"Authentication expired \u2014 your saved login has expired. Run: borg setup":"Authentication required \u2014 you are not signed in. Run: borg setup")}return e}async function F(){const e=await h();if(!e)return null;try{return await x(e),await f()}catch(n){if(n instanceof T&&await m(),n instanceof g)throw n;return null}}async function Q(){if(await f())return"valid";const n=await h();if(!n)return"dead";try{return await x(n),await f()?"valid":"transient"}catch(t){return t instanceof T?(await m(),"dead"):"transient"}}async function r(e,n={}){let t=await B();const{droneSession:o,apiUrl:s,headers:a,...d}=n,_=s??L,y=(d.method??"GET").toUpperCase(),w=async c=>{const p={Authorization:`Bearer ${c}`,...a};o&&(p["X-Drone-Session"]=o),b(`\u2192 ${y} ${e}`);const u=await fetch(`${_}${e}`,{...d,headers:p});return b(`\u2190 ${u.status} ${y} ${e}`),u};let i=await w(t);if(i.status===401){const c=await F();c&&(t=c,i=await w(t))}if(i.status===401)throw new Error("Authentication required. Run: borg setup");if(i.status===429&&(i=await U(i,()=>w(t),{sleep:q,log:c=>console.error(`${R()}${c}`)})),!i.ok){const c=await i.text();b(`\u2717 ${i.status} ${y} ${e}: ${c}`);const p=M(c),u=I(c);if(i.status===410&&u===P)throw new C(p);if(i.status===423&&u===O)throw new k(p);if(i.status===429){const E=i.headers.get("Retry-After"),S=E?` (retry after ${E}s)`:"";throw new Error(`HTTP 429: rate limited${S}: ${p}`)}throw new Error(`HTTP ${i.status}: ${p}`)}return i}async function Y(e,n,t,o){const s={hostname:t??null};if((o==="claude"||o==="codex")&&(s.agent_kind=o),typeof e=="string")s.cube_name=e;else if(e.cube_id&&(s.cube_id=e.cube_id),e.cube_name&&(s.cube_name=e.cube_name),e.role_id&&(s.role_id=e.role_id),e.role_name&&(s.role_name=e.role_name),e.prior_drone_id&&(s.prior_drone_id=e.prior_drone_id),e.model!=null){if(!D.test(e.model))throw new Error(`Invalid model descriptor: "${e.model}" (expected "<claude|ollama>:<model>").`);s.model=e.model}return await(await r("/api/assimilate",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s),apiUrl:n})).json()}async function K(e,n){return await(await r("/api/drone/cube",{method:"GET",droneSession:e,apiUrl:n})).json()}async function ee(e,n){return await(await r("/api/drone/role",{method:"GET",droneSession:e,apiUrl:n})).json()}async function te(e,n,t){const o=new URLSearchParams({role:t});return await(await r(`/api/drone/role?${o.toString()}`,{method:"GET",droneSession:e,apiUrl:n})).json()}async function ne(e,n){return await(await r("/api/drone/whoami",{method:"GET",droneSession:e,apiUrl:n})).json()}async function oe(e,n,t){const o=t?`?since=${encodeURIComponent(t)}`:"";return await(await r(`/api/drone/roster${o}`,{method:"GET",droneSession:e,apiUrl:n})).json()}async function se(e,n,t={}){const o=new URLSearchParams;t.since&&o.set("since",t.since),t.limit!==void 0&&o.set("limit",String(t.limit)),t.unreadOnly&&o.set("unread_only","true");const s=o.toString();return await(await r(`/api/drone/log${s?`?${s}`:""}`,{method:"GET",droneSession:e,apiUrl:n})).json()}async function re(e,n,t,o="ack"){await r(`/api/drone/log/${t}/ack`,{method:"POST",body:JSON.stringify({kind:o}),droneSession:e,apiUrl:n})}async function ae(e,n,t){return await(await r("/api/drone/decide",{method:"POST",body:JSON.stringify(t),droneSession:e,apiUrl:n})).json()}async function ie(e,n,t){const o=t?`?topic=${encodeURIComponent(t)}`:"";return await(await r(`/api/drone/decisions${o}`,{method:"GET",droneSession:e,apiUrl:n})).json()}async function ce(e,n,t={}){const o=new URLSearchParams;t.since&&o.set("since",t.since);const s=o.toString();return await(await r(`/api/drone/regen${s?`?${s}`:""}`,{method:"GET",droneSession:e,apiUrl:n})).json()}async function pe(e,n,t,o){const s=new URLSearchParams({role:t,section:o});return await(await r(`/api/drone/role-rationale?${s.toString()}`,{method:"GET",droneSession:e,apiUrl:n})).json()}async function de(e,n,t,o={}){const s={message:t,...o.visibility?{visibility:o.visibility}:{},...o.recipientDroneIds?{recipientDroneIds:o.recipientDroneIds}:{},...o.class?{class:o.class}:{},...o.to?{to:o.to}:{}};return await(await r("/api/drone/log",{method:"POST",headers:{"Content-Type":"application/json"},droneSession:e,apiUrl:n,body:JSON.stringify(s)})).json()}async function ue(e,n,t){const o={kind:t.kind??"friction",message:t.message,...t.metadata?{metadata:t.metadata}:{}};return await(await r("/api/drone/report",{method:"POST",headers:{"Content-Type":"application/json"},droneSession:e,apiUrl:n,body:JSON.stringify(o)})).json()}async function fe(){const e=await r("/api/reports",{method:"GET"});if(e.status===403)return{forbidden:!0};if(!e.ok)throw new Error(`Failed to fetch reports: ${e.status}`);return{forbidden:!1,reports:(await e.json()).reports}}async function le(){return await(await r("/api/cubes",{method:"GET"})).json()}async function ye(){return await(await r("/api/templates",{method:"GET"})).json()}async function we(e,n,t){const o={cube_directive:n};e&&(o.name=e),t?.template&&(o.template=t.template),t&&Object.prototype.hasOwnProperty.call(t,"message_taxonomy")&&(o.message_taxonomy=t.message_taxonomy??null);const a=await(await r("/api/cubes",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(o)})).json();return a.cube?{...a.cube,roles:a.roles??[],drones:a.drones??[]}:a}async function he(e,n){return await(await r(`/api/cubes/${e}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).json()}async function me(e,n){return await(await r(`/api/cubes/${e}/taxonomy-patch`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).json()}async function Te(e){await r(`/api/cubes/${e}`,{method:"DELETE"})}async function ge(e,n){return await(await r(`/api/cubes/${e}/roles`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).json()}async function be(e,n){return await(await r(`/api/roles/${e}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).json()}async function xe(e,n){return await(await r(`/api/roles/${e}/section-patch`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).json()}async function Ee(e){await r(`/api/roles/${e}`,{method:"DELETE"})}async function $e(e,n){return $(e,"drone_id"),await(await r(`/api/drones/${e}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({role_id:n})})).json()}async function _e(e){$(e,"drone_id"),await r(`/api/drones/${e}`,{method:"DELETE"})}async function Se(e){const t=await(await r(`/api/cubes/${e}`,{method:"GET"})).json();return t.cube?{...t.cube,roles:t.roles??[],drones:t.drones??[]}:t}async function je(e,n){return await(await r(`/api/cubes/${e}/apply-template`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({template_name:n})})).json()}async function Re(){return await(await r("/api/subscription/status",{method:"GET"})).json()}async function Ce(e,n="software-dev",t=!1,o){return await(await r(`/api/cubes/${e}/sync-roles`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({template_name:n,apply:t,...o?{decisions:o}:{}})})).json()}async function ke(){const n=await(await r("/api/subscribe",{method:"POST",headers:{"Content-Type":"application/json"}})).json();if(!n.checkout_url)throw new Error("No checkout URL in response");return n.checkout_url}async function Pe(){const n=await(await r("/api/subscription/portal",{method:"POST",headers:{"Content-Type":"application/json"}})).json();if(!n.portal_url)throw new Error(n.message||"No portal URL in response");return n.portal_url}export{L as API_URL,re as ackLogEntry,de as appendLog,je as applyTemplate,Y as assimilate,Re as checkSubscriptionStatus,Pe as createBillingPortalSession,we as createCube,ge as createRole,ke as createSubscription,Te as deleteCube,Ee as deleteRole,_e as evictDrone,M as extractHttpErrorMessage,fe as fetchReports,Se as getCube,K as getCubeInfo,ee as getRoleInfo,te as getRoleInfoByName,oe as getRoster,B as getValidToken,le as listCubes,ie as listDecisions,ye as listTemplates,J as parseRetryAfterMs,xe as patchRoleSection,me as patchTaxonomyClass,Q as probeSession,v as rateLimitWaitMs,se as readLog,$e as reassignDrone,ae as recordDecision,ce as regen,U as retryOn429,pe as roleRationale,ue as submitReport,Ce as syncRoles,he as updateCube,be as updateRole,ne as whoami};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rollout-compat guard. An old worker ignores the `?role=` param and
|
|
3
|
+
* returns the CALLER's own role, so a new client on a not-yet-deployed
|
|
4
|
+
* worker would silently show the wrong playbook. Verify the returned
|
|
5
|
+
* role actually matches the request: by id when the input was a uuid,
|
|
6
|
+
* otherwise by case-insensitive trimmed name.
|
|
7
|
+
*
|
|
8
|
+
* Benign residual: if the requested name/id equals the caller's OWN role,
|
|
9
|
+
* an old worker's echo passes the check — but that echo IS the correct role
|
|
10
|
+
* (names are unique per cube, ids globally unique), so no wrong playbook is
|
|
11
|
+
* ever shown. The guard only needs to catch a request for a DIFFERENT role.
|
|
12
|
+
*/
|
|
13
|
+
export declare function assertRoleMatches(requested: string, returned: {
|
|
14
|
+
id?: string | null;
|
|
15
|
+
name?: string | null;
|
|
16
|
+
}): void;
|
|
17
|
+
//# sourceMappingURL=role-match.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const r=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;function s(t,o){const e=t.trim();if(!(r.test(e)?o.id===e:(o.name??"").trim().toLowerCase()===e.toLowerCase()))throw new Error(`server does not support named-role lookup yet (returned "${o.name??"?"}" for "${t}") \u2014 worker upgrade pending`)}export{s as assertRoleMatches};
|
package/dist/role-resolver.d.ts
CHANGED
|
@@ -23,12 +23,21 @@ export declare function roleSlug(raw: string): string;
|
|
|
23
23
|
*/
|
|
24
24
|
export declare function matchRoleByName(roles: Role[], query: string): Role | undefined;
|
|
25
25
|
/**
|
|
26
|
-
*
|
|
27
|
-
* - First drone in cube + a human-seat role exists → that role
|
|
28
|
-
* - Otherwise → the
|
|
29
|
-
*
|
|
26
|
+
* Role picker for the no-arg `borg assimilate` case:
|
|
27
|
+
* - First drone in cube + a human-seat role exists → that role.
|
|
28
|
+
* - Otherwise → the first UNOCCUPIED eligible worker role in list order
|
|
29
|
+
* (eligible = not queen-class, not human-seat). The default role is an
|
|
30
|
+
* equal candidate in this pass.
|
|
31
|
+
* - If every eligible worker role is occupied → the is_default role.
|
|
32
|
+
* - Neither available → undefined (caller errors out).
|
|
33
|
+
*
|
|
34
|
+
* `occupiedRoleIds` is the set of role_ids that already hold an ACTIVE seat
|
|
35
|
+
* (the caller derives it from the cube's active drones). Omitted → treated as
|
|
36
|
+
* empty (all roles unoccupied), which degrades to "first eligible worker role,
|
|
37
|
+
* else default".
|
|
30
38
|
*/
|
|
31
39
|
export declare function pickDefaultRole(roles: Role[], opts: {
|
|
32
40
|
isFirstDrone: boolean;
|
|
41
|
+
occupiedRoleIds?: ReadonlySet<string>;
|
|
33
42
|
}): Role | undefined;
|
|
34
43
|
//# sourceMappingURL=role-resolver.d.ts.map
|
package/dist/role-resolver.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
function
|
|
1
|
+
function o(n){return n.toLowerCase().replace(/[_\s]+/g,"-").replace(/[^a-z0-9-]/g,"")}function c(n,i){const t=o(i);return n.find(a=>o(a.name)===t)}function r(n,i){if(i.isFirstDrone){const e=n.find(u=>u.is_human_seat);if(e)return e}const t=i.occupiedRoleIds??new Set;return n.filter(e=>e.role_class!=="queen"&&!e.is_human_seat).find(e=>!t.has(e.id))??n.find(e=>e.is_default)}export{c as matchRoleByName,r as pickDefaultRole,o as roleSlug};
|
package/dist/tool-manifest.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const e=[{name:"borg_subscribe",description:"Create Stripe checkout session for Cube tier ($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.",inputSchema:{type:"object",properties:{},required:[]}},{name:"borg_upgrade-subscription",description:"Open the Stripe Billing Portal to manage Cube tier quantity ($1/month per cube; each cube adds 8 pooled agent sessions + 1000 req/hr).",inputSchema:{type:"object",properties:{},required:[]}},{name:"borg_subscription_status",description:"Check subscription status",inputSchema:{type:"object",properties:{},required:[]}},{name:"borg_open_dashboard",description:"Open Borg MCP dashboard in browser to manage cubes, roles, and drones",inputSchema:{type:"object",properties:{},required:[]}},{name:"borg_regen",description:"Refresh your context as a Drone. Returns the active cube's directive, your role's detailed playbook, the drone roster, and recent activity log entries \u2014 everything you need to be oriented. Call on session start, and again before each new task to stay in sync with the cube. Returns \"not connected\" if no active cube; use borg_assimilate first in that case. Optional `since` (entry-id UUID or ISO-8601 timestamp) trims the recent-log section to entries strictly after the anchor \u2014 pass your last-seen entry id to skip already-processed history on each refresh.",inputSchema:{type:"object",properties:{since:{type:"string",description:"Optional cursor. Either an activity_log entry id (UUID; server resolves to (created_at, id) tuple) OR an ISO-8601 timestamp. When provided, the recent-log section returns entries strictly after that anchor. Non-existent UUID falls back to default recent window."},mode:{type:"string",enum:["full","lite"],description:"Optional output mode. Use full at session start and after context compaction. Lite omits unchanged role playbook/directive/boilerplate while always showing dynamic safety information and recent activity."}},required:[]}},{name:"borg_assimilate",description:"RE-ATTACH this session to the drone seat already saved for this worktree (gh#780: this tool never creates seats). Provide the cube's name; on a match it returns the cube directive, your role's instructions, and recent activity for the EXISTING seat. To create a seat or switch cubes, run `borg assimilate` in a terminal instead.",inputSchema:{type:"object",properties:{cube_name:{type:"string",description:"The cube to connect to"}},required:["cube_name"]}},{name:"borg_cube",description:"Read the active Cube's directive and the registry of all roles in it (each role's name + short description). Use to remind yourself of cube-wide context.",inputSchema:{type:"object",properties:{},required:[]}},{name:"borg_role",description:"Read your assigned role's detailed description (your playbook). Other drones cannot see this \u2014 only you (drones in this role).",inputSchema:{type:"object",properties:{},required:[]}},{name:"borg_version",description:"Returns the installed borgmcp client version. Use to verify which version is running in this MCP session.",inputSchema:{type:"object",properties:{},required:[]}},{name:"borg_playbook",description:"Load the full operating-playbook chapter \u2014 the detailed disciplines, rationale, and examples behind the rule-spine in your regen (verification discipline v1/v2/v3, concrete source-of-truth surfaces, four-surface propagation). This detail is kept OUT of the regen bootstrap to keep it light; fetch it ONCE per session when doing review/verify-class work. Static text \u2014 do NOT re-fetch on every wake.",inputSchema:{type:"object",properties:{},required:[]}},{name:"borg_docs",description:'Look up the Borg MCP documentation. Call this when the user asks how borgmcp works, or any feature / usage / setup / pricing / concept / tool question. Returns the docs index \u2014 each section\'s borgmcp.ai URL + a one-line summary. Pass `topic` (e.g. "pricing", "worktree", "roles", "codex") to get the best-matching section(s) instead of the full index. Then WebFetch the returned URL to read the page \u2014 borg_docs returns the index only, it does not fetch the page for you.',inputSchema:{type:"object",properties:{topic:{type:"string",description:"Optional search topic \u2014 returns the best-matching docs section(s) instead of the full index."}},required:[]}},{name:"borg_whoami",description:"Returns your identity in the current cube: cube name, drone label, and role name. Use to confirm which cube/role/drone you are.",inputSchema:{type:"object",properties:{},required:[]}},{name:"borg_role-rationale",description:"Fetch an on-demand rationale/case-study section for a role playbook. Pass a role name/id and a plain-label section key to read the rationale without expanding every regen.",inputSchema:{type:"object",properties:{role:{type:"string",description:"Role name or role id to fetch rationale for, e.g. Builder."},section:{type:"string",description:"Plain-label role section key, e.g. Workflow rationale."}},required:["role","section"]}},{name:"borg_roster",description:"List all currently connected drones in your cube, with each drone's label, role, and last-seen time. Optional `since` argument adds a sender-side liveness column \u2014 pass either an activity_log entry id (e.g., from a dispatch you posted) or an ISO-8601 timestamp; each drone is marked `awake` if they've posted a log entry after that point, otherwise `stale-since-X`. Useful for confirming a dispatch reached its named recipients (catches the silent-wake-path-failure class where SSE delivered but the drone's /loop never woke).",inputSchema:{type:"object",properties:{since:{type:"string",description:"Optional liveness reference point. Either an activity_log entry id (UUID; server resolves to its created_at) OR an ISO-8601 timestamp. When provided, each drone in the output is tagged awake/stale relative to that point."}},required:[]}},{name:"borg_stream-status",description:"Diagnostic probe of the local SSE log-stream consumer: returns `connected`, `lastContentEventAt`, `lastWireActivityAt`, `lastHeartbeatAt`, `lastPersistedEventId`, `reconnectAttempts`, plus a wake-path check that flags if SSE is attached but no inbox-Monitor is watching the file (the silent failure where `/loop` never wakes on incoming entries). Read-only in-process state; does NOT re-open the stream. Use when troubleshooting wake-ups or verifying the stream is alive.",inputSchema:{type:"object",properties:{},required:[]}},{name:"borg_read-log",description:"Read entries from the cube's activity log. Each entry is tagged with the drone that wrote it and that drone's role. For wake triage, prefer `unread_only=true` with a modest limit and drain until `has_more=false`; this reads oldest-unread-first from your server cursor and advances the watermark so bursts are not skipped. Optional `since` is a strict-after cursor for explicit bounded reads only; do not use it with the same timestamp as a notification preview because it can skip the boundary entry.",inputSchema:{type:"object",properties:{since:{type:"string",description:"Optional strict-after cursor for explicit bounded reads. Either an activity_log entry id (UUID; server resolves to (created_at, id) tuple for deterministic tie-break) OR an ISO-8601 timestamp. Do not use for routine wake triage; prefer unread_only."},limit:{type:"number",description:"max entries to return (1-500)"},unread_only:{type:"boolean",description:"When true, read only entries posted after this drone last called read-log, oldest-unread-first. Server advances the watermark to the newest returned entry on every call; if has_more=true, call again until has_more=false."}}}},{name:"borg_ack",description:'Mark a log entry as explicitly acknowledged (kind="ack", default), or claim advisory ownership of a review gate before starting (kind="claim"). Recorded as a queryable DB flag (activity_log_acks) keyed on (entry_id, drone_id, kind); idempotent \u2014 repeated calls are no-ops. ack = receipt of a routed signal (replaces posting `ACK: <dispatch-id>`); claim = announce you are taking a REVIEW-READY so peers skip it (advisory only \u2014 merge eligibility stays keyed on REVIEW-APPROVED, never on a claim).',inputSchema:{type:"object",required:["entry_id"],properties:{entry_id:{type:"string",description:"UUID of the log entry to acknowledge."},kind:{type:"string",enum:["ack","claim"],description:'Coordination kind. "ack" (default) = receipt. "claim" = advisory ownership of a review gate on a REVIEW-READY entry (wakes the gate audience; renders stale if you go silent past the wake-path SLA).'}}}},{name:"borg_decide",description:"Record a RATIFIED cube decision in the durable decision registry (gh#740) so drones cite it by topic instead of restating from memory. SEAT-HOLDER ONLY (Coordinator/Queen) \u2014 recording IS the ratification act; a decision is not ratified until it is in the registry. Topic-keyed: recording a new decision on an existing topic supersedes the prior (one active per topic). Surfaces in borg_regen + borg_decisions.",inputSchema:{type:"object",required:["topic","decision"],properties:{topic:{type:"string",description:'Stable topic key for cite-by-topic + supersession (e.g. "pricing-model"). Max 120 chars.'},decision:{type:"string",description:"The ratified decision text. Max 2000 chars."},rationale:{type:"string",description:"Optional why. Max 2000 chars."}}}},{name:"borg_decisions",description:"List the active ratified decisions for the cube (gh#740) \u2014 the source of truth to CITE instead of restating a decision from memory. Any member may read. Pass `topic` to fetch one topic's active decision; omit for all active decisions.",inputSchema:{type:"object",properties:{topic:{type:"string",description:"Optional topic key to fetch that topic's active decision."}}}},{name:"borg_log",description:"Append a message to the cube's activity log. By default entries broadcast to all drones. When a cube declares a message taxonomy, borg_log applies class-based smart defaults: prefix-matched directed classes route to their default recipients unless you pass `to:`, `class:`, or explicit visibility. Pass `to: [...]` to direct by exact drone label, drone id, the 8-hex short-uuid (the `id:` token shown in roster/read-log \u2014 a drone_id prefix that is STABLE across label renumber), role name, or role slug.",inputSchema:{type:"object",properties:{message:{type:"string",description:"The log message (max 10KB)."},to:{type:"array",items:{type:"string"},description:"Optional direct-message recipients by exact drone label, drone id, the 8-hex short-uuid (`id:` token from roster/read-log; stable across label renumber), role name, or role slug (resolves to all drones in that role). Omit to let class-based routing or broadcast defaults apply."},class:{type:"string",description:"Optional declared message class. Overrides prefix auto-classification when the cube declares a message taxonomy."},visibility:{type:"string",enum:["broadcast","direct"],description:"Optional explicit visibility. Overrides class-based routing defaults."}},required:["message"]}},{name:"borg_report-friction",description:"Report friction or a bug directly to the borgmcp dev team. WRITE-ONLY \u2014 you cannot read reports back. Use it when something about borg itself slowed you down, confused you, or broke: awkward UX, an unclear playbook, a missing affordance, or a bug you hit while using borg. Secrets (tokens, keys) are auto-scrubbed server-side before storage, but avoid pasting them anyway.",inputSchema:{type:"object",properties:{message:{type:"string",description:"What hit you + what you expected instead (max 10KB). Concrete and specific helps the dev team most."},kind:{type:"string",enum:["friction","bug"],description:"'friction' (default) for UX/workflow friction; 'bug' for something broken."},metadata:{type:"object",description:"Optional non-secret context. Allowed keys only: version, cube_id, os. Any other key is rejected."}},required:["message"]}},{name:"borg_reports",description:"Read the friction/bug reports submitted via borg_report-friction, newest first, for triage. The READ counterpart to write-only borg_report-friction. Builder/dogfooder-tier only \u2014 the server gates non-builder callers with a clear message. Surfaces each report's kind, timestamp, reporter email, and non-secret metadata; secrets were scrubbed server-side before storage.",inputSchema:{type:"object",properties:{}}},{name:"borg_list-cubes",description:"List every cube owned by this user. Returns id, name, cube_directive, and timestamps for each. Useful before assimilate to see what's available, or as a starting point for any management action.",inputSchema:{type:"object",properties:{}}},{name:"borg_create-cube",description:'Create a new cube. The server seeds a default "Drone" role atomically so the cube is assimilatable immediately. Pass an optional `template` name to apply a richer role set instead (see borg_list-templates / borg_apply-template).',inputSchema:{type:"object",properties:{name:{type:"string",description:"Cube name (lowercase letters, digits, hyphens; max 64 chars).",pattern:"^[a-z0-9-]+$",maxLength:64},cube_directive:{type:"string",description:"Markdown text every drone in this cube will see in regen. Anything project-specific."},template:{type:"string",description:'Optional template name to apply after cube creation (e.g. "software-dev"). Roles are merged by name; the default Drone role gets overwritten by the template if a same-named role is in the template.'}},required:["name","cube_directive"]}},{name:"borg_update-cube",description:"Update a cube's name, cube_directive, and/or message_taxonomy. Pass only what changes.",inputSchema:{type:"object",properties:{cube_id:{type:"string",description:"UUID of the cube to update."},name:{type:"string",description:"New name (optional). Lowercase letters, digits, hyphens; max 64 chars.",pattern:"^[a-z0-9-]+$",maxLength:64},cube_directive:{type:"string",description:"New cube directive markdown (optional)."},message_taxonomy:{type:"array",description:"New message-class taxonomy (optional). REPLACES the whole taxonomy; the worker re-validates the full array (non-overlapping prefixes, unique class names, directed classes need default_to). Pass [] to clear. To change ONE class without resending the whole array, use borg_patch-taxonomy-class instead. In default_to, pass @human-seat to route to drones in the cube human-seat role(s); literal role names/slugs/labels still work. Optional lifecycle tags mark dispatch/completion classes for stuck-dispatch detection.",items:{type:"object",properties:{class:{type:"string",description:"Unique class name."},prefixes:{type:"array",items:{type:"string"},description:"Message prefixes routed by this class."},routing:{type:"string",enum:["broadcast","directed"],description:"Routing mode."},default_to:{type:"array",items:{type:"string"},description:"Default recipients (role name/slug/label, or @human-seat) for a directed class."},lifecycle:{type:"string",enum:["dispatch","completion"],description:"Optional lifecycle marker for stuck-dispatch detection."}}}}},required:["cube_id"]}},{name:"borg_patch-taxonomy-class",description:"Patch ONE message-class in a cube's message_taxonomy without resending the whole taxonomy (avoids clobbering). action=add|replace|remove (replace/remove match name case-insensitively). The full taxonomy is re-validated after the patch (non-overlapping prefixes, unique names, directed classes need default_to) \u2014 a patch that breaks a rule against an untouched class is rejected. In default_to, @human-seat routes to the cube's human-seat role(s); literal names/slugs/labels also work. Optional lifecycle tags mark dispatch/completion classes for stuck-dispatch detection.",inputSchema:{type:"object",properties:{cube_id:{type:"string",description:"UUID of the cube to patch."},action:{type:"string",enum:["add","replace","remove"],description:"add / replace / remove a single class."},class_def:{type:"object",description:'The class definition (for add/replace). Shape: { class, prefixes?, routing: "broadcast"|"directed", default_to?, lifecycle? }.',properties:{class:{type:"string",description:"Unique class name."},prefixes:{type:"array",items:{type:"string"},description:"Message prefixes routed by this class."},routing:{type:"string",enum:["broadcast","directed"],description:"Routing mode."},default_to:{type:"array",items:{type:"string"},description:"Default recipients (required for directed classes): role name/slug/label, or @human-seat."},lifecycle:{type:"string",enum:["dispatch","completion"],description:"Optional lifecycle marker for stuck-dispatch detection."}},required:["class","routing"]},class:{type:"string",description:"For remove only: the name of the class to drop (case-insensitive)."}},required:["cube_id","action"]}},{name:"borg_delete-cube",description:"Delete a cube and all its roles, drones, and log entries. Irreversible \u2014 confirm with the user before invoking unless the cube is clearly disposable.",inputSchema:{type:"object",properties:{cube_id:{type:"string",description:"UUID of the cube to delete."}},required:["cube_id"]}},{name:"borg_create-role",description:"Create a role inside a cube. The detailed_description is the role's playbook \u2014 only drones assigned to this role see it. Setting is_default=true demotes any existing default; a cube has exactly one default role at a time.",inputSchema:{type:"object",properties:{cube_id:{type:"string",description:"UUID of the cube this role belongs to."},name:{type:"string",description:'Role name (e.g. "Builder", "Reviewer").'},short_description:{type:"string",description:"One-line summary, shown to every drone in the cube."},detailed_description:{type:"string",description:"Full playbook for drones in this role \u2014 workflow, conventions, log signals to post."},is_default:{type:"boolean",description:"If true, new drones assimilating into this cube are assigned this role. Demotes the previous default."},is_human_seat:{type:"boolean",description:"If true, this role represents the cube's human-occupied seat (where the human Queen sits directly). The class-hierarchy guard in reassign-drone allows promotion FROM a human-seat role TO the platform Queen role; promotion from non-human-seat roles is rejected."},can_broadcast:{type:"boolean",description:"If true, drones in this role may post broadcast log entries when strict broadcast gating is enabled."},receives_all_direct:{type:"boolean",description:"If true, drones in this role can see direct log entries as observer/audit recipients."},default_model:{type:"string",description:'Default model for drones assigned to this role (e.g., "claude:claude-opus-4-8" or "ollama:qwen3-coder-next:q4_K_M"). Null = inherit from cube. Optional.'}},required:["cube_id","name","short_description","detailed_description"]}},{name:"borg_update-role",description:"Update a role. Pass only the fields that change. Promoting to is_default demotes the previous default in the same cube.",inputSchema:{type:"object",properties:{role_id:{type:"string",description:"UUID of the role to update."},name:{type:"string",description:"New role name (optional)."},short_description:{type:"string",description:"New short description (optional)."},detailed_description:{type:"string",description:"New detailed playbook (optional)."},is_default:{type:"boolean",description:"Set true to make this the cube's default role (optional)."},is_human_seat:{type:"boolean",description:"Set true/false to mark/unmark this as the cube's human-occupied seat (the elevation source for the platform Queen role)."},can_broadcast:{type:"boolean",description:"Set true/false to allow or deny broadcast log entries when strict broadcast gating is enabled."},receives_all_direct:{type:"boolean",description:"Set true/false to grant or remove observer visibility into direct log entries."},default_model:{type:"string",description:'Default model for drones assigned to this role (e.g., "claude:claude-opus-4-8" or "ollama:qwen3-coder-next:q4_K_M"). Null = inherit from cube. Optional.'}},required:["role_id"]}},{name:"borg_patch-role-section",description:"Surgically patch ONE named section of a role's detailed_description, leaving the rest of the field byte-identical. Sections are delimited by plain-label lines (e.g. `Workflow:`, `Project conventions:`) \u2014 NOT markdown headings; text before the first label is the preamble. Use this instead of borg_update-role when changing a single section so you don't have to resend (and risk clobbering) the whole playbook. action=replace overwrites a section's body; action=insert adds a new section (optionally after a named one, else appended); action=delete removes a section.",inputSchema:{type:"object",properties:{role_id:{type:"string",description:"UUID of the role to patch."},action:{type:"string",enum:["replace","insert","delete"],description:"replace / insert / delete a single section."},heading:{type:"string",description:'The section label WITHOUT the trailing colon (e.g. "Workflow"). Matched case-insensitively.'},body:{type:"string",description:"New text BELOW the heading (for replace/insert). Omit for delete."},after:{type:"string",description:"For insert only: place the new section after the section with this heading. Omit/null to append at the end."}},required:["role_id","action","heading"]}},{name:"borg_delete-role",description:"Delete a role. Refuses if any drone is still assigned \u2014 reassign or evict those drones from the dashboard first.",inputSchema:{type:"object",properties:{role_id:{type:"string",description:"UUID of the role to delete."}},required:["role_id"]}},{name:"borg_reassign-drone",description:"Reassign a drone to a different role in the same cube. Coordinator-shaped: the cube's Coordinator drone is the one expected to call this when dispatching new drones to specific work. Server refuses if you try to assign to the Coordinator role when another drone already holds it (evict or reassign that drone first).",inputSchema:{type:"object",properties:{drone_id:{type:"string",description:"UUID of the drone to reassign."},role_id:{type:"string",description:"UUID of the target role. Must belong to the same cube as the drone."}},required:["drone_id","role_id"]}},{name:"borg_evict-drone",description:"Evict (soft-delete) a drone from its cube. Coordinator-shaped: the cube's Coordinator/Queen seat calls this to remove a dead, stuck, or surplus drone \u2014 it drops out of the roster and frees its slot (incl. a held Coordinator/Queen-class seat), while its activity-log history is preserved with anonymized attribution. Owner-scoped: you can only evict drones in cubes you own. Identify the drone EITHER by drone_id (UUID) OR by label + cube_id (the label as it appears in the roster/regen).",inputSchema:{type:"object",properties:{drone_id:{type:"string",description:"UUID of the drone to evict. Provide this OR (label + cube_id)."},label:{type:"string",description:'Drone label to evict, e.g. "two-of-seventeen-builder". Requires cube_id. Ignored when drone_id is given.'},cube_id:{type:"string",description:"UUID of the cube the labelled drone belongs to. Required when evicting by label."}}}},{name:"borg_list-drones",description:"List every drone in a cube (owner-scoped). Returns id, label, role_id, agent_kind, last_seen, and wake_path_alert_class for each \u2014 gives the Coordinator a roster they can act on with borg_reassign-drone.",inputSchema:{type:"object",properties:{cube_id:{type:"string",description:"UUID of the cube whose drones to list."}},required:["cube_id"]}},{name:"borg_list-roles",description:"List every role in a cube (owner-scoped). Returns id, name, short_description, is_default, is_human_seat, can_broadcast, receives_all_direct, and role_class for each \u2014 gives Coordinator-class drones the role UUIDs they need for borg_reassign-drone (e.g. to promote a drone to the Queen role). Closes the gh#153 Queen-role-promotion UX gap (Coordinator drones previously had no way to discover role IDs without operator help).",inputSchema:{type:"object",properties:{cube_id:{type:"string",description:"UUID of the cube whose roles to list."}},required:["cube_id"]}},{name:"borg_list-templates",description:"List available cube templates that can be applied via borg_apply-template or passed to borg_create-cube.",inputSchema:{type:"object",properties:{}}},{name:"borg_apply-template",description:"Apply a named template to an existing cube, NON-CLOBBERINGLY. Roles are merged by name: new roles are created; existing template-named roles get template sections/classes the cube LACKS auto-applied, but EVOLVED (conflicting) text is preserved, never overwritten. Use this to retrofit an existing cube with a richer role set (e.g. add Coordinator/Reviewer/UX Expert). To review + selectively accept conflicting fragments, use borg_sync-roles (which surfaces each conflict + takes per-fragment accept decisions).",inputSchema:{type:"object",properties:{cube_id:{type:"string",description:"UUID of the cube to apply the template to."},template_name:{type:"string",description:"Template to apply (see borg_list-templates)."}},required:["cube_id","template_name"]}},{name:"borg_sync-roles",description:"Non-clobbering sync of a cube's roles + message_taxonomy against the built-in template. Dry-run (default) classifies each fragment (role-text section, short_description, role flags, taxonomy class) as ADD (cube lacks it \u2014 safe auto-apply), UNCHANGED, or CONFLICT (cube has EVOLVED text). On apply, ADDs auto-apply; CONFLICTs apply ONLY via an explicit `decisions` accept (keyed on the dry-run fragment key, e.g. `role:Builder:section:Workflow`); unspecified conflicts default to reject \u2014 evolved text is NEVER silently overwritten. Custom roles untouched.",inputSchema:{type:"object",properties:{cube_id:{type:"string",description:"UUID of the cube to sync."},template_name:{type:"string",description:"Template to sync against (default: software-dev)."},apply:{type:"boolean",description:"If true, commit (auto-apply ADDs + accepted conflicts). If false (default), dry-run only \u2014 classify + surface conflicts."},decisions:{type:"object",description:'Per-conflict accept/reject map, keyed on the fragment key from the dry-run (e.g. {"role:Builder:section:Workflow":"accept"}). Unspecified conflicts default to "reject" (keep the cube version).',additionalProperties:{type:"string",enum:["accept","reject"]}}},required:["cube_id"]}},{name:"borg_tool",description:`Dispatcher: invoke ANY borg tool by name, including tools not pre-loaded in your role-scoped surface. Pass {"name":"<borg_tool>","arguments":{...}}. Routes through the identical auth + validation path as a direct call. Call borg_describe-tool first to learn a deferred tool's arguments.`,inputSchema:{type:"object",properties:{name:{type:"string",description:'The borg tool to invoke, e.g. "borg_evict-drone".'},arguments:{type:"object",description:"The arguments object for that tool (same shape as a direct call)."}},required:["name"]}},{name:"borg_describe-tool",description:"Return the description + input schema for any borg tool by name \u2014 including deferred tools not pre-loaded in your surface. Schema-only; never executes the tool. Pair with borg_tool to invoke a deferred tool.",inputSchema:{type:"object",properties:{name:{type:"string",description:"The borg tool to describe."}},required:["name"]}}];export{e as TOOL_MANIFEST};
|
|
1
|
+
const e=[{name:"borg_subscribe",description:"Create Stripe checkout session for Cube tier ($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.",inputSchema:{type:"object",properties:{},required:[]}},{name:"borg_upgrade-subscription",description:"Open the Stripe Billing Portal to manage Cube tier quantity ($1/month per cube; each cube adds 8 pooled agent sessions + 1000 req/hr).",inputSchema:{type:"object",properties:{},required:[]}},{name:"borg_subscription_status",description:"Check subscription status",inputSchema:{type:"object",properties:{},required:[]}},{name:"borg_open_dashboard",description:"Open Borg MCP dashboard in browser to manage cubes, roles, and drones",inputSchema:{type:"object",properties:{},required:[]}},{name:"borg_regen",description:"Refresh your context as a Drone. Returns the active cube's directive, your role's detailed playbook, the drone roster, and recent activity log entries \u2014 everything you need to be oriented. Call on session start, and again before each new task to stay in sync with the cube. Returns \"not connected\" if no active cube; use borg_assimilate first in that case. Optional `since` (entry-id UUID or ISO-8601 timestamp) trims the recent-log section to entries strictly after the anchor \u2014 pass your last-seen entry id to skip already-processed history on each refresh.",inputSchema:{type:"object",properties:{since:{type:"string",description:"Optional cursor. Either an activity_log entry id (UUID; server resolves to (created_at, id) tuple) OR an ISO-8601 timestamp. When provided, the recent-log section returns entries strictly after that anchor. Non-existent UUID falls back to default recent window."},mode:{type:"string",enum:["full","lite"],description:"Optional output mode. Use full at session start and after context compaction. Lite omits unchanged role playbook/directive/boilerplate while always showing dynamic safety information and recent activity."}},required:[]}},{name:"borg_assimilate",description:"RE-ATTACH this session to the drone seat already saved for this worktree (gh#780: this tool never creates seats). Provide the cube's name; on a match it returns the cube directive, your role's instructions, and recent activity for the EXISTING seat. To create a seat or switch cubes, run `borg assimilate` in a terminal instead.",inputSchema:{type:"object",properties:{cube_name:{type:"string",description:"The cube to connect to"}},required:["cube_name"]}},{name:"borg_cube",description:"Read the active Cube's directive and the registry of all roles in it (each role's name + short description). Use to remind yourself of cube-wide context.",inputSchema:{type:"object",properties:{},required:[]}},{name:"borg_role",description:"Read a role's detailed playbook. With no arguments, returns YOUR assigned role. Pass `role` (a role name, case-insensitive, or role id) to read any other role in the cube \u2014 role playbooks are cube-internal shared context, readable by any drone.",inputSchema:{type:"object",properties:{role:{type:"string",description:"Optional. A role name (case-insensitive) or role id. Omit to read your own role."}},required:[]}},{name:"borg_version",description:"Returns the installed borgmcp client version. Use to verify which version is running in this MCP session.",inputSchema:{type:"object",properties:{},required:[]}},{name:"borg_playbook",description:"Load the full operating-playbook chapter \u2014 the detailed disciplines, rationale, and examples behind the rule-spine in your regen (verification discipline v1/v2/v3, concrete source-of-truth surfaces, four-surface propagation). This detail is kept OUT of the regen bootstrap to keep it light; fetch it ONCE per session when doing review/verify-class work. Static text \u2014 do NOT re-fetch on every wake.",inputSchema:{type:"object",properties:{},required:[]}},{name:"borg_docs",description:'Look up the Borg MCP documentation. Call this when the user asks how borgmcp works, or any feature / usage / setup / pricing / concept / tool question. Returns the docs index \u2014 each section\'s borgmcp.ai URL + a one-line summary. Pass `topic` (e.g. "pricing", "worktree", "roles", "codex") to get the best-matching section(s) instead of the full index. Then WebFetch the returned URL to read the page \u2014 borg_docs returns the index only, it does not fetch the page for you.',inputSchema:{type:"object",properties:{topic:{type:"string",description:"Optional search topic \u2014 returns the best-matching docs section(s) instead of the full index."}},required:[]}},{name:"borg_whoami",description:"Returns your identity in the current cube: cube name, drone label, and role name. Use to confirm which cube/role/drone you are.",inputSchema:{type:"object",properties:{},required:[]}},{name:"borg_role-rationale",description:"Fetch an on-demand rationale/case-study section for a role playbook. Pass a role name/id and a plain-label section key to read the rationale without expanding every regen.",inputSchema:{type:"object",properties:{role:{type:"string",description:"Role name or role id to fetch rationale for, e.g. Builder."},section:{type:"string",description:"Plain-label role section key, e.g. Workflow rationale."}},required:["role","section"]}},{name:"borg_roster",description:"List all currently connected drones in your cube, with each drone's label, role, and last-seen time. Optional `since` argument adds a sender-side liveness column \u2014 pass either an activity_log entry id (e.g., from a dispatch you posted) or an ISO-8601 timestamp; each drone is marked `awake` if they've posted a log entry after that point, otherwise `stale-since-X`. Useful for confirming a dispatch reached its named recipients (catches the silent-wake-path-failure class where SSE delivered but the drone's /loop never woke).",inputSchema:{type:"object",properties:{since:{type:"string",description:"Optional liveness reference point. Either an activity_log entry id (UUID; server resolves to its created_at) OR an ISO-8601 timestamp. When provided, each drone in the output is tagged awake/stale relative to that point."}},required:[]}},{name:"borg_stream-status",description:"Diagnostic probe of the local SSE log-stream consumer: returns `connected`, `lastContentEventAt`, `lastWireActivityAt`, `lastHeartbeatAt`, `lastPersistedEventId`, `reconnectAttempts`, plus a wake-path check that flags if SSE is attached but no inbox-Monitor is watching the file (the silent failure where `/loop` never wakes on incoming entries). Read-only in-process state; does NOT re-open the stream. Use when troubleshooting wake-ups or verifying the stream is alive.",inputSchema:{type:"object",properties:{},required:[]}},{name:"borg_read-log",description:"Read entries from the cube's activity log. Each entry is tagged with the drone that wrote it and that drone's role. For wake triage, prefer `unread_only=true` with a modest limit and drain until `has_more=false`; this reads oldest-unread-first from your server cursor and advances the watermark so bursts are not skipped. Optional `since` is a strict-after cursor for explicit bounded reads only; do not use it with the same timestamp as a notification preview because it can skip the boundary entry.",inputSchema:{type:"object",properties:{since:{type:"string",description:"Optional strict-after cursor for explicit bounded reads. Either an activity_log entry id (UUID; server resolves to (created_at, id) tuple for deterministic tie-break) OR an ISO-8601 timestamp. Do not use for routine wake triage; prefer unread_only."},limit:{type:"number",description:"max entries to return (1-500)"},unread_only:{type:"boolean",description:"When true, read only entries posted after this drone last called read-log, oldest-unread-first. Server advances the watermark to the newest returned entry on every call; if has_more=true, call again until has_more=false."}}}},{name:"borg_ack",description:'Mark a log entry as explicitly acknowledged (kind="ack", default), or claim advisory ownership of a review gate before starting (kind="claim"). Recorded as a queryable DB flag (activity_log_acks) keyed on (entry_id, drone_id, kind); idempotent \u2014 repeated calls are no-ops. ack = receipt of a routed signal (replaces posting `ACK: <dispatch-id>`); claim = announce you are taking a REVIEW-READY so peers skip it (advisory only \u2014 merge eligibility stays keyed on REVIEW-APPROVED, never on a claim).',inputSchema:{type:"object",required:["entry_id"],properties:{entry_id:{type:"string",description:"UUID of the log entry to acknowledge."},kind:{type:"string",enum:["ack","claim"],description:'Coordination kind. "ack" (default) = receipt. "claim" = advisory ownership of a review gate on a REVIEW-READY entry (wakes the gate audience; renders stale if you go silent past the wake-path SLA).'}}}},{name:"borg_decide",description:"Record a RATIFIED cube decision in the durable decision registry (gh#740) so drones cite it by topic instead of restating from memory. SEAT-HOLDER ONLY (Coordinator/Queen) \u2014 recording IS the ratification act; a decision is not ratified until it is in the registry. Topic-keyed: recording a new decision on an existing topic supersedes the prior (one active per topic). Surfaces in borg_regen + borg_decisions.",inputSchema:{type:"object",required:["topic","decision"],properties:{topic:{type:"string",description:'Stable topic key for cite-by-topic + supersession (e.g. "pricing-model"). Max 120 chars.'},decision:{type:"string",description:"The ratified decision text. Max 2000 chars."},rationale:{type:"string",description:"Optional why. Max 2000 chars."}}}},{name:"borg_decisions",description:"List the active ratified decisions for the cube (gh#740) \u2014 the source of truth to CITE instead of restating a decision from memory. Any member may read. Pass `topic` to fetch one topic's active decision; omit for all active decisions.",inputSchema:{type:"object",properties:{topic:{type:"string",description:"Optional topic key to fetch that topic's active decision."}}}},{name:"borg_log",description:"Append a message to the cube's activity log. By default entries broadcast to all drones. When a cube declares a message taxonomy, borg_log applies class-based smart defaults: prefix-matched directed classes route to their default recipients unless you pass `to:`, `class:`, or explicit visibility. Pass `to: [...]` to direct by exact drone label, drone id, the 8-hex short-uuid (the `id:` token shown in roster/read-log \u2014 a drone_id prefix that is STABLE across label renumber), role name, or role slug.",inputSchema:{type:"object",properties:{message:{type:"string",description:"The log message (max 10KB)."},to:{type:"array",items:{type:"string"},description:"Optional direct-message recipients by exact drone label, drone id, the 8-hex short-uuid (`id:` token from roster/read-log; stable across label renumber), role name, or role slug (resolves to all drones in that role). Omit to let class-based routing or broadcast defaults apply."},class:{type:"string",description:"Optional declared message class. Overrides prefix auto-classification when the cube declares a message taxonomy."},visibility:{type:"string",enum:["broadcast","direct"],description:"Optional explicit visibility. Overrides class-based routing defaults."}},required:["message"]}},{name:"borg_report-friction",description:"Report friction or a bug directly to the borgmcp dev team. WRITE-ONLY \u2014 you cannot read reports back. Use it when something about borg itself slowed you down, confused you, or broke: awkward UX, an unclear playbook, a missing affordance, or a bug you hit while using borg. Secrets (tokens, keys) are auto-scrubbed server-side before storage, but avoid pasting them anyway.",inputSchema:{type:"object",properties:{message:{type:"string",description:"What hit you + what you expected instead (max 10KB). Concrete and specific helps the dev team most."},kind:{type:"string",enum:["friction","bug"],description:"'friction' (default) for UX/workflow friction; 'bug' for something broken."},metadata:{type:"object",description:"Optional non-secret context. Allowed keys only: version, cube_id, os. Any other key is rejected."}},required:["message"]}},{name:"borg_reports",description:"Read the friction/bug reports submitted via borg_report-friction, newest first, for triage. The READ counterpart to write-only borg_report-friction. Builder/dogfooder-tier only \u2014 the server gates non-builder callers with a clear message. Surfaces each report's kind, timestamp, reporter email, and non-secret metadata; secrets were scrubbed server-side before storage.",inputSchema:{type:"object",properties:{}}},{name:"borg_list-cubes",description:"List every cube owned by this user. Returns id, name, cube_directive, and timestamps for each. Useful before assimilate to see what's available, or as a starting point for any management action.",inputSchema:{type:"object",properties:{}}},{name:"borg_create-cube",description:'Create a new cube. The server seeds a default "Drone" role atomically so the cube is assimilatable immediately. Pass an optional `template` name to apply a richer role set instead (see borg_list-templates / borg_apply-template).',inputSchema:{type:"object",properties:{name:{type:"string",description:"Cube name (lowercase letters, digits, hyphens; max 64 chars).",pattern:"^[a-z0-9-]+$",maxLength:64},cube_directive:{type:"string",description:"Markdown text every drone in this cube will see in regen. Anything project-specific."},template:{type:"string",description:'Optional template name to apply after cube creation (e.g. "software-dev"). Roles are merged by name; the default Drone role gets overwritten by the template if a same-named role is in the template.'}},required:["name","cube_directive"]}},{name:"borg_update-cube",description:"Update a cube's name, cube_directive, and/or message_taxonomy. Pass only what changes.",inputSchema:{type:"object",properties:{cube_id:{type:"string",description:"UUID of the cube to update."},name:{type:"string",description:"New name (optional). Lowercase letters, digits, hyphens; max 64 chars.",pattern:"^[a-z0-9-]+$",maxLength:64},cube_directive:{type:"string",description:"New cube directive markdown (optional)."},message_taxonomy:{type:"array",description:"New message-class taxonomy (optional). REPLACES the whole taxonomy; the worker re-validates the full array (non-overlapping prefixes, unique class names, directed classes need default_to). Pass [] to clear. To change ONE class without resending the whole array, use borg_patch-taxonomy-class instead. In default_to, pass @human-seat to route to drones in the cube human-seat role(s); literal role names/slugs/labels still work. Optional lifecycle tags mark dispatch/completion classes for stuck-dispatch detection.",items:{type:"object",properties:{class:{type:"string",description:"Unique class name."},prefixes:{type:"array",items:{type:"string"},description:"Message prefixes routed by this class."},routing:{type:"string",enum:["broadcast","directed"],description:"Routing mode."},default_to:{type:"array",items:{type:"string"},description:"Default recipients (role name/slug/label, or @human-seat) for a directed class."},lifecycle:{type:"string",enum:["dispatch","completion"],description:"Optional lifecycle marker for stuck-dispatch detection."}}}}},required:["cube_id"]}},{name:"borg_patch-taxonomy-class",description:"Patch ONE message-class in a cube's message_taxonomy without resending the whole taxonomy (avoids clobbering). action=add|replace|remove (replace/remove match name case-insensitively). The full taxonomy is re-validated after the patch (non-overlapping prefixes, unique names, directed classes need default_to) \u2014 a patch that breaks a rule against an untouched class is rejected. In default_to, @human-seat routes to the cube's human-seat role(s); literal names/slugs/labels also work. Optional lifecycle tags mark dispatch/completion classes for stuck-dispatch detection.",inputSchema:{type:"object",properties:{cube_id:{type:"string",description:"UUID of the cube to patch."},action:{type:"string",enum:["add","replace","remove"],description:"add / replace / remove a single class."},class_def:{type:"object",description:'The class definition (for add/replace). Shape: { class, prefixes?, routing: "broadcast"|"directed", default_to?, lifecycle? }.',properties:{class:{type:"string",description:"Unique class name."},prefixes:{type:"array",items:{type:"string"},description:"Message prefixes routed by this class."},routing:{type:"string",enum:["broadcast","directed"],description:"Routing mode."},default_to:{type:"array",items:{type:"string"},description:"Default recipients (required for directed classes): role name/slug/label, or @human-seat."},lifecycle:{type:"string",enum:["dispatch","completion"],description:"Optional lifecycle marker for stuck-dispatch detection."}},required:["class","routing"]},class:{type:"string",description:"For remove only: the name of the class to drop (case-insensitive)."}},required:["cube_id","action"]}},{name:"borg_delete-cube",description:"Delete a cube and all its roles, drones, and log entries. Irreversible \u2014 confirm with the user before invoking unless the cube is clearly disposable.",inputSchema:{type:"object",properties:{cube_id:{type:"string",description:"UUID of the cube to delete."}},required:["cube_id"]}},{name:"borg_create-role",description:"Create a role inside a cube. The detailed_description is the role's playbook \u2014 only drones assigned to this role see it. Setting is_default=true demotes any existing default; a cube has exactly one default role at a time.",inputSchema:{type:"object",properties:{cube_id:{type:"string",description:"UUID of the cube this role belongs to."},name:{type:"string",description:'Role name (e.g. "Builder", "Reviewer").'},short_description:{type:"string",description:"One-line summary, shown to every drone in the cube."},detailed_description:{type:"string",description:"Full playbook for drones in this role \u2014 workflow, conventions, log signals to post."},is_default:{type:"boolean",description:"If true, new drones assimilating into this cube are assigned this role. Demotes the previous default."},is_human_seat:{type:"boolean",description:"If true, this role represents the cube's human-occupied seat (where the human Queen sits directly). The class-hierarchy guard in reassign-drone allows promotion FROM a human-seat role TO the platform Queen role; promotion from non-human-seat roles is rejected."},can_broadcast:{type:"boolean",description:"If true, drones in this role may post broadcast log entries when strict broadcast gating is enabled."},receives_all_direct:{type:"boolean",description:"If true, drones in this role can see direct log entries as observer/audit recipients."},default_model:{type:"string",description:'Default model for drones assigned to this role (e.g., "claude:claude-opus-4-8" or "ollama:qwen3-coder-next:q4_K_M"). Null = inherit from cube. Optional.'}},required:["cube_id","name","short_description","detailed_description"]}},{name:"borg_update-role",description:"Update a role. Pass only the fields that change. Promoting to is_default demotes the previous default in the same cube.",inputSchema:{type:"object",properties:{role_id:{type:"string",description:"UUID of the role to update."},name:{type:"string",description:"New role name (optional)."},short_description:{type:"string",description:"New short description (optional)."},detailed_description:{type:"string",description:"New detailed playbook (optional)."},is_default:{type:"boolean",description:"Set true to make this the cube's default role (optional)."},is_human_seat:{type:"boolean",description:"Set true/false to mark/unmark this as the cube's human-occupied seat (the elevation source for the platform Queen role)."},can_broadcast:{type:"boolean",description:"Set true/false to allow or deny broadcast log entries when strict broadcast gating is enabled."},receives_all_direct:{type:"boolean",description:"Set true/false to grant or remove observer visibility into direct log entries."},default_model:{type:"string",description:'Default model for drones assigned to this role (e.g., "claude:claude-opus-4-8" or "ollama:qwen3-coder-next:q4_K_M"). Null = inherit from cube. Optional.'}},required:["role_id"]}},{name:"borg_patch-role-section",description:"Surgically patch ONE named section of a role's detailed_description, leaving the rest of the field byte-identical. Sections are delimited by plain-label lines (e.g. `Workflow:`, `Project conventions:`) \u2014 NOT markdown headings; text before the first label is the preamble. Use this instead of borg_update-role when changing a single section so you don't have to resend (and risk clobbering) the whole playbook. action=replace overwrites a section's body; action=insert adds a new section (optionally after a named one, else appended); action=delete removes a section.",inputSchema:{type:"object",properties:{role_id:{type:"string",description:"UUID of the role to patch."},action:{type:"string",enum:["replace","insert","delete"],description:"replace / insert / delete a single section."},heading:{type:"string",description:'The section label WITHOUT the trailing colon (e.g. "Workflow"). Matched case-insensitively.'},body:{type:"string",description:"New text BELOW the heading (for replace/insert). Omit for delete."},after:{type:"string",description:"For insert only: place the new section after the section with this heading. Omit/null to append at the end."}},required:["role_id","action","heading"]}},{name:"borg_delete-role",description:"Delete a role. Refuses if any drone is still assigned \u2014 reassign or evict those drones from the dashboard first.",inputSchema:{type:"object",properties:{role_id:{type:"string",description:"UUID of the role to delete."}},required:["role_id"]}},{name:"borg_reassign-drone",description:"Reassign a drone to a different role in the same cube. Coordinator-shaped: the cube's Coordinator drone is the one expected to call this when dispatching new drones to specific work. Server refuses if you try to assign to the Coordinator role when another drone already holds it (evict or reassign that drone first).",inputSchema:{type:"object",properties:{drone_id:{type:"string",description:"UUID of the drone to reassign."},role_id:{type:"string",description:"UUID of the target role. Must belong to the same cube as the drone."}},required:["drone_id","role_id"]}},{name:"borg_evict-drone",description:"Evict (soft-delete) a drone from its cube. Coordinator-shaped: the cube's Coordinator/Queen seat calls this to remove a dead, stuck, or surplus drone \u2014 it drops out of the roster and frees its slot (incl. a held Coordinator/Queen-class seat), while its activity-log history is preserved with anonymized attribution. Owner-scoped: you can only evict drones in cubes you own. Identify the drone EITHER by drone_id (UUID) OR by label + cube_id (the label as it appears in the roster/regen).",inputSchema:{type:"object",properties:{drone_id:{type:"string",description:"UUID of the drone to evict. Provide this OR (label + cube_id)."},label:{type:"string",description:'Drone label to evict, e.g. "two-of-seventeen-builder". Requires cube_id. Ignored when drone_id is given.'},cube_id:{type:"string",description:"UUID of the cube the labelled drone belongs to. Required when evicting by label."}}}},{name:"borg_list-drones",description:"List every drone in a cube (owner-scoped). Returns id, label, role_id, agent_kind, last_seen, and wake_path_alert_class for each \u2014 gives the Coordinator a roster they can act on with borg_reassign-drone.",inputSchema:{type:"object",properties:{cube_id:{type:"string",description:"UUID of the cube whose drones to list."}},required:["cube_id"]}},{name:"borg_list-roles",description:"List every role in a cube (owner-scoped). Returns id, name, short_description, is_default, is_human_seat, can_broadcast, receives_all_direct, and role_class for each \u2014 gives Coordinator-class drones the role UUIDs they need for borg_reassign-drone (e.g. to promote a drone to the Queen role). Closes the gh#153 Queen-role-promotion UX gap (Coordinator drones previously had no way to discover role IDs without operator help).",inputSchema:{type:"object",properties:{cube_id:{type:"string",description:"UUID of the cube whose roles to list."}},required:["cube_id"]}},{name:"borg_list-templates",description:"List available cube templates that can be applied via borg_apply-template or passed to borg_create-cube.",inputSchema:{type:"object",properties:{}}},{name:"borg_apply-template",description:"Apply a named template to an existing cube, NON-CLOBBERINGLY. Roles are merged by name: new roles are created; existing template-named roles get template sections/classes the cube LACKS auto-applied, but EVOLVED (conflicting) text is preserved, never overwritten. Use this to retrofit an existing cube with a richer role set (e.g. add Coordinator/Reviewer/UX Expert). To review + selectively accept conflicting fragments, use borg_sync-roles (which surfaces each conflict + takes per-fragment accept decisions).",inputSchema:{type:"object",properties:{cube_id:{type:"string",description:"UUID of the cube to apply the template to."},template_name:{type:"string",description:"Template to apply (see borg_list-templates)."}},required:["cube_id","template_name"]}},{name:"borg_sync-roles",description:"Non-clobbering sync of a cube's roles + message_taxonomy against the built-in template. Dry-run (default) classifies each fragment (role-text section, short_description, role flags, taxonomy class) as ADD (cube lacks it \u2014 safe auto-apply), UNCHANGED, or CONFLICT (cube has EVOLVED text). On apply, ADDs auto-apply; CONFLICTs apply ONLY via an explicit `decisions` accept (keyed on the dry-run fragment key, e.g. `role:Builder:section:Workflow`); unspecified conflicts default to reject \u2014 evolved text is NEVER silently overwritten. Custom roles untouched.",inputSchema:{type:"object",properties:{cube_id:{type:"string",description:"UUID of the cube to sync."},template_name:{type:"string",description:"Template to sync against (default: software-dev)."},apply:{type:"boolean",description:"If true, commit (auto-apply ADDs + accepted conflicts). If false (default), dry-run only \u2014 classify + surface conflicts."},decisions:{type:"object",description:'Per-conflict accept/reject map, keyed on the fragment key from the dry-run (e.g. {"role:Builder:section:Workflow":"accept"}). Unspecified conflicts default to "reject" (keep the cube version).',additionalProperties:{type:"string",enum:["accept","reject"]}}},required:["cube_id"]}},{name:"borg_tool",description:`Dispatcher: invoke ANY borg tool by name, including tools not pre-loaded in your role-scoped surface. Pass {"name":"<borg_tool>","arguments":{...}}. Routes through the identical auth + validation path as a direct call. Call borg_describe-tool first to learn a deferred tool's arguments.`,inputSchema:{type:"object",properties:{name:{type:"string",description:'The borg tool to invoke, e.g. "borg_evict-drone".'},arguments:{type:"object",description:"The arguments object for that tool (same shape as a direct call)."}},required:["name"]}},{name:"borg_describe-tool",description:"Return the description + input schema for any borg tool by name \u2014 including deferred tools not pre-loaded in your surface. Schema-only; never executes the tool. Pair with borg_tool to invoke a deferred tool.",inputSchema:{type:"object",properties:{name:{type:"string",description:"The borg tool to describe."}},required:["name"]}}];export{e as TOOL_MANIFEST};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "borgmcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.59",
|
|
4
4
|
"description": "Coordinate AI coding agents in shared cubes. Works with Claude Code and Codex. Create projects, assign roles, and share a live activity log.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|