borgmcp 1.1.15 → 2.0.2
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/CONTRIBUTING.md +15 -0
- package/LICENSE +201 -33
- package/NOTICE +2 -0
- package/README.md +85 -36
- package/SECURITY.md +7 -0
- package/dist/agent-runtime.d.ts.map +1 -0
- package/dist/agent-runtime.js +63 -1
- package/dist/agent-runtime.js.map +1 -0
- package/dist/assimilate-cmd.d.ts +96 -17
- package/dist/assimilate-cmd.d.ts.map +1 -0
- package/dist/assimilate-cmd.js +1396 -48
- package/dist/assimilate-cmd.js.map +1 -0
- package/dist/assimilate-deps.d.ts.map +1 -0
- package/dist/assimilate-deps.js +384 -3
- package/dist/assimilate-deps.js.map +1 -0
- package/dist/assimilate-guard.d.ts +5 -7
- package/dist/assimilate-guard.d.ts.map +1 -0
- package/dist/assimilate-guard.js +60 -2
- package/dist/assimilate-guard.js.map +1 -0
- package/dist/assimilate-welcome.d.ts +1 -1
- package/dist/assimilate-welcome.d.ts.map +1 -0
- package/dist/assimilate-welcome.js +32 -2
- package/dist/assimilate-welcome.js.map +1 -0
- package/dist/auth-env.d.ts +7 -50
- package/dist/auth-env.d.ts.map +1 -0
- package/dist/auth-env.js +22 -1
- package/dist/auth-env.js.map +1 -0
- package/dist/backends/launch-all-pastelist.d.ts.map +1 -0
- package/dist/backends/launch-all-pastelist.js +11 -4
- package/dist/backends/launch-all-pastelist.js.map +1 -0
- package/dist/backends/launch-all-tmux.d.ts.map +1 -0
- package/dist/backends/launch-all-tmux.js +49 -1
- package/dist/backends/launch-all-tmux.js.map +1 -0
- package/dist/backends/launch-all-windows.d.ts.map +1 -0
- package/dist/backends/launch-all-windows.js +86 -12
- package/dist/backends/launch-all-windows.js.map +1 -0
- package/dist/bare-launch-menu.d.ts.map +1 -0
- package/dist/bare-launch-menu.js +83 -6
- package/dist/bare-launch-menu.js.map +1 -0
- package/dist/claude-launch-args.d.ts.map +1 -0
- package/dist/claude-launch-args.js +34 -1
- package/dist/claude-launch-args.js.map +1 -0
- package/dist/claude.d.ts.map +1 -0
- package/dist/claude.js +486 -14
- package/dist/claude.js.map +1 -0
- package/dist/cleanup-cmd.d.ts +5 -5
- package/dist/cleanup-cmd.d.ts.map +1 -0
- package/dist/cleanup-cmd.js +453 -14
- package/dist/cleanup-cmd.js.map +1 -0
- package/dist/clear-rewake-core.d.ts.map +1 -0
- package/dist/clear-rewake-core.js +23 -2
- package/dist/clear-rewake-core.js.map +1 -0
- package/dist/clear-rewake.d.ts.map +1 -0
- package/dist/clear-rewake.js +19 -1
- package/dist/clear-rewake.js.map +1 -0
- package/dist/cli-help.d.ts +5 -0
- package/dist/cli-help.d.ts.map +1 -0
- package/dist/cli-help.js +104 -56
- package/dist/cli-help.js.map +1 -0
- package/dist/cli-platform.d.ts.map +1 -0
- package/dist/cli-platform.js +106 -4
- package/dist/cli-platform.js.map +1 -0
- package/dist/cli-tool-approval.d.ts +84 -0
- package/dist/cli-tool-approval.d.ts.map +1 -0
- package/dist/cli-tool-approval.js +583 -0
- package/dist/cli-tool-approval.js.map +1 -0
- package/dist/codex-app-server.d.ts.map +1 -0
- package/dist/codex-app-server.js +228 -4
- package/dist/codex-app-server.js.map +1 -0
- package/dist/codex-app-wake.d.ts +9 -11
- package/dist/codex-app-wake.d.ts.map +1 -0
- package/dist/codex-app-wake.js +436 -2
- package/dist/codex-app-wake.js.map +1 -0
- package/dist/codex-launch.d.ts.map +1 -0
- package/dist/codex-launch.js +102 -2
- package/dist/codex-launch.js.map +1 -0
- package/dist/codex-remote.d.ts +6 -1
- package/dist/codex-remote.d.ts.map +1 -0
- package/dist/codex-remote.js +394 -1
- package/dist/codex-remote.js.map +1 -0
- package/dist/codex-wake-resolve.d.ts +4 -4
- package/dist/codex-wake-resolve.d.ts.map +1 -0
- package/dist/codex-wake-resolve.js +127 -1
- package/dist/codex-wake-resolve.js.map +1 -0
- package/dist/config-utils.d.ts +2 -2
- package/dist/config-utils.d.ts.map +1 -0
- package/dist/config-utils.js +766 -3
- package/dist/config-utils.js.map +1 -0
- package/dist/config.d.ts +98 -61
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +426 -1
- package/dist/config.js.map +1 -0
- package/dist/console-prefix.d.ts.map +1 -0
- package/dist/console-prefix.js +96 -1
- package/dist/console-prefix.js.map +1 -0
- package/dist/cube-name.d.ts.map +1 -0
- package/dist/cube-name.js +67 -1
- package/dist/cube-name.js.map +1 -0
- package/dist/cubes.d.ts +126 -18
- package/dist/cubes.d.ts.map +1 -0
- package/dist/cubes.js +412 -4
- package/dist/cubes.js.map +1 -0
- package/dist/debug.d.ts.map +1 -0
- package/dist/debug.js +71 -1
- package/dist/debug.js.map +1 -0
- package/dist/direct-log.d.ts.map +1 -0
- package/dist/direct-log.js +11 -1
- package/dist/direct-log.js.map +1 -0
- package/dist/docs-sections.d.ts +11 -10
- package/dist/docs-sections.d.ts.map +1 -0
- package/dist/docs-sections.js +113 -3
- package/dist/docs-sections.js.map +1 -0
- package/dist/drone-lifecycle.d.ts +5 -21
- package/dist/drone-lifecycle.d.ts.map +1 -0
- package/dist/drone-lifecycle.js +57 -8
- package/dist/drone-lifecycle.js.map +1 -0
- package/dist/drone-management.d.ts +29 -0
- package/dist/drone-management.d.ts.map +1 -0
- package/dist/drone-management.js +109 -0
- package/dist/drone-management.js.map +1 -0
- package/dist/ensure-mcp-config.d.ts.map +1 -0
- package/dist/ensure-mcp-config.js +36 -1
- package/dist/ensure-mcp-config.js.map +1 -0
- package/dist/evict-drone.d.ts +10 -2
- package/dist/evict-drone.d.ts.map +1 -0
- package/dist/evict-drone.js +60 -1
- package/dist/evict-drone.js.map +1 -0
- package/dist/gc-orphan-inboxes.d.ts.map +1 -0
- package/dist/gc-orphan-inboxes.js +177 -1
- package/dist/gc-orphan-inboxes.js.map +1 -0
- package/dist/inbox-monitor.d.ts +1 -1
- package/dist/inbox-monitor.d.ts.map +1 -0
- package/dist/inbox-monitor.js +856 -3
- package/dist/inbox-monitor.js.map +1 -0
- package/dist/index.d.ts +6 -4
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1034 -37
- package/dist/index.js.map +1 -0
- package/dist/launch-all-cmd.d.ts.map +1 -0
- package/dist/launch-all-cmd.js +377 -37
- package/dist/launch-all-cmd.js.map +1 -0
- package/dist/launch-all-command.d.ts.map +1 -0
- package/dist/launch-all-command.js +24 -1
- package/dist/launch-all-command.js.map +1 -0
- package/dist/launch-all-deps.d.ts +3 -8
- package/dist/launch-all-deps.d.ts.map +1 -0
- package/dist/launch-all-deps.js +99 -1
- package/dist/launch-all-deps.js.map +1 -0
- package/dist/launch-all-discovery.d.ts +1 -0
- package/dist/launch-all-discovery.d.ts.map +1 -0
- package/dist/launch-all-discovery.js +90 -4
- package/dist/launch-all-discovery.js.map +1 -0
- package/dist/launch-all-locks.d.ts.map +1 -0
- package/dist/launch-all-locks.js +54 -1
- package/dist/launch-all-locks.js.map +1 -0
- package/dist/launch-gate.d.ts +2 -2
- package/dist/launch-gate.d.ts.map +1 -0
- package/dist/launch-gate.js +73 -1
- package/dist/launch-gate.js.map +1 -0
- package/dist/lifecycle-log-guard.d.ts.map +1 -0
- package/dist/lifecycle-log-guard.js +93 -2
- package/dist/lifecycle-log-guard.js.map +1 -0
- package/dist/list-roles-render.d.ts.map +1 -0
- package/dist/list-roles-render.js +40 -6
- package/dist/list-roles-render.js.map +1 -0
- package/dist/local-log-routing.d.ts +19 -0
- package/dist/local-log-routing.d.ts.map +1 -0
- package/dist/local-log-routing.js +67 -0
- package/dist/local-log-routing.js.map +1 -0
- package/dist/local-manage-tool-result.d.ts +8 -0
- package/dist/local-manage-tool-result.d.ts.map +1 -0
- package/dist/local-manage-tool-result.js +10 -0
- package/dist/local-manage-tool-result.js.map +1 -0
- package/dist/local-server-cursor.d.ts +34 -0
- package/dist/local-server-cursor.d.ts.map +1 -0
- package/dist/local-server-cursor.js +151 -0
- package/dist/local-server-cursor.js.map +1 -0
- package/dist/log-audit.d.ts.map +1 -0
- package/dist/log-audit.js +194 -3
- package/dist/log-audit.js.map +1 -0
- package/dist/log-stream.d.ts +35 -18
- package/dist/log-stream.d.ts.map +1 -0
- package/dist/log-stream.js +1270 -12
- package/dist/log-stream.js.map +1 -0
- package/dist/model-presets.d.ts.map +1 -0
- package/dist/model-presets.js +19 -1
- package/dist/model-presets.js.map +1 -0
- package/dist/name-validator.d.ts.map +1 -0
- package/dist/name-validator.js +22 -1
- package/dist/name-validator.js.map +1 -0
- package/dist/opencode-drone.d.ts.map +1 -0
- package/dist/opencode-drone.js +438 -4
- package/dist/opencode-drone.js.map +1 -0
- package/dist/opencode-plugin.d.ts +1 -1
- package/dist/opencode-plugin.d.ts.map +1 -0
- package/dist/opencode-plugin.js +29 -4
- package/dist/opencode-plugin.js.map +1 -0
- package/dist/parse-assimilate-args.d.ts +1 -1
- package/dist/parse-assimilate-args.d.ts.map +1 -0
- package/dist/parse-assimilate-args.js +125 -1
- package/dist/parse-assimilate-args.js.map +1 -0
- package/dist/parse-launch-all-args.d.ts.map +1 -0
- package/dist/parse-launch-all-args.js +70 -1
- package/dist/parse-launch-all-args.js.map +1 -0
- package/dist/postinstall-banner.d.ts.map +1 -0
- package/dist/postinstall-banner.js +27 -2
- package/dist/postinstall-banner.js.map +1 -0
- package/dist/postinstall.d.ts.map +1 -0
- package/dist/postinstall.js +32 -5
- package/dist/postinstall.js.map +1 -0
- package/dist/readiness-probe.d.ts.map +1 -0
- package/dist/readiness-probe.js +9 -1
- package/dist/readiness-probe.js.map +1 -0
- package/dist/regen-format.d.ts.map +1 -0
- package/dist/regen-format.js +545 -13
- package/dist/regen-format.js.map +1 -0
- package/dist/regen.d.ts.map +1 -0
- package/dist/regen.js +141 -5
- package/dist/regen.js.map +1 -0
- package/dist/remote-client.d.ts +88 -173
- package/dist/remote-client.d.ts.map +1 -0
- package/dist/remote-client.js +997 -1
- package/dist/remote-client.js.map +1 -0
- package/dist/reset-local-seat-cmd.d.ts +50 -0
- package/dist/reset-local-seat-cmd.d.ts.map +1 -0
- package/dist/reset-local-seat-cmd.js +169 -0
- package/dist/reset-local-seat-cmd.js.map +1 -0
- package/dist/role-match.d.ts.map +1 -0
- package/dist/role-match.js +23 -1
- package/dist/role-match.js.map +1 -0
- package/dist/role-resolver.d.ts.map +1 -0
- package/dist/role-resolver.js +60 -1
- package/dist/role-resolver.js.map +1 -0
- package/dist/roster-render.d.ts.map +1 -0
- package/dist/roster-render.js +121 -3
- package/dist/roster-render.js.map +1 -0
- package/dist/seat-probe.d.ts +32 -13
- package/dist/seat-probe.d.ts.map +1 -0
- package/dist/seat-probe.js +78 -1
- package/dist/seat-probe.js.map +1 -0
- package/dist/seat-store.d.ts +76 -0
- package/dist/seat-store.d.ts.map +1 -0
- package/dist/seat-store.js +309 -0
- package/dist/seat-store.js.map +1 -0
- package/dist/seats.d.ts +375 -0
- package/dist/seats.d.ts.map +1 -0
- package/dist/seats.js +670 -0
- package/dist/seats.js.map +1 -0
- package/dist/self-path.d.ts +27 -0
- package/dist/self-path.d.ts.map +1 -0
- package/dist/self-path.js +43 -0
- package/dist/self-path.js.map +1 -0
- package/dist/server-endpoint.d.ts +9 -0
- package/dist/server-endpoint.d.ts.map +1 -0
- package/dist/server-endpoint.js +38 -0
- package/dist/server-endpoint.js.map +1 -0
- package/dist/server-errors.d.ts +50 -0
- package/dist/server-errors.d.ts.map +1 -0
- package/dist/server-errors.js +79 -0
- package/dist/server-errors.js.map +1 -0
- package/dist/server-handshake.d.ts +197 -0
- package/dist/server-handshake.d.ts.map +1 -0
- package/dist/server-handshake.js +533 -0
- package/dist/server-handshake.js.map +1 -0
- package/dist/server-response.d.ts +3 -0
- package/dist/server-response.d.ts.map +1 -0
- package/dist/server-response.js +67 -0
- package/dist/server-response.js.map +1 -0
- package/dist/server-trust.d.ts +14 -0
- package/dist/server-trust.d.ts.map +1 -0
- package/dist/server-trust.js +225 -0
- package/dist/server-trust.js.map +1 -0
- package/dist/session-continuity.d.ts +33 -0
- package/dist/session-continuity.d.ts.map +1 -0
- package/dist/session-continuity.js +220 -0
- package/dist/session-continuity.js.map +1 -0
- package/dist/setup-confirm.d.ts +3 -3
- package/dist/setup-confirm.d.ts.map +1 -0
- package/dist/setup-confirm.js +126 -2
- package/dist/setup-confirm.js.map +1 -0
- package/dist/setup.d.ts +5 -3
- package/dist/setup.d.ts.map +1 -0
- package/dist/setup.js +169 -46
- package/dist/setup.js.map +1 -0
- package/dist/shell-escape.d.ts.map +1 -0
- package/dist/shell-escape.js +22 -1
- package/dist/shell-escape.js.map +1 -0
- package/dist/spawn.d.ts +2 -2
- package/dist/spawn.d.ts.map +1 -0
- package/dist/spawn.js +29 -10
- package/dist/spawn.js.map +1 -0
- package/dist/startup-services.d.ts +0 -1
- package/dist/startup-services.d.ts.map +1 -0
- package/dist/startup-services.js +26 -1
- package/dist/startup-services.js.map +1 -0
- package/dist/stream-owner.d.ts +6 -2
- package/dist/stream-owner.d.ts.map +1 -0
- package/dist/stream-owner.js +547 -2
- package/dist/stream-owner.js.map +1 -0
- package/dist/stream-status.d.ts +1 -1
- package/dist/stream-status.d.ts.map +1 -0
- package/dist/stream-status.js +268 -3
- package/dist/stream-status.js.map +1 -0
- package/dist/sync-roles-render.d.ts.map +1 -0
- package/dist/sync-roles-render.js +118 -3
- package/dist/sync-roles-render.js.map +1 -0
- package/dist/sync.d.ts +1 -1
- package/dist/sync.d.ts.map +1 -0
- package/dist/sync.js +286 -22
- package/dist/sync.js.map +1 -0
- package/dist/terminal-title.d.ts.map +1 -0
- package/dist/terminal-title.js +68 -1
- package/dist/terminal-title.js.map +1 -0
- package/dist/token-store.d.ts +17 -111
- package/dist/token-store.d.ts.map +1 -0
- package/dist/token-store.js +81 -1
- package/dist/token-store.js.map +1 -0
- package/dist/tool-manifest.d.ts +4 -8
- package/dist/tool-manifest.d.ts.map +1 -0
- package/dist/tool-manifest.js +554 -1
- package/dist/tool-manifest.js.map +1 -0
- package/dist/tool-scope.d.ts +8 -12
- package/dist/tool-scope.d.ts.map +1 -0
- package/dist/tool-scope.js +97 -1
- package/dist/tool-scope.js.map +1 -0
- package/dist/types.d.ts +1 -5
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/unknown-subcommand.d.ts +1 -1
- package/dist/unknown-subcommand.d.ts.map +1 -0
- package/dist/unknown-subcommand.js +37 -1
- package/dist/unknown-subcommand.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +78 -2
- package/dist/version.js.map +1 -0
- package/dist/working-repo.d.ts.map +1 -0
- package/dist/working-repo.js +81 -1
- package/dist/working-repo.js.map +1 -0
- package/dist/worktree-lifecycle.d.ts.map +1 -0
- package/dist/worktree-lifecycle.js +205 -2
- package/dist/worktree-lifecycle.js.map +1 -0
- package/docs/EXTRACTION_PROVENANCE.md +42 -0
- package/docs/LOCAL_SERVER.md +103 -0
- package/docs/RELEASING.md +129 -0
- package/package.json +52 -34
- package/src/agent-runtime.ts +69 -0
- package/src/assimilate-cmd.ts +1864 -0
- package/src/assimilate-deps.ts +444 -0
- package/src/assimilate-guard.ts +84 -0
- package/src/assimilate-welcome.ts +39 -0
- package/src/auth-env.ts +21 -0
- package/src/backends/launch-all-pastelist.ts +18 -0
- package/src/backends/launch-all-tmux.ts +75 -0
- package/src/backends/launch-all-windows.ts +110 -0
- package/src/bare-launch-menu.ts +148 -0
- package/src/claude-launch-args.ts +35 -0
- package/src/claude.ts +561 -0
- package/src/cleanup-cmd.ts +581 -0
- package/src/clear-rewake-core.ts +34 -0
- package/src/clear-rewake.ts +18 -0
- package/src/cli-help.ts +116 -0
- package/src/cli-platform.ts +127 -0
- package/src/cli-tool-approval.ts +716 -0
- package/src/codex-app-server.ts +238 -0
- package/src/codex-app-wake.ts +524 -0
- package/src/codex-launch.ts +140 -0
- package/src/codex-remote.ts +500 -0
- package/src/codex-wake-resolve.ts +155 -0
- package/src/config-utils.ts +811 -0
- package/src/config.ts +595 -0
- package/src/console-prefix.ts +99 -0
- package/src/cube-name.ts +69 -0
- package/src/cubes.ts +583 -0
- package/src/debug.ts +73 -0
- package/src/direct-log.ts +9 -0
- package/src/docs-sections.ts +129 -0
- package/src/drone-lifecycle.ts +62 -0
- package/src/drone-management.ts +162 -0
- package/src/ensure-mcp-config.ts +54 -0
- package/src/evict-drone.ts +82 -0
- package/src/gc-orphan-inboxes.ts +229 -0
- package/src/inbox-monitor.ts +1005 -0
- package/src/index.ts +1201 -0
- package/src/launch-all-cmd.ts +450 -0
- package/src/launch-all-command.ts +30 -0
- package/src/launch-all-deps.ts +188 -0
- package/src/launch-all-discovery.ts +118 -0
- package/src/launch-all-locks.ts +74 -0
- package/src/launch-gate.ts +80 -0
- package/src/lifecycle-log-guard.ts +144 -0
- package/src/list-roles-render.ts +52 -0
- package/src/local-log-routing.ts +106 -0
- package/src/local-manage-tool-result.ts +10 -0
- package/src/local-server-cursor.ts +188 -0
- package/src/log-audit.ts +207 -0
- package/src/log-stream.ts +1567 -0
- package/src/model-presets.ts +22 -0
- package/src/name-validator.ts +23 -0
- package/src/opencode-drone.ts +528 -0
- package/src/opencode-plugin.ts +38 -0
- package/src/parse-assimilate-args.ts +125 -0
- package/src/parse-launch-all-args.ts +100 -0
- package/src/postinstall-banner.ts +37 -0
- package/src/postinstall.ts +36 -0
- package/src/readiness-probe.ts +12 -0
- package/src/regen-format.ts +651 -0
- package/src/regen.ts +150 -0
- package/src/remote-client.ts +1457 -0
- package/src/reset-local-seat-cmd.ts +227 -0
- package/src/role-match.ts +29 -0
- package/src/role-resolver.ts +93 -0
- package/src/roster-render.ts +196 -0
- package/src/seat-probe.ts +115 -0
- package/src/seat-store.ts +333 -0
- package/src/seats.ts +911 -0
- package/src/self-path.ts +51 -0
- package/src/server-endpoint.ts +38 -0
- package/src/server-errors.ts +95 -0
- package/src/server-handshake.ts +816 -0
- package/src/server-response.ts +76 -0
- package/src/server-trust.ts +245 -0
- package/src/session-continuity.ts +280 -0
- package/src/setup-confirm.ts +171 -0
- package/src/setup.ts +186 -0
- package/src/shell-escape.ts +22 -0
- package/src/spawn.ts +31 -0
- package/src/startup-services.ts +36 -0
- package/src/stream-owner.ts +686 -0
- package/src/stream-status.ts +337 -0
- package/src/sync-roles-render.ts +163 -0
- package/src/sync.ts +363 -0
- package/src/terminal-title.ts +76 -0
- package/src/token-store.ts +102 -0
- package/src/tool-manifest.ts +610 -0
- package/src/tool-scope.ts +110 -0
- package/src/types.ts +5 -0
- package/src/unknown-subcommand.ts +35 -0
- package/src/version.ts +82 -0
- package/src/working-repo.ts +99 -0
- package/src/worktree-lifecycle.ts +254 -0
- package/dist/auth-recovery.d.ts +0 -33
- package/dist/auth-recovery.js +0 -1
- package/dist/auth.d.ts +0 -103
- package/dist/auth.js +0 -39
- package/dist/device-auth.d.ts +0 -75
- package/dist/device-auth.js +0 -1
- package/dist/drone-address.d.ts +0 -26
- package/dist/drone-address.js +0 -1
- package/dist/get-started.d.ts +0 -20
- package/dist/get-started.js +0 -2
- package/dist/health-beat.d.ts +0 -134
- package/dist/health-beat.js +0 -1
- package/dist/role-section.d.ts +0 -110
- package/dist/role-section.js +0 -8
- package/dist/setup-action.d.ts +0 -20
- package/dist/setup-action.js +0 -1
- package/dist/stale-version-check.d.ts +0 -40
- package/dist/stale-version-check.js +0 -1
- package/dist/subscription-retry.d.ts +0 -40
- package/dist/subscription-retry.js +0 -1
- package/dist/templates.d.ts +0 -107
- package/dist/templates.js +0 -359
- package/dist/token-crypto.d.ts +0 -50
- package/dist/token-crypto.js +0 -1
|
@@ -0,0 +1,716 @@
|
|
|
1
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
2
|
+
import { chmod, copyFile, mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { homedir, tmpdir } from 'node:os';
|
|
4
|
+
import { dirname, join, resolve } from 'node:path';
|
|
5
|
+
import type { BorgCli } from './cubes.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Client #20: the narrow coordination surface a borg-launched agent needs
|
|
9
|
+
* without an approval round-trip. Keep the direct-tool list deliberately
|
|
10
|
+
* smaller than the complete Borg MCP surface. borg_tool's transitive scope is
|
|
11
|
+
* disclosed separately before consent.
|
|
12
|
+
*/
|
|
13
|
+
export const BORG_COORDINATION_TOOLS = [
|
|
14
|
+
'regen',
|
|
15
|
+
'log',
|
|
16
|
+
'read-log',
|
|
17
|
+
'roster',
|
|
18
|
+
'ack',
|
|
19
|
+
'stream-status',
|
|
20
|
+
'whoami',
|
|
21
|
+
// Required by the canonical lean orientation before acting/after compaction.
|
|
22
|
+
'cube',
|
|
23
|
+
'role',
|
|
24
|
+
'playbook',
|
|
25
|
+
'tool',
|
|
26
|
+
'describe-tool',
|
|
27
|
+
] as const;
|
|
28
|
+
|
|
29
|
+
export const CODEX_BORG_COORDINATION_TOOLS = BORG_COORDINATION_TOOLS.map(
|
|
30
|
+
(name) => `borg:${name}`
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
// OpenCode sanitizes MCP tool names as <server>_<raw tool name>, preserving
|
|
34
|
+
// hyphens. The Borg raw tool prefix is itself `borg_`.
|
|
35
|
+
export const OPENCODE_BORG_COORDINATION_TOOLS = BORG_COORDINATION_TOOLS.map(
|
|
36
|
+
(name) => `borg_borg_${name}`
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
export const BORG_DISPATCHER_APPROVAL_DISCLOSURE =
|
|
40
|
+
'This set includes borg_tool: approving the dispatcher also approves any Borg operation invoked through it.';
|
|
41
|
+
|
|
42
|
+
type ApprovalAction = 'auto' | 'prompt' | 'writes' | 'approve';
|
|
43
|
+
type OpenCodePermissionAction = 'allow' | 'ask' | 'deny';
|
|
44
|
+
|
|
45
|
+
export interface ApprovalInspection {
|
|
46
|
+
restrictiveTools: string[];
|
|
47
|
+
repairSnippet: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface LaunchApprovalDecision {
|
|
51
|
+
codexArgs: string[];
|
|
52
|
+
openCodePermission?: string;
|
|
53
|
+
warning?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function parseCodexModes(text: string): {
|
|
57
|
+
defaultMode?: ApprovalAction;
|
|
58
|
+
toolModes: Map<string, ApprovalAction>;
|
|
59
|
+
} {
|
|
60
|
+
let section: 'other' | 'borg' | { tool: string } = 'other';
|
|
61
|
+
let defaultMode: ApprovalAction | undefined;
|
|
62
|
+
const toolModes = new Map<string, ApprovalAction>();
|
|
63
|
+
|
|
64
|
+
for (const line of text.split(/\r?\n/)) {
|
|
65
|
+
const header = line.match(/^\s*\[([^\]]+)\]\s*(?:#.*)?$/);
|
|
66
|
+
if (header) {
|
|
67
|
+
if (header[1] === 'mcp_servers.borg') {
|
|
68
|
+
section = 'borg';
|
|
69
|
+
} else {
|
|
70
|
+
const tool = header[1].match(/^mcp_servers\.borg\.tools\."([^"]+)"$/);
|
|
71
|
+
section = tool ? { tool: tool[1] } : 'other';
|
|
72
|
+
}
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const value = line.match(
|
|
77
|
+
/^\s*(default_tools_approval_mode|approval_mode)\s*=\s*["'](auto|prompt|writes|approve)["']\s*(?:#.*)?$/
|
|
78
|
+
);
|
|
79
|
+
if (!value) continue;
|
|
80
|
+
const mode = value[2] as ApprovalAction;
|
|
81
|
+
if (section === 'borg' && value[1] === 'default_tools_approval_mode') {
|
|
82
|
+
defaultMode = mode;
|
|
83
|
+
} else if (typeof section === 'object' && value[1] === 'approval_mode') {
|
|
84
|
+
toolModes.set(section.tool, mode);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return { ...(defaultMode ? { defaultMode } : {}), toolModes };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function codexApprovalRepairSnippet(tools = CODEX_BORG_COORDINATION_TOOLS): string {
|
|
92
|
+
return tools
|
|
93
|
+
.map(
|
|
94
|
+
(tool) =>
|
|
95
|
+
`[mcp_servers.borg.tools."${tool}"]\n` +
|
|
96
|
+
`approval_mode = "auto"`
|
|
97
|
+
)
|
|
98
|
+
.join('\n\n');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function inspectCodexBorgApprovals(config: unknown): ApprovalInspection {
|
|
102
|
+
let defaultMode: ApprovalAction | undefined;
|
|
103
|
+
let toolModes = new Map<string, ApprovalAction>();
|
|
104
|
+
if (typeof config === 'string') {
|
|
105
|
+
({ defaultMode, toolModes } = parseCodexModes(config));
|
|
106
|
+
} else if (config && typeof config === 'object' && !Array.isArray(config)) {
|
|
107
|
+
const servers = (config as Record<string, unknown>).mcp_servers;
|
|
108
|
+
const borg = servers && typeof servers === 'object' && !Array.isArray(servers)
|
|
109
|
+
? (servers as Record<string, unknown>).borg
|
|
110
|
+
: undefined;
|
|
111
|
+
if (borg && typeof borg === 'object' && !Array.isArray(borg)) {
|
|
112
|
+
const record = borg as Record<string, unknown>;
|
|
113
|
+
if (typeof record.default_tools_approval_mode === 'string') {
|
|
114
|
+
defaultMode = record.default_tools_approval_mode as ApprovalAction;
|
|
115
|
+
}
|
|
116
|
+
if (record.tools && typeof record.tools === 'object' && !Array.isArray(record.tools)) {
|
|
117
|
+
toolModes = new Map(Object.entries(record.tools as Record<string, unknown>).flatMap(
|
|
118
|
+
([tool, value]) => value && typeof value === 'object' &&
|
|
119
|
+
typeof (value as Record<string, unknown>).approval_mode === 'string'
|
|
120
|
+
? [[tool, (value as Record<string, unknown>).approval_mode as ApprovalAction] as const]
|
|
121
|
+
: []
|
|
122
|
+
));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const restrictiveTools = CODEX_BORG_COORDINATION_TOOLS.filter((tool) => {
|
|
127
|
+
const mode = toolModes.get(tool) ?? defaultMode;
|
|
128
|
+
return mode !== undefined && mode !== 'auto';
|
|
129
|
+
});
|
|
130
|
+
return {
|
|
131
|
+
restrictiveTools,
|
|
132
|
+
repairSnippet: codexApprovalRepairSnippet(restrictiveTools.length > 0 ? restrictiveTools : CODEX_BORG_COORDINATION_TOOLS),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function globMatches(pattern: string, value: string): boolean {
|
|
137
|
+
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*').replace(/\?/g, '.');
|
|
138
|
+
return new RegExp(`^${escaped}$`).test(value);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function effectiveOpenCodePermission(
|
|
142
|
+
permission: unknown,
|
|
143
|
+
tool: string
|
|
144
|
+
): OpenCodePermissionAction | undefined {
|
|
145
|
+
if (permission === 'allow' || permission === 'ask' || permission === 'deny') {
|
|
146
|
+
return permission;
|
|
147
|
+
}
|
|
148
|
+
if (!permission || typeof permission !== 'object' || Array.isArray(permission)) return undefined;
|
|
149
|
+
|
|
150
|
+
let result: OpenCodePermissionAction | undefined;
|
|
151
|
+
for (const [pattern, action] of Object.entries(permission as Record<string, unknown>)) {
|
|
152
|
+
if (
|
|
153
|
+
globMatches(pattern, tool) &&
|
|
154
|
+
(action === 'allow' || action === 'ask' || action === 'deny')
|
|
155
|
+
) {
|
|
156
|
+
result = action;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return result;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function openCodeApprovalRepairObject(
|
|
163
|
+
tools = OPENCODE_BORG_COORDINATION_TOOLS
|
|
164
|
+
): Record<string, OpenCodePermissionAction> {
|
|
165
|
+
return Object.fromEntries(tools.map((tool) => [tool, 'allow'])) as Record<
|
|
166
|
+
string,
|
|
167
|
+
OpenCodePermissionAction
|
|
168
|
+
>;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Preserve every existing OpenCode permission rule, then append the exact
|
|
172
|
+
* Borg coordination allows. This remains safe whether OPENCODE_PERMISSION is
|
|
173
|
+
* deep-merged or replaces the configured permission value. */
|
|
174
|
+
export function mergeOpenCodePermission(
|
|
175
|
+
permission: unknown,
|
|
176
|
+
tools = OPENCODE_BORG_COORDINATION_TOOLS
|
|
177
|
+
): Record<string, unknown> {
|
|
178
|
+
let existing: Record<string, unknown> = {};
|
|
179
|
+
if (permission === 'allow' || permission === 'ask' || permission === 'deny') {
|
|
180
|
+
existing = { '*': permission };
|
|
181
|
+
} else if (permission && typeof permission === 'object' && !Array.isArray(permission)) {
|
|
182
|
+
existing = { ...(permission as Record<string, unknown>) };
|
|
183
|
+
}
|
|
184
|
+
// Reinsert exact keys at the END. OpenCode resolves matching permission
|
|
185
|
+
// patterns in order, so merely overwriting an earlier exact key would keep
|
|
186
|
+
// its old insertion position and a later wildcard could still win.
|
|
187
|
+
const toolSet = new Set<string>(tools);
|
|
188
|
+
const preserved = Object.fromEntries(
|
|
189
|
+
Object.entries(existing).filter(([key]) => !toolSet.has(key))
|
|
190
|
+
);
|
|
191
|
+
return { ...preserved, ...openCodeApprovalRepairObject(tools) };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function inspectOpenCodeBorgApprovals(config: unknown): ApprovalInspection {
|
|
195
|
+
const permission =
|
|
196
|
+
config && typeof config === 'object' && !Array.isArray(config)
|
|
197
|
+
? (config as Record<string, unknown>).permission
|
|
198
|
+
: undefined;
|
|
199
|
+
const restrictiveTools = OPENCODE_BORG_COORDINATION_TOOLS.filter((tool) => {
|
|
200
|
+
const action = effectiveOpenCodePermission(permission, tool);
|
|
201
|
+
return action === 'ask' || action === 'deny';
|
|
202
|
+
});
|
|
203
|
+
return {
|
|
204
|
+
restrictiveTools,
|
|
205
|
+
repairSnippet: JSON.stringify({ permission: mergeOpenCodePermission(permission, restrictiveTools.length > 0 ? restrictiveTools : OPENCODE_BORG_COORDINATION_TOOLS) }, null, 2),
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function codexBorgApprovalArgs(tools = CODEX_BORG_COORDINATION_TOOLS): string[] {
|
|
210
|
+
if (tools.length === 0) return [];
|
|
211
|
+
// Codex's dotted CLI override parser treats quotes in a segment literally,
|
|
212
|
+
// so `tools."borg:regen"` creates the wrong key. An inline TOML table is the
|
|
213
|
+
// supported way to address colon-named tools and deep-merges with the
|
|
214
|
+
// remaining effective tool config.
|
|
215
|
+
const toolTable = tools
|
|
216
|
+
.map((tool) => `${JSON.stringify(tool)}={approval_mode="auto"}`)
|
|
217
|
+
.join(',');
|
|
218
|
+
return ['-c', `mcp_servers.borg.tools={${toolTable}}`];
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export interface ApprovalIo {
|
|
222
|
+
readCodexConfig: (approvalArgs?: string[]) => Promise<unknown> | unknown;
|
|
223
|
+
readOpenCodeConfig: (permissionOverride?: string) => Promise<unknown> | unknown;
|
|
224
|
+
isTTY: () => boolean;
|
|
225
|
+
confirm: (message: string) => Promise<string>;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export interface EffectiveConfigOptions {
|
|
229
|
+
cwd: string;
|
|
230
|
+
env: NodeJS.ProcessEnv;
|
|
231
|
+
/** User-selected Codex profile/config flags, in their launch precedence. */
|
|
232
|
+
codexArgs: string[];
|
|
233
|
+
loadCodex?: (
|
|
234
|
+
args: string[],
|
|
235
|
+
cwd: string,
|
|
236
|
+
env: NodeJS.ProcessEnv,
|
|
237
|
+
profile?: string
|
|
238
|
+
) => Promise<unknown>;
|
|
239
|
+
loadOpenCode?: (cwd: string, env: NodeJS.ProcessEnv) => Promise<unknown> | unknown;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export interface CodexEffectiveConfigRuntime {
|
|
243
|
+
spawnProcess?: typeof spawn;
|
|
244
|
+
timeoutMs?: number;
|
|
245
|
+
maxResponseBytes?: number;
|
|
246
|
+
profile?: string;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** Keep only flags that app-server supports and that participate in config
|
|
250
|
+
* resolution. Runtime-only --profile/-p is resolved separately because Codex
|
|
251
|
+
* rejects it on the app-server subcommand. */
|
|
252
|
+
export function codexEffectiveConfigArgs(args: string[]): string[] {
|
|
253
|
+
const out: string[] = [];
|
|
254
|
+
const paired = new Set(['-c', '--config', '--enable', '--disable']);
|
|
255
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
256
|
+
const arg = args[i];
|
|
257
|
+
if (arg === '--') break;
|
|
258
|
+
if (arg === '-p' || arg === '--profile') {
|
|
259
|
+
if (args[i + 1] !== undefined) i += 1;
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
if (arg.startsWith('--profile=') || (arg.startsWith('-p') && arg.length > 2)) {
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
if (paired.has(arg)) {
|
|
266
|
+
if (args[i + 1] !== undefined) out.push(arg, args[++i]);
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
if (/^--(?:config|enable|disable)=/.test(arg) || arg === '--strict-config') {
|
|
270
|
+
out.push(arg);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return out;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** Resolve Codex's selected runtime profile. Short attached forms are accepted
|
|
277
|
+
* by Codex/clap; the final occurrence before -- wins. */
|
|
278
|
+
export function codexSelectedProfile(args: string[]): string | undefined {
|
|
279
|
+
let selected: string | undefined;
|
|
280
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
281
|
+
const arg = args[index];
|
|
282
|
+
if (arg === '--') break;
|
|
283
|
+
if (arg === '-p' || arg === '--profile') {
|
|
284
|
+
const value = args[index + 1];
|
|
285
|
+
if (value !== undefined && value.length > 0) {
|
|
286
|
+
selected = value;
|
|
287
|
+
index += 1;
|
|
288
|
+
}
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
if (arg.startsWith('--profile=')) {
|
|
292
|
+
const value = arg.slice('--profile='.length);
|
|
293
|
+
if (value.length > 0) selected = value;
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
if (arg.startsWith('-p') && arg.length > 2) {
|
|
297
|
+
const value = arg.slice(2).replace(/^=/, '');
|
|
298
|
+
if (value.length > 0) selected = value;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return selected;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
interface CodexConfigLayer {
|
|
305
|
+
name?: { type?: string; profile?: string | null };
|
|
306
|
+
config?: unknown;
|
|
307
|
+
disabledReason?: string | null;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
interface CodexConfigSnapshot {
|
|
311
|
+
config?: unknown;
|
|
312
|
+
layers?: CodexConfigLayer[] | null;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
316
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function mergeConfigValue(base: unknown, overlay: unknown): unknown {
|
|
320
|
+
if (!isRecord(base) || !isRecord(overlay)) return structuredClone(overlay);
|
|
321
|
+
const merged: Record<string, unknown> = structuredClone(base);
|
|
322
|
+
for (const [key, value] of Object.entries(overlay)) {
|
|
323
|
+
merged[key] = key in merged ? mergeConfigValue(merged[key], value) : structuredClone(value);
|
|
324
|
+
}
|
|
325
|
+
return merged;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function borgConfigFragment(config: unknown): unknown {
|
|
329
|
+
if (!isRecord(config) || !isRecord(config.mcp_servers)) return {};
|
|
330
|
+
const borg = config.mcp_servers.borg;
|
|
331
|
+
return isRecord(borg) ? borg : {};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/** Rebuild the native ordered layer stack with the selected profile inserted
|
|
335
|
+
* immediately above the base user layer, matching Codex's runtime loader. */
|
|
336
|
+
export function composeCodexProfileConfig(
|
|
337
|
+
snapshot: CodexConfigSnapshot,
|
|
338
|
+
profileConfig: unknown
|
|
339
|
+
): unknown {
|
|
340
|
+
const layers = snapshot.layers;
|
|
341
|
+
if (!Array.isArray(layers)) throw new Error('Codex effective-config layers were unavailable');
|
|
342
|
+
let mergedBorg: unknown = {};
|
|
343
|
+
let inserted = false;
|
|
344
|
+
// config/read returns highest precedence first; Codex's merge runs from
|
|
345
|
+
// system/base-user upward, so replay the array in reverse.
|
|
346
|
+
for (const layer of [...layers].reverse()) {
|
|
347
|
+
if (layer.disabledReason) continue;
|
|
348
|
+
mergedBorg = mergeConfigValue(mergedBorg, borgConfigFragment(layer.config));
|
|
349
|
+
if (!inserted && layer.name?.type === 'user' && layer.name.profile == null) {
|
|
350
|
+
mergedBorg = mergeConfigValue(mergedBorg, borgConfigFragment(profileConfig));
|
|
351
|
+
inserted = true;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
if (!inserted) throw new Error('Codex base user config layer was unavailable');
|
|
355
|
+
return { mcp_servers: { borg: mergedBorg } };
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
async function withNativeCodexProfileLayer<T>(
|
|
359
|
+
profile: string,
|
|
360
|
+
env: NodeJS.ProcessEnv,
|
|
361
|
+
query: (profileCwd: string, trustOverride: string) => Promise<CodexConfigSnapshot>
|
|
362
|
+
): Promise<T> {
|
|
363
|
+
const codexHome = resolve(env.CODEX_HOME || join(homedir(), '.codex'));
|
|
364
|
+
const profilePath = resolve(codexHome, `${profile}.config.toml`);
|
|
365
|
+
if (dirname(profilePath) !== codexHome) {
|
|
366
|
+
throw new Error('Codex profile path was invalid');
|
|
367
|
+
}
|
|
368
|
+
const temporaryHome = await mkdtemp(join(tmpdir(), 'borg-codex-profile-'));
|
|
369
|
+
try {
|
|
370
|
+
const dotCodex = join(temporaryHome, '.codex');
|
|
371
|
+
const temporaryConfig = join(dotCodex, 'config.toml');
|
|
372
|
+
await mkdir(dotCodex, { mode: 0o700 });
|
|
373
|
+
// Codex discovers project config from its configured project-root markers;
|
|
374
|
+
// an empty .git directory is sufficient and never invokes Git itself.
|
|
375
|
+
await mkdir(join(temporaryHome, '.git'), { mode: 0o700 });
|
|
376
|
+
try {
|
|
377
|
+
await copyFile(profilePath, temporaryConfig);
|
|
378
|
+
await chmod(temporaryConfig, 0o600);
|
|
379
|
+
} catch (error: any) {
|
|
380
|
+
if (error?.code !== 'ENOENT') throw error;
|
|
381
|
+
// Codex treats a selected but absent profile as an empty layer.
|
|
382
|
+
await writeFile(temporaryConfig, '', { mode: 0o600 });
|
|
383
|
+
}
|
|
384
|
+
const trustOverride = `projects={${JSON.stringify(temporaryHome)}={trust_level="trusted"}}`;
|
|
385
|
+
const snapshot = await query(temporaryHome, trustOverride);
|
|
386
|
+
const profileLayer = snapshot.layers?.find(
|
|
387
|
+
(layer) => !layer.disabledReason && layer.name?.type === 'project'
|
|
388
|
+
);
|
|
389
|
+
if (!profileLayer) throw new Error('Codex profile layer was unavailable');
|
|
390
|
+
return profileLayer.config as T;
|
|
391
|
+
} catch {
|
|
392
|
+
throw new Error('Codex selected-profile query failed');
|
|
393
|
+
} finally {
|
|
394
|
+
await rm(temporaryHome, { recursive: true, force: true }).catch(() => undefined);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
async function readCodexConfigSnapshot(
|
|
399
|
+
args: string[],
|
|
400
|
+
cwd: string,
|
|
401
|
+
env: NodeJS.ProcessEnv,
|
|
402
|
+
runtime: CodexEffectiveConfigRuntime
|
|
403
|
+
): Promise<CodexConfigSnapshot> {
|
|
404
|
+
return new Promise((resolveSnapshot, reject) => {
|
|
405
|
+
let child: ReturnType<typeof spawn>;
|
|
406
|
+
try {
|
|
407
|
+
child = (runtime.spawnProcess ?? spawn)(
|
|
408
|
+
'codex',
|
|
409
|
+
[...args, 'app-server', '--stdio'],
|
|
410
|
+
{ cwd, env, stdio: ['pipe', 'pipe', 'ignore'] }
|
|
411
|
+
);
|
|
412
|
+
} catch {
|
|
413
|
+
reject(new Error('Codex effective-config query failed'));
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
if (!child.stdin || !child.stdout) {
|
|
417
|
+
try {
|
|
418
|
+
child.kill();
|
|
419
|
+
} catch {
|
|
420
|
+
// No usable protocol streams exist; reject with a static failure.
|
|
421
|
+
}
|
|
422
|
+
reject(new Error('Codex effective-config query failed'));
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
const stdin = child.stdin;
|
|
426
|
+
const stdout = child.stdout;
|
|
427
|
+
let buffer = '';
|
|
428
|
+
let receivedBytes = 0;
|
|
429
|
+
let initialized = false;
|
|
430
|
+
let settled = false;
|
|
431
|
+
let timer: NodeJS.Timeout | undefined;
|
|
432
|
+
const finish = (error?: Error, value?: unknown) => {
|
|
433
|
+
if (settled) return;
|
|
434
|
+
settled = true;
|
|
435
|
+
if (timer) clearTimeout(timer);
|
|
436
|
+
try {
|
|
437
|
+
child.kill();
|
|
438
|
+
} catch {
|
|
439
|
+
// The query is already settled; process teardown is best-effort.
|
|
440
|
+
}
|
|
441
|
+
if (error) reject(error);
|
|
442
|
+
else resolveSnapshot(value as CodexConfigSnapshot);
|
|
443
|
+
};
|
|
444
|
+
const fail = () => finish(new Error('Codex effective-config query failed'));
|
|
445
|
+
const safeWrite = (payload: string): boolean => {
|
|
446
|
+
if (settled) return false;
|
|
447
|
+
try {
|
|
448
|
+
stdin.write(payload, (error) => {
|
|
449
|
+
if (error) fail();
|
|
450
|
+
});
|
|
451
|
+
return true;
|
|
452
|
+
} catch {
|
|
453
|
+
fail();
|
|
454
|
+
return false;
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
timer = setTimeout(
|
|
458
|
+
() => finish(new Error('Codex effective-config query timed out')),
|
|
459
|
+
runtime.timeoutMs ?? 5_000
|
|
460
|
+
);
|
|
461
|
+
child.on('error', fail);
|
|
462
|
+
stdin.on('error', fail);
|
|
463
|
+
stdout.on('error', fail);
|
|
464
|
+
child.on('exit', () => {
|
|
465
|
+
if (!settled) finish(new Error('Codex effective-config query exited before responding'));
|
|
466
|
+
});
|
|
467
|
+
stdout.on('data', (chunk: Buffer) => {
|
|
468
|
+
if (settled) return;
|
|
469
|
+
receivedBytes += chunk.length;
|
|
470
|
+
if (receivedBytes > (runtime.maxResponseBytes ?? 4 * 1024 * 1024)) {
|
|
471
|
+
finish(new Error('Codex effective-config response exceeded 4 MiB'));
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
buffer += chunk.toString('utf8');
|
|
475
|
+
for (;;) {
|
|
476
|
+
if (settled) return;
|
|
477
|
+
const newline = buffer.indexOf('\n');
|
|
478
|
+
if (newline < 0) break;
|
|
479
|
+
const line = buffer.slice(0, newline).trim();
|
|
480
|
+
buffer = buffer.slice(newline + 1);
|
|
481
|
+
if (!line) continue;
|
|
482
|
+
let message: any;
|
|
483
|
+
try {
|
|
484
|
+
message = JSON.parse(line);
|
|
485
|
+
} catch {
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
if (message.id === 1 && !initialized) {
|
|
489
|
+
initialized = true;
|
|
490
|
+
if (!safeWrite(`${JSON.stringify({ method: 'initialized', params: {} })}\n`)) return;
|
|
491
|
+
safeWrite(`${JSON.stringify({
|
|
492
|
+
id: 2,
|
|
493
|
+
method: 'config/read',
|
|
494
|
+
params: { cwd, includeLayers: true },
|
|
495
|
+
})}\n`);
|
|
496
|
+
} else if (message.id === 2) {
|
|
497
|
+
if (message.error) finish(new Error('Codex effective-config query was rejected'));
|
|
498
|
+
else finish(undefined, message.result ?? {});
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
});
|
|
502
|
+
safeWrite(`${JSON.stringify({
|
|
503
|
+
id: 1,
|
|
504
|
+
method: 'initialize',
|
|
505
|
+
params: {
|
|
506
|
+
clientInfo: { name: 'borgmcp', title: null, version: '0' },
|
|
507
|
+
capabilities: { experimentalApi: true },
|
|
508
|
+
},
|
|
509
|
+
})}\n`);
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
export async function readCodexEffectiveConfig(
|
|
514
|
+
args: string[],
|
|
515
|
+
cwd: string,
|
|
516
|
+
env: NodeJS.ProcessEnv,
|
|
517
|
+
runtime: CodexEffectiveConfigRuntime = {}
|
|
518
|
+
): Promise<unknown> {
|
|
519
|
+
const snapshot = await readCodexConfigSnapshot(args, cwd, env, runtime);
|
|
520
|
+
if (!runtime.profile) return snapshot.config ?? null;
|
|
521
|
+
const profileConfig = await withNativeCodexProfileLayer<unknown>(
|
|
522
|
+
runtime.profile,
|
|
523
|
+
env,
|
|
524
|
+
(profileCwd, trustOverride) => readCodexConfigSnapshot(
|
|
525
|
+
[...args.filter((arg) => arg === '--strict-config'), '-c', trustOverride],
|
|
526
|
+
profileCwd,
|
|
527
|
+
env,
|
|
528
|
+
{ ...runtime, profile: undefined }
|
|
529
|
+
)
|
|
530
|
+
);
|
|
531
|
+
return composeCodexProfileConfig(snapshot, profileConfig);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
export function readOpenCodeEffectiveConfig(
|
|
535
|
+
cwd: string,
|
|
536
|
+
env: NodeJS.ProcessEnv
|
|
537
|
+
): unknown {
|
|
538
|
+
const result = spawnSync('opencode', ['debug', 'config'], {
|
|
539
|
+
cwd,
|
|
540
|
+
env,
|
|
541
|
+
encoding: 'utf8',
|
|
542
|
+
timeout: 5_000,
|
|
543
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
544
|
+
});
|
|
545
|
+
if (result.error || result.status !== 0 || !result.stdout.trim()) {
|
|
546
|
+
throw new Error('OpenCode effective-config query failed');
|
|
547
|
+
}
|
|
548
|
+
return JSON.parse(result.stdout);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
export function defaultApprovalIo(
|
|
552
|
+
confirm: (message: string) => Promise<string>,
|
|
553
|
+
isTTY: () => boolean,
|
|
554
|
+
options: Partial<EffectiveConfigOptions> = {}
|
|
555
|
+
): ApprovalIo {
|
|
556
|
+
const cwd = options.cwd ?? process.cwd();
|
|
557
|
+
const env = options.env ?? process.env;
|
|
558
|
+
const selectedCodexArgs = codexEffectiveConfigArgs(options.codexArgs ?? []);
|
|
559
|
+
const selectedCodexProfile = codexSelectedProfile(options.codexArgs ?? []);
|
|
560
|
+
const loadCodex = options.loadCodex ?? (
|
|
561
|
+
(args: string[], loadCwd: string, loadEnv: NodeJS.ProcessEnv, profile?: string) =>
|
|
562
|
+
readCodexEffectiveConfig(args, loadCwd, loadEnv, { profile })
|
|
563
|
+
);
|
|
564
|
+
const loadOpenCode = options.loadOpenCode ?? readOpenCodeEffectiveConfig;
|
|
565
|
+
return {
|
|
566
|
+
readCodexConfig: (approvalArgs = []) => {
|
|
567
|
+
const args = [...approvalArgs, ...selectedCodexArgs];
|
|
568
|
+
return selectedCodexProfile === undefined
|
|
569
|
+
? loadCodex(args, cwd, env)
|
|
570
|
+
: loadCodex(args, cwd, env, selectedCodexProfile);
|
|
571
|
+
},
|
|
572
|
+
readOpenCodeConfig: (permissionOverride) =>
|
|
573
|
+
loadOpenCode(cwd, {
|
|
574
|
+
...env,
|
|
575
|
+
...(permissionOverride === undefined
|
|
576
|
+
? {}
|
|
577
|
+
: { OPENCODE_PERMISSION: permissionOverride }),
|
|
578
|
+
}),
|
|
579
|
+
isTTY,
|
|
580
|
+
confirm,
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function accepted(answer: string): boolean {
|
|
585
|
+
return /^(?:y|yes)$/i.test(answer.trim());
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
export async function resolveLaunchBorgApprovals(
|
|
589
|
+
cli: BorgCli,
|
|
590
|
+
io: ApprovalIo
|
|
591
|
+
): Promise<LaunchApprovalDecision> {
|
|
592
|
+
if (cli === 'claude') return { codexArgs: [] };
|
|
593
|
+
|
|
594
|
+
let inspection: ApprovalInspection;
|
|
595
|
+
let openCodeConfig: unknown;
|
|
596
|
+
try {
|
|
597
|
+
if (cli === 'codex') {
|
|
598
|
+
inspection = inspectCodexBorgApprovals(await io.readCodexConfig());
|
|
599
|
+
} else {
|
|
600
|
+
openCodeConfig = await io.readOpenCodeConfig();
|
|
601
|
+
inspection = inspectOpenCodeBorgApprovals(openCodeConfig);
|
|
602
|
+
}
|
|
603
|
+
} catch (error: any) {
|
|
604
|
+
return {
|
|
605
|
+
codexArgs: [],
|
|
606
|
+
warning: `Could not inspect ${cli} Borg tool approvals: ${error?.message ?? error}. No approval override was applied.`,
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
if (inspection.restrictiveTools.length === 0) return { codexArgs: [] };
|
|
610
|
+
|
|
611
|
+
const intro =
|
|
612
|
+
`${cli === 'codex' ? 'Codex' : 'OpenCode'} requires approval for ${inspection.restrictiveTools.length} Borg tool${inspection.restrictiveTools.length === 1 ? '' : 's'}. ` +
|
|
613
|
+
BORG_DISPATCHER_APPROVAL_DISCLOSURE;
|
|
614
|
+
if (!io.isTTY()) {
|
|
615
|
+
return {
|
|
616
|
+
codexArgs: [],
|
|
617
|
+
warning: `${intro} Re-run in a terminal to approve a launch-only fix, or add:\n${inspection.repairSnippet}`,
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
const answer = await io.confirm(`${intro} Apply this launch-only Borg approval set? [y/N] `);
|
|
622
|
+
if (!accepted(answer)) {
|
|
623
|
+
return {
|
|
624
|
+
codexArgs: [],
|
|
625
|
+
warning: `${intro} Continuing without the launch-only fix. To repair it globally, add:\n${inspection.repairSnippet}`,
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
if (cli === 'codex') {
|
|
630
|
+
const codexArgs = codexBorgApprovalArgs(inspection.restrictiveTools);
|
|
631
|
+
let effectiveWithOverride: ApprovalInspection;
|
|
632
|
+
try {
|
|
633
|
+
effectiveWithOverride = inspectCodexBorgApprovals(
|
|
634
|
+
await io.readCodexConfig(codexArgs)
|
|
635
|
+
);
|
|
636
|
+
} catch {
|
|
637
|
+
return {
|
|
638
|
+
codexArgs: [],
|
|
639
|
+
warning: 'Could not verify the Codex launch-only approval override against effective config. No override was applied.',
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
if (effectiveWithOverride.restrictiveTools.length > 0) {
|
|
643
|
+
return {
|
|
644
|
+
codexArgs: [],
|
|
645
|
+
warning: `Codex managed policy prevents the launch-only Borg approval override. Ask your Codex administrator to allow these tools:\n${effectiveWithOverride.repairSnippet}`,
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
return { codexArgs };
|
|
649
|
+
}
|
|
650
|
+
const openCodePermission = JSON.stringify(mergeOpenCodePermission(
|
|
651
|
+
openCodeConfig && typeof openCodeConfig === 'object'
|
|
652
|
+
? (openCodeConfig as Record<string, unknown>).permission
|
|
653
|
+
: undefined,
|
|
654
|
+
inspection.restrictiveTools
|
|
655
|
+
));
|
|
656
|
+
let effectiveWithOverride: ApprovalInspection;
|
|
657
|
+
try {
|
|
658
|
+
effectiveWithOverride = inspectOpenCodeBorgApprovals(
|
|
659
|
+
await io.readOpenCodeConfig(openCodePermission)
|
|
660
|
+
);
|
|
661
|
+
} catch {
|
|
662
|
+
return {
|
|
663
|
+
codexArgs: [],
|
|
664
|
+
warning: 'Could not verify the OpenCode launch-only approval override against effective config. No override was applied.',
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
if (effectiveWithOverride.restrictiveTools.length > 0) {
|
|
668
|
+
return {
|
|
669
|
+
codexArgs: [],
|
|
670
|
+
warning: `OpenCode managed policy prevents the launch-only Borg approval override. Ask your OpenCode administrator to allow these tools:\n${effectiveWithOverride.repairSnippet}`,
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
return {
|
|
674
|
+
codexArgs: [],
|
|
675
|
+
openCodePermission,
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
export function buildOpenCodeLaunchArgs(
|
|
680
|
+
cwd: string,
|
|
681
|
+
port: number,
|
|
682
|
+
prompt: string,
|
|
683
|
+
passthroughArgs: string[] = []
|
|
684
|
+
): string[] {
|
|
685
|
+
// Deliberately no `--auto`: that switch auto-approves unrelated shell/file
|
|
686
|
+
// actions. Exact Borg coordination consent is carried in the child env.
|
|
687
|
+
return [cwd, '--port', String(port), '--prompt', prompt, ...passthroughArgs];
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
export async function setupApprovalWarnings(
|
|
691
|
+
deps: Pick<ApprovalIo, 'readCodexConfig' | 'readOpenCodeConfig'>,
|
|
692
|
+
selected: { codex?: boolean; opencode?: boolean } = { codex: true, opencode: true }
|
|
693
|
+
): Promise<string[]> {
|
|
694
|
+
const warnings: string[] = [];
|
|
695
|
+
try {
|
|
696
|
+
const codex = selected.codex
|
|
697
|
+
? inspectCodexBorgApprovals(await deps.readCodexConfig())
|
|
698
|
+
: null;
|
|
699
|
+
if (codex && codex.restrictiveTools.length > 0) {
|
|
700
|
+
warnings.push(`Codex Borg approvals are restrictive. ${BORG_DISPATCHER_APPROVAL_DISCLOSURE} Borg launches will offer a launch-only fix. Global repair:\n${codex.repairSnippet}`);
|
|
701
|
+
}
|
|
702
|
+
} catch (error: any) {
|
|
703
|
+
warnings.push(`Could not inspect Codex Borg tool approvals: ${error?.message ?? error}`);
|
|
704
|
+
}
|
|
705
|
+
try {
|
|
706
|
+
const opencode = selected.opencode
|
|
707
|
+
? inspectOpenCodeBorgApprovals(await deps.readOpenCodeConfig())
|
|
708
|
+
: null;
|
|
709
|
+
if (opencode && opencode.restrictiveTools.length > 0) {
|
|
710
|
+
warnings.push(`OpenCode Borg approvals are restrictive. ${BORG_DISPATCHER_APPROVAL_DISCLOSURE} Borg launches will offer a launch-only fix. Global repair:\n${opencode.repairSnippet}`);
|
|
711
|
+
}
|
|
712
|
+
} catch (error: any) {
|
|
713
|
+
warnings.push(`Could not inspect OpenCode Borg tool approvals: ${error?.message ?? error}`);
|
|
714
|
+
}
|
|
715
|
+
return warnings;
|
|
716
|
+
}
|