borgmcp 1.1.14 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +15 -0
- package/LICENSE +201 -33
- package/NOTICE +2 -0
- package/README.md +86 -37
- 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.map +1 -0
- package/dist/codex-app-wake.js +432 -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.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 +31 -18
- package/dist/log-stream.d.ts.map +1 -0
- package/dist/log-stream.js +1214 -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 +65 -175
- package/dist/remote-client.d.ts.map +1 -0
- package/dist/remote-client.js +942 -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 +303 -0
- package/dist/seats.d.ts.map +1 -0
- package/dist/seats.js +551 -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 +505 -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/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 +40 -0
- package/docs/LOCAL_SERVER.md +103 -0
- package/docs/RELEASING.md +120 -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 +512 -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 +1505 -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 +1379 -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 +741 -0
- package/src/self-path.ts +51 -0
- package/src/server-endpoint.ts +38 -0
- package/src/server-errors.ts +93 -0
- package/src/server-handshake.ts +783 -0
- package/src/server-response.ts +76 -0
- package/src/server-trust.ts +245 -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,118 @@
|
|
|
1
|
+
// gh#556 Part 2 — drone-worktree discovery for `borg launch-all`.
|
|
2
|
+
//
|
|
3
|
+
// Scheme-agnostic: enumerates `git worktree list --porcelain` (the authoritative
|
|
4
|
+
// source covering BOTH old sibling paths and new ~/.borg/worktrees/<repo>/<name>
|
|
5
|
+
// paths from Part 1) and cross-references ~/.config/borgmcp/cubes.json, keeping
|
|
6
|
+
// only worktrees whose saved identity has cubeId === target.
|
|
7
|
+
|
|
8
|
+
import type { LaunchAllDeps, RunSyncFn } from './launch-all-deps.js';
|
|
9
|
+
|
|
10
|
+
export interface DroneCandidate {
|
|
11
|
+
worktreeDir: string;
|
|
12
|
+
cubeId: string;
|
|
13
|
+
droneId: string;
|
|
14
|
+
droneLabel: string;
|
|
15
|
+
sessionToken: string;
|
|
16
|
+
apiUrl: string;
|
|
17
|
+
serverTrustIdentity?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
21
|
+
function isUuid(s: string): boolean {
|
|
22
|
+
return UUID_RE.test(s);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* --only TIER-1 (local, no server call): exact case-insensitive droneLabel match,
|
|
27
|
+
* OR droneLabel prefix match (`--only drone` matches `drone-1`, `drone-2`, ...).
|
|
28
|
+
* Tier-2 (role-name) matching is best-effort in the orchestrator (spec §8.4).
|
|
29
|
+
*/
|
|
30
|
+
export function matchesOnlyLabel(droneLabel: string, only: string): boolean {
|
|
31
|
+
const l = droneLabel.toLowerCase();
|
|
32
|
+
const o = only.toLowerCase();
|
|
33
|
+
return l === o || l.startsWith(o);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Enumerate the LINKED worktree paths from `git worktree list --porcelain`,
|
|
38
|
+
* dropping the main worktree (always block[0]). Throws a user-readable error if
|
|
39
|
+
* the command fails (not inside a git repo).
|
|
40
|
+
*/
|
|
41
|
+
export function enumerateLinkedWorktrees(runSync: RunSyncFn): string[] {
|
|
42
|
+
let raw: string;
|
|
43
|
+
try {
|
|
44
|
+
raw = runSync('git', ['worktree', 'list', '--porcelain']);
|
|
45
|
+
} catch (e) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
`launch-all: git worktree list failed — must be run from inside a git repository\n` +
|
|
48
|
+
` (inner: ${e instanceof Error ? e.message : String(e)})`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
const blocks = raw.trim().split(/\n\n+/);
|
|
52
|
+
// blocks[0] is the main worktree — always drop it.
|
|
53
|
+
return blocks
|
|
54
|
+
.slice(1)
|
|
55
|
+
.map((block) => {
|
|
56
|
+
const m = block.match(/^worktree (.+)$/m);
|
|
57
|
+
return m ? m[1].trim() : null;
|
|
58
|
+
})
|
|
59
|
+
.filter((p): p is string => p !== null);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface DiscoverOpts {
|
|
63
|
+
targetCubeId: string;
|
|
64
|
+
/** --only filter (tier-1 label match applied here). */
|
|
65
|
+
only?: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Full discovery pipeline (spec §3.5): enumerate → cubes.json lookup → filter
|
|
70
|
+
* (dir-present / has-entry / cubeId-match / UUID-valid / --only) → candidates in
|
|
71
|
+
* stable porcelain order.
|
|
72
|
+
*/
|
|
73
|
+
export async function discoverDroneCandidates(
|
|
74
|
+
opts: DiscoverOpts,
|
|
75
|
+
deps: LaunchAllDeps
|
|
76
|
+
): Promise<DroneCandidate[]> {
|
|
77
|
+
const worktreePaths = enumerateLinkedWorktrees((cmd, args) => deps.runSync(cmd, args));
|
|
78
|
+
const identities = await deps.readAllProjectIdentities();
|
|
79
|
+
const byPath = new Map(identities.map((e) => [e.projectPath, e.cube]));
|
|
80
|
+
|
|
81
|
+
const candidates: DroneCandidate[] = [];
|
|
82
|
+
for (const worktreeDir of worktreePaths) {
|
|
83
|
+
// 1. directory not present on disk (orphaned worktree)
|
|
84
|
+
if (!deps.pathExists(worktreeDir)) {
|
|
85
|
+
deps.stderr(
|
|
86
|
+
`skipping ${worktreeDir}: directory not found (orphaned worktree — run \`git worktree prune\`)\n`
|
|
87
|
+
);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
// 2. no cubes.json entry → not a drone worktree; skip silently
|
|
91
|
+
const cube = byPath.get(worktreeDir);
|
|
92
|
+
if (!cube) continue;
|
|
93
|
+
// 3. cubeId mismatch → different cube; skip silently
|
|
94
|
+
if (cube.cubeId !== opts.targetCubeId) continue;
|
|
95
|
+
// 4. malformed entry (cubeId/droneId not UUID) → warn + skip
|
|
96
|
+
if (!isUuid(cube.cubeId) || !isUuid(cube.droneId)) {
|
|
97
|
+
deps.stderr(
|
|
98
|
+
`skipping ${worktreeDir}: its saved seat has a malformed cubeId/droneId — re-assimilate to fix\n`
|
|
99
|
+
);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
// 5. --only filter (tier-1 label) → skip silently (counted in the filter report)
|
|
103
|
+
if (opts.only !== undefined && !matchesOnlyLabel(cube.droneLabel, opts.only)) continue;
|
|
104
|
+
|
|
105
|
+
candidates.push({
|
|
106
|
+
worktreeDir,
|
|
107
|
+
cubeId: cube.cubeId,
|
|
108
|
+
droneId: cube.droneId,
|
|
109
|
+
droneLabel: cube.droneLabel,
|
|
110
|
+
sessionToken: cube.sessionToken,
|
|
111
|
+
apiUrl: cube.apiUrl,
|
|
112
|
+
...(cube.serverTrustIdentity === undefined
|
|
113
|
+
? {}
|
|
114
|
+
: { serverTrustIdentity: cube.serverTrustIdentity }),
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
return candidates;
|
|
118
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// gh#556 Part 2 — per-worktree launch lock markers (spec §6).
|
|
2
|
+
//
|
|
3
|
+
// Timestamp-based markers (the agent PID is not synchronously available when
|
|
4
|
+
// `borg assimilate --here` is dispatched via tmux send-keys). A marker <5min old
|
|
5
|
+
// means the seat appears live → skip unless --force. Markers are never deleted by
|
|
6
|
+
// launch-all itself; the next invocation sweeps mtime-stale (>5min) markers.
|
|
7
|
+
|
|
8
|
+
import { createHash } from 'node:crypto';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import type { LaunchAllDeps } from './launch-all-deps.js';
|
|
11
|
+
|
|
12
|
+
export const LOCK_STALE_MS = 5 * 60 * 1000;
|
|
13
|
+
|
|
14
|
+
/** SHA-1 hex of the worktree abs path → fixed-length collision-safe filename. */
|
|
15
|
+
export function worktreeLockName(absPath: string): string {
|
|
16
|
+
return createHash('sha1').update(absPath, 'utf8').digest('hex');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function locksDir(homeDir: string, cubeId: string): string {
|
|
20
|
+
return join(homeDir, '.config', 'borgmcp', 'locks', cubeId);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function lockPath(homeDir: string, cubeId: string, absPath: string): string {
|
|
24
|
+
return join(locksDir(homeDir, cubeId), worktreeLockName(absPath) + '.pid');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface LockMarker {
|
|
28
|
+
launchedAt: string;
|
|
29
|
+
droneLabel: string;
|
|
30
|
+
worktreeDir: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Write the launch marker (mkdir -p the cube's locks dir first). Mode 0o600. */
|
|
34
|
+
export function writeLockMarker(
|
|
35
|
+
deps: LaunchAllDeps,
|
|
36
|
+
cubeId: string,
|
|
37
|
+
droneLabel: string,
|
|
38
|
+
worktreeDir: string,
|
|
39
|
+
launchedAtISO: string
|
|
40
|
+
): void {
|
|
41
|
+
deps.mkdirp(locksDir(deps.homedir(), cubeId));
|
|
42
|
+
const marker: LockMarker = { launchedAt: launchedAtISO, droneLabel, worktreeDir };
|
|
43
|
+
deps.writeFile(lockPath(deps.homedir(), cubeId, worktreeDir), JSON.stringify(marker), 0o600);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Delete mtime-stale (>5min) `.pid` markers in locks/<cubeId>/ (crash cleanup). */
|
|
47
|
+
export function sweepStaleLocks(deps: LaunchAllDeps, cubeId: string, nowMs: number): void {
|
|
48
|
+
const dir = locksDir(deps.homedir(), cubeId);
|
|
49
|
+
for (const name of deps.listDir(dir)) {
|
|
50
|
+
if (!name.endsWith('.pid')) continue;
|
|
51
|
+
const p = join(dir, name);
|
|
52
|
+
const mtime = deps.statMtime(p);
|
|
53
|
+
if (mtime !== null && nowMs - mtime > LOCK_STALE_MS) deps.unlinkOpt(p);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** True iff a fresh (<=5min by its launchedAt content) marker exists for the seat. */
|
|
58
|
+
export function isLockLive(
|
|
59
|
+
deps: LaunchAllDeps,
|
|
60
|
+
cubeId: string,
|
|
61
|
+
worktreeDir: string,
|
|
62
|
+
nowMs: number
|
|
63
|
+
): { live: boolean; launchedAt?: string } {
|
|
64
|
+
const raw = deps.readFileOpt(lockPath(deps.homedir(), cubeId, worktreeDir));
|
|
65
|
+
if (raw === null) return { live: false };
|
|
66
|
+
try {
|
|
67
|
+
const marker = JSON.parse(raw) as LockMarker;
|
|
68
|
+
const t = Date.parse(marker.launchedAt);
|
|
69
|
+
if (!Number.isFinite(t)) return { live: false };
|
|
70
|
+
return { live: nowMs - t <= LOCK_STALE_MS, launchedAt: marker.launchedAt };
|
|
71
|
+
} catch {
|
|
72
|
+
return { live: false };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gh#673 P1 — the borg-launch ACTIVATION gate (WI-4 + WI-5-claude).
|
|
3
|
+
*
|
|
4
|
+
* The `borg` wrapper sets BORG_SESSION=1 in the agent's launch env
|
|
5
|
+
* (claude.ts / assimilate-cmd.ts launchEnv — same pattern as
|
|
6
|
+
* BORG_CODEX_REMOTE_WAKE). Claude Code spawns the borg-mcp MCP child and
|
|
7
|
+
* runs hook commands with that env inherited, so four activation bins
|
|
8
|
+
* gate on it:
|
|
9
|
+
* 1. the MCP tool surface (index.ts CallTool top) — a vanilla session
|
|
10
|
+
* gets a non-silent re-launch notice per tool, never a half-success;
|
|
11
|
+
* 2. the borg-regen SessionStart hook bin — exit-0 no-op;
|
|
12
|
+
* 3. the borg-clear-rewake clear-only hook bin — exit-0 no-op;
|
|
13
|
+
* 4. the borg-log-audit UserPromptSubmit hook bin — exit-0 no-op.
|
|
14
|
+
* Result: `claude` launched directly is vanilla Claude Code; `borg`
|
|
15
|
+
* launches get the full surface.
|
|
16
|
+
*
|
|
17
|
+
* Codex (V2/V2b probes): codex does NOT forward parent env to MCP
|
|
18
|
+
* children — only the pinned [mcp_servers.borg.env] list — so simple
|
|
19
|
+
* launchEnv inheritance can't deliver the marker there. But a
|
|
20
|
+
* PER-INVOCATION `-c 'mcp_servers.borg.env.BORG_SESSION="1"'` config
|
|
21
|
+
* override DOES reach the child (V2b-proven). The borg wrapper appends
|
|
22
|
+
* `codexBorgSessionConfigArgs()` to every codex launch, so codex rides
|
|
23
|
+
* the exact same isBorgSession() gate: vanilla codex children lack the
|
|
24
|
+
* var and gate dormant; borg-launched children carry it. The codex
|
|
25
|
+
* SessionStart hook bin gates via the codex PROCESS env, which the
|
|
26
|
+
* wrapper's launchEnv marks directly.
|
|
27
|
+
*
|
|
28
|
+
* SR-BINDING (1482e7f9): BORG_SESSION is ACTIVATION-ONLY. It is
|
|
29
|
+
* user-settable by design (manual BORG_SESSION=1 is a supported override
|
|
30
|
+
* for power users/tests) and MUST NEVER be consulted for an access or
|
|
31
|
+
* security decision — server-side drone-session auth is the
|
|
32
|
+
* security boundary and is unchanged by this gate.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import { envToggleOn } from './auth-env.js';
|
|
36
|
+
import { debugLog } from './debug.js';
|
|
37
|
+
|
|
38
|
+
export const BORG_SESSION_ENV = 'BORG_SESSION';
|
|
39
|
+
|
|
40
|
+
/** True when this process runs inside a borg-launched session. */
|
|
41
|
+
export function isBorgSession(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
42
|
+
return envToggleOn(env[BORG_SESSION_ENV]);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The non-silent per-tool notice a vanilla (non-borg-launched) session
|
|
47
|
+
* receives when invoking a borg_* tool. Explains the state — nothing is
|
|
48
|
+
* wrong with the cube — and points at the borg launch path.
|
|
49
|
+
*/
|
|
50
|
+
export function borgSessionToolNotice(toolName: string): string {
|
|
51
|
+
return (
|
|
52
|
+
`◼ ${toolName} is inactive: this session wasn't launched via \`borg\`, so the borg ` +
|
|
53
|
+
`coordination surface is dormant (vanilla Claude Code). To use cube coordination, ` +
|
|
54
|
+
`re-launch from a terminal with \`borg\` (or \`borg assimilate\` to set up a seat first). ` +
|
|
55
|
+
`Power users can opt in manually by launching with BORG_SESSION=1.`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The per-invocation codex config override that injects BORG_SESSION into
|
|
61
|
+
* the codex-spawned borg-mcp child's pinned env (V2b-proven mechanism —
|
|
62
|
+
* inherited env never reaches codex MCP children). Appended to the codex
|
|
63
|
+
* launch args by both launch sites (claude.ts + assimilate-cmd.ts).
|
|
64
|
+
* TOML string value, hence the inner quotes.
|
|
65
|
+
*/
|
|
66
|
+
export function codexBorgSessionConfigArgs(): string[] {
|
|
67
|
+
return ['-c', `mcp_servers.borg.env.${BORG_SESSION_ENV}="1"`];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Gate decision with BORG_DEBUG observability — one funnel so every bin
|
|
72
|
+
* logs refusals the same way.
|
|
73
|
+
*/
|
|
74
|
+
export function gateAllowsActivation(surface: string, env: NodeJS.ProcessEnv = process.env): boolean {
|
|
75
|
+
const allowed = isBorgSession(env);
|
|
76
|
+
if (!allowed) {
|
|
77
|
+
debugLog(`launch-gate: ${surface} dormant — BORG_SESSION absent (not a borg-launched session; gh#673)`);
|
|
78
|
+
}
|
|
79
|
+
return allowed;
|
|
80
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
|
|
5
|
+
const STATE_FILE = join(homedir(), '.config', 'borgmcp', 'lifecycle-log-state.json');
|
|
6
|
+
const ARRIVAL_DUPLICATE_WINDOW_MS = 10 * 60 * 1000;
|
|
7
|
+
|
|
8
|
+
export type LifecycleSignal = 'arrival' | 'ready';
|
|
9
|
+
|
|
10
|
+
export interface LifecycleLogSubject {
|
|
11
|
+
cubeId: string;
|
|
12
|
+
droneId: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface LifecycleStateEntry {
|
|
16
|
+
lastArrival?: {
|
|
17
|
+
message: string;
|
|
18
|
+
at: string;
|
|
19
|
+
};
|
|
20
|
+
idleReady?: {
|
|
21
|
+
message: string;
|
|
22
|
+
open: boolean;
|
|
23
|
+
at: string;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface LifecycleStateFile {
|
|
28
|
+
entries: Record<string, LifecycleStateEntry>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function lifecycleSignalForMessage(message: string): LifecycleSignal | null {
|
|
32
|
+
if (message.startsWith('ARRIVAL: ')) return 'arrival';
|
|
33
|
+
if (
|
|
34
|
+
message.startsWith('READY: ') &&
|
|
35
|
+
message.includes('capacity clean') &&
|
|
36
|
+
message.includes('awaiting next dispatch')
|
|
37
|
+
) {
|
|
38
|
+
return 'ready';
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function stateKey(subject: LifecycleLogSubject): string {
|
|
44
|
+
return `${subject.cubeId}:${subject.droneId}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function readState(): Promise<LifecycleStateFile> {
|
|
48
|
+
try {
|
|
49
|
+
const raw = await readFile(STATE_FILE, 'utf8');
|
|
50
|
+
const parsed = JSON.parse(raw);
|
|
51
|
+
if (
|
|
52
|
+
parsed &&
|
|
53
|
+
typeof parsed === 'object' &&
|
|
54
|
+
parsed.entries &&
|
|
55
|
+
typeof parsed.entries === 'object' &&
|
|
56
|
+
!Array.isArray(parsed.entries)
|
|
57
|
+
) {
|
|
58
|
+
return parsed as LifecycleStateFile;
|
|
59
|
+
}
|
|
60
|
+
} catch (err: any) {
|
|
61
|
+
if (err?.code !== 'ENOENT') throw err;
|
|
62
|
+
}
|
|
63
|
+
return { entries: {} };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function writeState(state: LifecycleStateFile): Promise<void> {
|
|
67
|
+
await mkdir(dirname(STATE_FILE), { recursive: true });
|
|
68
|
+
await writeFile(STATE_FILE, JSON.stringify(state, null, 2) + '\n', { mode: 0o600 });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function shouldSuppressLifecycleLogFromState(
|
|
72
|
+
message: string,
|
|
73
|
+
state: LifecycleStateEntry | undefined,
|
|
74
|
+
nowMs: number = Date.now()
|
|
75
|
+
): { suppress: boolean; signal: LifecycleSignal | null } {
|
|
76
|
+
const signal = lifecycleSignalForMessage(message);
|
|
77
|
+
if (!signal) return { suppress: false, signal: null };
|
|
78
|
+
|
|
79
|
+
if (signal === 'arrival') {
|
|
80
|
+
const lastArrivalAt = state?.lastArrival?.at
|
|
81
|
+
? new Date(state.lastArrival.at).getTime()
|
|
82
|
+
: NaN;
|
|
83
|
+
const isRecent =
|
|
84
|
+
Number.isFinite(lastArrivalAt) &&
|
|
85
|
+
nowMs - lastArrivalAt < ARRIVAL_DUPLICATE_WINDOW_MS;
|
|
86
|
+
return {
|
|
87
|
+
suppress: state?.lastArrival?.message === message && isRecent,
|
|
88
|
+
signal,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
suppress: state?.idleReady?.open === true && state.idleReady.message === message,
|
|
94
|
+
signal,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export async function shouldSuppressLifecycleLog(
|
|
99
|
+
subject: LifecycleLogSubject,
|
|
100
|
+
message: string
|
|
101
|
+
): Promise<{ suppress: boolean; signal: LifecycleSignal | null }> {
|
|
102
|
+
const state = await readState();
|
|
103
|
+
return shouldSuppressLifecycleLogFromState(
|
|
104
|
+
message,
|
|
105
|
+
state.entries[stateKey(subject)]
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function nextLifecycleStateAfterLog(
|
|
110
|
+
message: string,
|
|
111
|
+
current: LifecycleStateEntry | undefined,
|
|
112
|
+
nowIso: string = new Date().toISOString()
|
|
113
|
+
): LifecycleStateEntry {
|
|
114
|
+
const signal = lifecycleSignalForMessage(message);
|
|
115
|
+
if (signal === 'arrival') {
|
|
116
|
+
return {
|
|
117
|
+
...current,
|
|
118
|
+
lastArrival: { message, at: nowIso },
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
if (signal === 'ready') {
|
|
122
|
+
return {
|
|
123
|
+
...current,
|
|
124
|
+
idleReady: { message, open: true, at: nowIso },
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
if (current?.idleReady?.open) {
|
|
128
|
+
return {
|
|
129
|
+
...current,
|
|
130
|
+
idleReady: { ...current.idleReady, open: false, at: nowIso },
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
return current ?? {};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export async function recordLifecycleLog(
|
|
137
|
+
subject: LifecycleLogSubject,
|
|
138
|
+
message: string
|
|
139
|
+
): Promise<void> {
|
|
140
|
+
const state = await readState();
|
|
141
|
+
const key = stateKey(subject);
|
|
142
|
+
state.entries[key] = nextLifecycleStateAfterLog(message, state.entries[key]);
|
|
143
|
+
await writeState(state);
|
|
144
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sprint 6 / gh#153 — pure render function for `borg_list-roles` MCP tool.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from the inline `case 'borg_list-roles'` handler in
|
|
5
|
+
* `index.ts` per drone-3's QA-FAIL 2026-05-18T13:27:53Z so the render
|
|
6
|
+
* logic is unit-testable without exercising the full MCP tool dispatch
|
|
7
|
+
* stack. The handler simply calls `renderRoleList(roles, cubeId)` and
|
|
8
|
+
* returns its output.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export interface RoleForRender {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
short_description?: string;
|
|
15
|
+
is_default?: boolean;
|
|
16
|
+
is_mandatory?: boolean;
|
|
17
|
+
is_human_seat?: boolean;
|
|
18
|
+
can_broadcast?: boolean;
|
|
19
|
+
receives_all_direct?: boolean;
|
|
20
|
+
role_class?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Render the role registry for a cube as a markdown list with role IDs
|
|
25
|
+
* exposed for use with `borg_reassign-drone`. Returns the empty-roles
|
|
26
|
+
* placeholder when the input array is empty.
|
|
27
|
+
*
|
|
28
|
+
* Each role line shape:
|
|
29
|
+
* - **<name>**(<tags>) `<uuid>` — <description>
|
|
30
|
+
*
|
|
31
|
+
* Tags collected in order: Queen, human-seat, default, mandatory, can-broadcast,
|
|
32
|
+
* receives-all-direct. Joined with `, `; suffix omitted entirely when no tags apply.
|
|
33
|
+
*/
|
|
34
|
+
export function renderRoleList(roles: RoleForRender[], cubeId: string): string {
|
|
35
|
+
if (!roles.length) {
|
|
36
|
+
return 'No roles in this cube yet.';
|
|
37
|
+
}
|
|
38
|
+
const lines = roles.map((r) => {
|
|
39
|
+
const tags = [
|
|
40
|
+
r.role_class === 'queen' ? 'Queen' : null,
|
|
41
|
+
r.is_human_seat ? 'human-seat' : null,
|
|
42
|
+
r.is_default ? 'default' : null,
|
|
43
|
+
r.is_mandatory ? 'mandatory' : null,
|
|
44
|
+
r.can_broadcast ? 'can-broadcast' : null,
|
|
45
|
+
r.receives_all_direct ? 'receives-all-direct' : null,
|
|
46
|
+
].filter(Boolean).join(', ');
|
|
47
|
+
const tagSuffix = tags ? ` (${tags})` : '';
|
|
48
|
+
const desc = r.short_description || '_(no description)_';
|
|
49
|
+
return `- **${r.name}**${tagSuffix} \`${r.id}\` — ${desc}`;
|
|
50
|
+
});
|
|
51
|
+
return `Roles in cube ${cubeId} (${roles.length}):\n\n${lines.join('\n')}\n\nUse the role IDs above with \`borg_reassign-drone\` to change a drone's role.`;
|
|
52
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { roleSlug } from './role-resolver.js';
|
|
2
|
+
|
|
3
|
+
export interface LocalRoutingDrone {
|
|
4
|
+
id: string;
|
|
5
|
+
label?: string | null;
|
|
6
|
+
role_id?: string | null;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface LocalRoutingRole {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
is_human_seat?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Resolve the public `borg_log to:` addressing forms against the local
|
|
17
|
+
* server's authoritative cube roster. This intentionally mirrors the hosted
|
|
18
|
+
* route resolver: exact drone id/label first, then displayed short UUID, then
|
|
19
|
+
* role name/slug expansion. Every failure is closed before the log POST so a
|
|
20
|
+
* miss can never degrade into a broadcast.
|
|
21
|
+
*/
|
|
22
|
+
export function resolveLocalLogRecipients(
|
|
23
|
+
recipients: readonly string[],
|
|
24
|
+
drones: readonly LocalRoutingDrone[],
|
|
25
|
+
roles: readonly LocalRoutingRole[],
|
|
26
|
+
): string[] {
|
|
27
|
+
const ids = new Set<string>();
|
|
28
|
+
|
|
29
|
+
for (const recipient of recipients) {
|
|
30
|
+
if (recipient === '@human-seat') {
|
|
31
|
+
const roleIds = new Set(
|
|
32
|
+
roles.filter((role) => role.is_human_seat === true).map((role) => role.id),
|
|
33
|
+
);
|
|
34
|
+
const matches = drones.filter((drone) =>
|
|
35
|
+
drone.role_id !== null && drone.role_id !== undefined && roleIds.has(drone.role_id)
|
|
36
|
+
);
|
|
37
|
+
if (roleIds.size === 0 || matches.length === 0) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
'Direct-message role recipient has no active drones: @human-seat. ' +
|
|
40
|
+
'Use borg_roster to find active recipients, or omit to broadcast if your role can broadcast.',
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
for (const drone of matches) ids.add(drone.id);
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const droneMatches = drones.filter(
|
|
48
|
+
(drone) => drone.id === recipient || drone.label === recipient,
|
|
49
|
+
);
|
|
50
|
+
const uniqueDroneIds = [...new Set(droneMatches.map((drone) => drone.id))];
|
|
51
|
+
if (uniqueDroneIds.length > 1) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
`Ambiguous direct-message recipient: ${recipient} matches multiple drones`,
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
if (uniqueDroneIds.length === 1) {
|
|
57
|
+
ids.add(uniqueDroneIds[0]!);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const shortUuid = recipient.replace(/`/g, '').replace(/^id:/i, '').toLowerCase();
|
|
62
|
+
if (/^[0-9a-f]{8,}$/.test(shortUuid)) {
|
|
63
|
+
const prefixMatches = drones.filter((drone) =>
|
|
64
|
+
drone.id.toLowerCase().startsWith(shortUuid)
|
|
65
|
+
);
|
|
66
|
+
const uniquePrefixIds = [...new Set(prefixMatches.map((drone) => drone.id))];
|
|
67
|
+
if (uniquePrefixIds.length > 1) {
|
|
68
|
+
const listed = prefixMatches
|
|
69
|
+
.map((drone) => `${drone.id} (${drone.label ?? 'unlabeled'})`)
|
|
70
|
+
.join(', ');
|
|
71
|
+
throw new Error(
|
|
72
|
+
`Ambiguous short-uuid recipient: ${recipient} matches multiple drones — ${listed}. ` +
|
|
73
|
+
'Address by the full drone id.',
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
if (uniquePrefixIds.length === 1) {
|
|
77
|
+
ids.add(uniquePrefixIds[0]!);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const roleMatches = roles.filter((role) => roleSlug(role.name) === roleSlug(recipient));
|
|
83
|
+
const uniqueRoleIds = [...new Set(roleMatches.map((role) => role.id))];
|
|
84
|
+
if (uniqueRoleIds.length > 1) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
`Ambiguous direct-message recipient: ${recipient} matches multiple roles`,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
if (uniqueRoleIds.length === 0) {
|
|
90
|
+
throw new Error(
|
|
91
|
+
`Unknown direct-message recipient: ${recipient}. ` +
|
|
92
|
+
'Use an exact drone label, drone id, role name, or role slug.',
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
const roleDrones = drones.filter((drone) => drone.role_id === uniqueRoleIds[0]);
|
|
96
|
+
if (roleDrones.length === 0) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
`Direct-message role recipient has no active drones: ${recipient}. ` +
|
|
99
|
+
'Use borg_roster to find active recipients, or omit to broadcast if your role can broadcast.',
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
for (const drone of roleDrones) ids.add(drone.id);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return [...ids];
|
|
106
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { LocalManageRequiredError } from './server-errors.js';
|
|
2
|
+
|
|
3
|
+
export function formatLocalManageToolResult(error: unknown) {
|
|
4
|
+
if (!(error instanceof LocalManageRequiredError)) return null;
|
|
5
|
+
|
|
6
|
+
return {
|
|
7
|
+
content: [{ type: 'text' as const, text: error.message }],
|
|
8
|
+
isError: true as const,
|
|
9
|
+
};
|
|
10
|
+
}
|