borgmcp 1.1.7 → 1.1.9

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/README.md CHANGED
@@ -173,7 +173,12 @@ If `borg_regen` or `borg_stream-status` reports a broken wake path, follow the
173
173
  CLI-specific recovery it prints:
174
174
 
175
175
  - Claude Code: arm the inbox monitor command. The monitor wakes the agent
176
- session when another drone posts to the cube.
176
+ session when another drone posts to the cube. The printed command includes
177
+ its required worktree-local `--state-root`; keep that value intact so the
178
+ config inbox can remain read-only and monitor runtime files do not dirty Git.
179
+ If it reports a stale legacy `.monitor.pid` or `.monitor.heartbeat`, confirm
180
+ the old Monitor has stopped, remove those legacy files, and arm the printed
181
+ command again; the new Monitor deliberately never deletes them automatically.
177
182
  - Codex: check the remote-control socket status, relaunch with `borg --cli codex`
178
183
  or `borg assimilate --cli codex` if needed, and run `borg_regen` manually when
179
184
  returning to the session if no wake arrived.
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Agent CLI identity is independent from both the selected model and the
3
+ * Codex remote-wake transport. The child MCP process reports this value on its
4
+ * health beat so a relaunch of an existing seat can repair the server-side
5
+ * agent_kind without re-assimilating.
6
+ */
7
+ export type AgentKind = 'claude' | 'codex' | 'opencode';
8
+ /** Pinned into MCP-child environments by Borg launch paths. */
9
+ export declare const BORG_AGENT_KIND_ENV = "BORG_AGENT_KIND";
10
+ /** Transport capability only — never use it as the primary CLI identity. */
11
+ export declare const BORG_CODEX_REMOTE_WAKE_ENV = "BORG_CODEX_REMOTE_WAKE";
12
+ /** Legacy OpenCode runtime marker, retained for installed-config compatibility. */
13
+ export declare const BORG_OPENCODE_ENV = "BORG_OPENCODE";
14
+ /**
15
+ * Resolve the current MCP child's agent CLI. New Borg launches pin
16
+ * BORG_AGENT_KIND; the older wake-transport markers remain a fallback for
17
+ * already-installed clients.
18
+ */
19
+ export declare function resolveSessionAgentKind(env?: NodeJS.ProcessEnv): AgentKind;
20
+ /**
21
+ * Produce a clean agent-launch environment. Clearing stale transport markers
22
+ * is essential for a Codex → Claude relaunch: an inherited marker must not
23
+ * make the new Claude MCP child report Codex.
24
+ */
25
+ export declare function withAgentRuntimeEnv(env: NodeJS.ProcessEnv, agentKind: AgentKind): NodeJS.ProcessEnv;
26
+ /** Pin the selected Codex CLI identity into Codex's MCP-child env overlay. */
27
+ export declare function codexAgentKindConfigArgs(): string[];
28
+ /**
29
+ * Pin the remote-wake transport capability separately from the CLI identity.
30
+ *
31
+ * Codex MCP children read their configured env rather than the wrapper's
32
+ * inherited process env. Explicitly pinning "0" on a no-socket launch is
33
+ * therefore necessary to override legacy static configs that used to persist
34
+ * BORG_CODEX_REMOTE_WAKE="1" as an identity marker.
35
+ */
36
+ export declare function codexRemoteWakeConfigArgs(enabled?: boolean): string[];
37
+ //# sourceMappingURL=agent-runtime.d.ts.map
@@ -0,0 +1 @@
1
+ const o="BORG_AGENT_KIND",t="BORG_CODEX_REMOTE_WAKE",r="BORG_OPENCODE";function E(e){return e==="claude"||e==="codex"||e==="opencode"}function i(e=process.env){return E(e[o])?e[o]:e[r]==="1"?"opencode":e[t]==="1"?"codex":"claude"}function _(e,c){const n={...e};return delete n[o],delete n[t],delete n[r],n[o]=c,c==="opencode"&&(n[r]="1"),n}function s(){return["-c",`mcp_servers.borg.env.${o}="codex"`]}function d(e=!0){return["-c",`mcp_servers.borg.env.${t}="${e?"1":"0"}"`]}export{o as BORG_AGENT_KIND_ENV,t as BORG_CODEX_REMOTE_WAKE_ENV,r as BORG_OPENCODE_ENV,s as codexAgentKindConfigArgs,d as codexRemoteWakeConfigArgs,i as resolveSessionAgentKind,_ as withAgentRuntimeEnv};
@@ -1,48 +1,48 @@
1
- import{dirname as z,basename as R}from"node:path";import{randomUUID as ae}from"node:crypto";import{roleSlug as le,matchRoleByName as se,pickDefaultRole as ce}from"./role-resolver.js";import{deriveCubeName as ue,parseGitRemote as me,sanitizeRemoteUrl as de}from"./cube-name.js";import{validateName as J}from"./name-validator.js";import{renderAssimilationWelcome as fe}from"./assimilate-welcome.js";import{shellEscape as he}from"./shell-escape.js";import{withCodexCwdArg as ge}from"./codex-remote.js";import{buildAgentKickoffPrompt as we,buildKickoffWakePathClause as be,recordCodexWakeTarget as ke,socketPathFromRemoteArgs as ye}from"./codex-launch.js";import{perWorktreeBranchName as B,adoptWorktree as pe,computeWorktreePath as V,localBranchExists as X,isMerged as ve}from"./worktree-lifecycle.js";import{DroneEvictedError as $e}from"./drone-lifecycle.js";import{codexBorgSessionConfigArgs as xe}from"./launch-gate.js";import{inboxPathForDrone as Se}from"./cubes.js";import{resolveLaunchEnv as Ce,resolveOllamaBaseUrl as Re,parseModel as _e}from"./model-presets.js";import{unlinkSync as Ee}from"node:fs";import{gcOrphanInboxesForCube as Ie,defaultListInboxLogs as Ne,defaultInboxLivenessDeps as Ae,isInboxLive as Pe,ORPHAN_INBOX_STALE_MS as Le}from"./gc-orphan-inboxes.js";import{installBorgPlugin as Te}from"./opencode-plugin.js";import{computeOpenCodePort as De,connectOpenCodeDrone as Ue,injectInitialKickoff as Oe}from"./opencode-drone.js";async function ct(r,e){if(r.role!==void 0){const t=J(r.role);if(!t.ok)return e.stderr(t.error+`
2
- `),1}if(r.flags.worktree!==void 0){const t=J(r.flags.worktree);if(!t.ok)return e.stderr(t.error+`
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=ue(o,n),n){const c=de(n),f=c?me(c):null;c&&!f&&i&&e.stderr(`couldn't parse git remote '${c}' \u2014 using directory name '${i}' as cube name
1
+ import{dirname as J,basename as R}from"node:path";import{randomUUID as le}from"node:crypto";import{roleSlug as se,matchRoleByName as ce,pickDefaultRole as ue}from"./role-resolver.js";import{deriveCubeName as me,parseGitRemote as de,sanitizeRemoteUrl as fe}from"./cube-name.js";import{validateName as V}from"./name-validator.js";import{renderAssimilationWelcome as ge}from"./assimilate-welcome.js";import{shellEscape as he}from"./shell-escape.js";import{withCodexCwdArg as we}from"./codex-remote.js";import{buildAgentKickoffPrompt as be,buildKickoffWakePathClause as ke,recordCodexWakeTarget as ye,socketPathFromRemoteArgs as pe}from"./codex-launch.js";import{perWorktreeBranchName as F,adoptWorktree as ve,computeWorktreePath as X,localBranchExists as Q,isMerged as $e}from"./worktree-lifecycle.js";import{DroneEvictedError as xe}from"./drone-lifecycle.js";import{codexBorgSessionConfigArgs as Se}from"./launch-gate.js";import{codexAgentKindConfigArgs as Ce,codexRemoteWakeConfigArgs as Re,withAgentRuntimeEnv as _e}from"./agent-runtime.js";import{inboxPathForDrone as Ee}from"./cubes.js";import{monitorStateRootForWorktree as Ae}from"./inbox-monitor.js";import{resolveLaunchEnv as Ie,resolveOllamaBaseUrl as Ne,parseModel as Pe}from"./model-presets.js";import{unlinkSync as Le}from"node:fs";import{gcOrphanInboxesForCube as Te,defaultListInboxLogs as De,defaultInboxLivenessDeps as Ue,isInboxLive as Oe,ORPHAN_INBOX_STALE_MS as We}from"./gc-orphan-inboxes.js";import{installBorgPlugin as je}from"./opencode-plugin.js";import{computeOpenCodePort as Me,connectOpenCodeDrone as Be,injectInitialKickoff as He}from"./opencode-drone.js";async function wt(r,e){if(r.role!==void 0){const t=V(r.role);if(!t.ok)return e.stderr(t.error+`
2
+ `),1}if(r.flags.worktree!==void 0){const t=V(r.flags.worktree);if(!t.ok)return e.stderr(t.error+`
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=me(o,n),n){const c=fe(n),f=c?de(c):null;c&&!f&&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 _=e.cwd();e.stderr(`Checking your cubes\u2026
5
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
6
  `),a=await e.runSetup(),I=await e.listCubes(a.apiUrl,a.token);else throw t}const N=I.find(t=>t.name===i);if(!N&&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,A;if(N)u=await e.getCube(a.apiUrl,a.token,N.id),A=!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((v,$)=>{const E=$===0?" (default)":"";c.push(` ${$+1}) ${v.name}${E} \u2014 ${v.description}`)}),c.push(` ${n.length+1}) skip \u2014 no template`);const f=(await e.prompt(c.join(`
7
+ `),1;let u,P;if(N)u=await e.getCube(a.apiUrl,a.token,N.id),P=!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((v,$)=>{const A=$===0?" (default)":"";c.push(` ${$+1}) ${v.name}${A} \u2014 ${v.description}`)}),c.push(` ${n.length+1}) skip \u2014 no template`);const f=(await e.prompt(c.join(`
8
8
  `)+`
9
9
  [1]: `)).trim(),y=f===""?1:parseInt(f,10);if(Number.isNaN(y)||y<1||y>n.length+1)return e.stderr(`invalid choice "${f}"
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.
11
11
  Pass --template <name>, --no-template, or --yes (defaults to starter).
12
12
  `),1;t="starter"}e.stderr(i?`Creating cube '${i}'\u2026
13
13
  `:`Creating your cube\u2026
14
- `),u=await e.createCube(a.apiUrl,a.token,t?{name:i??void 0,template:t}:{name:i??void 0}),A=!0}let g;if(r.role!==void 0){if(g=se(u.roles,r.role),!g){const t=u.roles.map(f=>f.name).join(", "),n=Be(r.role,u.roles.map(f=>f.name)),c=n?` Did you mean "${n}"?`:"";return e.stderr(`no role matching "${r.role}" in cube "${u.name}". Available: ${t}.${c}
14
+ `),u=await e.createCube(a.apiUrl,a.token,t?{name:i??void 0,template:t}:{name:i??void 0}),P=!0}let h;if(r.role!==void 0){if(h=ce(u.roles,r.role),!h){const t=u.roles.map(f=>f.name).join(", "),n=Ge(r.role,u.roles.map(f=>f.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{const t=new Set((u.drones??[]).map(n=>n.role_id));if(g=ce(u.roles,{isFirstDrone:A,occupiedRoleIds:t}),!g)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.
16
+ `),1}}else{const t=new Set((u.drones??[]).map(n=>n.role_id));if(h=ue(u.roles,{isFirstDrone:P,occupiedRoleIds:t}),!h)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
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
- `),1;const H=r.flags.worktree!==void 0||x!==null&&!r.flags.here,Z=S??x?.droneId??null,P=H?null:await e.getLaunchModel(u.id,o,Z),b=r.flags.model??P?.model??g.default_model??null,L=Re(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
- `),1}const m=await e.resolveCli(r.flags.cli);e.stderr(`Joining cube '${u.name}' as ${g.name}\u2026
20
- `);let s;try{s=await e.assimilate(a.apiUrl,a.token,{cube_id:u.id,role_id:g.id,hostname:e.getHostname(),agent_kind:m,model:b,...S?{prior_drone_id:S}:{}})}catch(t){if(t instanceof $e&&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.
18
+ `),1;const K=r.flags.worktree!==void 0||x!==null&&!r.flags.here,ee=S??x?.droneId??null,L=K?null:await e.getLaunchModel(u.id,o,ee),b=r.flags.model??L?.model??h.default_model??null,T=Ne(process.env,b!=null&&b===L?.model?L?.ollamaBaseUrl:void 0);if(b){const t=await e.checkModelReachable(b,e.fetch,T);if(!t.ok)return e.stderr(`${t.message}
19
+ `),1}const m=await e.resolveCli(r.flags.cli);e.stderr(`Joining cube '${u.name}' as ${h.name}\u2026
20
+ `);let s;try{s=await e.assimilate(a.apiUrl,a.token,{cube_id:u.id,role_id:h.id,hostname:e.getHostname(),agent_kind:m,model:b,...S?{prior_drone_id:S}:{}})}catch(t){if(t instanceof xe&&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.
21
21
  `),1;const n=t instanceof Error?t.message:String(t);return e.stderr(`assimilate failed: ${n}
22
- `),1}const k=u.roles.find(t=>t.id===s.role_id)??g;s.reattached?e.stderr(`re-attached to existing seat ${s.drone_label} (session token rotated, no new drone minted)
23
- `):k.id!==g.id&&e.stderr(`Note: your invite didn't grant the "${g.name}" role \u2014 assimilated as "${k.name}" instead.
24
- `);const ee=H;let w=null;if(ee){const t=e.runSync("git",["rev-parse","--verify","HEAD"],o);if(t.status!==0)return e.stderr(`sibling worktree spawn requires HEAD pointing at a commit.
22
+ `),1}const k=u.roles.find(t=>t.id===s.role_id)??h;s.reattached?e.stderr(`re-attached to existing seat ${s.drone_label} (session token rotated, no new drone minted)
23
+ `):k.id!==h.id&&e.stderr(`Note: your invite didn't grant the "${h.name}" role \u2014 assimilated as "${k.name}" instead.
24
+ `);const te=K;let w=null;if(te){const t=e.runSync("git",["rev-parse","--verify","HEAD"],o);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"],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 f=t.stdout.trim(),y=e.runSync("git",["rev-parse",n],o).stdout.trim();f!==y&&e.stderr(`note: local HEAD (${f.slice(0,7)}) differs from ${n} (${y.slice(0,7)}); new worktree will start on ${n}
28
- `);const v=R(o),$=r.flags.worktree??le(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=e.homedir();let h=V(E,v,$),p=B(R(h),v),G=2;for(;e.pathExists(h)||Me(e,o,h)||X(e.runSync,o,p)&&!ve(e.runSync,o,p,n);)h=V(E,v,$,G),p=B(R(h),v),G++;e.mkdirp(z(h));const q=X(e.runSync,o,p)?e.runSync("git",["worktree","add",h,p],o):e.runSync("git",["worktree","add","-b",p,h,n],o);if(q.status!==0)return e.stderr(`git worktree add failed: ${Q(q.stderr)}
30
- `),1;e.stderr(`spawned sibling worktree at ${h} on branch ${p} (${n}); original dir is registered as active (edit ~/.config/borgmcp/cubes.json if stale).
31
- `),e.chdir(h),e.stderr(je(h,p,o)),w=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}
28
+ `);const v=R(o),$=r.flags.worktree??se(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 A=e.homedir();let g=X(A,v,$),p=F(R(g),v),q=2;for(;e.pathExists(g)||Ye(e,o,g)||Q(e.runSync,o,p)&&!$e(e.runSync,o,p,n);)g=X(A,v,$,q),p=F(R(g),v),q++;e.mkdirp(J(g));const z=Q(e.runSync,o,p)?e.runSync("git",["worktree","add",g,p],o):e.runSync("git",["worktree","add","-b",p,g,n],o);if(z.status!==0)return e.stderr(`git worktree add failed: ${Z(z.stderr)}
30
+ `),1;e.stderr(`spawned sibling worktree at ${g} on branch ${p} (${n}); original dir is registered as active (edit ~/.config/borgmcp/cubes.json if stale).
31
+ `),e.chdir(g),e.stderr(Fe(g,p,o)),w=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
  `),w){const c=e.runSync("git",["worktree","remove","--force",w],o);c.status===0?e.stderr(`rolled back spawned worktree at ${w}
33
- `):e.stderr(`manual cleanup needed: \`git worktree remove --force ${w}\` (rollback attempt failed: ${Q(c.stderr).trim()||"unknown"})
34
- `)}return 1}try{const t=Ae(),n=z(Se(s.cube_id,s.drone_id));Ie({cubeInboxDir:n,selfDroneId:s.drone_id,deps:{listInboxLogs:Ne,isLive:c=>Pe(c,t),droneState:()=>"absent",unlink:c=>Ee(c),now:t.now,staleMs:Le}})}catch{}e.setTerminalTitle(s.drone_label,u.name);const te=e.isTTY()&&!process.env.NO_COLOR&&!process.env.CI;e.stdout(fe(k.name,u.name,te));const d=e.cwd();try{e.installProjectSessionHook(d)}catch{e.stderr(`warning: could not install the project-local SessionStart hook in ${d}; it will be re-attempted on the next borg launch
35
- `)}if(!w){e.runSync("git",["fetch","origin","--prune"],d);const t=B(R(d),R(o)),n=pe(e.runSync,d,t,"origin/main");n.action==="adopted"?(e.stderr(`worktree: adopted branch ${t} at origin/main
36
- `),e.stderr(We(d,t))):n.message&&e.stderr(`worktree sync: ${n.message}
33
+ `):e.stderr(`manual cleanup needed: \`git worktree remove --force ${w}\` (rollback attempt failed: ${Z(c.stderr).trim()||"unknown"})
34
+ `)}return 1}const d=e.cwd(),D=e.findProjectRoot(d),U=Ae(D);try{const t=Ue(),n=J(Ee(s.cube_id,s.drone_id));Te({cubeInboxDir:n,selfDroneId:s.drone_id,deps:{listInboxLogs:De,isLive:c=>Oe(c,t,U),droneState:()=>"absent",unlink:c=>Le(c),now:t.now,staleMs:We},monitorStateRoot:U})}catch{}e.setTerminalTitle(s.drone_label,u.name);const re=e.isTTY()&&!process.env.NO_COLOR&&!process.env.CI;e.stdout(ge(k.name,u.name,re));try{e.installProjectSessionHook(d)}catch{e.stderr(`warning: could not install the project-local SessionStart hook in ${d}; it will be re-attempted on the next borg launch
35
+ `)}if(!w){e.runSync("git",["fetch","origin","--prune"],d);const t=F(R(d),R(o)),n=ve(e.runSync,d,t,"origin/main");n.action==="adopted"?(e.stderr(`worktree: adopted branch ${t} at origin/main
36
+ `),e.stderr(Ke(d,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 ${m} anyway \u2014 the kickoff prompt's ToolSearch fallback will recover if the MCP server takes longer to start.
38
- `);const re=e.getInboxPath(s.cube_id,s.drone_id),T=m==="codex"?`borg-wake-${ae()}`:null,ne=be(m,m==="claude"?re:null);let D,F=[],C,U=null,O=null;const K=e.findProjectRoot(d);b?await e.setLaunchModel(s.cube_id,K,{model:b,ollamaBaseUrl:_e(b).kind==="ollama"?L:null}):await e.clearLaunchModel(s.cube_id,K);const Y=Ce(b,L),j={...process.env,...Y.set,BORG_SESSION:"1"};for(const t of Y.unset)delete j[t];if(m==="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.",F=t.args,Object.keys(t.env).length>0&&Object.assign(j,t.env),U=ye(t.args),O=t.server?.cleanup??null}const W=we({cli:m,codexWakeNonce:T,monitorClause:ne,codexWakePathClause:D});let M;C=[W],m==="codex"?C=[...xe(),...F,...ge(C,d)]:m==="opencode"&&(M=De(s.drone_id),Te(),C=[d,"--port",String(M),"--auto","--prompt",W]);const oe=e.exec(m,C,d,j);if(m==="codex"&&U&&T&&ke({deps:e,cubeId:s.cube_id,droneId:s.drone_id,socketPath:U,cwd:d,previewNeedle:T,launchedAtSeconds:Math.floor(Date.now()/1e3)}),m==="opencode"){const t=`http://127.0.0.1:${M}`;Ue({serverUrl:t,directory:d,droneLabel:s.drone_label,cubeName:i??"borg"}).then(()=>Oe(W)).catch(()=>{})}const ie=await oe;if(O)try{O()}catch{}return w&&_!==w&&e.stderr(`
38
+ `);const ne=e.getInboxPath(s.cube_id,s.drone_id),O=m==="codex"?`borg-wake-${le()}`:null,oe=ke(m,m==="claude"?ne:null,m==="claude"?U:null);let W,Y=[],C,E=null,j=null;b?await e.setLaunchModel(s.cube_id,D,{model:b,ollamaBaseUrl:Pe(b).kind==="ollama"?T:null}):await e.clearLaunchModel(s.cube_id,D);const G=Ie(b,T),M={..._e(process.env,m),...G.set,BORG_SESSION:"1"};for(const t of G.unset)delete M[t];if(m==="codex"){const t=await e.prepareCodexRemoteLaunch();t.warning?(e.stderr(`warning: ${t.warning}
39
+ `),W="\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."):W="Codex wake-path capability check passed: remote-control socket established for this session.",Y=t.args,Object.keys(t.env).length>0&&Object.assign(M,t.env),E=pe(t.args),j=t.server?.cleanup??null}const B=be({cli:m,codexWakeNonce:O,monitorClause:oe,codexWakePathClause:W});let H;C=[B],m==="codex"?C=[...Se(),...Ce(),...Re(E!==null),...Y,...we(C,d)]:m==="opencode"&&(H=Me(s.drone_id),je(),C=[d,"--port",String(H),"--auto","--prompt",B]);const ie=e.exec(m,C,d,M);if(m==="codex"&&E&&O&&ye({deps:e,cubeId:s.cube_id,droneId:s.drone_id,socketPath:E,cwd:d,previewNeedle:O,launchedAtSeconds:Math.floor(Date.now()/1e3)}),m==="opencode"){const t=`http://127.0.0.1:${H}`;Be({serverUrl:t,directory:d,droneLabel:s.drone_label,cubeName:i??"borg"}).then(()=>He(B)).catch(()=>{})}const ae=await ie;if(j)try{j()}catch{}return w&&_!==w&&e.stderr(`
40
40
  Agent exited. You were working in ${w}; your shell is back in ${_}.
41
41
  To return:
42
42
  cd ${he(w)}
43
- `),ie}function je(r,e,a){return`
43
+ `),ae}function Fe(r,e,a){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 ${a}\` or operate on the primary checkout ${a}: 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 We(r,e){return`
45
+ `}function Ke(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 Q(r){return r.replace(/[\x00-\x1F\x7F]/g,"")}function Me(r,e,a){const o=r.runSync("git",["worktree","list","--porcelain"],e);return o.status!==0?!1:o.stdout.split(`
48
- `).some(i=>i===`worktree ${a}`)}function Be(r,e){if(e.length===0)return null;const a=r.toLowerCase();let o=null;for(const i of e){const l=He(a,i.toLowerCase());l<=2&&(o===null||l<o.distance)&&(o={name:i,distance:l})}return o?o.name:null}function He(r,e){if(r===e)return 0;if(r.length===0)return e.length;if(e.length===0)return r.length;const a=new Array(e.length+1),o=new Array(e.length+1);for(let i=0;i<=e.length;i++)a[i]=i;for(let i=1;i<=r.length;i++){o[0]=i;for(let l=1;l<=e.length;l++){const _=r[i-1]===e[l-1]?0:1;o[l]=Math.min(o[l-1]+1,a[l]+1,a[l-1]+_)}for(let l=0;l<=e.length;l++)a[l]=o[l]}return a[e.length]}export{ct as runAssimilate,Q as safeStderr,Be as suggestRoleName};
47
+ `}function Z(r){return r.replace(/[\x00-\x1F\x7F]/g,"")}function Ye(r,e,a){const o=r.runSync("git",["worktree","list","--porcelain"],e);return o.status!==0?!1:o.stdout.split(`
48
+ `).some(i=>i===`worktree ${a}`)}function Ge(r,e){if(e.length===0)return null;const a=r.toLowerCase();let o=null;for(const i of e){const l=qe(a,i.toLowerCase());l<=2&&(o===null||l<o.distance)&&(o={name:i,distance:l})}return o?o.name:null}function qe(r,e){if(r===e)return 0;if(r.length===0)return e.length;if(e.length===0)return r.length;const a=new Array(e.length+1),o=new Array(e.length+1);for(let i=0;i<=e.length;i++)a[i]=i;for(let i=1;i<=r.length;i++){o[0]=i;for(let l=1;l<=e.length;l++){const _=r[i-1]===e[l-1]?0:1;o[l]=Math.min(o[l-1]+1,a[l]+1,a[l-1]+_)}for(let l=0;l<=e.length;l++)a[l]=o[l]}return a[e.length]}export{wt as runAssimilate,Z as safeStderr,Ge as suggestRoleName};
package/dist/claude.js CHANGED
@@ -1,15 +1,15 @@
1
1
  #!/usr/bin/env node
2
- import{spawn as F}from"child_process";import{randomUUID as _}from"node:crypto";import{basename as W}from"node:path";import{createInterface as G}from"node:readline/promises";import i from"chalk";import{findProjectRoot as I,getActiveCube as U,getLaunchModel as Y,inboxPathForDrone as K,setCodexWakeTarget as V,pruneDeadCodexWakeTargets as j}from"./cubes.js";import{applyOllamaLaunchEnv as q,checkModelReachable as X}from"./model-presets.js";import{handleVersionFlag as z,getPackageVersion as w}from"./version.js";import{isHelpFlag as L,setupHelpText as J,topLevelHelpText as Q,assimilateHelpText as Z}from"./cli-help.js";import{runSpawn as ee}from"./spawn.js";import{buildClaudeLaunchArgs as re}from"./claude-launch-args.js";import{parseSyncArgs as oe,runSync as se}from"./sync.js";import{parseCleanupArgs as te,runCleanup as ie}from"./cleanup-cmd.js";import{parseAssimilateArgs as ae}from"./parse-assimilate-args.js";import{runAssimilate as ne}from"./assimilate-cmd.js";import{buildDefaultAssimilateDeps as ce}from"./assimilate-deps.js";import{parseLaunchAllArgs as R}from"./parse-launch-all-args.js";import{unknownSubcommand as le}from"./unknown-subcommand.js";import{runLaunchAll as O}from"./launch-all-cmd.js";import{buildDefaultLaunchAllDeps as b}from"./launch-all-deps.js";import{discoverDroneCandidates as de}from"./launch-all-discovery.js";import{explicitCliLaunchHint as pe,runBareLaunchMenu as ue,shouldResolveExplicitCliLaunchHintTargets as me,shouldShowLaunchMenu as fe}from"./bare-launch-menu.js";import{setTerminalTitle as ge}from"./terminal-title.js";import{initConsolePrefix as he,consolePrefix as t}from"./console-prefix.js";import{initDebugFromArgv as we}from"./debug.js";import{fetchLatestBorgmcpVersion as Ce,compareVersionsForStaleness as xe}from"./stale-version-check.js";import{defaultCliChoiceDeps as ve,detectCliAvailability as k,installedCliNames as D,parseCliFlag as be,resolveCliChoice as ke}from"./cli-platform.js";import{getRefreshToken as $e,getIdToken as Se}from"./config.js";import{composeGetStarted as ye,shouldShowGetStarted as Ae}from"./get-started.js";import{prepareCodexRemoteLaunch as Te,withCodexCwdArg as Pe,defaultCodexRemoteDeps as Ie,checkCodexBridgeHealthy as Le}from"./codex-remote.js";import{findLoadedCodexThread as Re}from"./codex-app-server.js";import{buildAgentKickoffPrompt as Oe,buildKickoffWakePathClause as De,recordCodexWakeTarget as Me,socketPathFromRemoteArgs as M}from"./codex-launch.js";import{codexBorgSessionConfigArgs as Ee}from"./launch-gate.js";import{addCodexMcpServer as He,addCodexSessionStartHook as Ne,addCodexUserPromptSubmitHook as Be,addMcpServer as Fe,addOpenCodeMcpServer as _e,addProjectSessionStartHook as We,addUserPromptSubmitHook as Ge,isCodexMcpServerConfigured as Ue,isMcpServerConfigured as Ye,isOpenCodeMcpServerConfigured as Ke,removeSessionStartHook as Ve}from"./config-utils.js";import{installBorgPlugin as je}from"./opencode-plugin.js";import{connectOpenCodeDrone as qe,computeOpenCodePort as E,injectInitialKickoff as Xe}from"./opencode-drone.js";async function ze(){we(process.argv),z(),await he();const c=(async()=>{if(!process.stderr.isTTY)return;const e=w(),r=await Ce();if(!r)return;const a=xe(e,r);a.stale&&a.message&&process.stderr.write(`${t()}${a.message}
3
- `)})();if((process.argv[2]==="--help"||process.argv[2]==="-h")&&(process.stdout.write(Q(w())),process.exit(0)),process.argv[2]==="setup"){L(process.argv[3])&&(process.stdout.write(J(w())),process.exit(0)),await import("./setup.js");return}if(process.argv[2]==="assimilate"){process.argv.slice(3).some(L)&&(process.stdout.write(Z(w())),process.exit(0));const e=ae(process.argv.slice(3));e.ok||(process.stderr.write(i.red(`${t()}\u25FC borg assimilate: ${e.error}
4
- `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const r=ce(),a=await ne({role:e.role,flags:e.flags},r);process.exit(a)}if(process.argv[2]==="spawn"){const e=await ee();process.exit(e)}if(process.argv[2]==="sync"){const e=oe(process.argv.slice(3));e.ok||(process.stderr.write(i.red(`${t()}\u25FC borg sync: ${e.error}
5
- `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const r=await se({},e.options);process.exit(r)}if(process.argv[2]==="cleanup"){const e=te(process.argv.slice(3));e.ok||(process.stderr.write(i.red(`${t()}\u25FC borg cleanup: ${e.error}
6
- `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const r=await ie({},e.options);process.exit(r)}if(process.argv[2]==="launch-all"){const e=R(process.argv.slice(3));e.ok||(process.stderr.write(i.red(`${t()}\u25FC borg launch-all: ${e.error}
7
- `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const r=b(),a=await O(e.args,r);process.exit(a)}const n=le(process.argv[2]);if(n!==null&&(process.stderr.write(i.red(`${t()}\u25FC unknown command: ${n}
8
- `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1)),Ae(await $e()!==null,await Se()!==null)){const e=D(k()).length>0;process.stdout.write(ye(e)),process.exit(0)}const d=be(process.argv.slice(2));d.error&&(process.stderr.write(i.red(`${t()}\u25FC ${d.error}
9
- `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const $=async e=>{const r=G({input:process.stdin,output:process.stdout});try{return await r.question(e)}finally{r.close()}};let s=await ke(d.cli,ve($,()=>process.stdin.isTTY===!0));Je();const o=await U();let C;const S=async()=>o?(C===void 0&&(C=(await de({targetCubeId:o.cubeId},b())).length>0),C):!1,x=process.stdin.isTTY===!0,v=process.stdout.isTTY===!0,y=pe({explicitCli:d.cli,stdinIsTTY:x,stdoutIsTTY:v,hasActiveCube:o!==null,hasLaunchAllTargets:me({explicitCli:d.cli,stdinIsTTY:x,stdoutIsTTY:v,hasActiveCube:o!==null})?await S():!1});if(y&&process.stderr.write(y),fe({extraArgs:process.argv.slice(2),stdinIsTTY:x,stdoutIsTTY:v})){const e=D(k()).filter(a=>a!==s),r=await ue({defaultCli:s,otherInstalledClis:e,hasLaunchAllTargets:await S()},$);if(r.kind==="launch-all"){const a=R([]),B=a.ok?await O(a.args,b()):1;process.exit(B)}s=r.cli}const l=d.rest;ge(o?{label:o.droneLabel,cubeName:o.name}:null,W(process.cwd()));const H=De(s,o&&s==="claude"?K(o.cubeId,o.droneId):null);await Promise.race([c,new Promise(e=>setTimeout(e,2e3))]);const A=s==="codex"?`borg-wake-${_()}`:null;let g,T=[],p={...process.env,BORG_SESSION:"1"},u=null,m=null;if(s==="codex"&&!l.includes("--remote")){console.error(`${t()}${i.gray("\u25FC Starting Codex remote-wake app-server\u2026")}`);const e=await Te(Ie());e.warning?(console.error(`${t()}${i.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.",T=e.args,p={...process.env,...e.env,BORG_SESSION:"1"},u=M(e.args),m=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.",u=M(l),u&&(p={...process.env,BORG_CODEX_REMOTE_WAKE:"1",BORG_SESSION:"1"}));if(o){const e=await Y(o.cubeId,I(),o.droneId),r=q(p,e,process.env);if(p=r.env,r.probe){const a=await X(r.probe.descriptor,fetch,r.probe.baseUrl);a.ok||console.error(`${t()}${i.yellow(`warning: ${a.message}`)}`)}}const f=Oe({cli:s,codexWakeNonce:A,monitorClause:H,codexWakePathClause:g});let h;if(s==="codex")h=[...Ee(),...T,...Pe([...l,f],process.cwd())];else if(s==="opencode"){const e=o?E(o.droneId):14096;je(),h=[process.cwd(),"--port",String(e),"--auto","--prompt",f,...l]}else h=re(l,f);const N=s==="claude"?"Claude Code":s==="codex"?"Codex":"OpenCode";console.error(`${t()}${i.blue(`\u25FC Launching ${N}\u2026`)}`);const P=F(s,h,{stdio:"inherit",shell:!1,env:p});if(s==="opencode"){const r=`http://127.0.0.1:${o?E(o.droneId):14096}`;qe({serverUrl:r,directory:process.cwd(),droneLabel:o?.droneLabel??"opencode",cubeName:o?.name??"borg"}).then(()=>Xe(f)).catch(()=>{})}s==="codex"&&o&&u&&(Me({deps:{setCodexWakeTarget:V,findLoadedCodexThread:Re},cubeId:o.cubeId,droneId:o.droneId,socketPath:u,passthroughArgs:l,previewNeedle:A??f.slice(0,120),cwd:process.cwd(),launchedAtSeconds:Math.floor(Date.now()/1e3)}),j(e=>Le(e))),P.on("error",e=>{if(m)try{m()}catch{}if(e.code==="ENOENT"){console.error(`${t()}${i.red(`
10
- \u25FC Failed to launch ${s}`)}`);const r=s==="opencode"?"opencode":s;console.error(`${t()}${i.gray(`Make sure ${r} is installed.
2
+ import{spawn as W}from"child_process";import{randomUUID as _}from"node:crypto";import{basename as G}from"node:path";import{createInterface as U}from"node:readline/promises";import i from"chalk";import{findProjectRoot as b,getActiveCube as Y,getLaunchModel as K,inboxPathForDrone as V,setCodexWakeTarget as j,pruneDeadCodexWakeTargets as q}from"./cubes.js";import{monitorStateRootForWorktree as X}from"./inbox-monitor.js";import{applyOllamaLaunchEnv as z,checkModelReachable as J}from"./model-presets.js";import{handleVersionFlag as Q,getPackageVersion as w}from"./version.js";import{isHelpFlag as L,setupHelpText as Z,topLevelHelpText as ee,assimilateHelpText as re}from"./cli-help.js";import{runSpawn as oe}from"./spawn.js";import{buildClaudeLaunchArgs as se}from"./claude-launch-args.js";import{parseSyncArgs as te,runSync as ie}from"./sync.js";import{parseCleanupArgs as ne,runCleanup as ae}from"./cleanup-cmd.js";import{parseAssimilateArgs as ce}from"./parse-assimilate-args.js";import{runAssimilate as le}from"./assimilate-cmd.js";import{buildDefaultAssimilateDeps as de}from"./assimilate-deps.js";import{parseLaunchAllArgs as O}from"./parse-launch-all-args.js";import{unknownSubcommand as pe}from"./unknown-subcommand.js";import{runLaunchAll as D}from"./launch-all-cmd.js";import{buildDefaultLaunchAllDeps as k}from"./launch-all-deps.js";import{discoverDroneCandidates as ue}from"./launch-all-discovery.js";import{explicitCliLaunchHint as me,runBareLaunchMenu as fe,shouldResolveExplicitCliLaunchHintTargets as ge,shouldShowLaunchMenu as he}from"./bare-launch-menu.js";import{setTerminalTitle as we}from"./terminal-title.js";import{initConsolePrefix as Ce,consolePrefix as t}from"./console-prefix.js";import{initDebugFromArgv as xe}from"./debug.js";import{fetchLatestBorgmcpVersion as ve,compareVersionsForStaleness as be}from"./stale-version-check.js";import{defaultCliChoiceDeps as ke,detectCliAvailability as $,installedCliNames as E,parseCliFlag as $e,resolveCliChoice as Se}from"./cli-platform.js";import{getRefreshToken as Ae,getIdToken as ye}from"./config.js";import{composeGetStarted as Te,shouldShowGetStarted as Pe}from"./get-started.js";import{prepareCodexRemoteLaunch as Ie,withCodexCwdArg as Re,defaultCodexRemoteDeps as Le,checkCodexBridgeHealthy as Oe}from"./codex-remote.js";import{BORG_CODEX_REMOTE_WAKE_ENV as De,codexAgentKindConfigArgs as Ee,codexRemoteWakeConfigArgs as Me,withAgentRuntimeEnv as S}from"./agent-runtime.js";import{findLoadedCodexThread as He}from"./codex-app-server.js";import{buildAgentKickoffPrompt as Ne,buildKickoffWakePathClause as Fe,recordCodexWakeTarget as Be,socketPathFromRemoteArgs as M}from"./codex-launch.js";import{codexBorgSessionConfigArgs as We}from"./launch-gate.js";import{addCodexMcpServer as _e,addCodexSessionStartHook as Ge,addCodexUserPromptSubmitHook as Ue,addMcpServer as Ye,addOpenCodeMcpServer as Ke,addProjectSessionStartHook as Ve,addUserPromptSubmitHook as je,isCodexMcpServerConfigured as qe,isMcpServerConfigured as Xe,isOpenCodeMcpServerConfigured as ze,removeSessionStartHook as Je}from"./config-utils.js";import{installBorgPlugin as Qe}from"./opencode-plugin.js";import{connectOpenCodeDrone as Ze,computeOpenCodePort as H,injectInitialKickoff as er}from"./opencode-drone.js";async function rr(){xe(process.argv),Q(),await Ce();const c=(async()=>{if(!process.stderr.isTTY)return;const e=w(),r=await ve();if(!r)return;const n=be(e,r);n.stale&&n.message&&process.stderr.write(`${t()}${n.message}
3
+ `)})();if((process.argv[2]==="--help"||process.argv[2]==="-h")&&(process.stdout.write(ee(w())),process.exit(0)),process.argv[2]==="setup"){L(process.argv[3])&&(process.stdout.write(Z(w())),process.exit(0)),await import("./setup.js");return}if(process.argv[2]==="assimilate"){process.argv.slice(3).some(L)&&(process.stdout.write(re(w())),process.exit(0));const e=ce(process.argv.slice(3));e.ok||(process.stderr.write(i.red(`${t()}\u25FC borg assimilate: ${e.error}
4
+ `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const r=de(),n=await le({role:e.role,flags:e.flags},r);process.exit(n)}if(process.argv[2]==="spawn"){const e=await oe();process.exit(e)}if(process.argv[2]==="sync"){const e=te(process.argv.slice(3));e.ok||(process.stderr.write(i.red(`${t()}\u25FC borg sync: ${e.error}
5
+ `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const r=await ie({},e.options);process.exit(r)}if(process.argv[2]==="cleanup"){const e=ne(process.argv.slice(3));e.ok||(process.stderr.write(i.red(`${t()}\u25FC borg cleanup: ${e.error}
6
+ `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const r=await ae({},e.options);process.exit(r)}if(process.argv[2]==="launch-all"){const e=O(process.argv.slice(3));e.ok||(process.stderr.write(i.red(`${t()}\u25FC borg launch-all: ${e.error}
7
+ `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const r=k(),n=await D(e.args,r);process.exit(n)}const a=pe(process.argv[2]);if(a!==null&&(process.stderr.write(i.red(`${t()}\u25FC unknown command: ${a}
8
+ `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1)),Pe(await Ae()!==null,await ye()!==null)){const e=E($()).length>0;process.stdout.write(Te(e)),process.exit(0)}const d=$e(process.argv.slice(2));d.error&&(process.stderr.write(i.red(`${t()}\u25FC ${d.error}
9
+ `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const A=async e=>{const r=U({input:process.stdin,output:process.stdout});try{return await r.question(e)}finally{r.close()}};let o=await Se(d.cli,ke(A,()=>process.stdin.isTTY===!0));or();const s=await Y();let C;const y=async()=>s?(C===void 0&&(C=(await ue({targetCubeId:s.cubeId},k())).length>0),C):!1,x=process.stdin.isTTY===!0,v=process.stdout.isTTY===!0,T=me({explicitCli:d.cli,stdinIsTTY:x,stdoutIsTTY:v,hasActiveCube:s!==null,hasLaunchAllTargets:ge({explicitCli:d.cli,stdinIsTTY:x,stdoutIsTTY:v,hasActiveCube:s!==null})?await y():!1});if(T&&process.stderr.write(T),he({extraArgs:process.argv.slice(2),stdinIsTTY:x,stdoutIsTTY:v})){const e=E($()).filter(n=>n!==o),r=await fe({defaultCli:o,otherInstalledClis:e,hasLaunchAllTargets:await y()},A);if(r.kind==="launch-all"){const n=O([]),B=n.ok?await D(n.args,k()):1;process.exit(B)}o=r.cli}const l=d.rest;we(s?{label:s.droneLabel,cubeName:s.name}:null,G(process.cwd()));const N=Fe(o,s&&o==="claude"?V(s.cubeId,s.droneId):null,s&&o==="claude"?X(b(process.cwd())):null);await Promise.race([c,new Promise(e=>setTimeout(e,2e3))]);const P=o==="codex"?`borg-wake-${_()}`:null;let g,I=[],u={...S(process.env,o),BORG_SESSION:"1"},p=null,m=null;if(o==="codex"&&!l.includes("--remote")){console.error(`${t()}${i.gray("\u25FC Starting Codex remote-wake app-server\u2026")}`);const e=await Ie(Le());e.warning?(console.error(`${t()}${i.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.",I=e.args,u={...S(process.env,o),...e.env,BORG_SESSION:"1"},p=M(e.args),m=e.server?.cleanup??null}else o==="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=M(l),p&&(u={...S(process.env,o),[De]:"1",BORG_SESSION:"1"}));if(s){const e=await K(s.cubeId,b(),s.droneId),r=z(u,e,process.env);if(u=r.env,r.probe){const n=await J(r.probe.descriptor,fetch,r.probe.baseUrl);n.ok||console.error(`${t()}${i.yellow(`warning: ${n.message}`)}`)}}const f=Ne({cli:o,codexWakeNonce:P,monitorClause:N,codexWakePathClause:g});let h;if(o==="codex")h=[...We(),...Ee(),...Me(p!==null),...I,...Re([...l,f],process.cwd())];else if(o==="opencode"){const e=s?H(s.droneId):14096;Qe(),h=[process.cwd(),"--port",String(e),"--auto","--prompt",f,...l]}else h=se(l,f);const F=o==="claude"?"Claude Code":o==="codex"?"Codex":"OpenCode";console.error(`${t()}${i.blue(`\u25FC Launching ${F}\u2026`)}`);const R=W(o,h,{stdio:"inherit",shell:!1,env:u});if(o==="opencode"){const r=`http://127.0.0.1:${s?H(s.droneId):14096}`;Ze({serverUrl:r,directory:process.cwd(),droneLabel:s?.droneLabel??"opencode",cubeName:s?.name??"borg"}).then(()=>er(f)).catch(()=>{})}o==="codex"&&s&&p&&(Be({deps:{setCodexWakeTarget:j,findLoadedCodexThread:He},cubeId:s.cubeId,droneId:s.droneId,socketPath:p,passthroughArgs:l,previewNeedle:P??f.slice(0,120),cwd:process.cwd(),launchedAtSeconds:Math.floor(Date.now()/1e3)}),q(e=>Oe(e))),R.on("error",e=>{if(m)try{m()}catch{}if(e.code==="ENOENT"){console.error(`${t()}${i.red(`
10
+ \u25FC Failed to launch ${o}`)}`);const r=o==="opencode"?"opencode":o;console.error(`${t()}${i.gray(`Make sure ${r} is installed.
11
11
  `)}`)}else console.error(`${t()}${i.red(`
12
- \u25FC Failed to launch ${s}: ${e.message}
13
- `)}`);process.exit(1)}),P.on("exit",e=>{if(m)try{m()}catch{}process.exit(e??0)})}function Je(){const c=k();if(c.claude)try{Ye()||Fe(),We(I(process.cwd())),Ve(),Ge()}catch(n){console.error(`${t()}${i.yellow(`warning: Claude Code integration check failed: ${n?.message??n}`)}`)}if(c.codex)try{Ue()||He(),Ne(),Be()}catch(n){console.error(`${t()}${i.yellow(`warning: Codex integration check failed: ${n?.message??n}`)}`)}if(c.opencode)try{Ke()||_e()}catch(n){console.error(`${t()}${i.yellow(`warning: OpenCode integration check failed: ${n?.message??n}`)}`)}}ze().catch(c=>{console.error(`${t()}${i.red(`
12
+ \u25FC Failed to launch ${o}: ${e.message}
13
+ `)}`);process.exit(1)}),R.on("exit",e=>{if(m)try{m()}catch{}process.exit(e??0)})}function or(){const c=$();if(c.claude)try{Xe()||Ye(),Ve(b(process.cwd())),Je(),je()}catch(a){console.error(`${t()}${i.yellow(`warning: Claude Code integration check failed: ${a?.message??a}`)}`)}if(c.codex)try{qe()||_e(),Ge(),Ue()}catch(a){console.error(`${t()}${i.yellow(`warning: Codex integration check failed: ${a?.message??a}`)}`)}if(c.opencode)try{ze()||Ke()}catch(a){console.error(`${t()}${i.yellow(`warning: OpenCode integration check failed: ${a?.message??a}`)}`)}}rr().catch(c=>{console.error(`${t()}${i.red(`
14
14
  \u25FC Error: ${c.message}
15
15
  `)}`),process.exit(1)});
@@ -5,7 +5,7 @@ export declare const CODEX_WAKE_PROMPT = "New Borg cube-log activity arrived.";
5
5
  export declare function formatCodexWakePrompt(inboxLine: string): string;
6
6
  export declare const CODEX_CATCHUP_PROMPT = "Borg cube activity arrived while you were busy. Wake triage: run `borg_read-log unread_only=true` and DRAIN \u2014 repeat until the returned page is under the limit and behind_by is 0 \u2014 so no entries are skipped. Then handle actionable entries; if none, resume the prior interrupted work.";
7
7
  export declare function isCodexRemoteWakeEnabled(env?: NodeJS.ProcessEnv): boolean;
8
- export declare function resolveSessionAgentKind(env?: NodeJS.ProcessEnv): 'claude' | 'codex' | 'opencode';
8
+ export { resolveSessionAgentKind } from './agent-runtime.js';
9
9
  export interface CodexWakeTarget {
10
10
  enabled: boolean;
11
11
  }
@@ -73,14 +73,17 @@ export declare function fireCodexHeartbeatTick(deps?: CodexWakeDeps, cadenceMs?:
73
73
  * fireCodexHeartbeatTick every cadence. CODEX-ONLY: claude wakes via the tail-F
74
74
  * inbox Monitor + /loop ScheduleWakeup and has NO app-server socket to inject
75
75
  * into, so the heartbeat is intrinsically a codex mechanism. The gate reads
76
- * agentKind LOCALLY from this child's own env (resolveSessionAgentKind →
77
- * BORG_CODEX_REMOTE_WAKE), never a mutable/server-recorded field, so a mislabel
78
- * can't silently defeat the backstop (gh#633 lesson). The timer is unref'd so the
79
- * heartbeat alone never keeps the process alive. Returns the timer, or null when
80
- * not a codex session. Injectable agentKind/intervalMs/tick for tests.
76
+ * agentKind and remote-wake capability LOCALLY from this child's own env,
77
+ * never a mutable/server-recorded field, so a mislabel can't silently defeat
78
+ * the backstop (gh#633 lesson). Agent CLI identity and remote transport are
79
+ * separate: a Codex CLI without a live remote transport has no app-server
80
+ * heartbeat to run. The timer is unref'd so it never keeps the process alive.
81
+ * Returns the timer, or null when this is not a remotely-wakeable Codex
82
+ * session. Injectable for tests.
81
83
  */
82
84
  export declare function startCodexHeartbeat(opts?: {
83
85
  agentKind?: 'claude' | 'codex' | 'opencode';
86
+ remoteWakeEnabled?: boolean;
84
87
  intervalMs?: number;
85
88
  tick?: () => void;
86
89
  }): ReturnType<typeof setInterval> | null;
@@ -1,2 +1,2 @@
1
- import{getActiveCube as C,getCodexWakeTarget as p,setCodexWakeTarget as R}from"./cubes.js";import{CodexAppServerClient as O}from"./codex-app-server.js";import{checkCodexBridgeHealthy as S}from"./codex-remote.js";import{recordEventReceipt as W}from"./health-beat.js";import{codexAppServerSocketFromEnv as K,pickFreshThread as B,wakeTargetChanged as M,wakeRetryBackoffMs as F,wakeRetryExpired as N,WAKE_RETRY_MAX_ATTEMPTS as H,shouldFireHeartbeat as j}from"./codex-wake-resolve.js";const L="New Borg cube-log activity arrived.";function ie(e){return`New Borg cube-log activity arrived:
2
- ${e}`}const D="Borg cube activity arrived while you were busy. Wake triage: run `borg_read-log unread_only=true` and DRAIN \u2014 repeat until the returned page is under the limit and behind_by is 0 \u2014 so no entries are skipped. Then handle actionable entries; if none, resume the prior interrupted work.";function P(e=process.env){return e.BORG_CODEX_REMOTE_WAKE==="1"}function X(e=process.env){return P(e)?"codex":e.BORG_OPENCODE==="1"?"opencode":"claude"}function q(e=process.env){return P(e)?{enabled:!0}:{enabled:!1}}async function ce(e,t={}){try{const r=await(t.getCodexWakeTarget??p)(e.cubeId,e.droneId);return r?(t.checkBridge??S)(r.socketPath):!1}catch{return null}}let f=!1;const h=[],d=new Set,w=[],$=100;let g=!1,k=null;function se(){return k}function m(e){k=(e.now??Date.now)()}let y=!1,v=!1;function T(){return v?!1:(v=!0,!0)}function A(){v=!1}function G(e){return e?.code==="ENOENT"}function V(e){return new Promise(t=>setTimeout(t,e))}function x(e,t){return t.createClient?t.createClient(e):new O(e)}async function E(e,t){const n=K(t.env??process.env);if(n){const a=x(n,t);await a.connect();try{const o=await a.loadedThreadIds(),i=[];for(const u of o){const s=await a.readThread(u);s&&i.push({id:s.id,cwd:s.cwd,updatedAt:s.updatedAt})}const c=B(i,{cwd:(t.cwd??(()=>process.cwd()))()});return c?(await Y(e,{socketPath:n,threadId:c},t),{socketPath:n,threadId:c}):null}finally{a.close()}}const r=await(t.getCodexWakeTarget??p)(e.cubeId,e.droneId);return r?{socketPath:r.socketPath,threadId:r.threadId}:null}async function Y(e,t,n){try{const r=n.getCodexWakeTarget??p,a=n.setCodexWakeTarget??R,o=await r(e.cubeId,e.droneId),i=o?{socketPath:o.socketPath,threadId:o.threadId}:null;M(i,t)&&await a(e.cubeId,e.droneId,t)}catch{}}function ue(e=L,t=process.env,n={}){q(t).enabled&&(h.push({reason:e,deps:n}),!f&&(f=!0,Q().finally(()=>{f=!1})))}async function Q(){for(;h.length>0;){const e=h.shift();await U(e.reason,e.deps)}}async function U(e,t){if(!T()){b(t);return}try{const n=await(t.getActiveCube??C)();if(!n)return;const r=await E(n,t);if(!r)return;const{socketPath:a,threadId:o}=r,i=`${o}\0${e}`;if(d.has(i))return;const c=x(a,t);await c.connect();try{if((await c.readThread(o))?.status?.type==="active"){b(t);return}await c.startTurn(o,e),W(),Z(i),m(t)}finally{c.close()}}catch{b(t)}finally{A()}}function b(e){g||(g=!0,z(e).finally(()=>{g=!1}))}async function z(e){const t=e.sleep??V,n=e.now??Date.now,r=e.jitter??(()=>Math.random()*500),a=e.maxAttempts??H,o=n();let i=0;for(;!N(o,n())&&i<a;)if(await t(F(i,r())),i++,!!T())try{const c=await(e.getActiveCube??C)();if(!c)continue;const u=await E(c,e);if(!u)continue;const{socketPath:s,threadId:I}=u,l=x(s,e);await l.connect();try{if((await l.readThread(I))?.status?.type==="active")continue;await l.startTurn(I,D),W(),m(e);return}finally{l.close()}}catch{}finally{A()}}const _=20*6e4;async function J(e={},t=_){if(y)return;const n=(e.now??Date.now)();if(j(k,n,t)&&!(e.isStreamOwner&&!e.isStreamOwner())&&T()){y=!0;try{const r=await(e.getActiveCube??C)();if(!r)return;const a=await E(r,e);if(!a)return;const o=x(a.socketPath,e);await o.connect();try{if((await o.readThread(a.threadId))?.status?.type==="active")return;await o.startTurn(a.threadId,D),m(e)}finally{o.close()}}catch(r){G(r)&&e.onAppServerSocketDead?.()}finally{y=!1,A()}}}function de(e={}){if((e.agentKind??X())!=="codex")return null;const n=e.intervalMs??_,r=e.tick??(()=>{J()}),a=setInterval(r,n);return a.unref?.(),a}function le(){f=!1,h.length=0,d.clear(),w.length=0,g=!1,k=null,y=!1,v=!1}function Z(e){if(!d.has(e))for(d.add(e),w.push(e);w.length>$;){const t=w.shift();t&&d.delete(t)}}export{D as CODEX_CATCHUP_PROMPT,_ as CODEX_HEARTBEAT_CADENCE_MS,L as CODEX_WAKE_PROMPT,J as fireCodexHeartbeatTick,ie as formatCodexWakePrompt,se as getLastDeliveredAt,P as isCodexRemoteWakeEnabled,ce as probeCodexBridgeArmed,le as resetCodexWakeForTests,q as resolveCodexWakeTarget,X as resolveSessionAgentKind,de as startCodexHeartbeat,ue as wakeCodexViaAppServer};
1
+ import{getActiveCube as x,getCodexWakeTarget as C,setCodexWakeTarget as R}from"./cubes.js";import{CodexAppServerClient as S}from"./codex-app-server.js";import{checkCodexBridgeHealthy as K}from"./codex-remote.js";import{recordEventReceipt as W}from"./health-beat.js";import{BORG_CODEX_REMOTE_WAKE_ENV as O,resolveSessionAgentKind as M}from"./agent-runtime.js";import{codexAppServerSocketFromEnv as B,pickFreshThread as F,wakeTargetChanged as N,wakeRetryBackoffMs as H,wakeRetryExpired as j,WAKE_RETRY_MAX_ATTEMPTS as L,shouldFireHeartbeat as X}from"./codex-wake-resolve.js";const q="New Borg cube-log activity arrived.";function se(e){return`New Borg cube-log activity arrived:
2
+ ${e}`}const _="Borg cube activity arrived while you were busy. Wake triage: run `borg_read-log unread_only=true` and DRAIN \u2014 repeat until the returned page is under the limit and behind_by is 0 \u2014 so no entries are skipped. Then handle actionable entries; if none, resume the prior interrupted work.";function D(e=process.env){return e[O]==="1"}import{resolveSessionAgentKind as ke}from"./agent-runtime.js";function V(e=process.env){return D(e)?{enabled:!0}:{enabled:!1}}async function le(e,t={}){try{const r=await(t.getCodexWakeTarget??C)(e.cubeId,e.droneId);return r?(t.checkBridge??K)(r.socketPath):!1}catch{return null}}let f=!1;const h=[],u=new Set,w=[],$=100;let g=!1,k=null;function ue(){return k}function T(e){k=(e.now??Date.now)()}let v=!1,y=!1;function p(){return y?!1:(y=!0,!0)}function A(){y=!1}function Y(e){return e?.code==="ENOENT"}function G(e){return new Promise(t=>setTimeout(t,e))}function m(e,t){return t.createClient?t.createClient(e):new S(e)}async function b(e,t){const n=B(t.env??process.env);if(n){const o=m(n,t);await o.connect();try{const a=await o.loadedThreadIds(),i=[];for(const l of a){const s=await o.readThread(l);s&&i.push({id:s.id,cwd:s.cwd,updatedAt:s.updatedAt})}const c=F(i,{cwd:(t.cwd??(()=>process.cwd()))()});return c?(await Q(e,{socketPath:n,threadId:c},t),{socketPath:n,threadId:c}):null}finally{o.close()}}const r=await(t.getCodexWakeTarget??C)(e.cubeId,e.droneId);return r?{socketPath:r.socketPath,threadId:r.threadId}:null}async function Q(e,t,n){try{const r=n.getCodexWakeTarget??C,o=n.setCodexWakeTarget??R,a=await r(e.cubeId,e.droneId),i=a?{socketPath:a.socketPath,threadId:a.threadId}:null;N(i,t)&&await o(e.cubeId,e.droneId,t)}catch{}}function de(e=q,t=process.env,n={}){V(t).enabled&&(h.push({reason:e,deps:n}),!f&&(f=!0,U().finally(()=>{f=!1})))}async function U(){for(;h.length>0;){const e=h.shift();await z(e.reason,e.deps)}}async function z(e,t){if(!p()){E(t);return}try{const n=await(t.getActiveCube??x)();if(!n)return;const r=await b(n,t);if(!r)return;const{socketPath:o,threadId:a}=r,i=`${a}\0${e}`;if(u.has(i))return;const c=m(o,t);await c.connect();try{if((await c.readThread(a))?.status?.type==="active"){E(t);return}await c.startTurn(a,e),W(),ee(i),T(t)}finally{c.close()}}catch{E(t)}finally{A()}}function E(e){g||(g=!0,J(e).finally(()=>{g=!1}))}async function J(e){const t=e.sleep??G,n=e.now??Date.now,r=e.jitter??(()=>Math.random()*500),o=e.maxAttempts??L,a=n();let i=0;for(;!j(a,n())&&i<o;)if(await t(H(i,r())),i++,!!p())try{const c=await(e.getActiveCube??x)();if(!c)continue;const l=await b(c,e);if(!l)continue;const{socketPath:s,threadId:I}=l,d=m(s,e);await d.connect();try{if((await d.readThread(I))?.status?.type==="active")continue;await d.startTurn(I,_),W(),T(e);return}finally{d.close()}}catch{}finally{A()}}const P=20*6e4;async function Z(e={},t=P){if(v)return;const n=(e.now??Date.now)();if(X(k,n,t)&&!(e.isStreamOwner&&!e.isStreamOwner())&&p()){v=!0;try{const r=await(e.getActiveCube??x)();if(!r)return;const o=await b(r,e);if(!o)return;const a=m(o.socketPath,e);await a.connect();try{if((await a.readThread(o.threadId))?.status?.type==="active")return;await a.startTurn(o.threadId,_),T(e)}finally{a.close()}}catch(r){Y(r)&&e.onAppServerSocketDead?.()}finally{v=!1,A()}}}function fe(e={}){const t=e.agentKind??M(),n=e.remoteWakeEnabled??D();if(t!=="codex"||!n)return null;const r=e.intervalMs??P,o=e.tick??(()=>{Z()}),a=setInterval(o,r);return a.unref?.(),a}function he(){f=!1,h.length=0,u.clear(),w.length=0,g=!1,k=null,v=!1,y=!1}function ee(e){if(!u.has(e))for(u.add(e),w.push(e);w.length>$;){const t=w.shift();t&&u.delete(t)}}export{_ as CODEX_CATCHUP_PROMPT,P as CODEX_HEARTBEAT_CADENCE_MS,q as CODEX_WAKE_PROMPT,Z as fireCodexHeartbeatTick,se as formatCodexWakePrompt,ue as getLastDeliveredAt,D as isCodexRemoteWakeEnabled,le as probeCodexBridgeArmed,he as resetCodexWakeForTests,V as resolveCodexWakeTarget,ke as resolveSessionAgentKind,fe as startCodexHeartbeat,de as wakeCodexViaAppServer};
@@ -9,7 +9,7 @@ import { type AgentKind } from './regen-format.js';
9
9
  * `monitorClause`. Codex wakes via the app-server (no tail-Monitor / `/loop`
10
10
  * to arm) → empty; no active cube (no inboxPath) → empty.
11
11
  */
12
- export declare function buildKickoffWakePathClause(agentKind: AgentKind, inboxPath: string | null): string;
12
+ export declare function buildKickoffWakePathClause(agentKind: AgentKind, inboxPath: string | null, monitorStateRoot?: string | null): string;
13
13
  export interface CodexWakeTargetDeps {
14
14
  setCodexWakeTarget: (cubeId: string, droneId: string, target: {
15
15
  threadId: string;
@@ -1,2 +1,2 @@
1
- import{wakePathArming as c}from"./regen-format.js";function i(e,t){return e==="claude"&&t?c("claude",t)+`
1
+ import{wakePathArming as c}from"./regen-format.js";function i(e,t,r){return e==="claude"&&t?c("claude",t,r)+`
2
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.",a="OpenCode wakes: the inbox Monitor is not available yet for OpenCode; check activity by calling borg_read-log periodically.",o=e.cli==="claude"?"/loop ":"",n=e.cli==="claude"?e.monitorClause:e.cli==="codex"?r:a;return`${o}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. '+n}function u(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 l(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 h(e){try{const t=e.passthroughArgs?l(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,i as buildKickoffWakePathClause,h as recordCodexWakeTarget,u as socketPathFromRemoteArgs,l as threadIdFromPassthroughArgs};
@@ -1 +1 @@
1
- import{mkdirSync as v,chmodSync as w,readdirSync as g,rmSync as C,writeFileSync as k,readFileSync as y}from"node:fs";import{homedir as S}from"node:os";import{join as u}from"node:path";import{randomBytes as b}from"node:crypto";import{spawn as A}from"node:child_process";import{CodexAppServerClient as E}from"./codex-app-server.js";import{codexBorgSessionConfigArgs as R,BORG_SESSION_ENV as _}from"./launch-gate.js";import{codexAppServerSocketConfigArgs as $}from"./codex-wake-resolve.js";const N=u(S(),".config","borgmcp","codex-remote");function G(e,r){return I(e)?e:["--cd",r,...e]}function I(e){return e.some(r=>r==="--cd"||r.startsWith("--cd=")||r==="-C")}function x(e){try{return process.kill(e,0),!0}catch(r){return r?.code==="EPERM"}}function X(e,r={}){if(!e)return null;const a=r.isAlive??x,i=r.readPidFile??(o=>y(o,"utf-8")),c=e.replace(/\.sock$/,".pid");try{const o=Number.parseInt(i(c).trim(),10);return Number.isNaN(o)?null:a(o)}catch{return null}}function s(e){try{C(e,{force:!0})}catch{}}function M(e,r){let a;try{a=g(e)}catch{return}for(const i of a){if(!i.endsWith(".pid"))continue;const c=u(e,i),o=u(e,i.replace(/\.pid$/,".sock"));let t;try{t=Number.parseInt(y(c,"utf-8").trim(),10)}catch{s(c);continue}(Number.isNaN(t)||!r(t))&&(s(o),s(c))}}function p(e){return{args:[],env:{},warning:e}}async function j(e){const r=e.runtimeDir??N,a=e.isAlive??x,i=e.readyTimeoutMs??8e3,c=e.pollIntervalMs??250;try{v(r,{recursive:!0,mode:448}),w(r,448),M(r,a)}catch(n){return p(`Codex remote-wake disabled: could not prepare ${r} (${n?.message??n}); run borg_regen manually.`)}const o=(e.socketId??(()=>b(16).toString("hex")))(),t=u(r,`${o}.sock`),m=u(r,`${o}.pid`);let l;try{l=e.spawnAppServer(t)}catch(n){return s(t),p(`Codex remote-wake disabled: could not start \`codex app-server\` (${n?.message??n}) \u2014 is Codex installed + up to date? This session only wakes on the ~30min /loop fallback; run borg_regen manually.`)}if(l.pid!=null)try{k(m,String(l.pid))}catch{}const f=()=>{try{l.kill()}catch{}s(t),s(m)},h=Math.max(1,Math.ceil(i/c));let d=!1;for(let n=0;n<h&&!d;n++){try{d=await e.probeReady(t)}catch{d=!1}!d&&n<h-1&&await e.sleep(c)}return d?{args:["--remote",`unix://${t}`],env:{BORG_CODEX_REMOTE_WAKE:"1"},server:{pid:l.pid,socketPath:t,cleanup:f}}:(f(),p(`Codex remote-wake disabled: could not reach a Codex app-server at ${t} within ${i}ms (is Codex up to date? \`codex app-server --listen\` is required). This session only wakes on the ~30min /loop fallback \u2014 run borg_regen manually when you return.`))}function q(){return{spawnAppServer:e=>{const r=A("codex",["app-server",...R(),...$(e),"--listen",`unix://${e}`],{stdio:"ignore",shell:!1,env:{...process.env,[_]:"1"}});return{pid:r.pid,kill:()=>{try{r.kill()}catch{}}}},probeReady:async e=>{const r=new E(e);try{return await r.connect(),await r.loadedThreadIds(),!0}catch{return!1}finally{try{r.close()}catch{}}},sleep:e=>new Promise(r=>setTimeout(r,e))}}export{N as DEFAULT_CODEX_REMOTE_DIR,X as checkCodexBridgeHealthy,q as defaultCodexRemoteDeps,x as defaultIsAlive,j as prepareCodexRemoteLaunch,G as withCodexCwdArg};
1
+ import{mkdirSync as C,chmodSync as v,readdirSync as w,rmSync as k,writeFileSync as A,readFileSync as y}from"node:fs";import{homedir as S}from"node:os";import{join as u}from"node:path";import{randomBytes as b}from"node:crypto";import{spawn as E}from"node:child_process";import{CodexAppServerClient as _}from"./codex-app-server.js";import{codexBorgSessionConfigArgs as N,BORG_SESSION_ENV as R}from"./launch-gate.js";import{codexAppServerSocketConfigArgs as $}from"./codex-wake-resolve.js";import{BORG_AGENT_KIND_ENV as I,BORG_CODEX_REMOTE_WAKE_ENV as x,codexAgentKindConfigArgs as T,codexRemoteWakeConfigArgs as M}from"./agent-runtime.js";const O=u(S(),".config","borgmcp","codex-remote");function q(e,r){return P(e)?e:["--cd",r,...e]}function P(e){return e.some(r=>r==="--cd"||r.startsWith("--cd=")||r==="-C")}function g(e){try{return process.kill(e,0),!0}catch(r){return r?.code==="EPERM"}}function H(e,r={}){if(!e)return null;const a=r.isAlive??g,i=r.readPidFile??(o=>y(o,"utf-8")),c=e.replace(/\.sock$/,".pid");try{const o=Number.parseInt(i(c).trim(),10);return Number.isNaN(o)?null:a(o)}catch{return null}}function s(e){try{k(e,{force:!0})}catch{}}function B(e,r){let a;try{a=w(e)}catch{return}for(const i of a){if(!i.endsWith(".pid"))continue;const c=u(e,i),o=u(e,i.replace(/\.pid$/,".sock"));let t;try{t=Number.parseInt(y(c,"utf-8").trim(),10)}catch{s(c);continue}(Number.isNaN(t)||!r(t))&&(s(o),s(c))}}function p(e){return{args:[],env:{},warning:e}}async function U(e){const r=e.runtimeDir??O,a=e.isAlive??g,i=e.readyTimeoutMs??8e3,c=e.pollIntervalMs??250;try{C(r,{recursive:!0,mode:448}),v(r,448),B(r,a)}catch(n){return p(`Codex remote-wake disabled: could not prepare ${r} (${n?.message??n}); run borg_regen manually.`)}const o=(e.socketId??(()=>b(16).toString("hex")))(),t=u(r,`${o}.sock`),m=u(r,`${o}.pid`);let l;try{l=e.spawnAppServer(t)}catch(n){return s(t),p(`Codex remote-wake disabled: could not start \`codex app-server\` (${n?.message??n}) \u2014 is Codex installed + up to date? This session only wakes on the ~30min /loop fallback; run borg_regen manually.`)}if(l.pid!=null)try{A(m,String(l.pid))}catch{}const f=()=>{try{l.kill()}catch{}s(t),s(m)},h=Math.max(1,Math.ceil(i/c));let d=!1;for(let n=0;n<h&&!d;n++){try{d=await e.probeReady(t)}catch{d=!1}!d&&n<h-1&&await e.sleep(c)}return d?{args:["--remote",`unix://${t}`],env:{[x]:"1"},server:{pid:l.pid,socketPath:t,cleanup:f}}:(f(),p(`Codex remote-wake disabled: could not reach a Codex app-server at ${t} within ${i}ms (is Codex up to date? \`codex app-server --listen\` is required). This session only wakes on the ~30min /loop fallback \u2014 run borg_regen manually when you return.`))}function z(){return{spawnAppServer:e=>{const r=E("codex",["app-server",...N(),...T(),...M(),...$(e),"--listen",`unix://${e}`],{stdio:"ignore",shell:!1,env:{...process.env,[R]:"1",[I]:"codex",[x]:"1"}});return{pid:r.pid,kill:()=>{try{r.kill()}catch{}}}},probeReady:async e=>{const r=new _(e);try{return await r.connect(),await r.loadedThreadIds(),!0}catch{return!1}finally{try{r.close()}catch{}}},sleep:e=>new Promise(r=>setTimeout(r,e))}}export{O as DEFAULT_CODEX_REMOTE_DIR,H as checkCodexBridgeHealthy,z as defaultCodexRemoteDeps,g as defaultIsAlive,U as prepareCodexRemoteLaunch,q as withCodexCwdArg};
@@ -119,10 +119,12 @@ export declare function isCodexUserPromptSubmitHookRegistered(hooksPath?: string
119
119
  export declare function isOpenCodeMcpServerConfigured(configPath?: string): boolean;
120
120
  /**
121
121
  * Add borg MCP server to OpenCode using `opencode mcp add` CLI.
122
- * Pins BORG_SESSION=1, BORG_OPENCODE=1, and BORG_API_URL in the server
123
- * environment so the MCP child inherits the activation gate + agent-kind
124
- * signal (same approach as codex's pinned env — opencode MCP children
125
- * only see pinned env, not parent process env).
122
+ * Pins BORG_SESSION=1, BORG_AGENT_KIND=opencode, the legacy BORG_OPENCODE=1,
123
+ * and BORG_API_URL in the server environment so the MCP child inherits the
124
+ * activation gate + explicit agent-kind signal (same approach as Codex's
125
+ * pinned env — OpenCode MCP children only see pinned env, not parent process
126
+ * env). Existing configs with BORG_OPENCODE remain supported by the runtime
127
+ * fallback.
126
128
  */
127
129
  export declare function addOpenCodeMcpServer(): void;
128
130
  //# sourceMappingURL=config-utils.d.ts.map
@@ -1,3 +1,3 @@
1
- import{execSync as d}from"child_process";import n from"fs";import f from"os";import i from"path";import{fileURLToPath as b}from"url";import{dirname as R}from"path";const v=b(import.meta.url),E=R(v),a="borg-regen",m="borg-log-audit",U=i.join(f.homedir(),".claude.json"),H=i.join(f.homedir(),".codex","config.toml"),u=i.join(f.homedir(),".codex","hooks.json"),j=i.join(f.homedir(),".config","opencode","opencode.json"),M="borg";function h(){return i.join(f.homedir(),".claude","settings.json")}function p(){const e=h();if(!n.existsSync(e))return{};const r=n.readFileSync(e,"utf-8");return r.trim()?JSON.parse(r):{}}function y(e){const r=h();n.mkdirSync(i.dirname(r),{recursive:!0}),n.writeFileSync(r,JSON.stringify(e,null,2)+`
2
- `,"utf-8")}function S(e){if(!n.existsSync(e))return{};const r=n.readFileSync(e,"utf-8");return r.trim()?JSON.parse(r):{}}function A(e,r){n.mkdirSync(i.dirname(e),{recursive:!0}),n.writeFileSync(e,JSON.stringify(r,null,2)+`
3
- `,"utf-8")}function $(){return O(h())}function D(e){return O(x(e))}function T(e){return w(x(e))}function x(e){return i.join(e,".claude","settings.local.json")}function O(e){let r;try{r=S(e)}catch(t){return console.error(`\u26A0 Could not parse ${e}: ${t.message}. Skipping hook registration; you can add it manually.`),!1}return r.hooks??={},r.hooks.SessionStart??=[],r.hooks.SessionStart.some(t=>Array.isArray(t?.hooks)&&t.hooks.some(s=>s?.type==="command"&&s?.command===a))?!1:(r.hooks.SessionStart.push({matcher:"*",hooks:[{type:"command",command:a}]}),A(e,r),!0)}function w(e){let r;try{r=S(e)}catch{return!1}const o=r?.hooks?.SessionStart;return Array.isArray(o)?o.some(t=>Array.isArray(t?.hooks)&&t.hooks.some(s=>s?.type==="command"&&s?.command===a)):!1}function J(){let e;try{e=p()}catch{return!1}const r=e?.hooks?.SessionStart;return Array.isArray(r)?r.some(o=>Array.isArray(o?.hooks)&&o.hooks.some(t=>t?.type==="command"&&t?.command===a)):!1}function K(){let e;try{e=p()}catch{return!1}const r=e?.hooks?.UserPromptSubmit;return Array.isArray(r)?r.some(o=>Array.isArray(o?.hooks)&&o.hooks.some(t=>t?.type==="command"&&t?.command===m)):!1}function X(){let e;try{e=p()}catch{return!1}if(!e?.hooks?.SessionStart)return!1;let r=!1;return e.hooks.SessionStart=e.hooks.SessionStart.map(o=>{if(!Array.isArray(o?.hooks))return o;const t=o.hooks.filter(s=>!(s?.type==="command"&&s?.command===a));return t.length!==o.hooks.length?(r=!0,{...o,hooks:t}):o}).filter(o=>Array.isArray(o?.hooks)&&o.hooks.length>0),e.hooks.SessionStart.length===0&&delete e.hooks.SessionStart,Object.keys(e.hooks).length===0&&delete e.hooks,r&&y(e),r}function W(){let e;try{e=p()}catch(o){return console.error(`\u26A0 Could not parse ${h()}: ${o.message}. Skipping audit hook registration.`),!1}return e.hooks??={},e.hooks.UserPromptSubmit??=[],e.hooks.UserPromptSubmit.some(o=>Array.isArray(o?.hooks)&&o.hooks.some(t=>t?.type==="command"&&t?.command===m))?!1:(e.hooks.UserPromptSubmit.push({matcher:"*",hooks:[{type:"command",command:m}]}),y(e),!0)}function Q(){let e;try{e=p()}catch{return!1}if(!e?.hooks?.UserPromptSubmit)return!1;let r=!1;return e.hooks.UserPromptSubmit=e.hooks.UserPromptSubmit.map(o=>{if(!Array.isArray(o?.hooks))return o;const t=o.hooks.filter(s=>!(s?.type==="command"&&s?.command===m));return t.length!==o.hooks.length?(r=!0,{...o,hooks:t}):o}).filter(o=>Array.isArray(o?.hooks)&&o.hooks.length>0),e.hooks.UserPromptSubmit.length===0&&delete e.hooks.UserPromptSubmit,Object.keys(e.hooks).length===0&&delete e.hooks,r&&y(e),r}function V(e=U){try{if(!n.existsSync(e))return!1;const r=n.readFileSync(e,"utf-8");if(!r.trim())return!1;const o=JSON.parse(r);if(!o||typeof o!="object")return!1;const t=o.mcpServers;return!t||typeof t!="object"||Array.isArray(t)?!1:M in t}catch{return!1}}function q(e=H){try{if(!n.existsSync(e))return!1;const r=n.readFileSync(e,"utf-8");return/^\s*\[mcp_servers\.borg\]\s*$/m.test(r)&&/^\s*BORG_CODEX_REMOTE_WAKE\s*=\s*"1"\s*$/m.test(r)}catch{return!1}}function z(){return i.join(E,"index.js")}function Y(){try{try{d("claude mcp remove --scope user borg",{stdio:"ignore"})}catch{}d("claude mcp add --scope user borg borg-mcp",{stdio:"inherit",env:{...process.env,BORG_API_URL:process.env.BORG_API_URL||"https://api.borgmcp.ai"}})}catch(e){throw e.message?.includes("command not found")?new Error("Claude CLI not found. Please install Claude Code first."):new Error(`Failed to add MCP server: ${e.message}`)}}function Z(){try{try{d("codex mcp remove borg",{stdio:"ignore"})}catch{}d("codex mcp add borg --env BORG_API_URL="+P(process.env.BORG_API_URL||"https://api.borgmcp.ai")+" --env BORG_CODEX_REMOTE_WAKE=1 -- borg-mcp",{stdio:"inherit",env:{...process.env,BORG_API_URL:process.env.BORG_API_URL||"https://api.borgmcp.ai",BORG_CODEX_REMOTE_WAKE:"1"}})}catch(e){throw e.message?.includes("command not found")?new Error("Codex CLI not found. Please install Codex first."):new Error(`Failed to add MCP server to Codex: ${e.message}`)}}function P(e){return`'${e.replace(/'/g,"'\\''")}'`}function _(e,r,o={}){let t;try{t=S(u)}catch(l){return console.error(`\u26A0 Could not parse ${u}: ${l.message}. Skipping Codex hook registration.`),!1}t.hooks??={},t.hooks[e]??=[];const s=t.hooks[e];if(!Array.isArray(s)||s.some(l=>Array.isArray(l?.hooks)&&l.hooks.some(g=>g?.type==="command"&&g?.command===r)))return!1;const c={hooks:[{type:"command",command:r}]};return o.matcher&&(c.matcher=o.matcher),typeof o.timeout=="number"&&(c.hooks[0].timeout=o.timeout),s.push(c),A(u,t),!0}function ee(){return _("SessionStart",a,{matcher:"startup|resume",timeout:30})}function re(){return _("UserPromptSubmit",m,{timeout:10})}function C(e,r,o=u){try{const s=S(o)?.hooks?.[e];return Array.isArray(s)?s.some(k=>Array.isArray(k?.hooks)&&k.hooks.some(c=>c?.type==="command"&&c?.command===r)):!1}catch{return!1}}function oe(e=u){return C("SessionStart",a,e)}function te(e=u){return C("UserPromptSubmit",m,e)}function se(e=j){try{if(!n.existsSync(e))return!1;const r=n.readFileSync(e,"utf-8");if(!r.trim())return!1;const o=JSON.parse(r);if(!o||typeof o!="object")return!1;const t=o.mcp?.borg;return!t||typeof t!="object"?!1:t.type==="local"}catch{return!1}}function ne(){try{const e=process.env.BORG_API_URL||"https://api.borgmcp.ai";d(`opencode mcp add borg --env BORG_SESSION=1 --env BORG_OPENCODE=1 --env BORG_API_URL=${P(e)} -- borg-mcp`,{stdio:"inherit"})}catch(e){throw e.message?.includes("command not found")?new Error("opencode CLI not found. Please install opencode first."):new Error(`Failed to add MCP server to opencode: ${e.message}`)}}export{Z as addCodexMcpServer,ee as addCodexSessionStartHook,re as addCodexUserPromptSubmitHook,Y as addMcpServer,ne as addOpenCodeMcpServer,D as addProjectSessionStartHook,$ as addSessionStartHook,W as addUserPromptSubmitHook,z as getBinaryPath,C as isCodexHookRegistered,q as isCodexMcpServerConfigured,oe as isCodexSessionStartHookRegistered,te as isCodexUserPromptSubmitHookRegistered,V as isMcpServerConfigured,se as isOpenCodeMcpServerConfigured,T as isProjectSessionStartHookRegistered,J as isSessionStartHookRegistered,K as isUserPromptSubmitHookRegistered,X as removeSessionStartHook,Q as removeUserPromptSubmitHook};
1
+ import{execSync as d}from"child_process";import n from"fs";import f from"os";import i from"path";import{fileURLToPath as v}from"url";import{dirname as R}from"path";import{BORG_AGENT_KIND_ENV as A,BORG_CODEX_REMOTE_WAKE_ENV as E,withAgentRuntimeEnv as U}from"./agent-runtime.js";const H=v(import.meta.url),j=R(H),a="borg-regen",m="borg-log-audit",N=i.join(f.homedir(),".claude.json"),w=i.join(f.homedir(),".codex","config.toml"),u=i.join(f.homedir(),".codex","hooks.json"),I=i.join(f.homedir(),".config","opencode","opencode.json"),G="borg";function h(){return i.join(f.homedir(),".claude","settings.json")}function p(){const e=h();if(!n.existsSync(e))return{};const r=n.readFileSync(e,"utf-8");return r.trim()?JSON.parse(r):{}}function y(e){const r=h();n.mkdirSync(i.dirname(r),{recursive:!0}),n.writeFileSync(r,JSON.stringify(e,null,2)+`
2
+ `,"utf-8")}function S(e){if(!n.existsSync(e))return{};const r=n.readFileSync(e,"utf-8");return r.trim()?JSON.parse(r):{}}function x(e,r){n.mkdirSync(i.dirname(e),{recursive:!0}),n.writeFileSync(e,JSON.stringify(r,null,2)+`
3
+ `,"utf-8")}function K(){return C(h())}function V(e){return C(P(e))}function X(e){return $(P(e))}function P(e){return i.join(e,".claude","settings.local.json")}function C(e){let r;try{r=S(e)}catch(t){return console.error(`\u26A0 Could not parse ${e}: ${t.message}. Skipping hook registration; you can add it manually.`),!1}return r.hooks??={},r.hooks.SessionStart??=[],r.hooks.SessionStart.some(t=>Array.isArray(t?.hooks)&&t.hooks.some(s=>s?.type==="command"&&s?.command===a))?!1:(r.hooks.SessionStart.push({matcher:"*",hooks:[{type:"command",command:a}]}),x(e,r),!0)}function $(e){let r;try{r=S(e)}catch{return!1}const o=r?.hooks?.SessionStart;return Array.isArray(o)?o.some(t=>Array.isArray(t?.hooks)&&t.hooks.some(s=>s?.type==="command"&&s?.command===a)):!1}function W(){let e;try{e=p()}catch{return!1}const r=e?.hooks?.SessionStart;return Array.isArray(r)?r.some(o=>Array.isArray(o?.hooks)&&o.hooks.some(t=>t?.type==="command"&&t?.command===a)):!1}function Q(){let e;try{e=p()}catch{return!1}const r=e?.hooks?.UserPromptSubmit;return Array.isArray(r)?r.some(o=>Array.isArray(o?.hooks)&&o.hooks.some(t=>t?.type==="command"&&t?.command===m)):!1}function q(){let e;try{e=p()}catch{return!1}if(!e?.hooks?.SessionStart)return!1;let r=!1;return e.hooks.SessionStart=e.hooks.SessionStart.map(o=>{if(!Array.isArray(o?.hooks))return o;const t=o.hooks.filter(s=>!(s?.type==="command"&&s?.command===a));return t.length!==o.hooks.length?(r=!0,{...o,hooks:t}):o}).filter(o=>Array.isArray(o?.hooks)&&o.hooks.length>0),e.hooks.SessionStart.length===0&&delete e.hooks.SessionStart,Object.keys(e.hooks).length===0&&delete e.hooks,r&&y(e),r}function z(){let e;try{e=p()}catch(o){return console.error(`\u26A0 Could not parse ${h()}: ${o.message}. Skipping audit hook registration.`),!1}return e.hooks??={},e.hooks.UserPromptSubmit??=[],e.hooks.UserPromptSubmit.some(o=>Array.isArray(o?.hooks)&&o.hooks.some(t=>t?.type==="command"&&t?.command===m))?!1:(e.hooks.UserPromptSubmit.push({matcher:"*",hooks:[{type:"command",command:m}]}),y(e),!0)}function Y(){let e;try{e=p()}catch{return!1}if(!e?.hooks?.UserPromptSubmit)return!1;let r=!1;return e.hooks.UserPromptSubmit=e.hooks.UserPromptSubmit.map(o=>{if(!Array.isArray(o?.hooks))return o;const t=o.hooks.filter(s=>!(s?.type==="command"&&s?.command===m));return t.length!==o.hooks.length?(r=!0,{...o,hooks:t}):o}).filter(o=>Array.isArray(o?.hooks)&&o.hooks.length>0),e.hooks.UserPromptSubmit.length===0&&delete e.hooks.UserPromptSubmit,Object.keys(e.hooks).length===0&&delete e.hooks,r&&y(e),r}function Z(e=N){try{if(!n.existsSync(e))return!1;const r=n.readFileSync(e,"utf-8");if(!r.trim())return!1;const o=JSON.parse(r);if(!o||typeof o!="object")return!1;const t=o.mcpServers;return!t||typeof t!="object"||Array.isArray(t)?!1:G in t}catch{return!1}}function ee(e=w){try{if(!n.existsSync(e))return!1;const r=n.readFileSync(e,"utf-8"),o=/^\s*\[mcp_servers\.borg\]\s*$/m.test(r),t=new RegExp(`^\\s*${A}\\s*=\\s*"codex"\\s*$`,"m").test(r),s=new RegExp(`^\\s*${E}\\s*=\\s*"1"\\s*$`,"m").test(r);return o&&(t||s)}catch{return!1}}function re(){return i.join(j,"index.js")}function oe(){try{try{d("claude mcp remove --scope user borg",{stdio:"ignore"})}catch{}d("claude mcp add --scope user borg borg-mcp",{stdio:"inherit",env:{...process.env,BORG_API_URL:process.env.BORG_API_URL||"https://api.borgmcp.ai"}})}catch(e){throw e.message?.includes("command not found")?new Error("Claude CLI not found. Please install Claude Code first."):new Error(`Failed to add MCP server: ${e.message}`)}}function te(){try{try{d("codex mcp remove borg",{stdio:"ignore"})}catch{}const e=process.env.BORG_API_URL||"https://api.borgmcp.ai",r=U(process.env,"codex");d("codex mcp add borg --env BORG_API_URL="+_(e)+` --env ${A}=codex -- borg-mcp`,{stdio:"inherit",env:{...r,BORG_API_URL:e}})}catch(e){throw e.message?.includes("command not found")?new Error("Codex CLI not found. Please install Codex first."):new Error(`Failed to add MCP server to Codex: ${e.message}`)}}function _(e){return`'${e.replace(/'/g,"'\\''")}'`}function O(e,r,o={}){let t;try{t=S(u)}catch(l){return console.error(`\u26A0 Could not parse ${u}: ${l.message}. Skipping Codex hook registration.`),!1}t.hooks??={},t.hooks[e]??=[];const s=t.hooks[e];if(!Array.isArray(s)||s.some(l=>Array.isArray(l?.hooks)&&l.hooks.some(g=>g?.type==="command"&&g?.command===r)))return!1;const c={hooks:[{type:"command",command:r}]};return o.matcher&&(c.matcher=o.matcher),typeof o.timeout=="number"&&(c.hooks[0].timeout=o.timeout),s.push(c),x(u,t),!0}function se(){return O("SessionStart",a,{matcher:"startup|resume",timeout:30})}function ne(){return O("UserPromptSubmit",m,{timeout:10})}function b(e,r,o=u){try{const s=S(o)?.hooks?.[e];return Array.isArray(s)?s.some(k=>Array.isArray(k?.hooks)&&k.hooks.some(c=>c?.type==="command"&&c?.command===r)):!1}catch{return!1}}function ie(e=u){return b("SessionStart",a,e)}function ae(e=u){return b("UserPromptSubmit",m,e)}function ce(e=I){try{if(!n.existsSync(e))return!1;const r=n.readFileSync(e,"utf-8");if(!r.trim())return!1;const o=JSON.parse(r);if(!o||typeof o!="object")return!1;const t=o.mcp?.borg;return!t||typeof t!="object"?!1:t.type==="local"}catch{return!1}}function me(){try{const e=process.env.BORG_API_URL||"https://api.borgmcp.ai";d(`opencode mcp add borg --env BORG_SESSION=1 --env BORG_AGENT_KIND=opencode --env BORG_OPENCODE=1 --env BORG_API_URL=${_(e)} -- borg-mcp`,{stdio:"inherit"})}catch(e){throw e.message?.includes("command not found")?new Error("opencode CLI not found. Please install opencode first."):new Error(`Failed to add MCP server to opencode: ${e.message}`)}}export{te as addCodexMcpServer,se as addCodexSessionStartHook,ne as addCodexUserPromptSubmitHook,oe as addMcpServer,me as addOpenCodeMcpServer,V as addProjectSessionStartHook,K as addSessionStartHook,z as addUserPromptSubmitHook,re as getBinaryPath,b as isCodexHookRegistered,ee as isCodexMcpServerConfigured,ie as isCodexSessionStartHookRegistered,ae as isCodexUserPromptSubmitHookRegistered,Z as isMcpServerConfigured,ce as isOpenCodeMcpServerConfigured,X as isProjectSessionStartHookRegistered,W as isSessionStartHookRegistered,Q as isUserPromptSubmitHookRegistered,q as removeSessionStartHook,Y as removeUserPromptSubmitHook};
@@ -48,7 +48,7 @@ export interface InboxLivenessDeps {
48
48
  * 2. a heartbeat sidecar present AND fresh (within the stale threshold)
49
49
  * 3. a pidfile whose PID is alive (kill-0)
50
50
  */
51
- export declare function isInboxLive(inboxPath: string, deps: InboxLivenessDeps): boolean;
51
+ export declare function isInboxLive(inboxPath: string, deps: InboxLivenessDeps, monitorStateRoot?: string | null): boolean;
52
52
  export interface OrphanGcDeps {
53
53
  /** list the `<drone_id>.log` entries in the cube inbox dir (excludes sidecars). */
54
54
  listInboxLogs: (cubeInboxDir: string) => OrphanInboxEntry[];
@@ -60,14 +60,19 @@ export interface OrphanGcDeps {
60
60
  }
61
61
  /**
62
62
  * Wire the GC for one cube dir: select orphans (excluding the just-assimilated
63
- * drone), then unlink each orphan's triplet (`.log` + `.monitor.pid` +
64
- * `.monitor.heartbeat`). Best-effort every unlink is swallowed per-file so a
63
+ * drone), then unlink each orphan's inbox plus its derived worktree-runtime
64
+ * PID/heartbeat state. Legacy inbox-adjacent artifacts are intentionally left
65
+ * for explicit operator cleanup: GC must never race an old binary that does
66
+ * not participate in modern state serialization. Best-effort — every unlink is
67
+ * swallowed per-file so a
65
68
  * single failure never aborts the sweep or blocks assimilate. Returns the paths
66
69
  * actually removed. Never rmdir's the cube dir (a live sibling may use it).
67
70
  */
68
71
  export declare function gcOrphanInboxesForCube(args: {
69
72
  cubeInboxDir: string;
70
73
  selfDroneId: string;
74
+ /** Explicit current-worktree root; legacy sidecars remain untouched. */
75
+ monitorStateRoot?: string | null;
71
76
  deps: OrphanGcDeps;
72
77
  }): string[];
73
78
  /** Real FS/process-backed deps, reusing the #795/#822 primitives. */
@@ -1 +1 @@
1
- import{spawnSync as d}from"node:child_process";import{readFileSync as m,readdirSync as p,statSync as c}from"node:fs";import{join as h}from"node:path";import{pidfilePathFor as l,heartbeatPathFor as f,HEARTBEAT_STALE_MS as M}from"./inbox-monitor.js";const y=720*60*60*1e3;function b(n){const{entries:t,isLive:e,droneState:r,now:o,staleMs:a}=n;return t.filter(i=>e(i.inboxPath)||o-i.mtimeMs<a?!1:r(i.droneId)!=="present")}function P(n,t){if(t.pgrepTailMatch(n))return!0;const e=t.readHeartbeatMtimeMs(f(n)),r=t.heartbeatStaleMs??M;if(e!==null&&t.now-e<r)return!0;const o=t.readPidfilePid(l(n));return!!(o!==null&&t.isAlive(o))}function L(n){const{cubeInboxDir:t,selfDroneId:e,deps:r}=n,o=r.listInboxLogs(t).filter(s=>s.droneId!==e),a=b({entries:o,isLive:r.isLive,droneState:r.droneState,now:r.now,staleMs:r.staleMs}),i=[];for(const s of a)for(const u of[s.inboxPath,l(s.inboxPath),f(s.inboxPath)])try{r.unlink(u),i.push(u)}catch{}return i}function v(n=Date.now()){return{pgrepTailMatch:t=>{try{const e=d("pgrep",["-f",t],{encoding:"utf-8",timeout:2e3});return e.error?!1:e.status===0&&e.stdout.trim().length>0}catch{return!1}},readHeartbeatMtimeMs:t=>{try{return c(t).mtimeMs}catch{return null}},readPidfilePid:t=>{try{const e=Number.parseInt(m(t,"utf8").trim(),10);return Number.isNaN(e)?null:e}catch{return null}},isAlive:t=>{try{return process.kill(t,0),!0}catch(e){return e?.code==="EPERM"}},now:n}}function A(n){let t;try{t=p(n)}catch{return[]}const e=[];for(const r of t){if(!r.endsWith(".log"))continue;const o=h(n,r);try{e.push({droneId:r.slice(0,-4),inboxPath:o,mtimeMs:c(o).mtimeMs})}catch{}}return e}export{y as ORPHAN_INBOX_STALE_MS,v as defaultInboxLivenessDeps,A as defaultListInboxLogs,L as gcOrphanInboxesForCube,P as isInboxLive,b as selectOrphanInboxes};
1
+ import{spawnSync as p}from"node:child_process";import{readFileSync as m,readdirSync as M,statSync as l}from"node:fs";import{join as b}from"node:path";import{pidfilePathFor as f,heartbeatPathFor as h,legacyPidfilePathFor as P,legacyHeartbeatPathFor as S,HEARTBEAT_STALE_MS as g}from"./inbox-monitor.js";const v=720*60*60*1e3;function x(r){const{entries:t,isLive:e,droneState:o,now:n,staleMs:a}=r;return t.filter(s=>e(s.inboxPath)||n-s.mtimeMs<a?!1:o(s.droneId)!=="present")}function A(r,t,e){if(t.pgrepTailMatch(r))return!0;const o=t.heartbeatStaleMs??g,n=[h(r,e)],a=[f(r,e)];e&&(n.push(S(r)),a.push(P(r)));for(const s of new Set(n)){const i=t.readHeartbeatMtimeMs(s);if(i!==null&&t.now-i<o)return!0}for(const s of new Set(a)){const i=t.readPidfilePid(s);if(i!==null&&t.isAlive(i))return!0}return!1}function E(r){const{cubeInboxDir:t,selfDroneId:e,monitorStateRoot:o,deps:n}=r,a=n.listInboxLogs(t).filter(c=>c.droneId!==e),s=x({entries:a,isLive:n.isLive,droneState:n.droneState,now:n.now,staleMs:n.staleMs}),i=[];for(const c of s){const d=[f(c.inboxPath,o),h(c.inboxPath,o)];for(const u of new Set([c.inboxPath,...d]))try{n.unlink(u),i.push(u)}catch{}}return i}function F(r=Date.now()){return{pgrepTailMatch:t=>{try{const e=p("pgrep",["-f",t],{encoding:"utf-8",timeout:2e3});return e.error?!1:e.status===0&&e.stdout.trim().length>0}catch{return!1}},readHeartbeatMtimeMs:t=>{try{return l(t).mtimeMs}catch{return null}},readPidfilePid:t=>{try{const e=Number.parseInt(m(t,"utf8").trim(),10);return Number.isNaN(e)?null:e}catch{return null}},isAlive:t=>{try{return process.kill(t,0),!0}catch(e){return e?.code==="EPERM"}},now:r}}function N(r){let t;try{t=M(r)}catch{return[]}const e=[];for(const o of t){if(!o.endsWith(".log"))continue;const n=b(r,o);try{e.push({droneId:o.slice(0,-4),inboxPath:n,mtimeMs:l(n).mtimeMs})}catch{}}return e}export{v as ORPHAN_INBOX_STALE_MS,F as defaultInboxLivenessDeps,N as defaultListInboxLogs,E as gcOrphanInboxesForCube,A as isInboxLive,x as selectOrphanInboxes};
@@ -43,10 +43,9 @@ export interface HealthPayload {
43
43
  wake_armed: boolean;
44
44
  /**
45
45
  * gh#634: the drone's agent_kind from LIVE runtime detection
46
- * (resolveSessionAgentKind). agent_kind is written only by the assimilate
47
- * INSERT (no update path), so a relaunched drone's recorded agent_kind is
48
- * frozen at first-join. Beating it self-heals the column via recordDroneHealth
49
- * COALESCE — same always-running-child self-heal pattern as wake_armed.
46
+ * (resolveSessionAgentKind). Assimilation captures agent_kind only at join,
47
+ * so a relaunch of an existing seat needs this always-running child to
48
+ * refresh the recorded value through recordDroneHealth's COALESCE update.
50
49
  */
51
50
  agent_kind: 'claude' | 'codex' | 'opencode';
52
51
  /**
@@ -1 +1 @@
1
- let a=null,i=null,l=null;function x(e=new Date){a=e}function y(){return a}function k(){return i}function v(){return l}function A(){a=null,i=null,l=null}function u(e,t,n,o,r,c){return{sse_connected:e,inbox_monitor_armed:t!==!1,wake_armed:n!==!1,agent_kind:o,hostname:r,version:c,last_event_at:a?a.toISOString():null}}async function d(e,t,n){try{const o=await n.getToken();await n.fetchImpl(`${e.apiUrl}/api/drone/health`,{method:"POST",headers:{Authorization:`Bearer ${o}`,"X-Drone-Session":e.sessionToken,"Content-Type":"application/json"},body:JSON.stringify(t)})}catch{}}async function f(e,t){const n=u(t.sseConnected,t.inboxMonitorHealthy,t.wakeArmed,t.agentKind,t.hostname,t.version);await d(e,n,t)}async function m(e){try{const t=await e.getActiveCube();if(!t)return;const n=e.getStreamConnected(),o=e.checkMonitor(e.getInboxPath(t));i=o;const r=e.resolveAgentKind(),c=r==="opencode"?await e.probeOpenCodeDrone?.(t)??null:e.isCodexRemoteWake()?await e.probeBridgeArmed(t):o;l=c;const h=e.resolveHostname(),s=e.resolveVersion();await f(t,{sseConnected:n,inboxMonitorHealthy:o,wakeArmed:c,agentKind:r,hostname:h,version:s,fetchImpl:e.fetchImpl,getToken:e.getToken})}catch{}}const g=6e4;function H(e,t=g){const n=setInterval(()=>{m(e)},t);return n.unref?.(),n}export{g as HEALTH_BEAT_INTERVAL_MS,A as __resetHealthBeatStateForTest,u as buildHealthPayload,f as emitHealthBeat,k as getCachedMonitorHealthy,v as getCachedWakeArmed,y as getLastEventReceivedAt,d as postHealthBeat,x as recordEventReceipt,m as runHealthBeatOnce,H as startHealthBeatTick};
1
+ let r=null,i=null,l=null;function g(e=new Date){r=e}function v(){return r}function y(){return i}function k(){return l}function A(){r=null,i=null,l=null}function d(e,t,n,o,a,c){return{sse_connected:e,inbox_monitor_armed:t!==!1,wake_armed:n!==!1,agent_kind:o,hostname:a,version:c,last_event_at:r?r.toISOString():null}}async function f(e,t,n){try{const o=await n.getToken();await n.fetchImpl(`${e.apiUrl}/api/drone/health`,{method:"POST",headers:{Authorization:`Bearer ${o}`,"X-Drone-Session":e.sessionToken,"Content-Type":"application/json"},body:JSON.stringify(t)})}catch{}}async function m(e,t){const n=d(t.sseConnected,t.inboxMonitorHealthy,t.wakeArmed,t.agentKind,t.hostname,t.version);await f(e,n,t)}async function h(e){try{const t=await e.getActiveCube();if(!t)return;const n=e.getStreamConnected(),o=e.checkMonitor(e.getInboxPath(t));i=o;const a=e.resolveAgentKind(),c=a==="opencode"?await e.probeOpenCodeDrone?.(t)??null:a==="codex"?e.isCodexRemoteWake()?await e.probeBridgeArmed(t):!1:o;l=c;const s=e.resolveHostname(),u=e.resolveVersion();await m(t,{sseConnected:n,inboxMonitorHealthy:o,wakeArmed:c,agentKind:a,hostname:s,version:u,fetchImpl:e.fetchImpl,getToken:e.getToken})}catch{}}const x=6e4;function H(e,t=x){h(e);const n=setInterval(()=>{h(e)},t);return n.unref?.(),n}export{x as HEALTH_BEAT_INTERVAL_MS,A as __resetHealthBeatStateForTest,d as buildHealthPayload,m as emitHealthBeat,y as getCachedMonitorHealthy,k as getCachedWakeArmed,v as getLastEventReceivedAt,f as postHealthBeat,g as recordEventReceipt,h as runHealthBeatOnce,H as startHealthBeatTick};