@pellux/goodvibes-tui 1.27.1 → 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/CHANGELOG.md +116 -2
- package/README.md +7 -9
- package/bin/goodvibes +2 -2
- package/bin/launcher-support.js +8 -4
- package/docs/README.md +11 -2
- package/docs/foundation-artifacts/operator-contract.json +6403 -2347
- package/package.json +14 -25
- package/scripts/postinstall.js +35 -35
- package/src/audio/spoken-turn-model-routing.ts +1 -1
- package/src/cli/bundle-command.ts +5 -5
- package/src/cli/completions/generate.ts +1 -1
- package/src/cli/doctor.ts +3 -3
- package/src/cli/entrypoint.ts +12 -7
- package/src/cli/help.ts +4 -5
- package/src/cli/hooks-command.ts +1 -1
- package/src/cli/index.ts +1 -4
- package/src/cli/launch-auto-update.ts +59 -7
- package/src/cli/management-commands.ts +6 -6
- package/src/cli/management-utils.ts +5 -5
- package/src/cli/management.ts +7 -7
- package/src/cli/network-posture.ts +1 -1
- package/src/cli/package-verification.ts +7 -6
- package/src/cli/plugin-bundles-command.ts +1 -1
- package/src/cli/plugin-command.ts +2 -2
- package/src/cli/service-command.ts +2 -2
- package/src/cli/service-posture.ts +4 -4
- package/src/cli/status.ts +9 -8
- package/src/cli/surface-command.ts +4 -4
- package/src/cli/tui-startup.ts +3 -3
- package/src/cli-flags.ts +3 -3
- package/src/config/goodvibes-home-audit.ts +0 -2
- package/src/config/secret-config.ts +37 -5
- package/src/config/tui-extension-settings.ts +1 -1
- package/src/core/approval-alert.ts +2 -2
- package/src/core/budget-breach-notifier.ts +3 -3
- package/src/core/conversation-compaction-render.ts +1 -1
- package/src/core/conversation-line-cache.ts +1 -1
- package/src/core/conversation-render-context.ts +1 -1
- package/src/core/conversation-rendering.ts +2 -2
- package/src/core/conversation.ts +10 -10
- package/src/core/long-task-notifier.ts +2 -2
- package/src/core/memory-diagnostics-gateway.ts +1 -1
- package/src/core/scriptable-statusline.ts +1 -1
- package/src/core/session-auto-titler.ts +1 -1
- package/src/core/session-resume-core.ts +2 -2
- package/src/core/stream-event-wiring.ts +24 -44
- package/src/core/terminal-notifier.ts +3 -3
- package/src/core/turn-cancellation.ts +1 -1
- package/src/core/turn-event-wiring.ts +5 -5
- package/src/input/command-registry.ts +6 -6
- package/src/input/commands/channel-pairing.ts +1 -1
- package/src/input/commands/cluster-runtime.ts +1 -1
- package/src/input/commands/codebase-runtime.ts +1 -1
- package/src/input/commands/devices-runtime.ts +1 -1
- package/src/input/commands/hosted-runtime.ts +330 -0
- package/src/input/commands/local-setup-review.ts +1 -1
- package/src/input/commands/local-setup-transfer.ts +2 -2
- package/src/input/commands/local-setup.ts +3 -3
- package/src/input/commands/managed-runtime.ts +2 -2
- package/src/input/commands/operator-rpc.ts +24 -110
- package/src/input/commands/payment-card-intake.ts +1 -1
- package/src/input/commands/permissions-provenance.ts +2 -2
- package/src/input/commands/plugin-runtime.ts +1 -1
- package/src/input/commands/provider.ts +1 -1
- package/src/input/commands/relay-runtime.ts +18 -9
- package/src/input/commands/rewind-runtime.ts +1 -1
- package/src/input/commands/runtime-services.ts +1 -1
- package/src/input/commands/services-runtime.ts +1 -1
- package/src/input/commands/session-workflow.ts +1 -1
- package/src/input/commands/settings-sync-runtime.ts +2 -2
- package/src/input/commands/share-runtime.ts +1 -1
- package/src/input/commands/tasks-runtime.ts +145 -55
- package/src/input/commands/update-runtime.ts +31 -28
- package/src/input/commands/work-plan-runtime.ts +2 -2
- package/src/input/commands/workstream-attempts.ts +1 -1
- package/src/input/commands/workstream-runtime.ts +2 -2
- package/src/input/commands.ts +2 -0
- package/src/input/feature-unit-layout.ts +1 -1
- package/src/input/feed-context-factory.ts +3 -3
- package/src/input/handler-content-actions.ts +2 -2
- package/src/input/handler-feed-routes.ts +1 -1
- package/src/input/handler-feed.ts +3 -3
- package/src/input/handler-modal-token-routes.ts +1 -1
- package/src/input/handler-onboarding-daemon-adopt.ts +3 -2
- package/src/input/handler-onboarding.ts +1 -1
- package/src/input/handler-types.ts +2 -2
- package/src/input/handler-ui-state.ts +1 -1
- package/src/input/handler.ts +4 -4
- package/src/input/input-history.ts +1 -1
- package/src/input/onboarding/onboarding-feature-units.ts +2 -2
- package/src/input/onboarding/onboarding-runtime-status.ts +15 -1
- package/src/input/onboarding/onboarding-wizard-apply.ts +17 -8
- package/src/input/onboarding/onboarding-wizard-cloudflare-step.ts +5 -3
- package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +1 -1
- package/src/input/onboarding/onboarding-wizard-types.ts +1 -1
- package/src/input/payments-config.ts +6 -6
- package/src/input/search.ts +1 -1
- package/src/input/session-picker-modal.ts +46 -0
- package/src/input/settings-modal-activation.ts +1 -1
- package/src/input/settings-modal-behavior.ts +4 -4
- package/src/input/settings-modal-commit.ts +11 -0
- package/src/input/settings-modal-data.ts +13 -80
- package/src/input/settings-modal-mutations.ts +33 -0
- package/src/input/settings-modal-secrets.ts +34 -0
- package/src/input/settings-modal-subscriptions.ts +1 -1
- package/src/input/settings-modal-types.ts +15 -1
- package/src/input/settings-modal.ts +28 -3
- package/src/input/worktree-setup-config.ts +1 -1
- package/src/main.ts +11 -9
- package/src/panels/agent-inspector-shared.ts +2 -2
- package/src/panels/base-panel.ts +2 -2
- package/src/panels/builtin/operations.ts +6 -8
- package/src/panels/builtin/session.ts +17 -0
- package/src/panels/builtin/shared.ts +5 -5
- package/src/panels/builtin-modals.ts +3 -3
- package/src/panels/confirm-state.ts +1 -1
- package/src/panels/cost-tracker-panel.ts +4 -4
- package/src/panels/diff-panel.ts +2 -2
- package/src/panels/diff-review-panel.ts +2 -2
- package/src/panels/expandable-list-panel.ts +2 -2
- package/src/panels/fleet-acts.ts +1 -1
- package/src/panels/fleet-observed-render.ts +1 -1
- package/src/panels/fleet-panel-format.ts +1 -1
- package/src/panels/fleet-panel.ts +1 -1
- package/src/panels/fleet-spawn.ts +1 -1
- package/src/panels/fleet-steer.ts +1 -1
- package/src/panels/fleet-transcript.ts +1 -1
- package/src/panels/git-panel.ts +1 -1
- package/src/panels/hosted-session-feed.ts +352 -0
- package/src/panels/hosted-session-panel.ts +221 -0
- package/src/panels/local-auth-panel.ts +3 -3
- package/src/panels/modals/devices-modal.ts +1 -1
- package/src/panels/modals/local-auth-modal.ts +1 -1
- package/src/panels/modals/pairing-modal.ts +1 -1
- package/src/panels/modals/services-modal.ts +1 -1
- package/src/panels/modals/settings-sync-modal.ts +1 -1
- package/src/panels/modals/subscription-modal.ts +1 -1
- package/src/panels/modals/work-plan-modal.ts +1 -1
- package/src/panels/notifications-panel.ts +1 -1
- package/src/panels/panel-confirm-overlay.ts +1 -1
- package/src/panels/plugins-panel.ts +2 -2
- package/src/panels/polish-core.ts +2 -2
- package/src/panels/polish-tables.ts +1 -1
- package/src/panels/polish.ts +2 -2
- package/src/panels/scrollable-list-panel.ts +3 -3
- package/src/panels/skills-panel.ts +3 -3
- package/src/panels/token-budget-panel.ts +5 -5
- package/src/panels/types.ts +1 -2
- package/src/permissions/prompt.ts +1 -1
- package/src/renderer/autocomplete-overlay.ts +1 -1
- package/src/renderer/block-actions-overlay.ts +1 -1
- package/src/renderer/bookmark-modal.ts +1 -1
- package/src/renderer/buffer.ts +1 -1
- package/src/renderer/code-block.ts +1 -1
- package/src/renderer/composer-fragments.ts +1 -1
- package/src/renderer/compositor.ts +2 -2
- package/src/renderer/config-modal.ts +1 -1
- package/src/renderer/context-inspector.ts +1 -1
- package/src/renderer/conversation-layout.ts +2 -2
- package/src/renderer/conversation-overlays.ts +1 -1
- package/src/renderer/conversation-surface.ts +2 -2
- package/src/renderer/diff-view.ts +1 -1
- package/src/renderer/diff.ts +1 -1
- package/src/renderer/file-picker-overlay.ts +1 -1
- package/src/renderer/fleet-tab-strip.ts +1 -1
- package/src/renderer/fullscreen-primitives.ts +2 -2
- package/src/renderer/fullscreen-workspace.ts +1 -1
- package/src/renderer/header-line.ts +1 -1
- package/src/renderer/help-overlay.ts +1 -1
- package/src/renderer/history-search-overlay.ts +2 -2
- package/src/renderer/layout.ts +5 -8
- package/src/renderer/markdown-inline.ts +1 -1
- package/src/renderer/markdown-table.ts +1 -1
- package/src/renderer/markdown.ts +1 -1
- package/src/renderer/mcp-workspace.ts +1 -1
- package/src/renderer/modal-factory.ts +2 -2
- package/src/renderer/model-workspace.ts +2 -2
- package/src/renderer/onboarding/onboarding-wizard.ts +1 -1
- package/src/renderer/overlay-box.ts +1 -1
- package/src/renderer/panel-composite.ts +1 -1
- package/src/renderer/panel-workspace-bar.ts +1 -1
- package/src/renderer/process-indicator.ts +1 -1
- package/src/renderer/profile-picker-modal.ts +1 -1
- package/src/renderer/search-overlay.ts +2 -2
- package/src/renderer/selection-modal-overlay.ts +1 -1
- package/src/renderer/session-picker-modal.ts +124 -3
- package/src/renderer/settings-modal-helpers.ts +2 -0
- package/src/renderer/settings-modal.ts +2 -2
- package/src/renderer/shell-surface.ts +2 -2
- package/src/renderer/system-message.ts +1 -1
- package/src/renderer/tab-strip.ts +1 -1
- package/src/renderer/theme-mode-config.ts +1 -1
- package/src/renderer/theme.ts +11 -12
- package/src/renderer/thinking.ts +1 -1
- package/src/renderer/tool-call.ts +2 -2
- package/src/renderer/tool-result-expanded-lines.ts +1 -1
- package/src/renderer/turn-injection.ts +7 -8
- package/src/renderer/ui-factory.ts +3 -3
- package/src/runtime/bootstrap-command-context.ts +4 -4
- package/src/runtime/bootstrap-command-parts.ts +5 -5
- package/src/runtime/bootstrap-core.ts +30 -30
- package/src/runtime/bootstrap-shell.ts +4 -4
- package/src/runtime/bootstrap.ts +128 -157
- package/src/runtime/client/approval-updates.ts +82 -0
- package/src/runtime/client/build-floors.ts +167 -0
- package/src/runtime/client/daemon-attach-handshake.ts +115 -0
- package/src/runtime/client/fleet-union.ts +147 -0
- package/src/runtime/client/host-service-status.ts +71 -0
- package/src/runtime/client/hosted-exit.ts +86 -0
- package/src/runtime/client/hosted-roster.ts +110 -0
- package/src/runtime/client/hosted-session-stream.ts +178 -0
- package/src/runtime/client/hosted-sessions.ts +187 -0
- package/src/runtime/client/operator-endpoint.ts +213 -0
- package/src/runtime/client/spine-adoption.ts +99 -0
- package/src/runtime/cloudflare-control-plane.ts +1 -1
- package/src/runtime/context-accounting-source.ts +1 -1
- package/src/runtime/context.ts +2 -2
- package/src/runtime/conversation-rewind-port.ts +42 -14
- package/src/runtime/daemon-attach-notices.ts +55 -19
- package/src/runtime/diagnostics/panels/ops.ts +1 -1
- package/src/runtime/disposal-wiring.ts +4 -24
- package/src/runtime/index.ts +119 -105
- package/src/runtime/install-self-check-startup.ts +2 -2
- package/src/runtime/interaction-seams.ts +1 -1
- package/src/runtime/knowledge-services.ts +8 -2
- package/src/runtime/legacy-daemon-migration.ts +4 -3
- package/src/runtime/legacy-daemon-reconcile.ts +3 -2
- package/src/runtime/memory-spine-transport.ts +37 -267
- package/src/runtime/notification-dispatch.ts +2 -2
- package/src/runtime/onboarding/apply.ts +2 -2
- package/src/runtime/onboarding/derivation.ts +2 -2
- package/src/runtime/onboarding/markers.ts +2 -2
- package/src/runtime/onboarding/progress.ts +2 -2
- package/src/runtime/onboarding/snapshot.ts +1 -1
- package/src/runtime/onboarding/state.ts +2 -2
- package/src/runtime/onboarding/types.ts +2 -2
- package/src/runtime/orchestrator-core-services.ts +1 -1
- package/src/runtime/path-shadow-startup.ts +28 -221
- package/src/runtime/power-keepawake-remote.ts +3 -2
- package/src/runtime/process-lifecycle.ts +2 -2
- package/src/runtime/provider-fallback.ts +10 -66
- package/src/runtime/recovery-autosave.ts +1 -1
- package/src/runtime/recovery-offer-wiring.ts +39 -23
- package/src/runtime/recovery-prompt.ts +31 -17
- package/src/runtime/relay-reachability-bridge.ts +53 -42
- package/src/runtime/release-artifacts.ts +2 -1
- package/src/runtime/runtime-services-types.ts +82 -35
- package/src/runtime/services.ts +305 -300
- package/src/runtime/session-resume-liveness-confirm.ts +1 -1
- package/src/runtime/ui-services.ts +7 -6
- package/src/runtime/update-check.ts +23 -43
- package/src/runtime/workspace-checkpointing.ts +1 -1
- package/src/runtime/wrfc-persistence.ts +2 -2
- package/src/shell/ui-openers.ts +30 -7
- package/src/utils/clipboard.ts +1 -1
- package/src/verification/verification-ledger.ts +10 -8
- package/src/version.ts +1 -1
- package/bin/goodvibes-daemon +0 -90
- package/src/cli/config-overrides.ts +0 -267
- package/src/cli/endpoints.ts +0 -109
- package/src/cli/parser.ts +0 -395
- package/src/cli/redaction.ts +0 -170
- package/src/cli/types.ts +0 -84
- package/src/cluster/commands.ts +0 -382
- package/src/cluster/remote-daemon-target.ts +0 -225
- package/src/cluster/render.ts +0 -268
- package/src/config/atomic-write.ts +0 -70
- package/src/config/credential-availability.ts +0 -122
- package/src/config/goodvibes-home.ts +0 -149
- package/src/config/index.ts +0 -88
- package/src/config/provider-model.ts +0 -23
- package/src/config/read-versioned.ts +0 -228
- package/src/core/alert-gating.ts +0 -67
- package/src/core/consolidation-receipt.ts +0 -41
- package/src/core/focus-tracker.ts +0 -41
- package/src/core/history.ts +0 -45
- package/src/core/memory-status.ts +0 -118
- package/src/core/orchestrator.ts +0 -7
- package/src/core/pairing-devices.ts +0 -49
- package/src/core/pairing-handoff.ts +0 -101
- package/src/core/pairing-offers.ts +0 -83
- package/src/core/pairing-origin.ts +0 -78
- package/src/core/rewind-turn-anchors.ts +0 -311
- package/src/core/session-recovery.ts +0 -210
- package/src/core/stable-host.ts +0 -171
- package/src/core/transcript-journal.ts +0 -503
- package/src/daemon/cli.ts +0 -693
- package/src/daemon/fatal-boot-report.ts +0 -111
- package/src/daemon/handlers/context.ts +0 -29
- package/src/daemon/handlers/contracts.ts +0 -77
- package/src/daemon/handlers/credentials.ts +0 -139
- package/src/daemon/handlers/drafts/draft-store.ts +0 -427
- package/src/daemon/handlers/drafts/index.ts +0 -17
- package/src/daemon/handlers/drafts/register.ts +0 -331
- package/src/daemon/handlers/errors.ts +0 -18
- package/src/daemon/handlers/inbox/cursor-store.ts +0 -457
- package/src/daemon/handlers/inbox/index.ts +0 -284
- package/src/daemon/handlers/inbox/mapping.ts +0 -192
- package/src/daemon/handlers/inbox/poller.ts +0 -212
- package/src/daemon/handlers/inbox/provider-adapter.ts +0 -156
- package/src/daemon/handlers/inbox/providers/discord.ts +0 -252
- package/src/daemon/handlers/inbox/providers/email.ts +0 -152
- package/src/daemon/handlers/inbox/providers/imap-client.ts +0 -300
- package/src/daemon/handlers/inbox/providers/route-util.ts +0 -24
- package/src/daemon/handlers/inbox/providers/slack.ts +0 -263
- package/src/daemon/handlers/index.ts +0 -117
- package/src/daemon/handlers/register.ts +0 -168
- package/src/daemon/handlers/remote/backends/cloud-terminal.ts +0 -142
- package/src/daemon/handlers/remote/backends/docker.ts +0 -79
- package/src/daemon/handlers/remote/backends/index.ts +0 -40
- package/src/daemon/handlers/remote/backends/local-process.ts +0 -113
- package/src/daemon/handlers/remote/backends/process-runner.ts +0 -127
- package/src/daemon/handlers/remote/backends/ssh.ts +0 -125
- package/src/daemon/handlers/remote/backends/types.ts +0 -97
- package/src/daemon/handlers/remote/dispatcher.ts +0 -181
- package/src/daemon/handlers/remote/index.ts +0 -119
- package/src/daemon/handlers/remote/peer-registry.ts +0 -357
- package/src/daemon/handlers/remote/service.ts +0 -191
- package/src/daemon/handlers/routing/inbox-bridge.ts +0 -71
- package/src/daemon/handlers/routing/index.ts +0 -261
- package/src/daemon/handlers/routing/route-store.ts +0 -319
- package/src/daemon/handlers/routing/routing-resolver.ts +0 -75
- package/src/daemon/handlers/sqlite-store.ts +0 -278
- package/src/daemon/handlers/triage/index.ts +0 -57
- package/src/daemon/handlers/triage/integration.ts +0 -213
- package/src/daemon/handlers/triage/pipeline.ts +0 -274
- package/src/daemon/handlers/triage/scorer.ts +0 -287
- package/src/daemon/handlers/triage/tagger/discord.ts +0 -187
- package/src/daemon/handlers/triage/tagger/imap.ts +0 -384
- package/src/daemon/handlers/triage/tagger/index.ts +0 -184
- package/src/daemon/handlers/triage/tagger/shared.ts +0 -70
- package/src/daemon/handlers/triage/tagger/slack.ts +0 -69
- package/src/daemon/handlers/triage/types.ts +0 -50
- package/src/daemon/lifecycle.ts +0 -41
- package/src/daemon/provision-wake-model.ts +0 -81
- package/src/daemon/safe-serve.ts +0 -61
- package/src/daemon/send/channels.ts +0 -200
- package/src/daemon/send/command.ts +0 -332
- package/src/daemon/send/composition.ts +0 -99
- package/src/daemon/send/failure-text.ts +0 -93
- package/src/daemon/send/inert-text.ts +0 -225
- package/src/daemon/send/stdin.ts +0 -24
- package/src/daemon/service-commands.ts +0 -329
- package/src/export/cost-utils.ts +0 -319
- package/src/input/selection.ts +0 -135
- package/src/plugins/loader.ts +0 -15
- package/src/renderer/bottom-bar.ts +0 -58
- package/src/renderer/conversation-tree.ts +0 -228
- package/src/runtime/agent-graph-composition.ts +0 -78
- package/src/runtime/channel-composition.ts +0 -46
- package/src/runtime/cluster-composition.ts +0 -124
- package/src/runtime/cluster-group-composition.ts +0 -284
- package/src/runtime/code-index-services.ts +0 -167
- package/src/runtime/daemon-handler-composition.ts +0 -76
- package/src/runtime/device-posture-composition.ts +0 -110
- package/src/runtime/durability-housekeeping.ts +0 -183
- package/src/runtime/durability-services.ts +0 -155
- package/src/runtime/embedded-service-factories.ts +0 -74
- package/src/runtime/feature-settings.ts +0 -154
- package/src/runtime/fleet-needs-input-push.ts +0 -70
- package/src/runtime/fleet-services.ts +0 -41
- package/src/runtime/idle-power-services.ts +0 -85
- package/src/runtime/mail-composition.ts +0 -65
- package/src/runtime/memory-fold.ts +0 -55
- package/src/runtime/operator-token-cleanup.ts +0 -109
- package/src/runtime/path-shadow.ts +0 -418
- package/src/runtime/perf/panel-health-monitor.ts +0 -18
- package/src/runtime/provider-optimizer-wiring.ts +0 -55
- package/src/runtime/reachability-notice.ts +0 -100
- package/src/runtime/remote-execution-composition.ts +0 -62
- package/src/runtime/session-liveness-marker.ts +0 -257
- package/src/runtime/session-pointer-surface.ts +0 -22
- package/src/runtime/session-storage-services.ts +0 -41
- package/src/runtime/surface-feature-flags.ts +0 -96
- package/src/runtime/terminal-output-guard.ts +0 -235
- package/src/runtime/trust/workspace-registration.ts +0 -192
- package/src/runtime/trust/workspace-trust.ts +0 -229
- package/src/runtime/ui/index.ts +0 -39
- package/src/runtime/ui/model-picker/data-provider.ts +0 -182
- package/src/runtime/ui/model-picker/health-enrichment.ts +0 -231
- package/src/runtime/ui/model-picker/index.ts +0 -59
- package/src/runtime/ui/model-picker/types.ts +0 -153
- package/src/runtime/ui/provider-health/data-provider.ts +0 -244
- package/src/runtime/ui/provider-health/fallback-visualizer.ts +0 -71
- package/src/runtime/ui/provider-health/index.ts +0 -46
- package/src/runtime/ui/provider-health/types.ts +0 -146
- package/src/runtime/ui-events.ts +0 -1
- package/src/runtime/ui-read-model-helpers.ts +0 -1
- package/src/runtime/ui-read-models-observability-maintenance.ts +0 -1
- package/src/runtime/ui-read-models-observability-options.ts +0 -1
- package/src/runtime/ui-read-models-observability-remote.ts +0 -1
- package/src/runtime/ui-read-models-observability-security.ts +0 -1
- package/src/runtime/ui-read-models-observability-system.ts +0 -1
- package/src/runtime/ui-read-models-observability.ts +0 -1
- package/src/runtime/ui-service-queries.ts +0 -1
- package/src/runtime/voice-setup-services.ts +0 -98
- package/src/runtime/workstream-attempts-validation.ts +0 -77
- package/src/runtime/workstream-draft-edits.ts +0 -95
- package/src/runtime/workstream-draft-store.ts +0 -274
- package/src/runtime/workstream-draft-types.ts +0 -42
- package/src/runtime/workstream-services.ts +0 -373
- package/src/tools/index.ts +0 -1
- package/src/types/grid.ts +0 -48
- package/src/types/sql-js.d.ts +0 -15
- package/src/work-plans/work-plan-store.ts +0 -702
|
@@ -1,284 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* cluster-group-composition.ts — this machine's membership of a LAN group.
|
|
3
|
-
*
|
|
4
|
-
* Leader election answers "which of us reads the inbox". This answers the
|
|
5
|
-
* question underneath it: "which machines are US". Without a group, any daemon
|
|
6
|
-
* that happened to be on the same network — a neighbour's, a colleague's, a
|
|
7
|
-
* container someone left running — would join the same coordination and one of
|
|
8
|
-
* you would silently stop receiving messages.
|
|
9
|
-
*
|
|
10
|
-
* The group layer owns the socket and the election rides on it: every
|
|
11
|
-
* coordination datagram is wrapped in the group envelope and signed with the
|
|
12
|
-
* current group key, so a datagram from outside the group never reaches the
|
|
13
|
-
* election at all. That is why `createClusterComposition` is handed a transport
|
|
14
|
-
* from here instead of opening one of its own.
|
|
15
|
-
*
|
|
16
|
-
* Key material lives ONLY in the encrypted secrets store. Never in config,
|
|
17
|
-
* never in a log line, never in `/status`.
|
|
18
|
-
*/
|
|
19
|
-
import {
|
|
20
|
-
ClusterGroupRuntime,
|
|
21
|
-
createClusterGroupVerbs,
|
|
22
|
-
createSystemClusterClock,
|
|
23
|
-
readClusterSettings,
|
|
24
|
-
rejoinGroup,
|
|
25
|
-
resolveClusterGroupSettings,
|
|
26
|
-
resolveNodeIdentity,
|
|
27
|
-
stillOnRoster,
|
|
28
|
-
UdpClusterTransport,
|
|
29
|
-
type ClusterGroupVerbSurface,
|
|
30
|
-
type ClusterSurfaceHolding,
|
|
31
|
-
type GroupOperationsContext,
|
|
32
|
-
} from '@pellux/goodvibes-sdk/platform/cluster';
|
|
33
|
-
import type { ConfigManager, SecretsManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
34
|
-
import type { ClusterTransport } from '@pellux/goodvibes-sdk/platform/cluster';
|
|
35
|
-
import { logger } from '@pellux/goodvibes-sdk/platform/utils';
|
|
36
|
-
import type { ShellPathService } from '@/runtime/index.ts';
|
|
37
|
-
import { createClusterComposition } from './cluster-composition.ts';
|
|
38
|
-
import type { ClusterCoordinator } from '@pellux/goodvibes-sdk/platform/cluster';
|
|
39
|
-
import { GOODVIBES_TUI_SURFACE_ROOT } from '../config/surface.ts';
|
|
40
|
-
import { VERSION } from '../version.ts';
|
|
41
|
-
|
|
42
|
-
export interface ClusterGroupComposition {
|
|
43
|
-
readonly runtime: ClusterGroupRuntime;
|
|
44
|
-
readonly verbs: ClusterGroupVerbSurface;
|
|
45
|
-
/** The transport the leader election must use, so its traffic is group-signed. */
|
|
46
|
-
readonly electionTransport: ClusterTransport;
|
|
47
|
-
/** Start the group layer. Safe to call more than once. */
|
|
48
|
-
start(): Promise<void>;
|
|
49
|
-
stop(): Promise<void>;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* A label for THIS machine in the member list.
|
|
54
|
-
*
|
|
55
|
-
* Deliberately NOT the hostname. The roster replicates to every member and the
|
|
56
|
-
* group name reaches the network in a beacon, so defaulting to a hostname would
|
|
57
|
-
* quietly publish "mikes-laptop" to anything listening. A short slice of the
|
|
58
|
-
* node id is meaningless and safe, and `cluster nodes` shows it plainly so an
|
|
59
|
-
* operator who wants a real name can see there is one to set.
|
|
60
|
-
*/
|
|
61
|
-
function defaultNodeDisplayName(nodeId: string): string {
|
|
62
|
-
return `machine ${nodeId.slice(0, 8)}`;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Build the group layer.
|
|
67
|
-
*
|
|
68
|
-
* Constructing it is inert: no socket is opened and no key material is read
|
|
69
|
-
* until `start()`, so composing a runtime in a test never touches the network
|
|
70
|
-
* or the secrets store.
|
|
71
|
-
*/
|
|
72
|
-
export function createClusterGroupComposition(options: {
|
|
73
|
-
readonly configManager: ConfigManager;
|
|
74
|
-
readonly shellPaths: ShellPathService;
|
|
75
|
-
readonly secretsManager: SecretsManager;
|
|
76
|
-
/**
|
|
77
|
-
* The leader election's own answer to "am I the master".
|
|
78
|
-
*
|
|
79
|
-
* Config replication needs exactly one machine issuing revisions, and it must
|
|
80
|
-
* be the SAME machine leadership already picked — two notions of master in
|
|
81
|
-
* one process would disagree the moment one of them changed.
|
|
82
|
-
*
|
|
83
|
-
* Late-bound because the coordinator is built from this composition's
|
|
84
|
-
* transport: it does not exist yet when this function runs.
|
|
85
|
-
*/
|
|
86
|
-
readonly isMaster?: (() => boolean) | undefined;
|
|
87
|
-
/**
|
|
88
|
-
* Which surfaces this machine currently holds, and why.
|
|
89
|
-
*
|
|
90
|
-
* The per-surface election establishes this; the group layer only reports it,
|
|
91
|
-
* in `cluster status` and in /status. Late-bound for the same reason
|
|
92
|
-
* `isMaster` is. Absent means the group layer reports that the information is
|
|
93
|
-
* unavailable rather than reporting an empty list as though this machine held
|
|
94
|
-
* nothing — a distinction an operator diagnosing a silent inbox depends on.
|
|
95
|
-
*/
|
|
96
|
-
readonly surfaceHoldings?: (() => readonly ClusterSurfaceHolding[]) | undefined;
|
|
97
|
-
}): ClusterGroupComposition {
|
|
98
|
-
const settings = readClusterSettings(options.configManager);
|
|
99
|
-
const groupSettings = resolveClusterGroupSettings(
|
|
100
|
-
(options.configManager as { getCategory?: (name: string) => unknown }).getCategory?.('cluster'),
|
|
101
|
-
);
|
|
102
|
-
// Surface-scoped, alongside the election's node identity and this surface's
|
|
103
|
-
// other durable state.
|
|
104
|
-
const stateDirectory = options.shellPaths.resolveProjectPath(GOODVIBES_TUI_SURFACE_ROOT, 'cluster');
|
|
105
|
-
const nodeId = resolveNodeIdentity({ stateDirectory, logger }).nodeId;
|
|
106
|
-
|
|
107
|
-
const runtime = new ClusterGroupRuntime({
|
|
108
|
-
settings: groupSettings,
|
|
109
|
-
transport: new UdpClusterTransport({
|
|
110
|
-
port: settings.port,
|
|
111
|
-
multicastGroup: settings.multicastGroup,
|
|
112
|
-
peers: settings.peers,
|
|
113
|
-
logger,
|
|
114
|
-
}),
|
|
115
|
-
// Narrower than the SecretsManager itself: the group layer gets get/set/
|
|
116
|
-
// delete on one key and nothing else.
|
|
117
|
-
secrets: {
|
|
118
|
-
get: (key) => options.secretsManager.get(key),
|
|
119
|
-
set: (key, value) => options.secretsManager.set(key, value),
|
|
120
|
-
delete: (key) => options.secretsManager.delete(key),
|
|
121
|
-
},
|
|
122
|
-
stateDirectory,
|
|
123
|
-
nodeId,
|
|
124
|
-
nodeDisplayName: defaultNodeDisplayName(nodeId),
|
|
125
|
-
version: VERSION,
|
|
126
|
-
clock: createSystemClusterClock(),
|
|
127
|
-
logger,
|
|
128
|
-
...(options.isMaster ? { isMaster: options.isMaster } : {}),
|
|
129
|
-
...(options.surfaceHoldings ? { surfaceHoldings: options.surfaceHoldings } : {}),
|
|
130
|
-
// Only daemon-owned, group-scoped keys ever reach this; the SDK's
|
|
131
|
-
// replication policy decides which, and refuses anything machine-specific.
|
|
132
|
-
config: {
|
|
133
|
-
get: (path) => (options.configManager as unknown as {
|
|
134
|
-
get(key: string): unknown;
|
|
135
|
-
}).get(path),
|
|
136
|
-
set: (path, value) => (options.configManager as unknown as {
|
|
137
|
-
set(key: string, value: unknown): void;
|
|
138
|
-
}).set(path, value),
|
|
139
|
-
},
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
const context: GroupOperationsContext = {
|
|
143
|
-
runtime,
|
|
144
|
-
secrets: {
|
|
145
|
-
get: (key) => options.secretsManager.get(key),
|
|
146
|
-
set: (key, value) => options.secretsManager.set(key, value),
|
|
147
|
-
delete: (key) => options.secretsManager.delete(key),
|
|
148
|
-
},
|
|
149
|
-
settings: groupSettings,
|
|
150
|
-
nodeId,
|
|
151
|
-
nodeDisplayName: defaultNodeDisplayName(nodeId),
|
|
152
|
-
version: VERSION,
|
|
153
|
-
now: () => Date.now(),
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
let started = false;
|
|
157
|
-
return {
|
|
158
|
-
runtime,
|
|
159
|
-
verbs: createClusterGroupVerbs(context),
|
|
160
|
-
electionTransport: runtime.electionTransport(),
|
|
161
|
-
start: async () => {
|
|
162
|
-
if (started) return;
|
|
163
|
-
started = true;
|
|
164
|
-
await runtime.start();
|
|
165
|
-
await announceReturn(runtime, context, nodeId);
|
|
166
|
-
},
|
|
167
|
-
stop: async () => {
|
|
168
|
-
started = false;
|
|
169
|
-
await runtime.stop();
|
|
170
|
-
},
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Build both halves of the LAN cluster, wired to each other.
|
|
176
|
-
*
|
|
177
|
-
* They are mutually dependent and the dependency runs both ways, which is why
|
|
178
|
-
* this exists rather than two calls at the composition root: the election
|
|
179
|
-
* coordinates over the GROUP's transport, and the group's config replication
|
|
180
|
-
* needs the ELECTION's answer to "am I the master". The master signal is read
|
|
181
|
-
* through a closure because the coordinator does not exist yet when the group
|
|
182
|
-
* layer is constructed.
|
|
183
|
-
*
|
|
184
|
-
* Constructing either is inert — no socket, no key material read — until
|
|
185
|
-
* `startCluster` runs.
|
|
186
|
-
*/
|
|
187
|
-
export function createClusterServices(options: {
|
|
188
|
-
readonly configManager: ConfigManager;
|
|
189
|
-
readonly shellPaths: ShellPathService;
|
|
190
|
-
readonly secretsManager: SecretsManager;
|
|
191
|
-
}): { readonly clusterGroup: ClusterGroupComposition; readonly clusterCoordinator: ClusterCoordinator } {
|
|
192
|
-
let coordinator: ClusterCoordinator | null = null;
|
|
193
|
-
const clusterGroup = createClusterGroupComposition({
|
|
194
|
-
...options,
|
|
195
|
-
isMaster: () => coordinator?.isMaster ?? false,
|
|
196
|
-
// The elections actually running are the only honest source for this, so
|
|
197
|
-
// the group layer reads them rather than keeping a second tally that could
|
|
198
|
-
// disagree. Before the coordinator exists there is nothing to report, and
|
|
199
|
-
// an empty list is the truthful answer at that point: no election has been
|
|
200
|
-
// held, so this machine holds nothing.
|
|
201
|
-
surfaceHoldings: () => coordinator?.surfaceHoldings() ?? [],
|
|
202
|
-
});
|
|
203
|
-
coordinator = createClusterComposition({
|
|
204
|
-
configManager: options.configManager,
|
|
205
|
-
shellPaths: options.shellPaths,
|
|
206
|
-
transport: clusterGroup.electionTransport,
|
|
207
|
-
});
|
|
208
|
-
return { clusterGroup, clusterCoordinator: coordinator };
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Start the LAN cluster, in the order it has to be started in.
|
|
213
|
-
*
|
|
214
|
-
* The group layer FIRST: it owns the socket the leader election coordinates
|
|
215
|
-
* over, and starting the election against a transport whose group is not yet
|
|
216
|
-
* loaded would sign its first datagrams with nothing. Both calls are
|
|
217
|
-
* idempotent, so a composition root that reaches this twice is fine.
|
|
218
|
-
*/
|
|
219
|
-
export async function startClusterServices(services: {
|
|
220
|
-
readonly clusterGroup: Pick<ClusterGroupComposition, 'start'>;
|
|
221
|
-
readonly clusterCoordinator: { start(): Promise<void> };
|
|
222
|
-
}): Promise<void> {
|
|
223
|
-
await services.clusterGroup.start();
|
|
224
|
-
await services.clusterCoordinator.start();
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Ask the group to take this machine back, on start.
|
|
229
|
-
*
|
|
230
|
-
* This is the zero-touch return. A machine that has been switched off for
|
|
231
|
-
* months — through many group-key rotations and possibly a join-key change —
|
|
232
|
-
* comes up holding stale keys, says "it is still me" with a key that never
|
|
233
|
-
* rotates, and is re-keyed to the current generation by whichever member
|
|
234
|
-
* answers. The operator does nothing.
|
|
235
|
-
*
|
|
236
|
-
* It runs only when this machine still believes it is on the roster. A machine
|
|
237
|
-
* that was REMOVED asks for nothing: it would be refused, and asking anyway
|
|
238
|
-
* would put a pointless refusal in everyone's logs every time it started.
|
|
239
|
-
*/
|
|
240
|
-
async function announceReturn(
|
|
241
|
-
runtime: ClusterGroupRuntime,
|
|
242
|
-
context: GroupOperationsContext,
|
|
243
|
-
nodeId: string,
|
|
244
|
-
): Promise<void> {
|
|
245
|
-
if (runtime.membership !== 'member') return;
|
|
246
|
-
if (!stillOnRoster(runtime.groupState, nodeId)) return;
|
|
247
|
-
const result = await rejoinGroup(context);
|
|
248
|
-
if (result.ok) {
|
|
249
|
-
logger.info('cluster: rejoined the group and took the current group key', {
|
|
250
|
-
group: result.data.groupName,
|
|
251
|
-
members: result.data.memberCount,
|
|
252
|
-
});
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
if (result.terminal) {
|
|
256
|
-
// A member refused this machine and proved it was a member when it did.
|
|
257
|
-
// Retrying will never succeed, so this is not a quiet debug line: without
|
|
258
|
-
// it the operator sees a healthy daemon that is simply never given any
|
|
259
|
-
// work, with nothing anywhere saying why. Stated at ERROR with the command
|
|
260
|
-
// that fixes it, the same standard a surface this node cannot serve is
|
|
261
|
-
// held to.
|
|
262
|
-
logger.error('cluster: this machine is no longer in its group and will not be given work', {
|
|
263
|
-
reason: result.error,
|
|
264
|
-
action: result.fix,
|
|
265
|
-
});
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
if (result.failure === 'unverifiable-replies') {
|
|
269
|
-
// Something answered and none of it could be authenticated. This machine
|
|
270
|
-
// cannot tell a removal it slept through from a stranger making noise, so
|
|
271
|
-
// it says exactly that and does not assert either. WARN rather than ERROR
|
|
272
|
-
// for the same reason: anything on the network can produce this, and a
|
|
273
|
-
// machine that shouted "you were removed" on cue would be worse than one
|
|
274
|
-
// that stayed quiet.
|
|
275
|
-
logger.warn('cluster: replies to this machine\'s return could not be authenticated', {
|
|
276
|
-
reason: result.error,
|
|
277
|
-
action: result.fix,
|
|
278
|
-
});
|
|
279
|
-
return;
|
|
280
|
-
}
|
|
281
|
-
// Not an error. A machine that starts first, or is alone on the network, has
|
|
282
|
-
// nobody to answer it and carries on with the keys it already holds.
|
|
283
|
-
logger.debug('cluster: no other machine answered the return announcement', { reason: result.error });
|
|
284
|
-
}
|
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
// ---------------------------------------------------------------------------
|
|
2
|
-
// code-index-services.ts — repo source-tree code index (TUI wiring)
|
|
3
|
-
//
|
|
4
|
-
// Constructs the TUI's repo source-tree code index: CodeIndexStore
|
|
5
|
-
// (@pellux/goodvibes-sdk/platform/state). Extracted into its own module rather than built inline in
|
|
6
|
-
// services.ts: services.ts sits at the architecture check's 800-line cap
|
|
7
|
-
// (scripts/check-architecture.ts), so any new service gets its own
|
|
8
|
-
// construction module and a single wiring call there (mirrors
|
|
9
|
-
// createWorkstreamServices's one-function-bundle shape).
|
|
10
|
-
//
|
|
11
|
-
// Schema-initialized eagerly (mirrors memoryStore: construction + init()
|
|
12
|
-
// happen unconditionally so the store is queryable — /codebase status, the
|
|
13
|
-
// fleet node — even before any build has ever run). The actual walk/chunk/
|
|
14
|
-
// embed build is NEVER auto-triggered by default: the hundreds of existing
|
|
15
|
-
// test fixtures (and every headless invocation) construct RuntimeServices
|
|
16
|
-
// without asking for a full source-tree walk, and init() alone never runs
|
|
17
|
-
// one (mirrors the SDK's own RuntimeServices wiring in
|
|
18
|
-
// platform/runtime/services.ts, which gates its equivalent scheduleBuild()
|
|
19
|
-
// call behind an explicit autoStartCodeIndex option).
|
|
20
|
-
//
|
|
21
|
-
// REALITY-WINS DIVERGENCE from the SDK's own shape: the SDK's
|
|
22
|
-
// RuntimeServicesOptions exposes autoStartCodeIndex as a constructor-time
|
|
23
|
-
// boolean because that call site is a library entrypoint threaded by each
|
|
24
|
-
// embedder. This TUI's createRuntimeServices has no such per-call knob —
|
|
25
|
-
// auto-start is decided from a TUI-local config key instead
|
|
26
|
-
// (CODE_INDEX_ENABLED_CONFIG_KEY, default OFF), so every construction path
|
|
27
|
-
// (interactive main.ts, the daemon, and every test fixture) shares one
|
|
28
|
-
// honest, user-visible on/off switch rather than needing to thread a new
|
|
29
|
-
// boolean through every call site. With it off (the default), `/codebase
|
|
30
|
-
// build` is the explicit, visible trigger — exactly the shape this design
|
|
31
|
-
// asks for.
|
|
32
|
-
//
|
|
33
|
-
// Shares memoryEmbeddingRegistry with MemoryStore (constructed in
|
|
34
|
-
// services.ts) so code + memory retrieval use one embedding provider and one
|
|
35
|
-
// dimensionality, per the SDK design doc (code-index-store.ts's constructor
|
|
36
|
-
// takes the SAME MemoryEmbeddingProviderRegistry instance memory uses).
|
|
37
|
-
// ---------------------------------------------------------------------------
|
|
38
|
-
|
|
39
|
-
import { join } from 'node:path';
|
|
40
|
-
import { CodeIndexStore, CodeIndexReindexScheduler } from '@pellux/goodvibes-sdk/platform/state';
|
|
41
|
-
import type { MemoryEmbeddingProviderRegistry } from '@pellux/goodvibes-sdk/platform/state';
|
|
42
|
-
import type { ConfigKey, ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
43
|
-
import { readBooleanConfig } from '../core/alert-gating.ts';
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* TUI-local synthetic config key (not yet in the SDK's ConfigKey union) that
|
|
47
|
-
* gates the code index's auto-build on construction. Default OFF — see this
|
|
48
|
-
* module's header doc. Surfaced in /config via a synthetic entry
|
|
49
|
-
* (settings-modal-data.ts) exactly like behavior.notifyAfterSeconds.
|
|
50
|
-
*/
|
|
51
|
-
export const CODE_INDEX_ENABLED_CONFIG_KEY = 'storage.codeIndexEnabled';
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Honest, generous bounds passed EXPLICITLY to CodeIndexStore (values equal
|
|
55
|
-
* to its own internal defaults) so /codebase status and the settings
|
|
56
|
-
* description can state them in one place without reaching into store
|
|
57
|
-
* internals or duplicating magic numbers.
|
|
58
|
-
*/
|
|
59
|
-
export const CODE_INDEX_MAX_FILES = 5000;
|
|
60
|
-
export const CODE_INDEX_MAX_FILE_BYTES = 512 * 1024;
|
|
61
|
-
export const CODE_INDEX_MAX_TOTAL_BYTES = 256 * 1024 * 1024;
|
|
62
|
-
|
|
63
|
-
export interface CodeIndexServicesDeps {
|
|
64
|
-
readonly workingDirectory: string;
|
|
65
|
-
readonly configManager: Pick<ConfigManager, 'get'>;
|
|
66
|
-
readonly memoryEmbeddingRegistry: MemoryEmbeddingProviderRegistry;
|
|
67
|
-
/**
|
|
68
|
-
* Memory-governor backpressure (optional): when the governor pauses the
|
|
69
|
-
* 'code-index-reindex' job, the tool-site reindex scheduler stops scheduling
|
|
70
|
-
* new work; the critical tier refuses new reindex work outright via
|
|
71
|
-
* admitExpensiveWork. Absent (most test fixtures) → never paused, always
|
|
72
|
-
* admitted, exactly as before.
|
|
73
|
-
*/
|
|
74
|
-
readonly isReindexPaused?: (() => boolean) | undefined;
|
|
75
|
-
readonly admitExpensiveWork?: ((label: string) => { allowed: boolean; reason?: string | undefined }) | undefined;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Whether code auto-injection / tool-site reindex may run: the SAME
|
|
80
|
-
* storage.codeIndexEnabled switch that gates auto-build. Read live so a runtime
|
|
81
|
-
* /config toggle takes effect without restart. (The separate, default-off
|
|
82
|
-
* `agent-passive-code-injection` feature flag gates injection additionally; this
|
|
83
|
-
* is only the storage-side setting the SDK asks embedders to respect.)
|
|
84
|
-
*/
|
|
85
|
-
export function isCodeInjectionSettingEnabled(configManager: Pick<ConfigManager, 'get'>): boolean {
|
|
86
|
-
return isCodeIndexAutoStartEnabled(configManager);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export interface CodeIndexServices {
|
|
90
|
-
readonly codeIndexStore: CodeIndexStore;
|
|
91
|
-
/**
|
|
92
|
-
* Tool-site incremental reindex scheduler, bound to codeIndexStore and
|
|
93
|
-
* gated live on storage.codeIndexEnabled. Threaded into both SDK orchestrators (main +
|
|
94
|
-
* agent) so a successful write/edit debounces an incremental reindex, and into the command
|
|
95
|
-
* context so /codebase status can report the last reindex activity honestly.
|
|
96
|
-
*/
|
|
97
|
-
readonly codeIndexReindexScheduler: CodeIndexReindexScheduler;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/** Absolute path to the TUI's code-index sqlite file, sibling to memory.sqlite under .goodvibes/tui/. */
|
|
101
|
-
export function codeIndexDbPath(workingDirectory: string): string {
|
|
102
|
-
return join(workingDirectory, '.goodvibes', 'tui', 'code-index.sqlite');
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Build the runtime's `rerootStores` implementation — called by
|
|
107
|
-
* WorkspaceSwapManager after it has verified a new working directory.
|
|
108
|
-
*
|
|
109
|
-
* Only working-tree-bound stores move. The memory store is deliberately NOT
|
|
110
|
-
* rerooted: it is the home-scoped canonical cross-surface store, and moving
|
|
111
|
-
* it per project would re-silo memory back into the per-project stores that
|
|
112
|
-
* were consolidated away.
|
|
113
|
-
*/
|
|
114
|
-
export function createStoreRerooter(deps: {
|
|
115
|
-
readonly codeIndexStore: Pick<CodeIndexStore, 'reroot'>;
|
|
116
|
-
readonly projectIndex: { reroot(dir: string): Promise<unknown> };
|
|
117
|
-
}): (newWorkingDir: string) => Promise<void> {
|
|
118
|
-
return async (newWorkingDir: string): Promise<void> => {
|
|
119
|
-
await deps.codeIndexStore.reroot(newWorkingDir, codeIndexDbPath(newWorkingDir));
|
|
120
|
-
await deps.projectIndex.reroot(newWorkingDir);
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Whether the code index's initial build should auto-start on construction.
|
|
126
|
-
* Exported so /codebase status and the settings modal read the exact same
|
|
127
|
-
* config key + default this module decides auto-start from.
|
|
128
|
-
*/
|
|
129
|
-
export function isCodeIndexAutoStartEnabled(configManager: Pick<ConfigManager, 'get'>): boolean {
|
|
130
|
-
return readBooleanConfig(
|
|
131
|
-
(key) => configManager.get(key as ConfigKey),
|
|
132
|
-
CODE_INDEX_ENABLED_CONFIG_KEY,
|
|
133
|
-
false,
|
|
134
|
-
);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Constructs the TUI's CodeIndexStore. Schema-init runs unconditionally
|
|
139
|
-
* (init() never throws — it degrades to an honest `available: false` +
|
|
140
|
-
* recorded error on failure, mirrored by CodeIndexStats/describeDegradation);
|
|
141
|
-
* the initial full build only fires when isCodeIndexAutoStartEnabled() is
|
|
142
|
-
* true, via the SAME fire-and-forget scheduleBuild() an explicit
|
|
143
|
-
* `/codebase build` invocation uses.
|
|
144
|
-
*/
|
|
145
|
-
export function createCodeIndexServices(deps: CodeIndexServicesDeps): CodeIndexServices {
|
|
146
|
-
const codeIndexStore = new CodeIndexStore(
|
|
147
|
-
deps.workingDirectory,
|
|
148
|
-
codeIndexDbPath(deps.workingDirectory),
|
|
149
|
-
deps.memoryEmbeddingRegistry,
|
|
150
|
-
{ maxFiles: CODE_INDEX_MAX_FILES, maxFileBytes: CODE_INDEX_MAX_FILE_BYTES, maxTotalBytes: CODE_INDEX_MAX_TOTAL_BYTES },
|
|
151
|
-
);
|
|
152
|
-
codeIndexStore.init();
|
|
153
|
-
if (isCodeIndexAutoStartEnabled(deps.configManager)) {
|
|
154
|
-
codeIndexStore.scheduleBuild();
|
|
155
|
-
}
|
|
156
|
-
const codeIndexReindexScheduler = new CodeIndexReindexScheduler({
|
|
157
|
-
target: codeIndexStore,
|
|
158
|
-
workingDirectory: deps.workingDirectory,
|
|
159
|
-
// Honor governor backpressure: a paused code-index reindex job stops
|
|
160
|
-
// scheduling new work until the governor resumes it, and the critical
|
|
161
|
-
// memory tier refuses new reindex work outright (mirrors the SDK).
|
|
162
|
-
isEnabled: () => isCodeInjectionSettingEnabled(deps.configManager)
|
|
163
|
-
&& !(deps.isReindexPaused?.() ?? false)
|
|
164
|
-
&& (deps.admitExpensiveWork?.('code-index reindex').allowed ?? true),
|
|
165
|
-
});
|
|
166
|
-
return { codeIndexStore, codeIndexReindexScheduler };
|
|
167
|
-
}
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* daemon-handler-composition.ts — the daemon's HOST-side handler surfaces.
|
|
3
|
-
*
|
|
4
|
-
* Attaches this repository's handlers to the SDK-auto-registered builtin
|
|
5
|
-
* gateway descriptors (channels.* / email.* / calendar.*) via
|
|
6
|
-
* catalog.register(descriptor, handler, { replace: true }) — the SDK owns
|
|
7
|
-
* every id, descriptor and schema; only the behaviour is ours. The remote
|
|
8
|
-
* surface reuses the SAME DistributedRuntimeManager the SDK facade injects.
|
|
9
|
-
*
|
|
10
|
-
* Split out of services.ts for the 800-line file cap, the same reason
|
|
11
|
-
* channel-composition.ts exists.
|
|
12
|
-
*
|
|
13
|
-
* The one behavioural decision that lives here: the inbox poller is handed to
|
|
14
|
-
* the cluster coordinator instead of being started eagerly. It is this
|
|
15
|
-
* product's own inbound consumer — the SDK facade does not know it exists —
|
|
16
|
-
* so if it is not gated here it is not gated anywhere, and two goodvibes nodes
|
|
17
|
-
* on one network each read the shared inbox and answer the same message twice.
|
|
18
|
-
*/
|
|
19
|
-
import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
20
|
-
import type { ClusterCoordinator } from '@pellux/goodvibes-sdk/platform/cluster';
|
|
21
|
-
import type { GatewayMethodCatalog } from '@pellux/goodvibes-sdk/platform/control-plane';
|
|
22
|
-
import type { SecretsManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
23
|
-
import { registerDaemonHandlers, type DaemonHandlerSurfaces } from '../daemon/handlers/index.ts';
|
|
24
|
-
import type { HandlerContext, HandlerLogger } from '../daemon/handlers/context.ts';
|
|
25
|
-
import { createDaemonCredentialStore } from '../daemon/handlers/credentials.ts';
|
|
26
|
-
import { registerRouting } from '../daemon/handlers/routing/index.ts';
|
|
27
|
-
import { registerInboxMethods } from '../daemon/handlers/inbox/index.ts';
|
|
28
|
-
import { registerTriagedInbox } from '../daemon/handlers/triage/index.ts';
|
|
29
|
-
import { registerDraftMethods } from '../daemon/handlers/drafts/index.ts';
|
|
30
|
-
import { registerRemoteSurface } from '../daemon/handlers/remote/index.ts';
|
|
31
|
-
import { inboxPollerGate } from './cluster-composition.ts';
|
|
32
|
-
|
|
33
|
-
export interface DaemonHandlerCompositionOptions {
|
|
34
|
-
readonly gatewayMethods: GatewayMethodCatalog;
|
|
35
|
-
readonly secretsManager: SecretsManager;
|
|
36
|
-
readonly configManager: ConfigManager;
|
|
37
|
-
readonly workingDirectory: string;
|
|
38
|
-
readonly homeDirectory: string;
|
|
39
|
-
readonly distributedRuntime: NonNullable<Parameters<typeof registerRemoteSurface>[1]>['manager'];
|
|
40
|
-
/**
|
|
41
|
-
* Decides whether THIS node polls the shared inbox. Always supplied by the
|
|
42
|
-
* composition root; the poller is never started outside it.
|
|
43
|
-
*/
|
|
44
|
-
readonly clusterCoordinator: ClusterCoordinator;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function createDaemonHandlerComposition(
|
|
48
|
-
options: DaemonHandlerCompositionOptions,
|
|
49
|
-
): DaemonHandlerSurfaces {
|
|
50
|
-
const handlerLogger: HandlerLogger = {
|
|
51
|
-
info: (message, meta) => console.info(message, meta ?? ''),
|
|
52
|
-
warn: (message, meta) => console.warn(message, meta ?? ''),
|
|
53
|
-
error: (message, meta) => console.error(message, meta ?? ''),
|
|
54
|
-
};
|
|
55
|
-
const handlerContext: HandlerContext = {
|
|
56
|
-
catalog: options.gatewayMethods,
|
|
57
|
-
credentials: createDaemonCredentialStore(options.secretsManager),
|
|
58
|
-
configManager: options.configManager,
|
|
59
|
-
workingDirectory: options.workingDirectory,
|
|
60
|
-
homeDirectory: options.homeDirectory,
|
|
61
|
-
logger: handlerLogger,
|
|
62
|
-
};
|
|
63
|
-
return registerDaemonHandlers(handlerContext, {
|
|
64
|
-
registerRouting,
|
|
65
|
-
registerInbox: (ctx, routing) =>
|
|
66
|
-
registerTriagedInbox(ctx, (inboxCtx) => registerInboxMethods(inboxCtx, routing, {
|
|
67
|
-
// Hands polling to leadership. The `channels.inbox.list` read stays
|
|
68
|
-
// available on every node — a standby still SERVES the persisted feed,
|
|
69
|
-
// it just does not FETCH into it.
|
|
70
|
-
gatePolling: (providerId, control) =>
|
|
71
|
-
options.clusterCoordinator.register(inboxPollerGate(providerId, control)),
|
|
72
|
-
})).unregister,
|
|
73
|
-
registerDrafts: (ctx) => registerDraftMethods(ctx),
|
|
74
|
-
registerRemote: (ctx) => registerRemoteSurface(ctx, { manager: options.distributedRuntime }),
|
|
75
|
-
});
|
|
76
|
-
}
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* device-posture-composition.ts — the paired-phone feature inside THIS daemon.
|
|
3
|
-
*
|
|
4
|
-
* A phone pairs with whichever daemon the person runs, and for this owner that
|
|
5
|
-
* daemon is hosted by the terminal app. Until this module existed the app had no
|
|
6
|
-
* device posture at all: `device.nodes.maxPaired` was enforced at the pairing
|
|
7
|
-
* path (SDK-side) and `device.capabilities.mode` was an onboarding toggle, while
|
|
8
|
-
* the other eleven `device.*` keys were recorded, read back, and governed
|
|
9
|
-
* nothing — the capability service they describe was never built here, so there
|
|
10
|
-
* was nothing for them to govern.
|
|
11
|
-
*
|
|
12
|
-
* The feature itself is platform-owned (`platform/devices`): the settings→policy
|
|
13
|
-
* mapping, the grants ledger, the capture store, the housekeeping sweeps, the
|
|
14
|
-
* confirmation flow, and the `phone` tool. This module supplies the three seams
|
|
15
|
-
* that are actually ours and nothing else:
|
|
16
|
-
*
|
|
17
|
-
* - the peer transport — the same DistributedRuntimeManager the remote surface
|
|
18
|
-
* pairs devices onto, so a phone paired here is a node here,
|
|
19
|
-
* - the approval path — the shared approval broker, so the confirmation
|
|
20
|
-
* appears wherever the person is looking (terminal, web app, companion),
|
|
21
|
-
* - the storage root and the live config manager.
|
|
22
|
-
*
|
|
23
|
-
* Constructing this starts nothing. `startHousekeeping()` is called from the
|
|
24
|
-
* bootstrap tail: grants and captures both outlive a restart, so a grant whose
|
|
25
|
-
* phone is gone, or a capture torn by a crash, is reaped BEFORE the first
|
|
26
|
-
* request of this run is served — and the periodic sweep after it is what keeps
|
|
27
|
-
* a long-running daemon from going days without one. A failed sweep is logged
|
|
28
|
-
* and the app still runs: housekeeping failing is a reason to say so, not a
|
|
29
|
-
* reason to refuse to start.
|
|
30
|
-
*/
|
|
31
|
-
import { logger } from '@pellux/goodvibes-sdk/platform/utils';
|
|
32
|
-
import { createDevicePostureRuntime, registerDevicePhoneTool } from '@pellux/goodvibes-sdk/platform/devices';
|
|
33
|
-
import type {
|
|
34
|
-
DeviceApprovalBridge,
|
|
35
|
-
DevicePeerTransport,
|
|
36
|
-
DevicePhoneToolRegistry,
|
|
37
|
-
DevicePostureRuntime,
|
|
38
|
-
} from '@pellux/goodvibes-sdk/platform/devices';
|
|
39
|
-
import { registerDevicesGatewayMethods } from '@pellux/goodvibes-sdk/platform/control-plane';
|
|
40
|
-
import type { GatewayMethodCatalog } from '@pellux/goodvibes-sdk/platform/control-plane';
|
|
41
|
-
import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
42
|
-
|
|
43
|
-
/** Who this surface records in the device audit trail. */
|
|
44
|
-
export const TUI_DEVICE_ACTOR = 'tui:phone-tool';
|
|
45
|
-
|
|
46
|
-
export interface DevicePostureCompositionOptions {
|
|
47
|
-
readonly configManager: ConfigManager;
|
|
48
|
-
/** The runtime devices pair onto; its listPeers/invokePeer are the transport. */
|
|
49
|
-
readonly distributedRuntime: DevicePeerTransport;
|
|
50
|
-
readonly approvals: DeviceApprovalBridge;
|
|
51
|
-
/** Surface-scoped directory the grants ledger, captures and disclosure live in. */
|
|
52
|
-
readonly stateDirectory: string;
|
|
53
|
-
/**
|
|
54
|
-
* Binding the catalog turns devices.nodes.list / devices.grants.* /
|
|
55
|
-
* devices.housekeeping.run from cataloged-but-unhandled into real handlers,
|
|
56
|
-
* which is what makes the grants surface work in the web app and the companion
|
|
57
|
-
* rather than only through this app's own tool.
|
|
58
|
-
*/
|
|
59
|
-
readonly gatewayMethods?: GatewayMethodCatalog | undefined;
|
|
60
|
-
readonly getSessionId?: (() => string | undefined) | undefined;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export interface DevicePostureServices {
|
|
64
|
-
readonly devicePosture: DevicePostureRuntime;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/** Build the device posture runtime and bind the device verbs to it. */
|
|
68
|
-
export function createDevicePostureServices(options: DevicePostureCompositionOptions): DevicePostureServices {
|
|
69
|
-
const devicePosture = createDevicePostureRuntime({
|
|
70
|
-
transport: options.distributedRuntime,
|
|
71
|
-
approvals: options.approvals,
|
|
72
|
-
config: options.configManager,
|
|
73
|
-
stateDirectory: options.stateDirectory,
|
|
74
|
-
actor: TUI_DEVICE_ACTOR,
|
|
75
|
-
...(options.getSessionId ? { getSessionId: options.getSessionId } : {}),
|
|
76
|
-
});
|
|
77
|
-
if (options.gatewayMethods) registerDevicesGatewayMethods(options.gatewayMethods, devicePosture);
|
|
78
|
-
return { devicePosture };
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Everything a host with a tool registry has to do once it is up: register the
|
|
83
|
-
* `phone` tool — the only path that reaches the capability service, so without it
|
|
84
|
-
* the posture keys govern nothing a session can observe — and start housekeeping.
|
|
85
|
-
*
|
|
86
|
-
* One call, because these two belong to the same feature and a host that did the
|
|
87
|
-
* first and forgot the second would serve requests while never reaping a grant
|
|
88
|
-
* whose phone is gone.
|
|
89
|
-
*/
|
|
90
|
-
export function installDevicePosture(
|
|
91
|
-
toolRegistry: DevicePhoneToolRegistry,
|
|
92
|
-
devicePosture: DevicePostureRuntime,
|
|
93
|
-
): void {
|
|
94
|
-
registerDevicePhoneTool(toolRegistry, devicePosture);
|
|
95
|
-
startDeviceHousekeeping(devicePosture);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* The recovery sweep plus the periodic timer. Separate from construction so
|
|
100
|
-
* composing a runtime in a test starts no timer and touches no disk, and
|
|
101
|
-
* separate from the call above so the standalone daemon — which registers no
|
|
102
|
-
* tools — still sweeps.
|
|
103
|
-
*/
|
|
104
|
-
export function startDeviceHousekeeping(devicePosture: DevicePostureRuntime): void {
|
|
105
|
-
void devicePosture.startHousekeeping().catch((error: unknown) => {
|
|
106
|
-
logger.warn('Device housekeeping sweep failed at startup', {
|
|
107
|
-
error: error instanceof Error ? error.message : String(error),
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
}
|