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,188 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { mkdir, open, readFile, rename, stat, unlink, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { dirname, join } from 'node:path';
|
|
5
|
+
|
|
6
|
+
const CURSOR_FILE = join(homedir(), '.config', 'borgmcp', 'local-server-cursors.json');
|
|
7
|
+
const CURSOR_LOCK = `${CURSOR_FILE}.lock`;
|
|
8
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
9
|
+
|
|
10
|
+
export interface LocalServerCursor {
|
|
11
|
+
id: string;
|
|
12
|
+
created_at: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface LocalServerCursorBinding {
|
|
16
|
+
origin: string;
|
|
17
|
+
trustIdentity: string;
|
|
18
|
+
cubeId: string;
|
|
19
|
+
droneId: string;
|
|
20
|
+
/**
|
|
21
|
+
* client#41: cursor-purpose namespace. Absent (the default) is the UNREAD
|
|
22
|
+
* WATERMARK — the point `read-log unread_only` reads from and advances only
|
|
23
|
+
* on an explicit successful drain. `'stream'` is the SSE DELIVERY/RESUME
|
|
24
|
+
* cursor the live tail advances as it delivers events. Keeping these under
|
|
25
|
+
* separate keys stops SSE delivery from consuming the unread watermark (a
|
|
26
|
+
* wake-triggering entry would otherwise disappear from `unread_only` before
|
|
27
|
+
* the agent drained it — a silent missed wake).
|
|
28
|
+
*/
|
|
29
|
+
purpose?: 'stream';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface CursorFile {
|
|
33
|
+
version: 1;
|
|
34
|
+
cursors: Record<string, LocalServerCursor>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function cursorKey(binding: LocalServerCursorBinding): string {
|
|
38
|
+
if (
|
|
39
|
+
new URL(binding.origin).origin !== binding.origin ||
|
|
40
|
+
!UUID_RE.test(binding.cubeId) ||
|
|
41
|
+
!UUID_RE.test(binding.droneId)
|
|
42
|
+
) {
|
|
43
|
+
throw new Error('invalid local Borg server cursor binding');
|
|
44
|
+
}
|
|
45
|
+
const hash = createHash('sha256')
|
|
46
|
+
.update(binding.origin)
|
|
47
|
+
.update('\0')
|
|
48
|
+
.update(binding.trustIdentity)
|
|
49
|
+
.update('\0')
|
|
50
|
+
.update(binding.cubeId)
|
|
51
|
+
.update('\0')
|
|
52
|
+
.update(binding.droneId);
|
|
53
|
+
// The purpose component is appended ONLY when present, so the unread-watermark
|
|
54
|
+
// key (purpose absent) stays byte-identical to the pre-client#41 key — already
|
|
55
|
+
// persisted watermarks are not orphaned by the upgrade. The 'stream' delivery
|
|
56
|
+
// cursor gets a distinct key.
|
|
57
|
+
if (binding.purpose) {
|
|
58
|
+
hash.update('\0').update(binding.purpose);
|
|
59
|
+
}
|
|
60
|
+
return hash.digest('hex');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function validCursor(value: unknown): value is LocalServerCursor {
|
|
64
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) return false;
|
|
65
|
+
const cursor = value as Record<string, unknown>;
|
|
66
|
+
return UUID_RE.test(String(cursor.id ?? '')) &&
|
|
67
|
+
typeof cursor.created_at === 'string' &&
|
|
68
|
+
Number.isFinite(Date.parse(cursor.created_at)) &&
|
|
69
|
+
new Date(cursor.created_at).toISOString() === cursor.created_at;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function readState(): Promise<CursorFile> {
|
|
73
|
+
try {
|
|
74
|
+
const parsed = JSON.parse(await readFile(CURSOR_FILE, 'utf8')) as Partial<CursorFile>;
|
|
75
|
+
if (
|
|
76
|
+
parsed.version !== 1 ||
|
|
77
|
+
typeof parsed.cursors !== 'object' ||
|
|
78
|
+
parsed.cursors === null ||
|
|
79
|
+
Array.isArray(parsed.cursors) ||
|
|
80
|
+
Object.values(parsed.cursors).some((cursor) => !validCursor(cursor))
|
|
81
|
+
) {
|
|
82
|
+
throw new Error('invalid');
|
|
83
|
+
}
|
|
84
|
+
return parsed as CursorFile;
|
|
85
|
+
} catch (error) {
|
|
86
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
87
|
+
return { version: 1, cursors: {} };
|
|
88
|
+
}
|
|
89
|
+
throw new Error('local Borg server cursor state is corrupt');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function writeState(state: CursorFile): Promise<void> {
|
|
94
|
+
await mkdir(dirname(CURSOR_FILE), { recursive: true });
|
|
95
|
+
const temporary = `${CURSOR_FILE}.${process.pid}.${Date.now()}.tmp`;
|
|
96
|
+
await writeFile(temporary, JSON.stringify(state, null, 2) + '\n', { mode: 0o600 });
|
|
97
|
+
await rename(temporary, CURSOR_FILE);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function withLock<T>(operation: () => Promise<T>): Promise<T> {
|
|
101
|
+
await mkdir(dirname(CURSOR_LOCK), { recursive: true });
|
|
102
|
+
for (let attempt = 0; attempt < 200; attempt += 1) {
|
|
103
|
+
let handle;
|
|
104
|
+
try {
|
|
105
|
+
handle = await open(CURSOR_LOCK, 'wx', 0o600);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;
|
|
108
|
+
try {
|
|
109
|
+
const metadata = await stat(CURSOR_LOCK);
|
|
110
|
+
if (Date.now() - metadata.mtimeMs > 30_000) {
|
|
111
|
+
await unlink(CURSOR_LOCK);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
} catch (inspectionError) {
|
|
115
|
+
if ((inspectionError as NodeJS.ErrnoException).code === 'ENOENT') continue;
|
|
116
|
+
throw inspectionError;
|
|
117
|
+
}
|
|
118
|
+
await new Promise((resolvePromise) => setTimeout(resolvePromise, 10));
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
return await operation();
|
|
123
|
+
} finally {
|
|
124
|
+
await handle.close();
|
|
125
|
+
try {
|
|
126
|
+
await unlink(CURSOR_LOCK);
|
|
127
|
+
} catch (error) {
|
|
128
|
+
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
throw new Error('local Borg server cursor state is busy');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export async function getLocalServerCursor(
|
|
136
|
+
binding: LocalServerCursorBinding,
|
|
137
|
+
): Promise<LocalServerCursor | null> {
|
|
138
|
+
const key = cursorKey(binding);
|
|
139
|
+
const state = await readState();
|
|
140
|
+
return state.cursors[key] ?? null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export async function advanceLocalServerCursor(
|
|
144
|
+
binding: LocalServerCursorBinding,
|
|
145
|
+
cursor: LocalServerCursor,
|
|
146
|
+
): Promise<void> {
|
|
147
|
+
if (!validCursor(cursor)) throw new Error('invalid local Borg server cursor');
|
|
148
|
+
const key = cursorKey(binding);
|
|
149
|
+
await withLock(async () => {
|
|
150
|
+
const state = await readState();
|
|
151
|
+
const prior = state.cursors[key];
|
|
152
|
+
if (
|
|
153
|
+
prior &&
|
|
154
|
+
(prior.created_at > cursor.created_at ||
|
|
155
|
+
(prior.created_at === cursor.created_at && prior.id >= cursor.id))
|
|
156
|
+
) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
state.cursors[key] = cursor;
|
|
160
|
+
await writeState(state);
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* client#42: reset (delete) a persisted cursor for `binding`. Used by the SSE
|
|
166
|
+
* recovery path when the server returns 410 CURSOR_EXPIRED for the stream's
|
|
167
|
+
* resume cursor — the pointed-at entry has been pruned server-side, so the
|
|
168
|
+
* stale cursor can never be resumed and must be cleared, letting the next
|
|
169
|
+
* stream connect re-establish from a fresh valid point (the current tail)
|
|
170
|
+
* instead of looping forever on the dead cursor. No-op when no cursor is
|
|
171
|
+
* stored for the binding.
|
|
172
|
+
*/
|
|
173
|
+
export async function clearLocalServerCursor(
|
|
174
|
+
binding: LocalServerCursorBinding,
|
|
175
|
+
): Promise<void> {
|
|
176
|
+
const key = cursorKey(binding);
|
|
177
|
+
await withLock(async () => {
|
|
178
|
+
const state = await readState();
|
|
179
|
+
if (!(key in state.cursors)) return;
|
|
180
|
+
delete state.cursors[key];
|
|
181
|
+
await writeState(state);
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function encodeLocalServerCursor(cursor: LocalServerCursor): string {
|
|
186
|
+
if (!validCursor(cursor)) throw new Error('invalid local Borg server cursor');
|
|
187
|
+
return Buffer.from(JSON.stringify(cursor), 'utf8').toString('base64url');
|
|
188
|
+
}
|
package/src/log-audit.ts
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* borg-log-audit
|
|
4
|
+
*
|
|
5
|
+
* Domain-agnostic nudge: scans the Claude Code transcript and emits a
|
|
6
|
+
* one-line warning to stdout if the drone has accumulated MATERIAL_THRESHOLD
|
|
7
|
+
* or more state-changing tool calls (Edit / Write / Bash / etc.) since the
|
|
8
|
+
* last borg_log post. Wired in as a UserPromptSubmit hook so the warning
|
|
9
|
+
* becomes additional context for the next turn.
|
|
10
|
+
*
|
|
11
|
+
* Two refinements vs the v1 1-tool threshold (per drone-6's review):
|
|
12
|
+
* 1. Counts material tools across all assistant turns until either the
|
|
13
|
+
* threshold is hit OR a borg_log call is found (cooldown). One
|
|
14
|
+
* diagnostic Bash no longer triggers; substantive work always does.
|
|
15
|
+
* 2. Any borg_log in the scanback suppresses the nudge — so the drone
|
|
16
|
+
* gets a turn of breathing room after each post.
|
|
17
|
+
*
|
|
18
|
+
* Stays generic — knows nothing about git, branches, or any project's
|
|
19
|
+
* conventions. Only the Anthropic tool name `mcp__borg__borg_log` and a
|
|
20
|
+
* small set of canonical mutating tool names. If no cube is active in
|
|
21
|
+
* this project, silently exits.
|
|
22
|
+
*
|
|
23
|
+
* Hook input arrives as JSON on stdin (Claude Code's standard hook
|
|
24
|
+
* contract). The relevant field is `transcript_path`.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
28
|
+
import { getActiveCube } from './cubes.js';
|
|
29
|
+
import { handleVersionFlag } from './version.js';
|
|
30
|
+
import { gateAllowsActivation } from './launch-gate.js';
|
|
31
|
+
|
|
32
|
+
const MATERIAL_TOOLS = new Set([
|
|
33
|
+
'Edit',
|
|
34
|
+
'Write',
|
|
35
|
+
'MultiEdit',
|
|
36
|
+
'NotebookEdit',
|
|
37
|
+
'Bash',
|
|
38
|
+
'apply_patch',
|
|
39
|
+
'exec_command',
|
|
40
|
+
'functions.exec_command',
|
|
41
|
+
'functions.apply_patch',
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
const LOG_TOOL = 'mcp__borg__borg_log';
|
|
45
|
+
|
|
46
|
+
// Number of state-changing tool calls since the last borg_log that the
|
|
47
|
+
// drone is allowed before the audit nudges. 1 false-positives on
|
|
48
|
+
// diagnostic Bash (git status, ls, etc.); 3 has been comfortable in
|
|
49
|
+
// dogfooding — any substantive work crosses it within a turn or two.
|
|
50
|
+
const MATERIAL_THRESHOLD = 3;
|
|
51
|
+
|
|
52
|
+
// Cap on how many transcript entries we scan backwards before giving up.
|
|
53
|
+
// Sessions with thousands of turns still resolve in milliseconds at this
|
|
54
|
+
// bound, and anything truly old is no longer "the last span the drone
|
|
55
|
+
// failed to log."
|
|
56
|
+
const MAX_SCAN = 400;
|
|
57
|
+
|
|
58
|
+
interface HookInput {
|
|
59
|
+
transcript_path?: string;
|
|
60
|
+
cwd?: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function isUserPrompt(entry: any): boolean {
|
|
64
|
+
const type = entry?.type ?? entry?.role;
|
|
65
|
+
if (type !== 'user') return false;
|
|
66
|
+
const content = entry?.message?.content ?? entry?.content;
|
|
67
|
+
if (typeof content === 'string') return content.trim().length > 0;
|
|
68
|
+
if (!Array.isArray(content)) return false;
|
|
69
|
+
// A "real" user prompt has at least one text block. A tool_result-only
|
|
70
|
+
// user message is a continuation of an assistant span, not a prompt.
|
|
71
|
+
return content.some((b: any) => b?.type === 'text');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function isAssistant(entry: any): boolean {
|
|
75
|
+
const type = entry?.type ?? entry?.role;
|
|
76
|
+
return type === 'assistant' || type === 'response_item';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
type ScanFinding =
|
|
80
|
+
| { kind: 'cooldown' } // hit a borg_log before threshold — suppress
|
|
81
|
+
| { kind: 'over_threshold'; count: number } // accumulated >= threshold
|
|
82
|
+
| { kind: 'under_threshold'; count: number }; // ran out of transcript
|
|
83
|
+
|
|
84
|
+
function scanAssistant(entry: any, state: { material: number; loggedRecently: boolean }): void {
|
|
85
|
+
const payload = entry?.payload;
|
|
86
|
+
const payloadToolName =
|
|
87
|
+
payload?.type === 'function_call' || payload?.type === 'custom_tool_call'
|
|
88
|
+
? payload.name
|
|
89
|
+
: null;
|
|
90
|
+
if (typeof payloadToolName === 'string') {
|
|
91
|
+
if (payloadToolName === LOG_TOOL) {
|
|
92
|
+
state.loggedRecently = true;
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (MATERIAL_TOOLS.has(payloadToolName)) state.material += 1;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const content = entry?.message?.content ?? entry?.content ?? [];
|
|
99
|
+
if (!Array.isArray(content)) return;
|
|
100
|
+
// Walk the blocks newest-first WITHIN the entry. The caller already
|
|
101
|
+
// visits entries newest-first. Counting forward within an entry would
|
|
102
|
+
// either miss post-log material work (if log is in the same entry as
|
|
103
|
+
// later material blocks) or inflate the count with pre-log work that
|
|
104
|
+
// the log already covered. Reversing here keeps "material since the
|
|
105
|
+
// last log" honest at block granularity.
|
|
106
|
+
for (let i = content.length - 1; i >= 0; i--) {
|
|
107
|
+
const block = content[i];
|
|
108
|
+
if (block?.type !== 'tool_use') continue;
|
|
109
|
+
if (block.name === LOG_TOOL) {
|
|
110
|
+
state.loggedRecently = true;
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (MATERIAL_TOOLS.has(block.name)) state.material += 1;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function readStdin(): Promise<string> {
|
|
118
|
+
if (process.stdin.isTTY) return '';
|
|
119
|
+
const chunks: Buffer[] = [];
|
|
120
|
+
for await (const chunk of process.stdin) chunks.push(chunk as Buffer);
|
|
121
|
+
return Buffer.concat(chunks).toString('utf-8');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function main(): Promise<void> {
|
|
125
|
+
handleVersionFlag();
|
|
126
|
+
// gh#673 P1 (WI-4): the log-audit nudge only activates in
|
|
127
|
+
// borg-launched sessions — vanilla `claude` gets no borg hook output.
|
|
128
|
+
// Exit-0 no-op (a UserPromptSubmit hook must never block the prompt).
|
|
129
|
+
// ACTIVATION-only, never a security gate.
|
|
130
|
+
if (!gateAllowsActivation('borg-log-audit UserPromptSubmit hook')) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const raw = await readStdin();
|
|
134
|
+
let input: HookInput = {};
|
|
135
|
+
if (raw.trim()) {
|
|
136
|
+
try {
|
|
137
|
+
input = JSON.parse(raw);
|
|
138
|
+
} catch {
|
|
139
|
+
// No usable input — silent exit.
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (!input.transcript_path || !existsSync(input.transcript_path)) return;
|
|
145
|
+
if (input.cwd && existsSync(input.cwd)) {
|
|
146
|
+
try {
|
|
147
|
+
process.chdir(input.cwd);
|
|
148
|
+
} catch {
|
|
149
|
+
// Best-effort only; fall back to the hook process cwd.
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Only nudge if there's an active cube in this project. Otherwise the
|
|
154
|
+
// hook is fully inert.
|
|
155
|
+
const active = await getActiveCube();
|
|
156
|
+
if (!active) return;
|
|
157
|
+
|
|
158
|
+
const lines = readFileSync(input.transcript_path, 'utf-8').split('\n').filter(Boolean);
|
|
159
|
+
if (lines.length === 0) return;
|
|
160
|
+
|
|
161
|
+
// Walk the transcript backwards from the end. The trailing entry MAY
|
|
162
|
+
// be the user prompt that triggered this hook; skip it if so. From
|
|
163
|
+
// there, accumulate material tool calls until either we hit a borg_log
|
|
164
|
+
// (cooldown — suppress) or we cross MATERIAL_THRESHOLD (nudge). The
|
|
165
|
+
// scan stops after MAX_SCAN entries to bound work on huge sessions.
|
|
166
|
+
let i = lines.length - 1;
|
|
167
|
+
const tail = safeParse(lines[i]);
|
|
168
|
+
if (tail && isUserPrompt(tail)) i--;
|
|
169
|
+
|
|
170
|
+
const state = { material: 0, loggedRecently: false };
|
|
171
|
+
let scanned = 0;
|
|
172
|
+
for (; i >= 0 && scanned < MAX_SCAN; i--, scanned++) {
|
|
173
|
+
const entry = safeParse(lines[i]);
|
|
174
|
+
if (!entry) continue;
|
|
175
|
+
if (isAssistant(entry)) {
|
|
176
|
+
scanAssistant(entry, state);
|
|
177
|
+
// Threshold has primacy over cooldown: when both could fire on the
|
|
178
|
+
// same entry (e.g. an entry containing [log, Bash, Bash, Bash]
|
|
179
|
+
// where the reversed scan first counts 3 material blocks before
|
|
180
|
+
// hitting the log), we want the nudge — the post-log material
|
|
181
|
+
// work hasn't been logged yet.
|
|
182
|
+
if (state.material >= MATERIAL_THRESHOLD) {
|
|
183
|
+
process.stdout.write(
|
|
184
|
+
`Heads up: ${state.material}+ state-changing tool calls since the last \`borg_log\` post. ` +
|
|
185
|
+
'If that work was a substantive unit (a change that ships, a blocker hit, a finding ' +
|
|
186
|
+
"worth sharing), post to the cube log per your role's conventions before continuing.\n"
|
|
187
|
+
);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (state.loggedRecently) return; // cooldown
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// Reached MAX_SCAN or start of transcript without finding either a
|
|
194
|
+
// log call or enough material work. Silent.
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function safeParse(line: string): any | null {
|
|
198
|
+
try {
|
|
199
|
+
return JSON.parse(line);
|
|
200
|
+
} catch {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
main().catch(() => {
|
|
206
|
+
// Never fail a hook — silent on error.
|
|
207
|
+
});
|