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,444 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Real-IO factory for the `borg assimilate` orchestrator. Produces a
|
|
3
|
+
* fully-wired `AssimilateDeps` whose seams call into the existing
|
|
4
|
+
* client modules (remote-client HTTP, cubes.ts persistence, auth.ts
|
|
5
|
+
* setup wizard, terminal-title helper).
|
|
6
|
+
*
|
|
7
|
+
* Test code never calls this — tests construct stub deps directly
|
|
8
|
+
* (see `client/__tests__/assimilate-cmd.test.ts:makeStubDeps`).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { spawnSync, spawn as spawnChild } from 'node:child_process';
|
|
12
|
+
import { randomBytes } from 'node:crypto';
|
|
13
|
+
import { existsSync, mkdirSync } from 'node:fs';
|
|
14
|
+
import { hostname as osHostname, homedir as osHomedir } from 'node:os';
|
|
15
|
+
import { createInterface } from 'node:readline/promises';
|
|
16
|
+
import prompts from 'prompts';
|
|
17
|
+
import { readinessProbeEnv } from './readiness-probe.js';
|
|
18
|
+
import { resolveMcpBinaryPath } from './self-path.js';
|
|
19
|
+
|
|
20
|
+
import type { AssimilateDeps } from './assimilate-cmd.js';
|
|
21
|
+
import {
|
|
22
|
+
listCubes as remoteListCubes,
|
|
23
|
+
getCube as remoteGetCube,
|
|
24
|
+
} from './remote-client.js';
|
|
25
|
+
import {
|
|
26
|
+
DEFAULT_LOCAL_SERVER_ORIGIN,
|
|
27
|
+
connectLocalBorgServer,
|
|
28
|
+
createLocalBorgServerCube,
|
|
29
|
+
enrollLocalBorgServer,
|
|
30
|
+
probeLocalBorgServer,
|
|
31
|
+
resumeLocalBorgServerEnrollment,
|
|
32
|
+
sendBorgServerAttach,
|
|
33
|
+
} from './server-handshake.js';
|
|
34
|
+
import {
|
|
35
|
+
findIncompleteSiblingAttempt,
|
|
36
|
+
observeSeat,
|
|
37
|
+
prepareSeat,
|
|
38
|
+
seatRef,
|
|
39
|
+
type ActivateSeatOutcome,
|
|
40
|
+
type SeatOperation as ServerSessionOperation,
|
|
41
|
+
} from './seats.js';
|
|
42
|
+
import {
|
|
43
|
+
readPersistedLocalSeat,
|
|
44
|
+
} from './cubes.js';
|
|
45
|
+
import { loadBorgServerTrust } from './server-trust.js';
|
|
46
|
+
import { defaultProbeSeat } from './seat-probe.js';
|
|
47
|
+
import { BorgServerError } from './server-errors.js';
|
|
48
|
+
import {
|
|
49
|
+
findProjectRoot as cubesFindProjectRoot,
|
|
50
|
+
getActiveCube as cubesGetActive,
|
|
51
|
+
hasPersistedActiveCube as cubesHasPersistedActive,
|
|
52
|
+
setActiveCube as cubesSetActive,
|
|
53
|
+
inboxPathForDrone,
|
|
54
|
+
setCodexWakeTarget,
|
|
55
|
+
} from './cubes.js';
|
|
56
|
+
import { addProjectSessionStartHook } from './config-utils.js';
|
|
57
|
+
import { setTerminalTitle as setTitle } from './terminal-title.js';
|
|
58
|
+
import { defaultCliChoiceDeps, resolveCliChoice } from './cli-platform.js';
|
|
59
|
+
import { prepareCodexRemoteLaunch, defaultCodexRemoteDeps } from './codex-remote.js';
|
|
60
|
+
import { findLoadedCodexThread } from './codex-app-server.js';
|
|
61
|
+
import { defaultApprovalIo, resolveLaunchBorgApprovals } from './cli-tool-approval.js';
|
|
62
|
+
|
|
63
|
+
export function buildDefaultAssimilateDeps(): AssimilateDeps {
|
|
64
|
+
return {
|
|
65
|
+
runSync: (cmd, args, cwd) => {
|
|
66
|
+
const r = spawnSync(cmd, args, { cwd, encoding: 'utf-8' });
|
|
67
|
+
return {
|
|
68
|
+
status: r.status,
|
|
69
|
+
stdout: r.stdout ?? '',
|
|
70
|
+
stderr: r.stderr ?? '',
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
pathExists: (p) => existsSync(p),
|
|
74
|
+
cwd: () => process.cwd(),
|
|
75
|
+
chdir: (p) => process.chdir(p),
|
|
76
|
+
// gh#556 Part 1: ~/.borg/worktrees relocation. homedir seams $HOME;
|
|
77
|
+
// mkdirp is a plain recursive create (NO chmod of existing parents — the
|
|
78
|
+
// ~/.borg credentials file's perms stay untouched).
|
|
79
|
+
homedir: () => osHomedir(),
|
|
80
|
+
mkdirp: (dir) => mkdirSync(dir, { recursive: true }),
|
|
81
|
+
exec: (cmd, args, cwd, env) =>
|
|
82
|
+
new Promise<number>((resolveExit, rejectExit) => {
|
|
83
|
+
// assimilate-cmd builds the complete child env before calling exec. Use
|
|
84
|
+
// it directly so caller-selected runtime values are not overwritten.
|
|
85
|
+
const child = spawnChild(cmd, args, {
|
|
86
|
+
cwd,
|
|
87
|
+
stdio: 'inherit',
|
|
88
|
+
shell: false,
|
|
89
|
+
env: env ?? process.env,
|
|
90
|
+
});
|
|
91
|
+
child.on('error', rejectExit);
|
|
92
|
+
child.on('exit', (code) => resolveExit(code ?? 0));
|
|
93
|
+
}),
|
|
94
|
+
|
|
95
|
+
stderr: (line) => process.stderr.write(line),
|
|
96
|
+
stdout: (line) => process.stdout.write(line),
|
|
97
|
+
prompt: async (message: string): Promise<string> => {
|
|
98
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
99
|
+
try {
|
|
100
|
+
return await rl.question(message);
|
|
101
|
+
} finally {
|
|
102
|
+
rl.close();
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
promptSecret: async (message: string): Promise<string> => {
|
|
106
|
+
const result = await prompts({
|
|
107
|
+
type: 'password',
|
|
108
|
+
name: 'invitation',
|
|
109
|
+
message,
|
|
110
|
+
});
|
|
111
|
+
return typeof result.invitation === 'string' ? result.invitation : '';
|
|
112
|
+
},
|
|
113
|
+
isTTY: () => process.stdin.isTTY === true,
|
|
114
|
+
resolveCliApprovals: (cli, cwd) => resolveLaunchBorgApprovals(
|
|
115
|
+
cli,
|
|
116
|
+
defaultApprovalIo(
|
|
117
|
+
async (message) => {
|
|
118
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
119
|
+
try {
|
|
120
|
+
return await rl.question(message);
|
|
121
|
+
} finally {
|
|
122
|
+
rl.close();
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
() => process.stdin.isTTY === true,
|
|
126
|
+
{ cwd, env: process.env, codexArgs: [] }
|
|
127
|
+
)
|
|
128
|
+
),
|
|
129
|
+
|
|
130
|
+
getHostname: () => osHostname(),
|
|
131
|
+
setTerminalTitle: (label, cubeName) => {
|
|
132
|
+
setTitle({ label, cubeName }, cubeName);
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
getActiveCube: () => cubesGetActive(),
|
|
136
|
+
hasPersistedActiveCube: () => cubesHasPersistedActive(),
|
|
137
|
+
readPersistedLocalSeat: () => readPersistedLocalSeat(),
|
|
138
|
+
peekServerSessionRecord: async (credentialRef, binding) =>
|
|
139
|
+
(await observeSeat(credentialRef, binding)).state !== 'absent',
|
|
140
|
+
// CR#3: recover an in-flight implicit-sibling attempt (unbound pending sibling
|
|
141
|
+
// record) by source repo, so a rerun re-derives the EXACT seat + reuses the bearer.
|
|
142
|
+
findIncompleteSiblingAttempt: async (binding) => {
|
|
143
|
+
const record = await findIncompleteSiblingAttempt(binding);
|
|
144
|
+
if (!record) return null;
|
|
145
|
+
return { operation: record.operation, roleId: record.roleId, credentialRef: seatRef(record) };
|
|
146
|
+
},
|
|
147
|
+
probeSeat: (sessionToken, apiUrl, serverTrustIdentity) =>
|
|
148
|
+
defaultProbeSeat(sessionToken, apiUrl, serverTrustIdentity),
|
|
149
|
+
setActiveCube: (a) => cubesSetActive(a),
|
|
150
|
+
// Single-store FINALIZE: the merged activate+bind (reached via the injected
|
|
151
|
+
// `activate` thunk from sendBorgServerAttach) stamps the exact
|
|
152
|
+
// digest-matched PENDING record ACTIVE and binds the decided worktree in ONE
|
|
153
|
+
// atomic commit. PREPARE-time revalidation already ran (prepareSeat), so the
|
|
154
|
+
// only outcomes here are committed or a post-mint activation failure (CR#5:
|
|
155
|
+
// missing/replaced/throw → the pending record is the rerunnable locator; the
|
|
156
|
+
// caller PRESERVES the worktree). expectation-mismatch is produced upstream at
|
|
157
|
+
// PREPARE (result.prepareAborted), never here.
|
|
158
|
+
finalizeServerSeat: async ({ active, activate }) => {
|
|
159
|
+
const binding = {
|
|
160
|
+
worktree: cubesFindProjectRoot(process.cwd()),
|
|
161
|
+
name: active.name,
|
|
162
|
+
droneLabel: active.droneLabel,
|
|
163
|
+
...(active.roleName !== undefined ? { roleName: active.roleName } : {}),
|
|
164
|
+
...(active.roleClass !== undefined ? { roleClass: active.roleClass } : {}),
|
|
165
|
+
...(active.isHumanSeat !== undefined ? { isHumanSeat: active.isHumanSeat } : {}),
|
|
166
|
+
};
|
|
167
|
+
let outcome: ActivateSeatOutcome;
|
|
168
|
+
try {
|
|
169
|
+
outcome = (await activate(binding)) as ActivateSeatOutcome;
|
|
170
|
+
} catch {
|
|
171
|
+
return { committed: false, reason: 'activation-failed' };
|
|
172
|
+
}
|
|
173
|
+
return outcome === 'activated'
|
|
174
|
+
? { committed: true }
|
|
175
|
+
: { committed: false, reason: 'activation-failed' };
|
|
176
|
+
},
|
|
177
|
+
findProjectRoot: (cwd) => cubesFindProjectRoot(cwd),
|
|
178
|
+
|
|
179
|
+
// gh#673 P2 (WI-1): project-local SessionStart hook for the launch root.
|
|
180
|
+
installProjectSessionHook: (projectRoot) => {
|
|
181
|
+
addProjectSessionStartHook(projectRoot);
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
// #1015: discovery is advisory but still verifies the server-owned CA.
|
|
185
|
+
detectLocalServer: async () =>
|
|
186
|
+
(await probeLocalBorgServer(DEFAULT_LOCAL_SERVER_ORIGIN))
|
|
187
|
+
? DEFAULT_LOCAL_SERVER_ORIGIN
|
|
188
|
+
: null,
|
|
189
|
+
connectServer: async (apiUrl, enrollment) => {
|
|
190
|
+
if (enrollment) {
|
|
191
|
+
return enrollLocalBorgServer(apiUrl, enrollment.invitation, {
|
|
192
|
+
clientName: osHostname(),
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
return connectLocalBorgServer(apiUrl);
|
|
196
|
+
},
|
|
197
|
+
resumeServerEnrollment: async (apiUrl, onPending) =>
|
|
198
|
+
resumeLocalBorgServerEnrollment(apiUrl, {
|
|
199
|
+
...(onPending === undefined ? {} : { onPending }),
|
|
200
|
+
}),
|
|
201
|
+
|
|
202
|
+
listCubes: async (apiUrl, token, serverTrustIdentity) => {
|
|
203
|
+
if (serverTrustIdentity === undefined) {
|
|
204
|
+
throw new Error('Selected Borg server authority state is missing or unreadable');
|
|
205
|
+
}
|
|
206
|
+
const { cubes } = await remoteListCubes({
|
|
207
|
+
apiUrl,
|
|
208
|
+
authToken: token,
|
|
209
|
+
serverTrustIdentity,
|
|
210
|
+
});
|
|
211
|
+
return cubes.map((c: any) => ({ id: c.id, name: c.name }));
|
|
212
|
+
},
|
|
213
|
+
getCube: async (apiUrl, token, cubeId, serverTrustIdentity) => {
|
|
214
|
+
if (serverTrustIdentity === undefined) {
|
|
215
|
+
throw new Error('Selected Borg server authority state is missing or unreadable');
|
|
216
|
+
}
|
|
217
|
+
// BUG-2 fix (v0.9.2): remote-client now unwraps the server's
|
|
218
|
+
// `{cube, roles, drones}` shape, so the returned object is
|
|
219
|
+
// already flat with id/name/roles at the top level.
|
|
220
|
+
const cube = await remoteGetCube(cubeId, {
|
|
221
|
+
apiUrl,
|
|
222
|
+
authToken: token,
|
|
223
|
+
serverTrustIdentity,
|
|
224
|
+
});
|
|
225
|
+
return {
|
|
226
|
+
id: cube.id,
|
|
227
|
+
name: cube.name,
|
|
228
|
+
roles: cube.roles,
|
|
229
|
+
drones: (cube.drones ?? []).map((d: { role_id: string }) => ({ role_id: d.role_id })),
|
|
230
|
+
};
|
|
231
|
+
},
|
|
232
|
+
createCube: async (apiUrl, token, params, serverTrustIdentity) => {
|
|
233
|
+
if (serverTrustIdentity === undefined) {
|
|
234
|
+
throw new Error('Selected Borg server authority state is missing or unreadable');
|
|
235
|
+
}
|
|
236
|
+
{
|
|
237
|
+
if (!params.name || !params.projectRoot) {
|
|
238
|
+
throw new Error('Local Borg server cube creation requires a repository name and root');
|
|
239
|
+
}
|
|
240
|
+
const created = await createLocalBorgServerCube(
|
|
241
|
+
apiUrl,
|
|
242
|
+
serverTrustIdentity,
|
|
243
|
+
token,
|
|
244
|
+
{ projectRoot: params.projectRoot, name: params.name },
|
|
245
|
+
);
|
|
246
|
+
const cube = await remoteGetCube(created.cube_id, {
|
|
247
|
+
apiUrl,
|
|
248
|
+
authToken: token,
|
|
249
|
+
serverTrustIdentity,
|
|
250
|
+
});
|
|
251
|
+
if (
|
|
252
|
+
cube.id !== created.cube_id ||
|
|
253
|
+
!Array.isArray(cube.roles) ||
|
|
254
|
+
!cube.roles.some((role: { id?: string }) => role.id === created.default_worker_role_id)
|
|
255
|
+
) {
|
|
256
|
+
throw new Error('Borg server returned cube details outside the creation result');
|
|
257
|
+
}
|
|
258
|
+
return {
|
|
259
|
+
id: cube.id,
|
|
260
|
+
name: cube.name,
|
|
261
|
+
roles: cube.roles,
|
|
262
|
+
drones: cube.drones ?? [],
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
assimilate: async (apiUrl, token, params, serverTrustIdentity) => {
|
|
267
|
+
if (serverTrustIdentity === undefined) {
|
|
268
|
+
throw new Error('Selected Borg server authority state is missing or unreadable');
|
|
269
|
+
}
|
|
270
|
+
{
|
|
271
|
+
if (params.session_operation === undefined) {
|
|
272
|
+
throw new Error('Borg server attach operation identity is missing');
|
|
273
|
+
}
|
|
274
|
+
const operation = params.session_operation;
|
|
275
|
+
const trust = await loadBorgServerTrust(apiUrl);
|
|
276
|
+
if (trust.identity !== serverTrustIdentity) {
|
|
277
|
+
throw new Error('Borg server trust identity changed; refusing the attach');
|
|
278
|
+
}
|
|
279
|
+
// CR #1: MINT under the SINGLE store flock via prepareSeat, which REVALIDATES
|
|
280
|
+
// the typed prepare-time expectation and mints the PENDING record in one lock
|
|
281
|
+
// hold — so a reset/writer that wins before PREPARE aborts the attach here
|
|
282
|
+
// before any credential is created or sent. Eviction-remint discards the
|
|
283
|
+
// known-invalid saved record (scrubBeforeMint) in the same flock. A fresh
|
|
284
|
+
// sibling spawn has no in-place binding to revalidate, so it passes
|
|
285
|
+
// revalidate:false but still mints under the one flock (never a bypass). The
|
|
286
|
+
// client CSPRNG-generates the bearer here; a lost-response retry / crash-in-gap
|
|
287
|
+
// is recovered by prepareSeat's idempotent mint-or-reuse (identical bearer).
|
|
288
|
+
const seed = {
|
|
289
|
+
origin: apiUrl,
|
|
290
|
+
trustIdentity: serverTrustIdentity,
|
|
291
|
+
cubeId: params.cube_id,
|
|
292
|
+
roleId: params.role_id,
|
|
293
|
+
operation,
|
|
294
|
+
credential: randomBytes(32).toString('base64url'),
|
|
295
|
+
};
|
|
296
|
+
const preparedMint = await prepareSeat({
|
|
297
|
+
expected: params.session_expected ?? { kind: 'absent' },
|
|
298
|
+
revalidate: params.revalidate_at_prepare === true && params.session_expected !== undefined,
|
|
299
|
+
scrubBeforeMint: params.remint_invalid_prior === true,
|
|
300
|
+
seed,
|
|
301
|
+
});
|
|
302
|
+
if (!preparedMint.ok) {
|
|
303
|
+
return {
|
|
304
|
+
cube_id: params.cube_id,
|
|
305
|
+
drone_id: '',
|
|
306
|
+
drone_label: '',
|
|
307
|
+
role_id: params.role_id,
|
|
308
|
+
prepareAborted: true,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
const pending = preparedMint.record;
|
|
312
|
+
// Network only — the pending→ACTIVE flip + worktree BIND are merged into the
|
|
313
|
+
// single-store activate+bind op, deferred to the FINALIZE thunk so the
|
|
314
|
+
// binding lands ATOMICALLY WITH activation (ACTIVE-without-binding unreachable).
|
|
315
|
+
const prepared = await sendBorgServerAttach(
|
|
316
|
+
apiUrl,
|
|
317
|
+
serverTrustIdentity,
|
|
318
|
+
token,
|
|
319
|
+
{
|
|
320
|
+
cubeId: params.cube_id,
|
|
321
|
+
roleId: params.role_id,
|
|
322
|
+
operation,
|
|
323
|
+
...(params.prior_drone_id === undefined
|
|
324
|
+
? {}
|
|
325
|
+
: { priorDroneId: params.prior_drone_id }),
|
|
326
|
+
},
|
|
327
|
+
pending.credential,
|
|
328
|
+
{ fetchImpl: trust.fetchImpl },
|
|
329
|
+
);
|
|
330
|
+
if (params.prior_drone_id) {
|
|
331
|
+
// The seat identity did not match the reattach/remint intent. Scrub the
|
|
332
|
+
// caller's own pending record (no-op unless it is still ours + pending)
|
|
333
|
+
// before surfacing, so no orphan pending lingers.
|
|
334
|
+
if (params.remint_invalid_prior) {
|
|
335
|
+
if (prepared.result !== 'created' || prepared.drone.id === params.prior_drone_id) {
|
|
336
|
+
await prepared.scrubPending();
|
|
337
|
+
throw new Error('Borg server did not remint a fresh seat after eviction');
|
|
338
|
+
}
|
|
339
|
+
} else if (prepared.result !== 'reused' || prepared.drone.id !== params.prior_drone_id) {
|
|
340
|
+
await prepared.scrubPending();
|
|
341
|
+
throw new BorgServerError(
|
|
342
|
+
'ATTACH_CONFLICT',
|
|
343
|
+
'Borg server did not reattach the saved seat',
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return {
|
|
348
|
+
cube_id: prepared.cube.id,
|
|
349
|
+
drone_id: prepared.drone.id,
|
|
350
|
+
drone_label: prepared.drone.label,
|
|
351
|
+
role_id: prepared.role.id,
|
|
352
|
+
result: prepared.result,
|
|
353
|
+
local_session: {
|
|
354
|
+
credential_ref: prepared.credentialRef,
|
|
355
|
+
expires_at: prepared.session.expiresAt,
|
|
356
|
+
},
|
|
357
|
+
// Handles for the cube-lock-held FINALIZE (assimilate-cmd Step 8).
|
|
358
|
+
finalize: {
|
|
359
|
+
activate: prepared.activate,
|
|
360
|
+
scrubPending: prepared.scrubPending,
|
|
361
|
+
// CR#2: bind the surviving PENDING record to the preserved worktree on an
|
|
362
|
+
// activation failure so the rerun-from-there resumes the exact operation.
|
|
363
|
+
bindPending: prepared.bindPending,
|
|
364
|
+
},
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
},
|
|
369
|
+
// CR-PE-F1 wiring (Phase E merge brought this seam in): compute the
|
|
370
|
+
// inbox file path used by the borg-inbox-monitor command in step 8
|
|
371
|
+
// kickoff. Pure helper from cubes.ts; same computation claude.ts uses.
|
|
372
|
+
getInboxPath: (cubeId, droneId) => inboxPathForDrone(cubeId, droneId),
|
|
373
|
+
|
|
374
|
+
// BUG-5 / v0.9.3 wiring: spawn `borg-mcp` as a stdio child, send
|
|
375
|
+
// an initialize request, await a response (or timeout), then kill
|
|
376
|
+
// the child. Probe success indicates the MCP server starts cleanly
|
|
377
|
+
// and tools/list is reachable; failure means the launched Claude
|
|
378
|
+
// session will hit the race and need the kickoff prompt's
|
|
379
|
+
// ToolSearch recovery clause.
|
|
380
|
+
probeMcpReady: () =>
|
|
381
|
+
new Promise<boolean>((resolveProbe) => {
|
|
382
|
+
// gh#client#18: use absolute path to THIS installation's binary so the
|
|
383
|
+
// readiness probe starts the same server version that will be registered.
|
|
384
|
+
const child = spawnChild(resolveMcpBinaryPath(), [], {
|
|
385
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
386
|
+
shell: false,
|
|
387
|
+
env: readinessProbeEnv(),
|
|
388
|
+
});
|
|
389
|
+
let buffer = '';
|
|
390
|
+
let settled = false;
|
|
391
|
+
const settle = (result: boolean) => {
|
|
392
|
+
if (settled) return;
|
|
393
|
+
settled = true;
|
|
394
|
+
try { child.kill('SIGTERM'); } catch { /* ignore */ }
|
|
395
|
+
resolveProbe(result);
|
|
396
|
+
};
|
|
397
|
+
const timeout = setTimeout(() => settle(false), 2000);
|
|
398
|
+
child.on('error', () => { clearTimeout(timeout); settle(false); });
|
|
399
|
+
child.on('exit', () => { clearTimeout(timeout); settle(settled); });
|
|
400
|
+
child.stdout?.on('data', (chunk: Buffer) => {
|
|
401
|
+
buffer += chunk.toString('utf-8');
|
|
402
|
+
// initialize response contains "result" with "protocolVersion"
|
|
403
|
+
// and "capabilities". Light parse: line-buffered JSON-RPC.
|
|
404
|
+
for (const line of buffer.split('\n')) {
|
|
405
|
+
if (line.includes('"protocolVersion"') && line.includes('"result"')) {
|
|
406
|
+
clearTimeout(timeout);
|
|
407
|
+
settle(true);
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
const initReq = JSON.stringify({
|
|
413
|
+
jsonrpc: '2.0',
|
|
414
|
+
id: 1,
|
|
415
|
+
method: 'initialize',
|
|
416
|
+
params: {
|
|
417
|
+
protocolVersion: '2024-11-05',
|
|
418
|
+
capabilities: {},
|
|
419
|
+
clientInfo: { name: 'borg-assimilate-probe', version: '0.9.3' },
|
|
420
|
+
},
|
|
421
|
+
});
|
|
422
|
+
try {
|
|
423
|
+
child.stdin?.write(initReq + '\n');
|
|
424
|
+
} catch {
|
|
425
|
+
clearTimeout(timeout);
|
|
426
|
+
settle(false);
|
|
427
|
+
}
|
|
428
|
+
}),
|
|
429
|
+
resolveCli: (explicit) => resolveCliChoice(
|
|
430
|
+
explicit,
|
|
431
|
+
defaultCliChoiceDeps(async (message) => {
|
|
432
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
433
|
+
try {
|
|
434
|
+
return await rl.question(message);
|
|
435
|
+
} finally {
|
|
436
|
+
rl.close();
|
|
437
|
+
}
|
|
438
|
+
}, () => process.stdin.isTTY === true)
|
|
439
|
+
),
|
|
440
|
+
prepareCodexRemoteLaunch: () => prepareCodexRemoteLaunch(defaultCodexRemoteDeps()),
|
|
441
|
+
setCodexWakeTarget,
|
|
442
|
+
findLoadedCodexThread,
|
|
443
|
+
};
|
|
444
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gh#780 option (ii): in-session borg_assimilate is RE-ATTACH-ONLY
|
|
3
|
+
* (Queen ruling 33a62d94).
|
|
4
|
+
*
|
|
5
|
+
* Root-cause context: the retired attach path always
|
|
6
|
+
* mints a new drone row — so agents
|
|
7
|
+
* "recovering" from auth blips spawned orphan seats. The tool is now
|
|
8
|
+
* structurally incapable of minting: it either re-attaches to the
|
|
9
|
+
* worktree's saved identity (cubes.json) or refuses with CLI guidance.
|
|
10
|
+
* Seat CREATION stays in the CLI (`borg assimilate` in a terminal), where
|
|
11
|
+
* worktree spawn + identity persistence are handled coherently.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { ActiveCube } from './cubes.js';
|
|
15
|
+
|
|
16
|
+
export type AssimilateDecision =
|
|
17
|
+
| { kind: 'reattach' }
|
|
18
|
+
| { kind: 'no-identity' }
|
|
19
|
+
| { kind: 'different-cube'; activeCubeName: string };
|
|
20
|
+
|
|
21
|
+
/** Cube names are lowercase server-side; tolerate caller case/whitespace. */
|
|
22
|
+
function normalizeCubeName(name: string): string {
|
|
23
|
+
return name.trim().toLowerCase();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Classify an in-session assimilate request against the worktree's saved
|
|
28
|
+
* identity. Pure — I/O and rendering stay outside this helper.
|
|
29
|
+
*/
|
|
30
|
+
export function classifyInSessionAssimilate(
|
|
31
|
+
active: Pick<ActiveCube, 'name'> | null,
|
|
32
|
+
requestedCubeName: string
|
|
33
|
+
): AssimilateDecision {
|
|
34
|
+
if (!active) return { kind: 'no-identity' };
|
|
35
|
+
if (normalizeCubeName(active.name) === normalizeCubeName(requestedCubeName)) {
|
|
36
|
+
return { kind: 'reattach' };
|
|
37
|
+
}
|
|
38
|
+
return { kind: 'different-cube', activeCubeName: active.name };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Agent-facing refusal text for the two non-reattach decisions. Always
|
|
43
|
+
* directs seat creation to the CLI; never suggests an in-session mint.
|
|
44
|
+
*/
|
|
45
|
+
export function reattachOnlyRefusal(
|
|
46
|
+
decision: Exclude<AssimilateDecision, { kind: 'reattach' }>,
|
|
47
|
+
requestedCubeName: string
|
|
48
|
+
): string {
|
|
49
|
+
if (decision.kind === 'no-identity') {
|
|
50
|
+
return (
|
|
51
|
+
`◼ This session has no drone seat for this worktree, and in-session borg_assimilate is ` +
|
|
52
|
+
`re-attach-only (it never creates seats — gh#780). To create a seat for cube ` +
|
|
53
|
+
`"${requestedCubeName}", run \`borg assimilate\` in a terminal — it spawns the worktree, ` +
|
|
54
|
+
`persists the identity, and launches the agent in one step.`
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
return (
|
|
58
|
+
`◼ This worktree is attached to cube "${decision.activeCubeName}"; in-session ` +
|
|
59
|
+
`borg_assimilate is re-attach-only and cannot switch to "${requestedCubeName}" (gh#780). ` +
|
|
60
|
+
`To work in "${requestedCubeName}", run \`borg assimilate\` in a terminal from that ` +
|
|
61
|
+
`project (or spawn a fresh worktree for it).`
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Failure advice when a re-attach's server-validated calls fail.
|
|
67
|
+
*
|
|
68
|
+
* The seat is unreachable (evicted seat, revoked session, dead cube):
|
|
69
|
+
* surface the server error verbatim plus CLI guidance. NEVER advise an
|
|
70
|
+
* in-session re-mint (SR cond-4: no fabricated success, lean on server
|
|
71
|
+
* re-validation).
|
|
72
|
+
*/
|
|
73
|
+
export function reattachFailureMessage(error: {
|
|
74
|
+
name?: string;
|
|
75
|
+
message?: string;
|
|
76
|
+
}): string {
|
|
77
|
+
const detail = error.message ?? String(error);
|
|
78
|
+
return (
|
|
79
|
+
`◼ Re-attach failed — this worktree's saved seat is unreachable (likely evicted or its ` +
|
|
80
|
+
`session was revoked). Server said: ${detail}\n` +
|
|
81
|
+
`Recover by running \`borg assimilate\` in a terminal to create a fresh seat; ` +
|
|
82
|
+
`in-session borg_assimilate never re-mints (gh#780).`
|
|
83
|
+
);
|
|
84
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Pedagogical hint rendered to stdout immediately after assimilation
|
|
2
|
+
// completes, before Claude Code launches. The cube-agnostic shape points
|
|
3
|
+
// the user at borg_whoami / borg_roster rather than embedding role-specific
|
|
4
|
+
// text — so
|
|
5
|
+
// cubes using non-default templates (writers-room, ops, etc.) render identically
|
|
6
|
+
// to software-dev cubes. The concrete drone, role seat, and cube names come from
|
|
7
|
+
// the completed server attach response.
|
|
8
|
+
//
|
|
9
|
+
// Color: ANSI green on the ✓ glyph only; gated on the caller's useColor
|
|
10
|
+
// boolean (computed from process.stdout.isTTY && !NO_COLOR && !CI in the
|
|
11
|
+
// assimilate-cmd call site). Body text carries no ANSI by design — color
|
|
12
|
+
// is hierarchy-cueing, not decoration.
|
|
13
|
+
|
|
14
|
+
const GREEN = '\x1b[32m';
|
|
15
|
+
const RESET = '\x1b[0m';
|
|
16
|
+
|
|
17
|
+
export function renderAssimilationWelcome(
|
|
18
|
+
droneLabel: string,
|
|
19
|
+
roleName: string,
|
|
20
|
+
cubeName: string,
|
|
21
|
+
useColor: boolean,
|
|
22
|
+
localApiUrl?: string,
|
|
23
|
+
): string {
|
|
24
|
+
const check = useColor ? `${GREEN}✓${RESET}` : '✓';
|
|
25
|
+
const teammateLines = localApiUrl === undefined
|
|
26
|
+
? [`Add a teammate: run \`borg assimilate <role>\` in another terminal.`]
|
|
27
|
+
: [
|
|
28
|
+
`Add a teammate from the intended worktree:`,
|
|
29
|
+
`run \`borg assimilate --host ${localApiUrl} <role>\` in another terminal.`,
|
|
30
|
+
];
|
|
31
|
+
return [
|
|
32
|
+
`${check} Attached \`${droneLabel}\` to \`${roleName}\` in cube \`${cubeName}\`.`,
|
|
33
|
+
``,
|
|
34
|
+
`In the launched agent, run \`borg_whoami\` and \`borg_roster\` to verify the seat`,
|
|
35
|
+
`and begin coordinating.`,
|
|
36
|
+
...teammateLines,
|
|
37
|
+
``,
|
|
38
|
+
].join('\n');
|
|
39
|
+
}
|
package/src/auth-env.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local environment / capability primitives.
|
|
3
|
+
*
|
|
4
|
+
* Pure helpers kept free of side effects (env is injected) so the decision logic
|
|
5
|
+
* is unit-testable. (The OS-keychain availability probe was removed with the
|
|
6
|
+
* Queen rescope — credentials now rest in the 0600 file store, so there is no
|
|
7
|
+
* keychain to probe.)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A BORG_* toggle is "on" only when present and not one of the falsy
|
|
12
|
+
* spellings. Mirrors how the rest of the client reads boolean env vars:
|
|
13
|
+
* an unset var and the explicit "0"/"false"/"" spellings are all off.
|
|
14
|
+
* Used by the gh#673 launch-gate (BORG_SESSION) so every BORG_* boolean
|
|
15
|
+
* reads through one convention.
|
|
16
|
+
*/
|
|
17
|
+
export function envToggleOn(value: string | undefined): boolean {
|
|
18
|
+
if (value === undefined) return false;
|
|
19
|
+
const v = value.trim().toLowerCase();
|
|
20
|
+
return v !== '' && v !== '0' && v !== 'false' && v !== 'no';
|
|
21
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// gh#556 Part 2 — pastelist backend (never-fail floor, spec §4.3).
|
|
2
|
+
// Prints copy-pasteable `cd <dir> && borg assimilate --here` lines.
|
|
3
|
+
|
|
4
|
+
import type { DroneCandidate } from '../launch-all-discovery.js';
|
|
5
|
+
import type { LaunchAllDeps } from '../launch-all-deps.js';
|
|
6
|
+
import { buildLaunchCommand } from '../launch-all-command.js';
|
|
7
|
+
|
|
8
|
+
export function runPastelistBackend(
|
|
9
|
+
candidates: DroneCandidate[],
|
|
10
|
+
borgPath: string,
|
|
11
|
+
deps: LaunchAllDeps
|
|
12
|
+
): void {
|
|
13
|
+
deps.stdout('# borg launch-all: open each worktree in a terminal window and run:\n\n');
|
|
14
|
+
for (const c of candidates) {
|
|
15
|
+
// No keep-open-on-fail: the operator owns their own shell in pastelist mode.
|
|
16
|
+
deps.stdout(buildLaunchCommand(c.worktreeDir, borgPath) + '\n');
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// gh#556 Part 2 — tmux backend (primary, spec §4.1).
|
|
2
|
+
// One detached `borg-<cube>` session, one window per drone, each running
|
|
3
|
+
// `borg assimilate --here`. Idempotent: reuses an existing session (adds windows).
|
|
4
|
+
|
|
5
|
+
import type { DroneCandidate } from '../launch-all-discovery.js';
|
|
6
|
+
import type { LaunchAllDeps } from '../launch-all-deps.js';
|
|
7
|
+
import { buildLaunchCommand } from '../launch-all-command.js';
|
|
8
|
+
import { writeLockMarker } from '../launch-all-locks.js';
|
|
9
|
+
|
|
10
|
+
export interface TmuxOpts {
|
|
11
|
+
sessionName: string;
|
|
12
|
+
borgPath: string;
|
|
13
|
+
/** 'attach' = attach-session; 'switch' = switch-client (nested tmux); 'none' = skip. */
|
|
14
|
+
attachMode: 'attach' | 'switch' | 'none';
|
|
15
|
+
/** ISO-8601 captured before the first send-keys (lock-marker launchedAt). */
|
|
16
|
+
launchedAtISO: string;
|
|
17
|
+
/** Stagger between drone launches (ms) to avoid the rate limiter; 0 disables. */
|
|
18
|
+
launchDelayMs: number;
|
|
19
|
+
/** Injectable sleep (real setTimeout in prod; no-op spy in tests). */
|
|
20
|
+
sleep: (ms: number) => Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function runTmuxBackend(
|
|
24
|
+
candidates: DroneCandidate[],
|
|
25
|
+
opts: TmuxOpts,
|
|
26
|
+
deps: LaunchAllDeps
|
|
27
|
+
): Promise<void> {
|
|
28
|
+
const { sessionName, borgPath, attachMode, launchedAtISO, launchDelayMs, sleep } = opts;
|
|
29
|
+
|
|
30
|
+
const sessionExists = deps.runSyncExitCode('tmux', ['has-session', '-t', sessionName]) === 0;
|
|
31
|
+
|
|
32
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
33
|
+
const c = candidates[i];
|
|
34
|
+
|
|
35
|
+
// Rate-limit stagger: wait BEFORE each launch after the first, so a fleet's
|
|
36
|
+
// agents don't all bootstrap (assimilate + regen + roster) simultaneously and
|
|
37
|
+
// trip the per-user/IP limiter. Window creation is ~instant otherwise, so
|
|
38
|
+
// without this N agents start within ~100ms. 0 → no delay (first never waits).
|
|
39
|
+
if (i > 0 && launchDelayMs > 0) {
|
|
40
|
+
await sleep(launchDelayMs);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// CR f98b2d70: target windows by their CAPTURED id (`-P -F '#{window_id}'` prints
|
|
44
|
+
// e.g. `@3`), NOT a computed 0-based index. Index-targeting hard-failed under the
|
|
45
|
+
// common `set -g base-index 1` config (new-session creates window 1, not 0) and was
|
|
46
|
+
// fragile to index gaps on the reuse path. Capturing the real id is base-index-proof
|
|
47
|
+
// and gap-proof. new-session AND new-window both print the new window's id with -P -F.
|
|
48
|
+
let windowId: string;
|
|
49
|
+
if (i === 0 && !sessionExists) {
|
|
50
|
+
windowId = deps
|
|
51
|
+
.runSync('tmux', ['new-session', '-d', '-P', '-F', '#{window_id}', '-s', sessionName, '-c', c.worktreeDir])
|
|
52
|
+
.trim();
|
|
53
|
+
} else {
|
|
54
|
+
windowId = deps
|
|
55
|
+
.runSync('tmux', ['new-window', '-P', '-F', '#{window_id}', '-t', sessionName, '-c', c.worktreeDir])
|
|
56
|
+
.trim();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// tmux-level window name (status bar). The agent's own OSC-0 may update it later.
|
|
60
|
+
deps.runSync('tmux', ['rename-window', '-t', windowId, c.droneLabel]);
|
|
61
|
+
|
|
62
|
+
const cmd = buildLaunchCommand(c.worktreeDir, borgPath, { keepOpenOnFail: true });
|
|
63
|
+
deps.runSync('tmux', ['send-keys', '-t', windowId, cmd, 'Enter']);
|
|
64
|
+
|
|
65
|
+
// Lock marker written immediately after dispatch (best-effort liveness signal).
|
|
66
|
+
writeLockMarker(deps, c.cubeId, c.droneLabel, c.worktreeDir, launchedAtISO);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (attachMode === 'switch') {
|
|
70
|
+
deps.attachInteractive('tmux', ['switch-client', '-t', sessionName]);
|
|
71
|
+
} else if (attachMode === 'attach') {
|
|
72
|
+
deps.attachInteractive('tmux', ['attach-session', '-t', sessionName]);
|
|
73
|
+
}
|
|
74
|
+
// 'none' → caller prints the cheat-sheet instead.
|
|
75
|
+
}
|