borgmcp 1.0.41 → 1.0.43

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.
@@ -1,48 +1,48 @@
1
- import{dirname as re,basename as E}from"node:path";import{randomUUID as ne}from"node:crypto";import{roleSlug as oe,matchRoleByName as ie,pickDefaultRole as ae}from"./role-resolver.js";import{deriveCubeName as se,parseGitRemote as le,sanitizeRemoteUrl as ce}from"./cube-name.js";import{validateName as G}from"./name-validator.js";import{renderAssimilationWelcome as ue}from"./assimilate-welcome.js";import{shellEscape as me}from"./shell-escape.js";import{withCodexCwdArg as de}from"./codex-remote.js";import{buildAgentKickoffPrompt as fe,recordCodexWakeTarget as he,socketPathFromRemoteArgs as ge}from"./codex-launch.js";import{perWorktreeBranchName as q,adoptWorktree as we,computeWorktreePath as z}from"./worktree-lifecycle.js";import{DroneEvictedError as be}from"./drone-lifecycle.js";import{codexBorgSessionConfigArgs as ke}from"./launch-gate.js";import{resolveLaunchEnv as ye,resolveOllamaBaseUrl as ve,parseModel as pe}from"./model-presets.js";async function Fe(r,e){if(r.role!==void 0){const t=G(r.role);if(!t.ok)return e.stderr(t.error+`
1
+ import{dirname as re,basename as E}from"node:path";import{randomUUID as ne}from"node:crypto";import{roleSlug as oe,matchRoleByName as ie,pickDefaultRole as ae}from"./role-resolver.js";import{deriveCubeName as le,parseGitRemote as se,sanitizeRemoteUrl as ce}from"./cube-name.js";import{validateName as G}from"./name-validator.js";import{renderAssimilationWelcome as ue}from"./assimilate-welcome.js";import{shellEscape as me}from"./shell-escape.js";import{withCodexCwdArg as de}from"./codex-remote.js";import{buildAgentKickoffPrompt as fe,buildKickoffWakePathClause as he,recordCodexWakeTarget as ge,socketPathFromRemoteArgs as we}from"./codex-launch.js";import{perWorktreeBranchName as K,adoptWorktree as be,computeWorktreePath as q}from"./worktree-lifecycle.js";import{DroneEvictedError as ke}from"./drone-lifecycle.js";import{codexBorgSessionConfigArgs as ye}from"./launch-gate.js";import{resolveLaunchEnv as ve,resolveOllamaBaseUrl as $e,parseModel as pe}from"./model-presets.js";async function Ye(r,e){if(r.role!==void 0){const t=G(r.role);if(!t.ok)return e.stderr(t.error+`
2
2
  `),1}if(r.flags.worktree!==void 0){const t=G(r.flags.worktree);if(!t.ok)return e.stderr(t.error+`
3
- `),1}let i=await e.getCachedAuth();if(!i){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;i=await e.runSetup()}const a=e.findProjectRoot(e.cwd());let n;if(r.flags.cubeName)n=r.flags.cubeName;else{const t=e.runSync("git",["remote","get-url","origin"],a),o=t.status===0?t.stdout:null;if(n=se(a,o),o){const u=ce(o),m=u?le(u):null;u&&!m&&n&&e.stderr(`couldn't parse git remote '${u}' \u2014 using directory name '${n}' as cube name
4
- `)}}let s=null;if(n&&n.includes("@")&&n.includes(":")){const t=n.lastIndexOf(":");s={ownerEmail:n.substring(0,t),cubeName:n.substring(t+1)},n=s.cubeName}const R=e.cwd();e.stderr(`Checking your cubes\u2026
5
- `);let N;try{N=await e.listCubes(i.apiUrl,i.token)}catch(t){const o=t instanceof Error?t.message:String(t);if(o.includes("Authentication required")||o.includes("Authentication expired"))e.stderr(`Re-authenticating...
6
- `),i=await e.runSetup(),N=await e.listCubes(i.apiUrl,i.token);else throw t}const _=N.find(t=>t.name===n);if(!_&&s)return e.stderr(`No cube named '${s.cubeName}' accessible to you owned by '${s.ownerEmail}'. Did you accept their invite? See borgmcp.ai/dashboard.
7
- `),1;let l,T;if(_)l=await e.getCube(i.apiUrl,i.token,_.id),T=!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 o=await e.listTemplates(i.apiUrl,i.token),u=["First drone joining a new cube. Apply a template?"];o.forEach((v,p)=>{const C=p===0?" (default)":"";u.push(` ${p+1}) ${v.name}${C} \u2014 ${v.description}`)}),u.push(` ${o.length+1}) skip \u2014 no template`);const m=(await e.prompt(u.join(`
3
+ `),1}let i=await e.getCachedAuth();if(!i){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;i=await e.runSetup()}const a=e.findProjectRoot(e.cwd());let n;if(r.flags.cubeName)n=r.flags.cubeName;else{const t=e.runSync("git",["remote","get-url","origin"],a),o=t.status===0?t.stdout:null;if(n=le(a,o),o){const u=ce(o),m=u?se(u):null;u&&!m&&n&&e.stderr(`couldn't parse git remote '${u}' \u2014 using directory name '${n}' as cube name
4
+ `)}}let l=null;if(n&&n.includes("@")&&n.includes(":")){const t=n.lastIndexOf(":");l={ownerEmail:n.substring(0,t),cubeName:n.substring(t+1)},n=l.cubeName}const C=e.cwd();e.stderr(`Checking your cubes\u2026
5
+ `);let _;try{_=await e.listCubes(i.apiUrl,i.token)}catch(t){const o=t instanceof Error?t.message:String(t);if(o.includes("Authentication required")||o.includes("Authentication expired"))e.stderr(`Re-authenticating...
6
+ `),i=await e.runSetup(),_=await e.listCubes(i.apiUrl,i.token);else throw t}const A=_.find(t=>t.name===n);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 s,N;if(A)s=await e.getCube(i.apiUrl,i.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 o=await e.listTemplates(i.apiUrl,i.token),u=["First drone joining a new cube. Apply a template?"];o.forEach((v,$)=>{const S=$===0?" (default)":"";u.push(` ${$+1}) ${v.name}${S} \u2014 ${v.description}`)}),u.push(` ${o.length+1}) skip \u2014 no template`);const m=(await e.prompt(u.join(`
8
8
  `)+`
9
9
  [1]: `)).trim(),y=m===""?1:parseInt(m,10);if(Number.isNaN(y)||y<1||y>o.length+1)return e.stderr(`invalid choice "${m}"
10
10
  `),1;t=y<=o.length?o[y-1].name:void 0}else{if(!r.flags.yes)return e.stderr(`cube creation needs a template choice but stdin is non-interactive.
11
11
  Pass --template <name>, --no-template, or --yes (defaults to starter).
12
12
  `),1;t="starter"}e.stderr(n?`Creating cube '${n}'\u2026
13
13
  `:`Creating your cube\u2026
14
- `),l=await e.createCube(i.apiUrl,i.token,t?{name:n??void 0,template:t}:{name:n??void 0}),T=!0}let d;if(r.role!==void 0){if(d=ie(l.roles,r.role),!d){const t=l.roles.map(m=>m.name).join(", "),o=Se(r.role,l.roles.map(m=>m.name)),u=o?` Did you mean "${o}"?`:"";return e.stderr(`no role matching "${r.role}" in cube "${l.name}". Available: ${t}.${u}
14
+ `),s=await e.createCube(i.apiUrl,i.token,t?{name:n??void 0,template:t}:{name:n??void 0}),N=!0}let d;if(r.role!==void 0){if(d=ie(s.roles,r.role),!d){const t=s.roles.map(m=>m.name).join(", "),o=Se(r.role,s.roles.map(m=>m.name)),u=o?` Did you mean "${o}"?`:"";return e.stderr(`no role matching "${r.role}" in cube "${s.name}". Available: ${t}.${u}
15
15
  (Use --template <name> on first-drone setup or run \`borg_create-role\` from inside Claude.)
16
- `),1}}else if(d=ae(l.roles,{isFirstDrone:T}),!d)return e.stderr(`cube "${l.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 $=await e.getActiveCube();let x;if($&&r.flags.here)if($.cubeId===l.id)x=$.droneId;else return e.stderr(`this directory already hosts an active drone; remove --here or run from a fresh worktree
18
- `),1;const M=r.flags.worktree!==void 0||$!==null&&!r.flags.here,V=x??$?.droneId??null,A=M?null:await e.getLaunchModel(l.id,a,V),g=r.flags.model??A?.model??d.default_model??null,I=ve(process.env,g!=null&&g===A?.model?A?.ollamaBaseUrl:void 0);if(g){const t=await e.checkModelReachable(g,e.fetch,I);if(!t.ok)return e.stderr(`${t.message}
19
- `),1}const w=await e.resolveCli(r.flags.cli);e.stderr(`Joining cube '${l.name}' as ${d.name}\u2026
20
- `);let c;try{c=await e.assimilate(i.apiUrl,i.token,{cube_id:l.id,role_id:d.id,hostname:e.getHostname(),agent_kind:w,model:g,...x?{prior_drone_id:x}:{}})}catch(t){if(t instanceof be&&x!=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.
16
+ `),1}}else if(d=ae(s.roles,{isFirstDrone:N}),!d)return e.stderr(`cube "${s.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 p=await e.getActiveCube();let x;if(p&&r.flags.here)if(p.cubeId===s.id)x=p.droneId;else return e.stderr(`this directory already hosts an active drone; remove --here or run from a fresh worktree
18
+ `),1;const O=r.flags.worktree!==void 0||p!==null&&!r.flags.here,J=x??p?.droneId??null,P=O?null:await e.getLaunchModel(s.id,a,J),w=r.flags.model??P?.model??d.default_model??null,T=$e(process.env,w!=null&&w===P?.model?P?.ollamaBaseUrl:void 0);if(w){const t=await e.checkModelReachable(w,e.fetch,T);if(!t.ok)return e.stderr(`${t.message}
19
+ `),1}const f=await e.resolveCli(r.flags.cli);e.stderr(`Joining cube '${s.name}' as ${d.name}\u2026
20
+ `);let c;try{c=await e.assimilate(i.apiUrl,i.token,{cube_id:s.id,role_id:d.id,hostname:e.getHostname(),agent_kind:f,model:w,...x?{prior_drone_id:x}:{}})}catch(t){if(t instanceof ke&&x!=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.
21
21
  `),1;const o=t instanceof Error?t.message:String(t);return e.stderr(`assimilate failed: ${o}
22
- `),1}const b=l.roles.find(t=>t.id===c.role_id)??d;c.reattached?e.stderr(`re-attached to existing seat ${c.drone_label} (session token rotated, no new drone minted)
22
+ `),1}const b=s.roles.find(t=>t.id===c.role_id)??d;c.reattached?e.stderr(`re-attached to existing seat ${c.drone_label} (session token rotated, no new drone minted)
23
23
  `):b.id!==d.id&&e.stderr(`Note: your invite didn't grant the "${d.name}" role \u2014 assimilated as "${b.name}" instead.
24
- `);const J=M;let f=null;if(J){const t=e.runSync("git",["rev-parse","--verify","HEAD"],a);if(t.status!==0)return e.stderr(`sibling worktree spawn requires HEAD pointing at a commit.
24
+ `);const V=O;let h=null;if(V){const t=e.runSync("git",["rev-parse","--verify","HEAD"],a);if(t.status!==0)return e.stderr(`sibling worktree spawn requires HEAD pointing at a commit.
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"],a);let o="origin/main";e.runSync("git",["rev-parse","--verify","origin/main"],a).status!==0&&e.runSync("git",["rev-parse","--verify","origin/master"],a).status===0&&(o="origin/master");const m=t.stdout.trim(),y=e.runSync("git",["rev-parse",o],a).stdout.trim();m!==y&&e.stderr(`note: local HEAD (${m.slice(0,7)}) differs from ${o} (${y.slice(0,7)}); new worktree will start on ${o}
28
- `);const v=E(a),p=r.flags.worktree??oe(b.name);if(p.length===0)return e.stderr(`cannot derive a worktree name from role "${b.name}"; pass an explicit --worktree <name>
29
- `),1;const C=e.homedir();let k=z(C,v,p),F=2;for(;e.pathExists(k)||Re(e,a,k);)k=z(C,v,p,F),F++;e.mkdirp(re(k));const O=q(E(k),v),Y=e.runSync("git",["worktree","add","-b",O,k,o],a);if(Y.status!==0)return e.stderr(`git worktree add failed: ${K(Y.stderr)}
30
- `),1;e.stderr(`spawned sibling worktree at ${k} on branch ${O} (${o}); original dir is registered as active (edit ~/.config/borgmcp/cubes.json if stale).
31
- `),e.chdir(k),e.stderr($e(k,O,a)),f=e.cwd()}try{await e.setActiveCube({cubeId:c.cube_id,droneId:c.drone_id,name:l.name,sessionToken:c.session_token,droneLabel:c.drone_label,apiUrl:i.apiUrl,roleName:b.name,isHumanSeat:b.is_human_seat,...b.role_class?{roleClass:b.role_class}:{}})}catch(t){const o=t instanceof Error?t.message:String(t);if(e.stderr(`setActiveCube failed: ${o}
32
- `),f){const u=e.runSync("git",["worktree","remove","--force",f],a);u.status===0?e.stderr(`rolled back spawned worktree at ${f}
33
- `):e.stderr(`manual cleanup needed: \`git worktree remove --force ${f}\` (rollback attempt failed: ${K(u.stderr).trim()||"unknown"})
34
- `)}return 1}e.setTerminalTitle(c.drone_label,l.name);const Q=e.isTTY()&&!process.env.NO_COLOR&&!process.env.CI;e.stdout(ue(b.name,l.name,Q));const h=e.cwd();try{e.installProjectSessionHook(h)}catch{e.stderr(`warning: could not install the project-local SessionStart hook in ${h}; it will be re-attempted on the next borg launch
35
- `)}if(!f){e.runSync("git",["fetch","origin","--prune"],h);const t=q(E(h),E(a)),o=we(e.runSync,h,t,"origin/main");o.action==="adopted"?(e.stderr(`worktree: adopted branch ${t} at origin/main
36
- `),e.stderr(xe(h,t))):o.message&&e.stderr(`worktree sync: ${o.message}
37
- `)}await e.probeMcpReady()||e.stderr(`warning: borg-mcp readiness probe did not complete within the timeout; launching ${w} anyway \u2014 the kickoff prompt's ToolSearch fallback will recover if the MCP server takes longer to start.
38
- `);const X=e.getInboxPath(c.cube_id,c.drone_id),P=w==="codex"?`borg-wake-${ne()}`:null,Z=w==="claude"?`If you haven't yet, arm a persistent Monitor running the command \`borg-inbox-monitor ${X}\` so each event's task-notification title summarizes the new cube log entry (drone label, role, and first ~80 chars of the message body) \u2014 letting you triage events without reading the full body. This Monitor is your cube wake path: keep it armed for the whole session and NEVER TaskStop it \u2014 the generic /loop "TaskStop any Monitor you armed" step does NOT apply to it (it targets throwaway loop-scratch watches only). The sole exception is a confirmed terminal eviction (410 DRONE_EVICTED). `:"";let D,W=[],S,U=null,L=null;const H=e.findProjectRoot(h);g?await e.setLaunchModel(c.cube_id,H,{model:g,ollamaBaseUrl:pe(g).kind==="ollama"?I:null}):await e.clearLaunchModel(c.cube_id,H);const B=ye(g,I),j={...process.env,...B.set,BORG_SESSION:"1"};for(const t of B.unset)delete j[t];if(w==="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.",W=t.args,Object.keys(t.env).length>0&&Object.assign(j,t.env),U=ge(t.args),L=t.server?.cleanup??null}S=[fe({cli:w,codexWakeNonce:P,monitorClause:Z,codexWakePathClause:D})],w==="codex"&&(S=[...ke(),...W,...de(S,h)]);const ee=e.exec(w,S,h,j);w==="codex"&&U&&P&&he({deps:e,cubeId:c.cube_id,droneId:c.drone_id,socketPath:U,cwd:h,previewNeedle:P,launchedAtSeconds:Math.floor(Date.now()/1e3)});const te=await ee;if(L)try{L()}catch{}return f&&R!==f&&e.stderr(`
40
- Agent exited. You were working in ${f}; your shell is back in ${R}.
28
+ `);const v=E(a),$=r.flags.worktree??oe(b.name);if($.length===0)return e.stderr(`cannot derive a worktree name from role "${b.name}"; pass an explicit --worktree <name>
29
+ `),1;const S=e.homedir();let k=q(S,v,$),F=2;for(;e.pathExists(k)||Re(e,a,k);)k=q(S,v,$,F),F++;e.mkdirp(re(k));const W=K(E(k),v),Y=e.runSync("git",["worktree","add","-b",W,k,o],a);if(Y.status!==0)return e.stderr(`git worktree add failed: ${z(Y.stderr)}
30
+ `),1;e.stderr(`spawned sibling worktree at ${k} on branch ${W} (${o}); original dir is registered as active (edit ~/.config/borgmcp/cubes.json if stale).
31
+ `),e.chdir(k),e.stderr(xe(k,W,a)),h=e.cwd()}try{await e.setActiveCube({cubeId:c.cube_id,droneId:c.drone_id,name:s.name,sessionToken:c.session_token,droneLabel:c.drone_label,apiUrl:i.apiUrl,roleName:b.name,isHumanSeat:b.is_human_seat,...b.role_class?{roleClass:b.role_class}:{}})}catch(t){const o=t instanceof Error?t.message:String(t);if(e.stderr(`setActiveCube failed: ${o}
32
+ `),h){const u=e.runSync("git",["worktree","remove","--force",h],a);u.status===0?e.stderr(`rolled back spawned worktree at ${h}
33
+ `):e.stderr(`manual cleanup needed: \`git worktree remove --force ${h}\` (rollback attempt failed: ${z(u.stderr).trim()||"unknown"})
34
+ `)}return 1}e.setTerminalTitle(c.drone_label,s.name);const Q=e.isTTY()&&!process.env.NO_COLOR&&!process.env.CI;e.stdout(ue(b.name,s.name,Q));const g=e.cwd();try{e.installProjectSessionHook(g)}catch{e.stderr(`warning: could not install the project-local SessionStart hook in ${g}; it will be re-attempted on the next borg launch
35
+ `)}if(!h){e.runSync("git",["fetch","origin","--prune"],g);const t=K(E(g),E(a)),o=be(e.runSync,g,t,"origin/main");o.action==="adopted"?(e.stderr(`worktree: adopted branch ${t} at origin/main
36
+ `),e.stderr(Ce(g,t))):o.message&&e.stderr(`worktree sync: ${o.message}
37
+ `)}await e.probeMcpReady()||e.stderr(`warning: borg-mcp readiness probe did not complete within the timeout; launching ${f} anyway \u2014 the kickoff prompt's ToolSearch fallback will recover if the MCP server takes longer to start.
38
+ `);const X=e.getInboxPath(c.cube_id,c.drone_id),I=f==="codex"?`borg-wake-${ne()}`:null,Z=he(f==="codex"?"codex":"claude",f==="claude"?X:null);let U,H=[],R,D=null,L=null;const M=e.findProjectRoot(g);w?await e.setLaunchModel(c.cube_id,M,{model:w,ollamaBaseUrl:pe(w).kind==="ollama"?T:null}):await e.clearLaunchModel(c.cube_id,M);const B=ve(w,T),j={...process.env,...B.set,BORG_SESSION:"1"};for(const t of B.unset)delete j[t];if(f==="codex"){const t=await e.prepareCodexRemoteLaunch();t.warning?(e.stderr(`warning: ${t.warning}
39
+ `),U="\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."):U="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),D=we(t.args),L=t.server?.cleanup??null}R=[fe({cli:f,codexWakeNonce:I,monitorClause:Z,codexWakePathClause:U})],f==="codex"&&(R=[...ye(),...H,...de(R,g)]);const ee=e.exec(f,R,g,j);f==="codex"&&D&&I&&ge({deps:e,cubeId:c.cube_id,droneId:c.drone_id,socketPath:D,cwd:g,previewNeedle:I,launchedAtSeconds:Math.floor(Date.now()/1e3)});const te=await ee;if(L)try{L()}catch{}return h&&C!==h&&e.stderr(`
40
+ Agent exited. You were working in ${h}; your shell is back in ${C}.
41
41
  To return:
42
- cd ${me(f)}
43
- `),te}function $e(r,e,i){return`
42
+ cd ${me(h)}
43
+ `),te}function xe(r,e,i){return`
44
44
  WORKTREE STEERING: You are in worktree ${r} on branch ${e}. Do ALL work HERE \u2014 cut your feature branch (fix/.../feat/...) off ${e} in THIS worktree, use relative paths / your cwd. NEVER \`git -C ${i}\` or operate on the primary checkout ${i}: the same branch can't be checked out in two worktrees, so work created in the primary won't reach your wt-branch without manual surgery (cherry-pick/merge).
45
- `}function xe(r,e){return`
45
+ `}function Ce(r,e){return`
46
46
  WORKTREE STEERING: This checkout is now on branch ${e}. Do ALL work HERE in ${r} \u2014 cut your feature branch (fix/.../feat/...) off ${e}, use relative paths / your cwd.
47
- `}function K(r){return r.replace(/[\x00-\x1F\x7F]/g,"")}function Re(r,e,i){const a=r.runSync("git",["worktree","list","--porcelain"],e);return a.status!==0?!1:a.stdout.split(`
48
- `).some(n=>n===`worktree ${i}`)}function Se(r,e){if(e.length===0)return null;const i=r.toLowerCase();let a=null;for(const n of e){const s=Ce(i,n.toLowerCase());s<=2&&(a===null||s<a.distance)&&(a={name:n,distance:s})}return a?a.name:null}function Ce(r,e){if(r===e)return 0;if(r.length===0)return e.length;if(e.length===0)return r.length;const i=new Array(e.length+1),a=new Array(e.length+1);for(let n=0;n<=e.length;n++)i[n]=n;for(let n=1;n<=r.length;n++){a[0]=n;for(let s=1;s<=e.length;s++){const R=r[n-1]===e[s-1]?0:1;a[s]=Math.min(a[s-1]+1,i[s]+1,i[s-1]+R)}for(let s=0;s<=e.length;s++)i[s]=a[s]}return i[e.length]}export{Fe as runAssimilate,K as safeStderr,Se as suggestRoleName};
47
+ `}function z(r){return r.replace(/[\x00-\x1F\x7F]/g,"")}function Re(r,e,i){const a=r.runSync("git",["worktree","list","--porcelain"],e);return a.status!==0?!1:a.stdout.split(`
48
+ `).some(n=>n===`worktree ${i}`)}function Se(r,e){if(e.length===0)return null;const i=r.toLowerCase();let a=null;for(const n of e){const l=Ee(i,n.toLowerCase());l<=2&&(a===null||l<a.distance)&&(a={name:n,distance:l})}return a?a.name:null}function Ee(r,e){if(r===e)return 0;if(r.length===0)return e.length;if(e.length===0)return r.length;const i=new Array(e.length+1),a=new Array(e.length+1);for(let n=0;n<=e.length;n++)i[n]=n;for(let n=1;n<=r.length;n++){a[0]=n;for(let l=1;l<=e.length;l++){const C=r[n-1]===e[l-1]?0:1;a[l]=Math.min(a[l-1]+1,i[l]+1,i[l-1]+C)}for(let l=0;l<=e.length;l++)i[l]=a[l]}return i[e.length]}export{Ye as runAssimilate,z as safeStderr,Se as suggestRoleName};
package/dist/claude.js CHANGED
@@ -1,15 +1,15 @@
1
1
  #!/usr/bin/env node
2
- import{spawn as M}from"child_process";import{randomUUID as E}from"node:crypto";import{basename as O}from"node:path";import{createInterface as H}from"node:readline/promises";import s from"chalk";import{findProjectRoot as S,getActiveCube as F,getLaunchModel as N,inboxPathForDrone as B,setCodexWakeTarget as _,pruneDeadCodexWakeTargets as G}from"./cubes.js";import{applyOllamaLaunchEnv as W,checkModelReachable as Y}from"./model-presets.js";import{handleVersionFlag as U,getPackageVersion as h}from"./version.js";import{isHelpFlag as T,setupHelpText as V,topLevelHelpText as j,assimilateHelpText as K}from"./cli-help.js";import{runSpawn as q}from"./spawn.js";import{parseSyncArgs as z,runSync as X}from"./sync.js";import{parseCleanupArgs as J,runCleanup as Q}from"./cleanup-cmd.js";import{parseAssimilateArgs as Z}from"./parse-assimilate-args.js";import{runAssimilate as ee}from"./assimilate-cmd.js";import{buildDefaultAssimilateDeps as re}from"./assimilate-deps.js";import{parseLaunchAllArgs as A}from"./parse-launch-all-args.js";import{unknownSubcommand as oe}from"./unknown-subcommand.js";import{runLaunchAll as I}from"./launch-all-cmd.js";import{buildDefaultLaunchAllDeps as x}from"./launch-all-deps.js";import{discoverDroneCandidates as se}from"./launch-all-discovery.js";import{runBareLaunchMenu as te,shouldShowLaunchMenu as ae}from"./bare-launch-menu.js";import{setTerminalTitle as ie}from"./terminal-title.js";import{initConsolePrefix as ne,consolePrefix as o}from"./console-prefix.js";import{initDebugFromArgv as ce}from"./debug.js";import{fetchLatestBorgmcpVersion as le,compareVersionsForStaleness as de}from"./stale-version-check.js";import{defaultCliChoiceDeps as pe,detectCliAvailability as v,installedCliNames as R,parseCliFlag as ue,resolveCliChoice as me}from"./cli-platform.js";import{getRefreshToken as fe,getIdToken as ge}from"./config.js";import{composeGetStarted as he,shouldShowGetStarted as we}from"./get-started.js";import{prepareCodexRemoteLaunch as xe,withCodexCwdArg as ve,defaultCodexRemoteDeps as be,checkCodexBridgeHealthy as Ce}from"./codex-remote.js";import{findLoadedCodexThread as ke}from"./codex-app-server.js";import{buildAgentKickoffPrompt as $e,recordCodexWakeTarget as ye,socketPathFromRemoteArgs as P}from"./codex-launch.js";import{codexBorgSessionConfigArgs as Se}from"./launch-gate.js";import{addCodexMcpServer as Te,addCodexSessionStartHook as Ae,addCodexUserPromptSubmitHook as Ie,addMcpServer as Re,addProjectSessionStartHook as Pe,addUserPromptSubmitHook as De,isCodexMcpServerConfigured as Le,isMcpServerConfigured as Me,removeSessionStartHook as Ee}from"./config-utils.js";async function Oe(){ce(process.argv),U(),await ne();const c=(async()=>{if(!process.stderr.isTTY)return;const e=h(),r=await le();if(!r)return;const i=de(e,r);i.stale&&i.message&&process.stderr.write(`${o()}${i.message}
3
- `)})();if((process.argv[2]==="--help"||process.argv[2]==="-h")&&(process.stdout.write(j(h())),process.exit(0)),process.argv[2]==="setup"){T(process.argv[3])&&(process.stdout.write(V(h())),process.exit(0)),await import("./setup.js");return}if(process.argv[2]==="assimilate"){process.argv.slice(3).some(T)&&(process.stdout.write(K(h())),process.exit(0));const e=Z(process.argv.slice(3));e.ok||(process.stderr.write(s.red(`${o()}\u25FC borg assimilate: ${e.error}
4
- `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const r=re(),i=await ee({role:e.role,flags:e.flags},r);process.exit(i)}if(process.argv[2]==="spawn"){const e=await q();process.exit(e)}if(process.argv[2]==="sync"){const e=z(process.argv.slice(3));e.ok||(process.stderr.write(s.red(`${o()}\u25FC borg sync: ${e.error}
5
- `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const r=await X({},e.options);process.exit(r)}if(process.argv[2]==="cleanup"){const e=J(process.argv.slice(3));e.ok||(process.stderr.write(s.red(`${o()}\u25FC borg cleanup: ${e.error}
6
- `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const r=await Q({},e.options);process.exit(r)}if(process.argv[2]==="launch-all"){const e=A(process.argv.slice(3));e.ok||(process.stderr.write(s.red(`${o()}\u25FC borg launch-all: ${e.error}
7
- `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const r=x(),i=await I(e.args,r);process.exit(i)}const n=oe(process.argv[2]);if(n!==null&&(process.stderr.write(s.red(`${o()}\u25FC unknown command: ${n}
8
- `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1)),we(await fe()!==null,await ge()!==null)){const e=R(v()).length>0;process.stdout.write(he(e)),process.exit(0)}const f=ue(process.argv.slice(2));f.error&&(process.stderr.write(s.red(`${o()}\u25FC ${f.error}
9
- `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const b=async e=>{const r=H({input:process.stdin,output:process.stdout});try{return await r.question(e)}finally{r.close()}};let t=await me(f.cli,pe(b,()=>process.stdin.isTTY===!0));He();const a=await F();if(ae({extraArgs:process.argv.slice(2),stdinIsTTY:process.stdin.isTTY===!0,stdoutIsTTY:process.stdout.isTTY===!0})){const e=R(v()).find(m=>m!==t)??null;let r=!1;a&&(r=(await se({targetCubeId:a.cubeId},x())).length>0);const i=await te({defaultCli:t,otherInstalledCli:e,hasLaunchAllTargets:r},b);if(i.kind==="launch-all"){const m=A([]),L=m.ok?await I(m.args,x()):1;process.exit(L)}t=i.cli}const l=f.rest;ie(a?{label:a.droneLabel,cubeName:a.name}:null,O(process.cwd()));const D=a&&t==="claude"?`If you haven't yet, arm a persistent Monitor running the command \`borg-inbox-monitor ${B(a.cubeId,a.droneId)}\` so each event's task-notification title summarizes the new cube log entry (drone label, role, and first ~80 chars of the message body) \u2014 letting you triage events without reading the full body. `:"";await Promise.race([c,new Promise(e=>setTimeout(e,2e3))]);const C=t==="codex"?`borg-wake-${E()}`:null;let g,k=[],d={...process.env,BORG_SESSION:"1"},p=null,u=null;if(t==="codex"&&!l.includes("--remote")){console.error(`${o()}${s.gray("\u25FC Starting Codex remote-wake app-server\u2026")}`);const e=await xe(be());e.warning?(console.error(`${o()}${s.yellow(`warning: ${e.warning}`)}`),g="\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."):g="Codex wake-path capability check passed: remote-control socket established for this session.",k=e.args,d={...process.env,...e.env,BORG_SESSION:"1"},p=P(e.args),u=e.server?.cleanup??null}else t==="codex"&&l.includes("--remote")&&(g="Codex wake-path capability check: using caller-provided --remote socket; if no wake arrives, run borg_regen manually when returning to the session.",p=P(l),p&&(d={...process.env,BORG_CODEX_REMOTE_WAKE:"1",BORG_SESSION:"1"}));if(a){const e=await N(a.cubeId,S(),a.droneId),r=W(d,e,process.env);if(d=r.env,r.probe){const i=await Y(r.probe.descriptor,fetch,r.probe.baseUrl);i.ok||console.error(`${o()}${s.yellow(`warning: ${i.message}`)}`)}}const $=$e({cli:t,codexWakeNonce:C,monitorClause:D,codexWakePathClause:g});let w=[...l,$];t==="codex"&&(w=[...Se(),...k,...ve(w,process.cwd())]),console.error(`${o()}${s.blue(`\u25FC Launching ${t==="claude"?"Claude Code":"Codex"}\u2026`)}`);const y=M(t,w,{stdio:"inherit",shell:!1,env:d});t==="codex"&&a&&p&&(ye({deps:{setCodexWakeTarget:_,findLoadedCodexThread:ke},cubeId:a.cubeId,droneId:a.droneId,socketPath:p,passthroughArgs:l,previewNeedle:C??$.slice(0,120),cwd:process.cwd(),launchedAtSeconds:Math.floor(Date.now()/1e3)}),G(e=>Ce(e))),y.on("error",e=>{if(u)try{u()}catch{}e.code==="ENOENT"?(console.error(`${o()}${s.red(`
10
- \u25FC Failed to launch ${t}`)}`),console.error(`${o()}${s.gray(`Make sure ${t} is installed.
11
- `)}`)):console.error(`${o()}${s.red(`
12
- \u25FC Failed to launch ${t}: ${e.message}
13
- `)}`),process.exit(1)}),y.on("exit",e=>{if(u)try{u()}catch{}process.exit(e??0)})}function He(){const c=v();if(c.claude)try{Me()||Re(),Pe(S(process.cwd())),Ee(),De()}catch(n){console.error(`${o()}${s.yellow(`warning: Claude Code integration check failed: ${n?.message??n}`)}`)}if(c.codex)try{Le()||Te(),Ae(),Ie()}catch(n){console.error(`${o()}${s.yellow(`warning: Codex integration check failed: ${n?.message??n}`)}`)}}Oe().catch(c=>{console.error(`${o()}${s.red(`
14
- \u25FC Error: ${c.message}
2
+ import{spawn as E}from"child_process";import{randomUUID as M}from"node:crypto";import{basename as O}from"node:path";import{createInterface as H}from"node:readline/promises";import t from"chalk";import{findProjectRoot as y,getActiveCube as F,getLaunchModel as N,inboxPathForDrone as B,setCodexWakeTarget as _,pruneDeadCodexWakeTargets as W}from"./cubes.js";import{applyOllamaLaunchEnv as G,checkModelReachable as Y}from"./model-presets.js";import{handleVersionFlag as U,getPackageVersion as h}from"./version.js";import{isHelpFlag as T,setupHelpText as V,topLevelHelpText as K,assimilateHelpText as j}from"./cli-help.js";import{runSpawn as q}from"./spawn.js";import{parseSyncArgs as X,runSync as z}from"./sync.js";import{parseCleanupArgs as J,runCleanup as Q}from"./cleanup-cmd.js";import{parseAssimilateArgs as Z}from"./parse-assimilate-args.js";import{runAssimilate as ee}from"./assimilate-cmd.js";import{buildDefaultAssimilateDeps as re}from"./assimilate-deps.js";import{parseLaunchAllArgs as A}from"./parse-launch-all-args.js";import{unknownSubcommand as oe}from"./unknown-subcommand.js";import{runLaunchAll as I}from"./launch-all-cmd.js";import{buildDefaultLaunchAllDeps as x}from"./launch-all-deps.js";import{discoverDroneCandidates as se}from"./launch-all-discovery.js";import{runBareLaunchMenu as te,shouldShowLaunchMenu as ae}from"./bare-launch-menu.js";import{setTerminalTitle as ie}from"./terminal-title.js";import{initConsolePrefix as ce,consolePrefix as o}from"./console-prefix.js";import{initDebugFromArgv as ne}from"./debug.js";import{fetchLatestBorgmcpVersion as le,compareVersionsForStaleness as de}from"./stale-version-check.js";import{defaultCliChoiceDeps as pe,detectCliAvailability as C,installedCliNames as P,parseCliFlag as ue,resolveCliChoice as me}from"./cli-platform.js";import{getRefreshToken as fe,getIdToken as ge}from"./config.js";import{composeGetStarted as he,shouldShowGetStarted as we}from"./get-started.js";import{prepareCodexRemoteLaunch as xe,withCodexCwdArg as Ce,defaultCodexRemoteDeps as ke,checkCodexBridgeHealthy as ve}from"./codex-remote.js";import{findLoadedCodexThread as be}from"./codex-app-server.js";import{buildAgentKickoffPrompt as $e,buildKickoffWakePathClause as Se,recordCodexWakeTarget as ye,socketPathFromRemoteArgs as R}from"./codex-launch.js";import{codexBorgSessionConfigArgs as Te}from"./launch-gate.js";import{addCodexMcpServer as Ae,addCodexSessionStartHook as Ie,addCodexUserPromptSubmitHook as Pe,addMcpServer as Re,addProjectSessionStartHook as De,addUserPromptSubmitHook as Le,isCodexMcpServerConfigured as Ee,isMcpServerConfigured as Me,removeSessionStartHook as Oe}from"./config-utils.js";async function He(){ne(process.argv),U(),await ce();const n=(async()=>{if(!process.stderr.isTTY)return;const e=h(),r=await le();if(!r)return;const i=de(e,r);i.stale&&i.message&&process.stderr.write(`${o()}${i.message}
3
+ `)})();if((process.argv[2]==="--help"||process.argv[2]==="-h")&&(process.stdout.write(K(h())),process.exit(0)),process.argv[2]==="setup"){T(process.argv[3])&&(process.stdout.write(V(h())),process.exit(0)),await import("./setup.js");return}if(process.argv[2]==="assimilate"){process.argv.slice(3).some(T)&&(process.stdout.write(j(h())),process.exit(0));const e=Z(process.argv.slice(3));e.ok||(process.stderr.write(t.red(`${o()}\u25FC borg assimilate: ${e.error}
4
+ `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const r=re(),i=await ee({role:e.role,flags:e.flags},r);process.exit(i)}if(process.argv[2]==="spawn"){const e=await q();process.exit(e)}if(process.argv[2]==="sync"){const e=X(process.argv.slice(3));e.ok||(process.stderr.write(t.red(`${o()}\u25FC borg sync: ${e.error}
5
+ `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const r=await z({},e.options);process.exit(r)}if(process.argv[2]==="cleanup"){const e=J(process.argv.slice(3));e.ok||(process.stderr.write(t.red(`${o()}\u25FC borg cleanup: ${e.error}
6
+ `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const r=await Q({},e.options);process.exit(r)}if(process.argv[2]==="launch-all"){const e=A(process.argv.slice(3));e.ok||(process.stderr.write(t.red(`${o()}\u25FC borg launch-all: ${e.error}
7
+ `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const r=x(),i=await I(e.args,r);process.exit(i)}const c=oe(process.argv[2]);if(c!==null&&(process.stderr.write(t.red(`${o()}\u25FC unknown command: ${c}
8
+ `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1)),we(await fe()!==null,await ge()!==null)){const e=P(C()).length>0;process.stdout.write(he(e)),process.exit(0)}const f=ue(process.argv.slice(2));f.error&&(process.stderr.write(t.red(`${o()}\u25FC ${f.error}
9
+ `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const k=async e=>{const r=H({input:process.stdin,output:process.stdout});try{return await r.question(e)}finally{r.close()}};let s=await me(f.cli,pe(k,()=>process.stdin.isTTY===!0));Fe();const a=await F();if(ae({extraArgs:process.argv.slice(2),stdinIsTTY:process.stdin.isTTY===!0,stdoutIsTTY:process.stdout.isTTY===!0})){const e=P(C()).find(m=>m!==s)??null;let r=!1;a&&(r=(await se({targetCubeId:a.cubeId},x())).length>0);const i=await te({defaultCli:s,otherInstalledCli:e,hasLaunchAllTargets:r},k);if(i.kind==="launch-all"){const m=A([]),L=m.ok?await I(m.args,x()):1;process.exit(L)}s=i.cli}const l=f.rest;ie(a?{label:a.droneLabel,cubeName:a.name}:null,O(process.cwd()));const D=Se(s==="codex"?"codex":"claude",a&&s==="claude"?B(a.cubeId,a.droneId):null);await Promise.race([n,new Promise(e=>setTimeout(e,2e3))]);const v=s==="codex"?`borg-wake-${M()}`:null;let g,b=[],d={...process.env,BORG_SESSION:"1"},p=null,u=null;if(s==="codex"&&!l.includes("--remote")){console.error(`${o()}${t.gray("\u25FC Starting Codex remote-wake app-server\u2026")}`);const e=await xe(ke());e.warning?(console.error(`${o()}${t.yellow(`warning: ${e.warning}`)}`),g="\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."):g="Codex wake-path capability check passed: remote-control socket established for this session.",b=e.args,d={...process.env,...e.env,BORG_SESSION:"1"},p=R(e.args),u=e.server?.cleanup??null}else s==="codex"&&l.includes("--remote")&&(g="Codex wake-path capability check: using caller-provided --remote socket; if no wake arrives, run borg_regen manually when returning to the session.",p=R(l),p&&(d={...process.env,BORG_CODEX_REMOTE_WAKE:"1",BORG_SESSION:"1"}));if(a){const e=await N(a.cubeId,y(),a.droneId),r=G(d,e,process.env);if(d=r.env,r.probe){const i=await Y(r.probe.descriptor,fetch,r.probe.baseUrl);i.ok||console.error(`${o()}${t.yellow(`warning: ${i.message}`)}`)}}const $=$e({cli:s,codexWakeNonce:v,monitorClause:D,codexWakePathClause:g});let w=[...l,$];s==="codex"&&(w=[...Te(),...b,...Ce(w,process.cwd())]),console.error(`${o()}${t.blue(`\u25FC Launching ${s==="claude"?"Claude Code":"Codex"}\u2026`)}`);const S=E(s,w,{stdio:"inherit",shell:!1,env:d});s==="codex"&&a&&p&&(ye({deps:{setCodexWakeTarget:_,findLoadedCodexThread:be},cubeId:a.cubeId,droneId:a.droneId,socketPath:p,passthroughArgs:l,previewNeedle:v??$.slice(0,120),cwd:process.cwd(),launchedAtSeconds:Math.floor(Date.now()/1e3)}),W(e=>ve(e))),S.on("error",e=>{if(u)try{u()}catch{}e.code==="ENOENT"?(console.error(`${o()}${t.red(`
10
+ \u25FC Failed to launch ${s}`)}`),console.error(`${o()}${t.gray(`Make sure ${s} is installed.
11
+ `)}`)):console.error(`${o()}${t.red(`
12
+ \u25FC Failed to launch ${s}: ${e.message}
13
+ `)}`),process.exit(1)}),S.on("exit",e=>{if(u)try{u()}catch{}process.exit(e??0)})}function Fe(){const n=C();if(n.claude)try{Me()||Re(),De(y(process.cwd())),Oe(),Le()}catch(c){console.error(`${o()}${t.yellow(`warning: Claude Code integration check failed: ${c?.message??c}`)}`)}if(n.codex)try{Ee()||Ae(),Ie(),Pe()}catch(c){console.error(`${o()}${t.yellow(`warning: Codex integration check failed: ${c?.message??c}`)}`)}}He().catch(n=>{console.error(`${o()}${t.red(`
14
+ \u25FC Error: ${n.message}
15
15
  `)}`),process.exit(1)});
@@ -1,4 +1,15 @@
1
1
  import type { BorgCli } from './cubes.js';
2
+ import { type AgentKind } from './regen-format.js';
3
+ /**
4
+ * The claude kickoff prompt's wake-path section (gh#929) — the SAME shared
5
+ * `wakePathArming` the SessionStart hook + /clear orientation use (one place,
6
+ * not three), plus a one-line NEVER-TaskStop safety reminder preserved from
7
+ * the pre-gh#929 monitorClause. Built by both launch call sites
8
+ * (claude.ts + assimilate-cmd.ts) and passed to `buildAgentKickoffPrompt` as
9
+ * `monitorClause`. Codex wakes via the app-server (no tail-Monitor / `/loop`
10
+ * to arm) → empty; no active cube (no inboxPath) → empty.
11
+ */
12
+ export declare function buildKickoffWakePathClause(agentKind: AgentKind, inboxPath: string | null): string;
2
13
  export interface CodexWakeTargetDeps {
3
14
  setCodexWakeTarget: (cubeId: string, droneId: string, target: {
4
15
  threadId: string;
@@ -1 +1,2 @@
1
- function s(e){const r=e.codexWakeNonce?`Wake target nonce: ${e.codexWakeNonce}. `:"",t="On every Monitor wake and every ScheduleWakeup heartbeat, triage with borg_read-log unread_only=true first \u2014 it returns only entries since your last read, advancing your server-side read cursor (oldest-unread first), so you never skip a burst; and it keeps last_seen fresh. DRAIN: if it returns a full set (count == limit) or borg_roster shows behind_by>0, call read-log unread_only=true again until the return is < limit. Do NOT triage with a manual since cursor or a bare limit window \u2014 since is strict-after (skips the boundary entry) and a limit-bounded read skips older-unread during bursts (and codex has no per-entry inbox-Monitor backstop to catch them); reserve limit for explicit bounded reads. Call borg_regen only when you are about to act and need full cube/role/roster context, on session start, after context compaction, or periodically every 4-5 wakes / 15-30 minutes. Never reflexively call borg_regen for routine text-only wakes. ",a=e.codexWakePathClause??"Codex Borg wakeups use remote-control when available; if no wake arrives, run borg_regen manually when returning to the session.";return`${e.cli==="claude"?"/loop ":""}Call borg_regen and follow the playbook in its response. `+r+'Note: at session start the borg MCP server is still spinning up in parallel \u2014 if a system reminder claims "MCP server disconnected" or the borg tools are not yet registered, do NOT bail. Recover via `ToolSearch({query: "select:mcp__borg__borg_regen,mcp__borg__borg_log,Monitor", max_results: 3})` to load the bootstrap tools in one call, then call borg_regen. The server typically becomes available within a few seconds. '+e.monitorClause+"Coordinator/Queen seats: before posting bare `Standing.`, run the anti-passive-Standing stale-check per your role text \u2014 for every in-flight dispatch / REVIEW-READY / synthesis-pending, compare elapsed-since-last-transition against the cadence-table PING thresholds; escalate per the 4-step ladder (PING / probe-liveness / reassign / suspect-systemic) when any row is overdue. Append the awaited signal when you do post Standing (`Standing for X`, never bare). "+t+(e.cli==="claude"?"Wake-path capability check: if borg_regen shows a wake-path warning, arm the Monitor before starting work. Use a 30-minute (1800s) fallback heartbeat for ScheduleWakeup.":a)}function i(e){const r=e.indexOf("--remote");if(r<0)return null;const t=e[r+1];return t?.startsWith("unix://")?t.slice(7):null}function o(e){if(e[0]==="resume"&&e[1]&&!e[1].startsWith("-"))return e[1];const r=e.indexOf("--resume");return r>=0&&e[r+1]?e[r+1]:null}async function l(e){try{const r=e.passthroughArgs?o(e.passthroughArgs):null;if(r){await e.deps.setCodexWakeTarget(e.cubeId,e.droneId,{threadId:r,socketPath:e.socketPath});return}const t=Date.now()+15e3;for(;Date.now()<t;){const a=await e.deps.findLoadedCodexThread({socketPath:e.socketPath,cwd:e.cwd,previewIncludes:e.previewNeedle,updatedAfter:e.launchedAtSeconds-5});if(a){await e.deps.setCodexWakeTarget(e.cubeId,e.droneId,{threadId:a,socketPath:e.socketPath});return}await new Promise(n=>setTimeout(n,500))}}catch{}}export{s as buildAgentKickoffPrompt,l as recordCodexWakeTarget,i as socketPathFromRemoteArgs,o as threadIdFromPassthroughArgs};
1
+ import{wakePathArming as n}from"./regen-format.js";function s(e,t){return e!=="claude"||!t?"":n("claude",t)+`
2
+ Keep this Monitor armed for the whole session \u2014 NEVER TaskStop it (the generic /loop "TaskStop any Monitor you armed" step does NOT apply to the cube inbox Monitor; it targets throwaway loop-scratch watches only). The sole exception is a confirmed terminal eviction (410 DRONE_EVICTED). `}function d(e){const t=e.codexWakeNonce?`Wake target nonce: ${e.codexWakeNonce}. `:"",r=e.codexWakePathClause??"Codex Borg wakeups use remote-control when available; if no wake arrives, run borg_regen manually when returning to the session.";return`${e.cli==="claude"?"/loop ":""}Call borg_regen and follow the playbook in its response. `+t+'Note: at session start the borg MCP server is still spinning up in parallel \u2014 if a system reminder claims "MCP server disconnected" or the borg tools are not yet registered, do NOT bail. Recover via `ToolSearch({query: "select:mcp__borg__borg_regen,mcp__borg__borg_log,Monitor", max_results: 3})` to load the bootstrap tools in one call, then call borg_regen. The server typically becomes available within a few seconds. '+(e.cli==="claude"?e.monitorClause:r)}function i(e){const t=e.indexOf("--remote");if(t<0)return null;const r=e[t+1];return r?.startsWith("unix://")?r.slice(7):null}function c(e){if(e[0]==="resume"&&e[1]&&!e[1].startsWith("-"))return e[1];const t=e.indexOf("--resume");return t>=0&&e[t+1]?e[t+1]:null}async function u(e){try{const t=e.passthroughArgs?c(e.passthroughArgs):null;if(t){await e.deps.setCodexWakeTarget(e.cubeId,e.droneId,{threadId:t,socketPath:e.socketPath});return}const r=Date.now()+15e3;for(;Date.now()<r;){const a=await e.deps.findLoadedCodexThread({socketPath:e.socketPath,cwd:e.cwd,previewIncludes:e.previewNeedle,updatedAfter:e.launchedAtSeconds-5});if(a){await e.deps.setCodexWakeTarget(e.cubeId,e.droneId,{threadId:a,socketPath:e.socketPath});return}await new Promise(o=>setTimeout(o,500))}}catch{}}export{d as buildAgentKickoffPrompt,s as buildKickoffWakePathClause,u as recordCodexWakeTarget,i as socketPathFromRemoteArgs,c as threadIdFromPassthroughArgs};
@@ -5,6 +5,99 @@
5
5
  * Lives in its own module so regen.ts can import these without pulling in
6
6
  * index.ts's stdio MCP server bootstrap.
7
7
  */
8
+ /**
9
+ * Extract the SessionStart `source` from a Claude Code hook payload (gh#926).
10
+ *
11
+ * SessionStart hooks receive a JSON object on stdin whose `source` field is
12
+ * one of `startup` / `resume` / `clear` / `compact`. The `borg-regen`
13
+ * SessionStart hook uses this to detect a `/clear` re-orientation, which is
14
+ * the FIRST time the hook is the SOLE orientation path (the launch kickoff
15
+ * prompt is gone) AND the moment Claude Code clears the session-scoped
16
+ * `/loop` + `ScheduleWakeup` — so the re-injected orientation must instruct
17
+ * an operational re-arm.
18
+ *
19
+ * Best-effort + total: empty input (manual / TTY run with no stdin),
20
+ * malformed JSON, a missing `source`, or a non-string `source` all return
21
+ * `null` so the caller falls back to the default (full-regen) behavior. A
22
+ * hook bin must never throw on unexpected stdin.
23
+ */
24
+ export declare function parseHookSource(raw: string): string | null;
25
+ /** The agent runtime a session runs under — drives the wake-path branch. */
26
+ export type AgentKind = 'claude' | 'codex';
27
+ /**
28
+ * The agent-branched WAKE-PATH ARMING sub-block (gh#929/gh#927) — the single
29
+ * shared "re-establish your wake path" instruction reused by the launch
30
+ * kickoff prompt, the lean SessionStart-hook orientation, and the /clear
31
+ * re-orient. Factored to ONE place so the most load-bearing (and most
32
+ * drift-prone) liveness instruction can never diverge across the three
33
+ * surfaces.
34
+ *
35
+ * Agent-branched on the existing env-agnostic signal (BORG_SESSION-style
36
+ * `isCodexRemoteWakeEnabled`), NOT on a mutable server-recorded field:
37
+ * - claude: arm the inbox-file tail Monitor + engage `/loop` + set a ~1800s
38
+ * `ScheduleWakeup` fallback heartbeat (three independent wake mechanisms).
39
+ * - codex: wakes via the app-server remote-wake injection — it has NO
40
+ * tail-Monitor and NO `/loop` heartbeat to arm, so instructing either would
41
+ * be wrong; the recovery is a manual `borg_regen` on return.
42
+ *
43
+ * `inboxPath` is the deterministic client-generated UUID path
44
+ * (`~/.config/borgmcp/inboxes/<cubeId>/<droneId>.log`) — no user-controlled
45
+ * metacharacters, same source as today's launch monitorClause.
46
+ */
47
+ export declare function wakePathArming(agentKind: AgentKind, inboxPath: string): string;
48
+ /**
49
+ * Resolve the lean-orientation identity (gh#927), preferring the fresh
50
+ * network `regen()` result and falling back per-field to the local
51
+ * `getActiveCube` state. When `result` is null — the net-free fallback path
52
+ * taken on a `regen()` network failure — identity comes entirely from local
53
+ * state, so a weak drone that hits a SessionStart network blip still gets
54
+ * oriented (with its wake-path arming) instead of left dormant.
55
+ */
56
+ export declare function resolveLeanIdentity(active: {
57
+ name: string;
58
+ droneLabel: string;
59
+ roleName?: string | null;
60
+ }, result: {
61
+ cube?: any;
62
+ drone?: any;
63
+ role?: any;
64
+ } | null): {
65
+ cubeName: string;
66
+ droneLabel: string;
67
+ roleName: string | null;
68
+ };
69
+ /**
70
+ * The canonical LEAN orientation core (gh#929/gh#927) — the single shared
71
+ * "minimal operational orientation" rendered for a drone at launch, on every
72
+ * SessionStart source (startup/resume/clear/compact), and on /clear. It
73
+ * SUPERSEDES the per-surface variants: the SessionStart hook renders this
74
+ * instead of the full ~20.7KB `formatRegenMarkdown` (which the harness
75
+ * truncates to a ~2KB preview, leaving weak models partially oriented), and
76
+ * the /clear re-orient is just this with `source: 'clear'`.
77
+ *
78
+ * Three load-bearing parts, all kept (per the SEC/PM/CR rails):
79
+ * - IDENTITY: cube + drone label + role, so a weak model knows who it is.
80
+ * - WAKE-PATH ARMING: the shared `wakePathArming` block (liveness — correct
81
+ * to carry pre-`borg_regen`).
82
+ * - `borg_regen` POINTER: the path to the FULL context AND the safety floor
83
+ * (NEVER-TaskStop / 410-EVICTED / 423-FROZEN rules live in role-text,
84
+ * reachable only via this pointer). Kept in EVERY render.
85
+ *
86
+ * Template-agnostic (#921): the escalation target is "your cube's coordinating
87
+ * role" — NEVER a hardcoded `coordinator` / `drone-1` (this is the
88
+ * single most-rendered instruction surface in the product, and the first
89
+ * thing a weak model on a NON-sw-dev template reads). `roleName` is optional
90
+ * so the net-free fallback can render from local `getActiveCube` state when a
91
+ * `regen()` network call is unavailable.
92
+ */
93
+ export declare function formatLeanOrientation(args: {
94
+ cubeName: string;
95
+ droneLabel: string;
96
+ roleName?: string | null;
97
+ inboxPath: string;
98
+ agentKind: AgentKind;
99
+ source?: string | null;
100
+ }): string;
8
101
  /**
9
102
  * Build the universal drone playbook.
10
103
  *
@@ -1,9 +1,11 @@
1
- import{ROLE_SCOPED_SAFETY_DISCIPLINES as m,UNIVERSAL_SAFETY_DISCIPLINES as f}from"./templates.js";import{parseRoleSections as O}from"./role-section.js";import{formatRoleAgentLabel as S}from"./roster-render.js";function y(){return"## How to operate as a Drone\n\nYou're a Drone in a Cube. Coordinate with other drones through the activity log.\n\n**Tools:**\n- `borg_regen` \u2014 refresh full state (your role, roster, unread-log COUNT, and fetch-on-demand pointers) in one call; the cube directive (\u2192 `borg_cube`), the operating-playbook detail (\u2192 `borg_playbook`), and the recent-log payload (\u2192 `borg_read-log` when count >0) are NOT inlined \u2014 fetch them on demand\n- `borg_cube` \u2014 re-read the cube directive and the role overview\n- `borg_role` \u2014 re-read your role's detailed playbook\n- `borg_roster` \u2014 see who else is connected\n- `borg_read-log unread_only=true [limit]` \u2014 drain unread log entries from your server-side cursor\n- `borg_log <message>` \u2014 append to the log\n- `borg_assimilate <cube>` \u2014 switch to a different cube\n\n**How coordination works:** the Cube gives primitives, not workflows. Your role's `detailed_description` (above) is your playbook \u2014 its conventions + signals come from there, not the system. The log is the coordination channel. Different cubes, different conventions.\n\n**Default: act autonomously, coordinate through the log.** Don't wait for user input. Need input \u2192 post the question, continue other work, other drones respond. The human supervisor is reachable through your cube's coordinating / human-seat role (the role your cube designates for direction + integration), or the Queen role when the seat is delegated to a drone \u2014 one continuous seat. Your role's `detailed_description` says when to escalate + which decisions need human input; follow it.\n\n**Operating loop \u2014 each wake, in order:**\n1. Drain unread: `borg_read-log unread_only=true` (oldest-first, repeat until `behind_by=0`) before acting. The \"Cube log\" section gives your UNREAD COUNT.\n2. Apply your role's conventions to each entry. Act on: questions you can answer; blocked peers you can unblock; unowned work you can claim; decisions affecting you.\n3. Actionable signal \u2192 act + post the convention. Don't wait to be asked.\n4. User prompt waiting \u2192 respond, informed by cube context; log substantive units (shipped changes, blockers, findings) regardless of who initiated.\n5. Nothing actionable + no prompt \u2192 done; wait for next wake.\n\n**On a `<task-notification>` wake:** the payload is a truncatable preview; the full entry is in the DB. Drain: `borg_read-log unread_only=true limit=20`, repeat until `behind_by=0`. Do NOT triage with `since=<notification timestamp>` (strict-after \u2014 skips the boundary entry) or a bare window (skips older-unread during bursts).\n\n**On first wake this session:** post one `ARRIVAL: <your-label> (<your-role>) online on <hostname> at <project-path>` (run `hostname`; use cwd for the path). One-time per session \u2014 don't repeat on later wakes; skip if already posted this session (e.g. after a `/mcp` reconnect).\n\n**When a log entry routes work to you** (a routing/assignment-class entry per your cube's conventions that names your label + asks for action, or a direct `<your-label>:` mention): call `borg_ack entry_id=<id>` within ~60s. Use the `borg_ack` TOOL, not an in-band `ACK:` post (it records a queryable flag + wakes the author's Monitor + keeps the log clean). Ack = receipt, not completion (`STARTING` / `DONE` still apply). Ack only routing-class signals \u2014 not every mention.\n\n**When stuck:** post your blocker per your role's conventions, continue other work. Escalation is per your role detail, not by stalling.\n\n**Anti-passive (lane idle = no work routed to you, no actionable signal in the log):**\n- If your work arrives via dispatch / a work queue: when your lane goes idle, post your role's availability signal (capacity clean, awaiting next assignment from your coordinating role) \u2014 once per idle period, don't spam. No assignment in ~15 min \u2192 ping your coordinating role (capacity available since <time>; any queue item to pick up?).\n- If your work is SELF-DIRECTED (not dispatch-driven): do NOT post an availability signal \u2014 proactively surface lane-substantive work per your role (reviews, audits, proposals, coherence / quality sweeps on relevant in-flight work).\n- Route work-asks through your cube's coordinating role, never directly to the human Queen.\n\n**Verify factual claims:** verify any verifiable claim \u2014 versions, code-state, prod behavior, npm state \u2014 against the SOURCE-OF-TRUTH surface (`git tag` / `git show <ref>:<path>` / grep, `curl` / `wrangler tail`, `npm view`, the live DB) BEFORE writing it; never a derivative artifact (another post, summary, or your own prior framing). The full discipline \u2014 the v1/v2/v3 sharpening levels, the per-claim-type concrete surfaces, and four-surface propagation (brainstorm / comment / review / issue-filing) \u2014 is in the operating-playbook chapter (`borg_playbook`; loaded via the session-start block in your regen).\n\n**Posting to the log:** post per your role's conventions whenever you start/finish a task, get stuck, answer a drone, or learn something others need \u2014 regardless of who initiated (a log signal, your own scan, or a user prompt). Conventions live in your role detail; the system is vocabulary-agnostic.\n\n**Routing posts \u2014 widen the directed default:** the taxonomy routes most prefixes DIRECTED to your cube's coordinating role; your `to:` / `visibility:` overrides it. Widen when a post must reach more than the coordinating role:\n- Posting a verdict / decision / result a specific drone is waiting on: add `to:[that drone]` so they're WOKEN \u2014 without it they can be left UNAWARE of their own merge or feedback. Directed governs the WAKE; it is NOT read-confidentiality: every member can read every entry \u2014 the cube is the trust boundary \u2014 so never post secrets relying on `to:[x]`.\n- Any drone posting a multi-seat DELIVERABLE (spec / security classification / review artifact 3+ seats build or gate against): pass `visibility:broadcast` (or `to:[the seats]`) EVEN IF your prefix (`DONE` etc.) is a directed status class \u2014 else only your coordinating role wakes (taxonomy routes by prefix, not payload) and the building/gating seats miss it.\n\n**Pre-commit git hygiene (universal):**\n\nAny drone that commits code: run `git diff --staged --stat` before `git commit` to verify file count + LOC direction + paths match your intent. Catches deleted files / anomalous -LOC / wrong paths pre-push. Your role may layer more git rules (code-implementing + coordinating roles typically carry the full set)."}const $=y();function F(){return'## Operating playbook \u2014 full disciplines (borg_playbook chapter)\n\nThis is the on-demand detail behind the rule-spine in your regen. Load it ONCE per session; it is static \u2014 do not re-fetch on every wake.\n\n**Verifying factual claims:**\n\nAny time you make a factual claim that could be verified \u2014 "this shipped as version Y", "function Z does W", "endpoint A returns B in prod", "package P is at version Q on npm" \u2014 verify the claim against a SOURCE-OF-TRUTH surface BEFORE writing it, not against a derivative artifact (another post, doc, summary, or your own prior framing). Three sharpening levels:\n\n- **v1 (verify against the actual surface):** check the claim against the surface it describes (e.g. a code-state claim \u2192 grep the file). Apply when the claim is about code-state.\n- **v2 (source-of-truth vs derivative artifacts):** when the verification surface itself could carry the original error chain (another post citing the same wrong claim, a doc copy-mirrored from the post you\'re checking), verify against the canonical source-of-truth: `git tag` for version-attribution, code-by-grep / direct file read for code-state, live `curl` or `wrangler tail` for prod-state, `npm view` for npm-state. Apply when version numbers, deploy timestamps, or other discrete facts are in scope.\n- **v3 (end-to-end execution path vs originating mechanism):** when verifying a live-mechanism claim ("the watchdog wakes silent drones"), verify the END-TO-END execution path, not just each isolated component \u2014 each isolated mechanism can be correct while the path between them silently breaks. Apply when live-mechanism correctness is being claimed; trace the path the wake/value/state actually takes from origin to terminal observer.\n\n**Concrete verification surfaces by claim type:**\n- Version attribution \u2192 `git tag --contains <sha>` or `git log --oneline <tag>`\n- Code state \u2192 match the grep surface to the claim surface:\n - Local uncommitted claim \u2192 `grep -n "<symbol>" <file>` or direct file read in the working tree\n - `origin/main`, PR head, branch, merge-SHA, or tag claim \u2192 `git show <ref>:<path> | grep -n "<symbol>"` (examples: `git show origin/main:workers/heartbeat.ts | grep -n "last_log_post"`; `git show origin/feat/foo:client/src/log-stream.ts | grep -n "ownDrone"`; `git show abc1234:workers/cubes.ts | grep -n "visibility"`)\n- Prod state \u2192 `curl https://<endpoint>` or `wrangler tail --env production`\n- npm registry state \u2192 `npm view <package>@<version>` or `npm view <package>@latest`\n- DB state \u2192 query through the existing `db` interface; never trust a doc claim about row counts / column values\n- Cube log state \u2192 `borg_read-log unread_only=true` for wake triage, draining until `behind_by=0`; don\'t cite from memory or from another drone\'s summary\n\n**The discipline is universal to reviewer-class actions** (Code Reviewer formal gates + Security Auditor SR gates + PM-courtesy verifications + UX-courtesy reviews + any drone making a verification-worthy factual claim in their cube-log post). It lives in this universal playbook rather than any one role\'s text because it applies to ALL reviewers.\n\n**Four-surface propagation:**\n\nThe discipline applies at FOUR surfaces. Catches at the surface closest to origin are cheapest; catches at later surfaces have already propagated through earlier consumers:\n\n- **Surface 1 (brainstorm-proposal time)**: when a brainstorm contribution names specific code identifiers / API field names / enum values / column names / function signatures, the PROPOSING drone source-grep\'s the referenced file BEFORE composing the proposal. If the proposal cites current `origin/main` or a branch/SHA, grep that ref via `git show <ref>:<path> | grep`; working-tree grep is only for explicitly local/uncommitted claims. Cheapest catch surface; one drone catches one error.\n- **Surface 2 (comment/JSDoc/docstring writing time)**: when an implementation comment cites cross-file invariants (other modules\' thresholds, schema columns, enum values, semantic contracts), the WRITING drone source-grep\'s the referenced file BEFORE writing the comment. If the comment describes a merged/base/PR-head state, grep the named ref via `git show <ref>:<path> | grep`; don\'t let a stale local checkout stand in for the ref being described. Mid-cost catch; one drone catches one error but downstream reviewers may inherit the wrong mental model from the comment.\n- **Surface 3 (review-time verification)**: the existing review-class discipline (Code Reviewer formal gates + Security Auditor SR gates + PM/UX/QA courtesy reviews). Late catch opportunity; if the error propagated through Surfaces 1 + 2, multiple reviewers may have already trusted the framing instead of source-grepping themselves.\n- **Surface 4 (durable-tracking-artifact-writing time)**: when filing a deferred-tracking issue from a cube event payload, the FILING drone fetches the originating entry\'s full body from the cube log BEFORE composing the issue body. For routine wake triage, use `borg_read-log unread_only=true` and drain until caught up; do not rely on a truncated event preview or a `since=<same timestamp>` read, which can skip the boundary entry. Cube event previews can truncate substantive content (mid-paragraph cuts on long entries); filing from the truncated preview trusts a derivative artifact instead of the source-of-truth full entry. Most expensive surface \u2014 the filed issue becomes the cube\'s durable cross-cycle memory; correcting it requires a follow-up correction post, and later pickup drones inherit the incomplete framing if the correction is missed.'}function R(e){const o=typeof e=="string"?new Date(e):e,r=Date.now()-o.getTime();if(!Number.isFinite(r)||r<0)return"just now";const n=Math.floor(r/1e3);if(n<60)return`${n}s ago`;const t=Math.floor(n/60);if(t<60)return`${t}m ago`;const i=Math.floor(t/60);return i<24?`${i}h ago`:`${Math.floor(i/24)}d ago`}function T(e){return e==null||Array.isArray(e)&&e.length===0?"Tip: no message taxonomy declared \u2014 set one to enable intent-based smart routing (#468). Use borg_update-cube with a taxonomy array, or add classes with borg_patch-taxonomy-class.":""}function U(e,o){return e.drone?.label??o??null}let d=!1,h=null;function Y(){d=!1,h=null}function A(e){const o=e??"",r=m.filter(n=>o.includes(n));return[...f,...r]}function D(e,o){return`rationale \u2192 borg_role-rationale ${JSON.stringify(e)} ${JSON.stringify(o)}`}function B(e){const o=e.match(/borg_role-rationale\s+("(?:(?:\\.)|[^"\\])*")\s+("(?:(?:\\.)|[^"\\])*")/);if(!o)return null;try{return{role:JSON.parse(o[1]),section:JSON.parse(o[2])}}catch{return null}}const C=[...f,...m];function N(e,o){return O(o??"").map(t=>{if(t.kind!=="label"||t.heading==null||!t.heading.trim().toLowerCase().endsWith("rationale")||C.some(u=>t.body.includes(u)))return t.body;const s=t.body.indexOf(`
2
- `);return(s===-1?t.body+`
3
- `:t.body.slice(0,s+1))+D(e,t.heading)+`
4
- `}).join("")}function M(e,o={}){const r=o.mode??"full",n=e.roles.map(a=>`- **${a.name}**${a.is_default?" _(default)_":""} \u2014 ${a.short_description||"_(no short description)_"}`).join(`
5
- `),t=e.drones.map(a=>{const _=e.roles.find(x=>x.id===a.role_id),E=S(_?.name??"?",a.agent_kind);return`- **${a.label}** (${E}) \u2014 last seen ${R(new Date(a.last_seen))}`}).join(`
6
- `)||"_(no drones connected)_",i=typeof e.behind_by=="number"?e.behind_by:null,s=i===null?"Call `borg_read-log unread_only=true` to check for and drain any unread log entries (the log payload is not inlined in regen).":i>0?`You have **${i}** unread log ${i===1?"entry":"entries"}. Drain them with \`borg_read-log unread_only=true\` (oldest-unread first; repeat until \`behind_by=0\`). The log payload is not inlined here \u2014 fetch on demand.`:"You're caught up \u2014 **0** unread log entries. No need to read the log right now.",u=(i??0)===0&&e.drones.length<=1?["## Getting started","","Welcome to your first cube. Here's how to get going:","",'1. Post your first activity: `borg_log message="Starting work on <your task>"`',"2. Invite another agent session: open a new terminal and run `borg assimilate --worktree <name>`","3. Check who's here: `borg_roster`","","---",""].join(`
7
- `):"",g=T(e.cube.message_taxonomy),l=e.role.detailed_description_hash??null,v=e.role.detailed_description?N(e.role.name,e.role.detailed_description):"_(no detailed description set)_",w="Before you post or act, load your full operating context \u2014 once per session; static, do NOT re-fetch on every wake:\n- `borg_playbook` \u2014 your full operating disciplines (verification, four-surface propagation, ack / routing / idle detail).\n- `borg_cube` \u2014 the cube directive + conventions (log vocabulary, project / git / dispatch conventions).",p=r==="full"||l==null||l!==h,k=r==="full"||!d,c=[u+`# Cube: ${e.cube.name} \u2014 ${e.drone.label}`,"",`**Your role:** ${e.role.name}`,""];return r==="lite"&&c.push('_(lite regen \u2014 the role playbook may be omitted when unchanged; your operating context (playbook + cube directive) loads via the Session-start block (borg_playbook + borg_cube). If the playbook is NOT in your current context (e.g. after a context-compaction), call `borg_regen mode="full"` to re-orient.)_',""),c.push(r==="full"?"## Session start \u2014 required before acting":"## Session start",r==="full"?w:'Operating context (playbook + cube directive) was loaded at session start \u2014 re-fetch `borg_playbook` / `borg_cube` ONLY after a context-compaction (a `mode="full"` regen), not on every wake.',"",...g?[g,""]:[],`## Your role: ${e.role.name}`,p?v:["_(role playbook unchanged since your last full/lite regen; omitted in lite mode)_","",...A(e.role.detailed_description)].join(`
8
- `),"","## Roles in this cube",n,"","## Connected drones",t,"","## Cube log",s),k&&(c.push("",y()),d=!0),p&&l!=null&&(h=l),c.join(`
9
- `)}function W(e,o,r){const n=o.get(e.drone_id),t=n?r.get(n.role_id):null,i=new Date(e.created_at).toISOString(),s=typeof e.id=="string"&&e.id.length>0?` [entry_id: ${e.id}]`:"";return`**[${i}]**${s} ${n?.label??"?"} (${t?.name??"?"}): ${e.message}`}export{$ as DRONE_PLAYBOOK,Y as __resetRegenSessionState,N as compressRoleText,W as formatLogEntryMarkdown,D as formatRationalePointer,M as formatRegenMarkdown,y as getDronePlaybook,F as getDronePlaybookChapter,R as humanAgo,T as nullTaxonomyTip,B as parseRationalePointer,U as regenWakePathDroneLabel};
1
+ import{ROLE_SCOPED_SAFETY_DISCIPLINES as f,UNIVERSAL_SAFETY_DISCIPLINES as y}from"./templates.js";import{parseRoleSections as O}from"./role-section.js";import{formatRoleAgentLabel as S}from"./roster-render.js";function F(e){if(!e||!e.trim())return null;try{const r=JSON.parse(e)?.source;return typeof r=="string"?r:null}catch{return null}}function R(e,o){return e==="codex"?"Wake path: Codex wakes via the app-server remote-wake injection \u2014 there is no tail-Monitor or `/loop` heartbeat to arm. If no wake arrives when you return to the session, run `borg_regen` manually.":["Arm your wake path before working:",`1. **Inbox Monitor** (wake path) \u2014 run a persistent Monitor on \`borg-inbox-monitor ${o}\` so cube posts wake you in real time.`,"2. **Engage `/loop`** (self-paced) so you keep waking to triage the cube.","3. **Fallback heartbeat** \u2014 set a ~1800s (30-min) `ScheduleWakeup` so you never go fully silent."].join(`
2
+ `)}function Y(e,o){return{cubeName:o?.cube?.name??e.name,droneLabel:o?.drone?.label??e.droneLabel,roleName:o?.role?.name??e.roleName??null}}function B(e){const{cubeName:o,droneLabel:r,roleName:n,inboxPath:t,agentKind:i,source:a}=e,u=a==="clear"?"\n_(`/clear` cleared your conversation + session-scoped `/loop` and `ScheduleWakeup` heartbeat \u2014 re-establish them now.)_\n":"";return[`# Cube: ${o} \u2014 ${r}`,"",`**Your role:** ${n||"_(call `borg_regen` to load)_"}`,u,"You are a Borg drone \u2014 coordinate through the cube log, and never pause for the user. Blocked \u2192 escalate to your cube's coordinating role.","",R(i,t),"","This orientation is intentionally lean. For your full cube directive, role playbook, and roster \u2014 and your complete operating disciplines \u2014 call `borg_regen` (and load `borg_playbook` / `borg_cube` once per session).",""].join(`
3
+ `)}function b(){return"## How to operate as a Drone\n\nYou're a Drone in a Cube. Coordinate with other drones through the activity log.\n\n**Tools:**\n- `borg_regen` \u2014 refresh full state (your role, roster, unread-log COUNT, and fetch-on-demand pointers) in one call; the cube directive (\u2192 `borg_cube`), the operating-playbook detail (\u2192 `borg_playbook`), and the recent-log payload (\u2192 `borg_read-log` when count >0) are NOT inlined \u2014 fetch them on demand\n- `borg_cube` \u2014 re-read the cube directive and the role overview\n- `borg_role` \u2014 re-read your role's detailed playbook\n- `borg_roster` \u2014 see who else is connected\n- `borg_read-log unread_only=true [limit]` \u2014 drain unread log entries from your server-side cursor\n- `borg_log <message>` \u2014 append to the log\n- `borg_assimilate <cube>` \u2014 switch to a different cube\n\n**How coordination works:** the Cube gives primitives, not workflows. Your role's `detailed_description` (above) is your playbook \u2014 its conventions + signals come from there, not the system. The log is the coordination channel. Different cubes, different conventions.\n\n**Default: act autonomously, coordinate through the log.** Don't wait for user input. Need input \u2192 post the question, continue other work, other drones respond. The human supervisor is reachable through your cube's coordinating / human-seat role (the role your cube designates for direction + integration), or the Queen role when the seat is delegated to a drone \u2014 one continuous seat. Your role's `detailed_description` says when to escalate + which decisions need human input; follow it.\n\n**Operating loop \u2014 each wake, in order:**\n1. Drain unread: `borg_read-log unread_only=true` (oldest-first, repeat until `behind_by=0`) before acting. The \"Cube log\" section gives your UNREAD COUNT.\n2. Apply your role's conventions to each entry. Act on: questions you can answer; blocked peers you can unblock; unowned work you can claim; decisions affecting you.\n3. Actionable signal \u2192 act + post the convention. Don't wait to be asked.\n4. User prompt waiting \u2192 respond, informed by cube context; log substantive units (shipped changes, blockers, findings) regardless of who initiated.\n5. Nothing actionable + no prompt \u2192 done; wait for next wake.\n\n**On a `<task-notification>` wake:** the payload is a truncatable preview; the full entry is in the DB. Drain: `borg_read-log unread_only=true limit=20`, repeat until `behind_by=0`. Do NOT triage with `since=<notification timestamp>` (strict-after \u2014 skips the boundary entry) or a bare window (skips older-unread during bursts).\n\n**On first wake this session:** post one `ARRIVAL: <your-label> (<your-role>) online on <hostname> at <project-path>` (run `hostname`; use cwd for the path). One-time per session \u2014 don't repeat on later wakes; skip if already posted this session (e.g. after a `/mcp` reconnect).\n\n**When a log entry routes work to you** (a routing/assignment-class entry per your cube's conventions that names your label + asks for action, or a direct `<your-label>:` mention): call `borg_ack entry_id=<id>` within ~60s. Use the `borg_ack` TOOL, not an in-band `ACK:` post (it records a queryable flag + wakes the author's Monitor + keeps the log clean). Ack = receipt, not completion (`STARTING` / `DONE` still apply). Ack only routing-class signals \u2014 not every mention.\n\n**When stuck:** post your blocker per your role's conventions, continue other work. Escalation is per your role detail, not by stalling.\n\n**Anti-passive (lane idle = no work routed to you, no actionable signal in the log):**\n- If your work arrives via dispatch / a work queue: when your lane goes idle, post your role's availability signal (capacity clean, awaiting next assignment from your coordinating role) \u2014 once per idle period, don't spam. No assignment in ~15 min \u2192 ping your coordinating role (capacity available since <time>; any queue item to pick up?).\n- If your work is SELF-DIRECTED (not dispatch-driven): do NOT post an availability signal \u2014 proactively surface lane-substantive work per your role (reviews, audits, proposals, coherence / quality sweeps on relevant in-flight work).\n- Route work-asks through your cube's coordinating role, never directly to the human Queen.\n\n**Verify factual claims:** verify any verifiable claim \u2014 versions, code-state, prod behavior, npm state \u2014 against the SOURCE-OF-TRUTH surface (`git tag` / `git show <ref>:<path>` / grep, `curl` / `wrangler tail`, `npm view`, the live DB) BEFORE writing it; never a derivative artifact (another post, summary, or your own prior framing). The full discipline \u2014 the v1/v2/v3 sharpening levels, the per-claim-type concrete surfaces, and four-surface propagation (brainstorm / comment / review / issue-filing) \u2014 is in the operating-playbook chapter (`borg_playbook`; loaded via the session-start block in your regen).\n\n**Posting to the log:** post per your role's conventions whenever you start/finish a task, get stuck, answer a drone, or learn something others need \u2014 regardless of who initiated (a log signal, your own scan, or a user prompt). Conventions live in your role detail; the system is vocabulary-agnostic.\n\n**Routing posts \u2014 widen the directed default:** the taxonomy routes most prefixes DIRECTED to your cube's coordinating role; your `to:` / `visibility:` overrides it. Widen when a post must reach more than the coordinating role:\n- Posting a verdict / decision / result a specific drone is waiting on: add `to:[that drone]` so they're WOKEN \u2014 without it they can be left UNAWARE of their own merge or feedback. Directed governs the WAKE; it is NOT read-confidentiality: every member can read every entry \u2014 the cube is the trust boundary \u2014 so never post secrets relying on `to:[x]`.\n- Any drone posting a multi-seat DELIVERABLE (spec / security classification / review artifact 3+ seats build or gate against): pass `visibility:broadcast` (or `to:[the seats]`) EVEN IF your prefix (`DONE` etc.) is a directed status class \u2014 else only your coordinating role wakes (taxonomy routes by prefix, not payload) and the building/gating seats miss it.\n\n**Pre-commit git hygiene (universal):**\n\nAny drone that commits code: run `git diff --staged --stat` before `git commit` to verify file count + LOC direction + paths match your intent. Catches deleted files / anomalous -LOC / wrong paths pre-push. Your role may layer more git rules (code-implementing + coordinating roles typically carry the full set)."}const U=b();function M(){return'## Operating playbook \u2014 full disciplines (borg_playbook chapter)\n\nThis is the on-demand detail behind the rule-spine in your regen. Load it ONCE per session; it is static \u2014 do not re-fetch on every wake.\n\n**Verifying factual claims:**\n\nAny time you make a factual claim that could be verified \u2014 "this shipped as version Y", "function Z does W", "endpoint A returns B in prod", "package P is at version Q on npm" \u2014 verify the claim against a SOURCE-OF-TRUTH surface BEFORE writing it, not against a derivative artifact (another post, doc, summary, or your own prior framing). Three sharpening levels:\n\n- **v1 (verify against the actual surface):** check the claim against the surface it describes (e.g. a code-state claim \u2192 grep the file). Apply when the claim is about code-state.\n- **v2 (source-of-truth vs derivative artifacts):** when the verification surface itself could carry the original error chain (another post citing the same wrong claim, a doc copy-mirrored from the post you\'re checking), verify against the canonical source-of-truth: `git tag` for version-attribution, code-by-grep / direct file read for code-state, live `curl` or `wrangler tail` for prod-state, `npm view` for npm-state. Apply when version numbers, deploy timestamps, or other discrete facts are in scope.\n- **v3 (end-to-end execution path vs originating mechanism):** when verifying a live-mechanism claim ("the watchdog wakes silent drones"), verify the END-TO-END execution path, not just each isolated component \u2014 each isolated mechanism can be correct while the path between them silently breaks. Apply when live-mechanism correctness is being claimed; trace the path the wake/value/state actually takes from origin to terminal observer.\n\n**Concrete verification surfaces by claim type:**\n- Version attribution \u2192 `git tag --contains <sha>` or `git log --oneline <tag>`\n- Code state \u2192 match the grep surface to the claim surface:\n - Local uncommitted claim \u2192 `grep -n "<symbol>" <file>` or direct file read in the working tree\n - `origin/main`, PR head, branch, merge-SHA, or tag claim \u2192 `git show <ref>:<path> | grep -n "<symbol>"` (examples: `git show origin/main:workers/heartbeat.ts | grep -n "last_log_post"`; `git show origin/feat/foo:client/src/log-stream.ts | grep -n "ownDrone"`; `git show abc1234:workers/cubes.ts | grep -n "visibility"`)\n- Prod state \u2192 `curl https://<endpoint>` or `wrangler tail --env production`\n- npm registry state \u2192 `npm view <package>@<version>` or `npm view <package>@latest`\n- DB state \u2192 query through the existing `db` interface; never trust a doc claim about row counts / column values\n- Cube log state \u2192 `borg_read-log unread_only=true` for wake triage, draining until `behind_by=0`; don\'t cite from memory or from another drone\'s summary\n\n**The discipline is universal to reviewer-class actions** (Code Reviewer formal gates + Security Auditor SR gates + PM-courtesy verifications + UX-courtesy reviews + any drone making a verification-worthy factual claim in their cube-log post). It lives in this universal playbook rather than any one role\'s text because it applies to ALL reviewers.\n\n**Four-surface propagation:**\n\nThe discipline applies at FOUR surfaces. Catches at the surface closest to origin are cheapest; catches at later surfaces have already propagated through earlier consumers:\n\n- **Surface 1 (brainstorm-proposal time)**: when a brainstorm contribution names specific code identifiers / API field names / enum values / column names / function signatures, the PROPOSING drone source-grep\'s the referenced file BEFORE composing the proposal. If the proposal cites current `origin/main` or a branch/SHA, grep that ref via `git show <ref>:<path> | grep`; working-tree grep is only for explicitly local/uncommitted claims. Cheapest catch surface; one drone catches one error.\n- **Surface 2 (comment/JSDoc/docstring writing time)**: when an implementation comment cites cross-file invariants (other modules\' thresholds, schema columns, enum values, semantic contracts), the WRITING drone source-grep\'s the referenced file BEFORE writing the comment. If the comment describes a merged/base/PR-head state, grep the named ref via `git show <ref>:<path> | grep`; don\'t let a stale local checkout stand in for the ref being described. Mid-cost catch; one drone catches one error but downstream reviewers may inherit the wrong mental model from the comment.\n- **Surface 3 (review-time verification)**: the existing review-class discipline (Code Reviewer formal gates + Security Auditor SR gates + PM/UX/QA courtesy reviews). Late catch opportunity; if the error propagated through Surfaces 1 + 2, multiple reviewers may have already trusted the framing instead of source-grepping themselves.\n- **Surface 4 (durable-tracking-artifact-writing time)**: when filing a deferred-tracking issue from a cube event payload, the FILING drone fetches the originating entry\'s full body from the cube log BEFORE composing the issue body. For routine wake triage, use `borg_read-log unread_only=true` and drain until caught up; do not rely on a truncated event preview or a `since=<same timestamp>` read, which can skip the boundary entry. Cube event previews can truncate substantive content (mid-paragraph cuts on long entries); filing from the truncated preview trusts a derivative artifact instead of the source-of-truth full entry. Most expensive surface \u2014 the filed issue becomes the cube\'s durable cross-cycle memory; correcting it requires a follow-up correction post, and later pickup drones inherit the incomplete framing if the correction is missed.'}function N(e){const o=typeof e=="string"?new Date(e):e,r=Date.now()-o.getTime();if(!Number.isFinite(r)||r<0)return"just now";const n=Math.floor(r/1e3);if(n<60)return`${n}s ago`;const t=Math.floor(n/60);if(t<60)return`${t}m ago`;const i=Math.floor(t/60);return i<24?`${i}h ago`:`${Math.floor(i/24)}d ago`}function T(e){return e==null||Array.isArray(e)&&e.length===0?"Tip: no message taxonomy declared \u2014 set one to enable intent-based smart routing (#468). Use borg_update-cube with a taxonomy array, or add classes with borg_patch-taxonomy-class.":""}function W(e,o){return e.drone?.label??o??null}let h=!1,g=null;function j(){h=!1,g=null}function A(e){const o=e??"",r=f.filter(n=>o.includes(n));return[...y,...r]}function C(e,o){return`rationale \u2192 borg_role-rationale ${JSON.stringify(e)} ${JSON.stringify(o)}`}function H(e){const o=e.match(/borg_role-rationale\s+("(?:(?:\\.)|[^"\\])*")\s+("(?:(?:\\.)|[^"\\])*")/);if(!o)return null;try{return{role:JSON.parse(o[1]),section:JSON.parse(o[2])}}catch{return null}}const D=[...y,...f];function I(e,o){return O(o??"").map(t=>{if(t.kind!=="label"||t.heading==null||!t.heading.trim().toLowerCase().endsWith("rationale")||D.some(d=>t.body.includes(d)))return t.body;const a=t.body.indexOf(`
4
+ `);return(a===-1?t.body+`
5
+ `:t.body.slice(0,a+1))+C(e,t.heading)+`
6
+ `}).join("")}function q(e,o={}){const r=o.mode??"full",n=e.roles.map(s=>`- **${s.name}**${s.is_default?" _(default)_":""} \u2014 ${s.short_description||"_(no short description)_"}`).join(`
7
+ `),t=e.drones.map(s=>{const _=e.roles.find(E=>E.id===s.role_id),x=S(_?.name??"?",s.agent_kind);return`- **${s.label}** (${x}) \u2014 last seen ${N(new Date(s.last_seen))}`}).join(`
8
+ `)||"_(no drones connected)_",i=typeof e.behind_by=="number"?e.behind_by:null,a=i===null?"Call `borg_read-log unread_only=true` to check for and drain any unread log entries (the log payload is not inlined in regen).":i>0?`You have **${i}** unread log ${i===1?"entry":"entries"}. Drain them with \`borg_read-log unread_only=true\` (oldest-unread first; repeat until \`behind_by=0\`). The log payload is not inlined here \u2014 fetch on demand.`:"You're caught up \u2014 **0** unread log entries. No need to read the log right now.",d=(i??0)===0&&e.drones.length<=1?["## Getting started","","Welcome to your first cube. Here's how to get going:","",'1. Post your first activity: `borg_log message="Starting work on <your task>"`',"2. Invite another agent session: open a new terminal and run `borg assimilate --worktree <name>`","3. Check who's here: `borg_roster`","","---",""].join(`
9
+ `):"",p=T(e.cube.message_taxonomy),l=e.role.detailed_description_hash??null,v=e.role.detailed_description?I(e.role.name,e.role.detailed_description):"_(no detailed description set)_",w="Before you post or act, load your full operating context \u2014 once per session; static, do NOT re-fetch on every wake:\n- `borg_playbook` \u2014 your full operating disciplines (verification, four-surface propagation, ack / routing / idle detail).\n- `borg_cube` \u2014 the cube directive + conventions (log vocabulary, project / git / dispatch conventions).",m=r==="full"||l==null||l!==g,k=r==="full"||!h,c=[d+`# Cube: ${e.cube.name} \u2014 ${e.drone.label}`,"",`**Your role:** ${e.role.name}`,""];return r==="lite"&&c.push('_(lite regen \u2014 the role playbook may be omitted when unchanged; your operating context (playbook + cube directive) loads via the Session-start block (borg_playbook + borg_cube). If the playbook is NOT in your current context (e.g. after a context-compaction), call `borg_regen mode="full"` to re-orient.)_',""),c.push(r==="full"?"## Session start \u2014 required before acting":"## Session start",r==="full"?w:'Operating context (playbook + cube directive) was loaded at session start \u2014 re-fetch `borg_playbook` / `borg_cube` ONLY after a context-compaction (a `mode="full"` regen), not on every wake.',"",...p?[p,""]:[],`## Your role: ${e.role.name}`,m?v:["_(role playbook unchanged since your last full/lite regen; omitted in lite mode)_","",...A(e.role.detailed_description)].join(`
10
+ `),"","## Roles in this cube",n,"","## Connected drones",t,"","## Cube log",a),k&&(c.push("",b()),h=!0),m&&l!=null&&(g=l),c.join(`
11
+ `)}function V(e,o,r){const n=o.get(e.drone_id),t=n?r.get(n.role_id):null,i=new Date(e.created_at).toISOString(),a=typeof e.id=="string"&&e.id.length>0?` [entry_id: ${e.id}]`:"";return`**[${i}]**${a} ${n?.label??"?"} (${t?.name??"?"}): ${e.message}`}export{U as DRONE_PLAYBOOK,j as __resetRegenSessionState,I as compressRoleText,B as formatLeanOrientation,V as formatLogEntryMarkdown,C as formatRationalePointer,q as formatRegenMarkdown,b as getDronePlaybook,M as getDronePlaybookChapter,N as humanAgo,T as nullTaxonomyTip,F as parseHookSource,H as parseRationalePointer,W as regenWakePathDroneLabel,Y as resolveLeanIdentity,R as wakePathArming};
package/dist/regen.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import{regen as o,listCubes as n}from"./remote-client.js";import{getActiveCube as i}from"./cubes.js";import{formatRegenMarkdown as r}from"./regen-format.js";import{handleVersionFlag as a}from"./version.js";import{gateAllowsActivation as c}from"./launch-gate.js";async function u(){if(a(),!c("borg-regen SessionStart hook"))return;const e=await i();if(!e){await h();return}const t=await o(e.sessionToken,e.apiUrl);process.stdout.write(r(t)+`
3
- `)}async function h(){const e=[];e.push("# Borg MCP \u2014 not connected to a cube"),e.push(""),e.push("The borg MCP server is installed in this project but no cube has been joined here yet."),e.push(""),e.push("**Drone instructions:**"),e.push("1. **Tell the user** which cubes exist on their account (see the list below) and ask them which one to join, or whether to create a new cube. This is the first thing you should surface in this session."),e.push("2. **Do not** call `borg_assimilate`, `borg_create-cube`, or any other state-changing borg tool until the user has chosen."),e.push("3. The user may also decline to use borg in this project at all \u2014 that's a valid choice; just stop suggesting it."),e.push("");try{const{cubes:t}=await n();if(t.length>0){e.push("## Cubes on your account");for(const s of t)e.push(`- **${s.name}** (id: ${s.id})`)}else e.push("## Cubes on your account"),e.push("_(none yet \u2014 offer to create one via `borg_create-cube` once the user confirms)_")}catch(t){e.push("## Cubes on your account"),e.push("_(could not list \u2014 "+(t?.message??String(t))+")_")}e.push(""),e.push("## Tools you can call once the user has chosen"),e.push('- Join an existing cube: `borg_assimilate cube_name="<their choice>"`'),e.push('- Create a new cube: `borg_create-cube name="<name>" cube_directive="<markdown>"` (optionally `template="software-dev"`)'),e.push("- See available templates: `borg_list-templates`"),process.stdout.write(e.join(`
2
+ import{regen as i,listCubes as a}from"./remote-client.js";import{getActiveCube as c,inboxPathForDrone as u}from"./cubes.js";import{parseHookSource as h,formatLeanOrientation as l,resolveLeanIdentity as p}from"./regen-format.js";import{isCodexRemoteWakeEnabled as b}from"./codex-app-wake.js";import{handleVersionFlag as d}from"./version.js";import{gateAllowsActivation as g}from"./launch-gate.js";async function m(){if(process.stdin.isTTY)return"";try{const e=[];for await(const t of process.stdin)e.push(t);return Buffer.concat(e).toString("utf-8")}catch{return""}}async function f(){if(d(),!g("borg-regen SessionStart hook"))return;const e=h(await m()),t=await c();if(!t){await w();return}const o=u(t.cubeId,t.droneId),n=b()?"codex":"claude";let s=null;try{s=await i(t.sessionToken,t.apiUrl)}catch{s=null}const r=p(t,s);process.stdout.write(l({...r,inboxPath:o,agentKind:n,source:e})+`
3
+ `)}async function w(){const e=[];e.push("# Borg MCP \u2014 not connected to a cube"),e.push(""),e.push("The borg MCP server is installed in this project but no cube has been joined here yet."),e.push(""),e.push("**Drone instructions:**"),e.push("1. **Tell the user** which cubes exist on their account (see the list below) and ask them which one to join, or whether to create a new cube. This is the first thing you should surface in this session."),e.push("2. **Do not** call `borg_assimilate`, `borg_create-cube`, or any other state-changing borg tool until the user has chosen."),e.push("3. The user may also decline to use borg in this project at all \u2014 that's a valid choice; just stop suggesting it."),e.push("");try{const{cubes:t}=await a();if(t.length>0){e.push("## Cubes on your account");for(const o of t)e.push(`- **${o.name}** (id: ${o.id})`)}else e.push("## Cubes on your account"),e.push("_(none yet \u2014 offer to create one via `borg_create-cube` once the user confirms)_")}catch(t){e.push("## Cubes on your account"),e.push("_(could not list \u2014 "+(t?.message??String(t))+")_")}e.push(""),e.push("## Tools you can call once the user has chosen"),e.push('- Join an existing cube: `borg_assimilate cube_name="<their choice>"`'),e.push('- Create a new cube: `borg_create-cube name="<name>" cube_directive="<markdown>"` (optionally `template="software-dev"`)'),e.push("- See available templates: `borg_list-templates`"),process.stdout.write(e.join(`
4
4
  `)+`
5
- `)}u().catch(e=>{const t=e?.message??String(e);process.stderr.write(`borg-regen: ${t}
5
+ `)}f().catch(e=>{const t=e?.message??String(e);process.stderr.write(`borg-regen: ${t}
6
6
  `),process.exit(1)});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "borgmcp",
3
- "version": "1.0.41",
3
+ "version": "1.0.43",
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",