@pellux/goodvibes-tui 1.27.0 → 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 +125 -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 +12 -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 +15 -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 -641
- 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 -58
- 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,183 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* durability-housekeeping.ts — the reclaim half of every crash-durability
|
|
3
|
-
* artefact this product writes to disk.
|
|
4
|
-
*
|
|
5
|
-
* The standing rule this module implements: if something is persisted across
|
|
6
|
-
* restarts and crashes, recovery has to do the housekeeping. Each individual
|
|
7
|
-
* store already knew how to WRITE its artefact and how to retire it on a clean
|
|
8
|
-
* path; none of them reclaimed what a SIGKILL left behind. Four stores, four
|
|
9
|
-
* kinds of residue:
|
|
10
|
-
*
|
|
11
|
-
* - liveness markers `<home>/.goodvibes/<root>/liveness/<id>.json`
|
|
12
|
-
* removed only on a clean exit, so a crash leaks one
|
|
13
|
-
* file per session id ever opened.
|
|
14
|
-
* - transcript journals `<home>/.goodvibes/<root>/transcript-<id>.journal`
|
|
15
|
-
* deleted on snapshot or replay, so a session that
|
|
16
|
-
* crashes and is never resumed leaks its journal.
|
|
17
|
-
* - quarantine files `<path>.unrecognized` — written so a human can
|
|
18
|
-
* inspect a corrupt file, never removed afterwards.
|
|
19
|
-
* - anchor sidecars `<sessionsDir>/<id>.anchors.json` — outlives the
|
|
20
|
-
* session JSONL that owns it.
|
|
21
|
-
*
|
|
22
|
-
* Every reap here is bounded by BOTH an age window and a count cap, decides
|
|
23
|
-
* what to keep by reading content rather than by a file merely existing, never
|
|
24
|
-
* touches the current session, tolerates a competing sweeper deleting the same
|
|
25
|
-
* file first, and reports what it removed. `startDurabilityHousekeeping` runs
|
|
26
|
-
* the sweep at startup and then on a repeating timer, because a long-lived
|
|
27
|
-
* process that only sweeps at boot never sweeps.
|
|
28
|
-
*
|
|
29
|
-
* Best-effort throughout: a failure in one reap never aborts the others and
|
|
30
|
-
* nothing here ever throws to its caller.
|
|
31
|
-
*/
|
|
32
|
-
import { logger, summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
33
|
-
import { join } from 'node:path';
|
|
34
|
-
import { reapQuarantinedFiles } from '@/config/read-versioned.ts';
|
|
35
|
-
import { reapOrphanedAnchorSidecars } from '@/core/rewind-turn-anchors.ts';
|
|
36
|
-
import { reapOrphanedJournals } from '@/core/transcript-journal.ts';
|
|
37
|
-
import { checkSessionLiveness, reapStaleLivenessMarkers } from '@/runtime/session-liveness-marker.ts';
|
|
38
|
-
import type { SessionSurface } from '@/runtime/index.ts';
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* How often the sweep repeats after startup: 6 hours. Hours, not minutes — the
|
|
42
|
-
* residue this reclaims accumulates per crashed session, not continuously, and
|
|
43
|
-
* a directory scan is not something a foreground TUI should be doing often.
|
|
44
|
-
*/
|
|
45
|
-
export const DURABILITY_HOUSEKEEPING_INTERVAL_MS = 6 * 60 * 60 * 1000;
|
|
46
|
-
|
|
47
|
-
export interface DurabilityHousekeepingInput {
|
|
48
|
-
/** The app's declare-once storage handle; every swept path derives from it. */
|
|
49
|
-
readonly surface: SessionSurface;
|
|
50
|
-
/**
|
|
51
|
-
* The session this process is using right now, read at sweep time rather
|
|
52
|
-
* than captured once — a resume or fork can repoint it between sweeps.
|
|
53
|
-
* Returning null means "not known yet", which is safe: the age and liveness
|
|
54
|
-
* rules independently protect an actively written artefact.
|
|
55
|
-
*/
|
|
56
|
-
readonly currentSessionId?: () => string | null;
|
|
57
|
-
/** Extra directories to scan for `.unrecognized` quarantine files. */
|
|
58
|
-
readonly extraQuarantineDirs?: readonly string[];
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export interface DurabilityHousekeepingOutcome {
|
|
62
|
-
readonly livenessMarkers: number;
|
|
63
|
-
readonly transcriptJournals: number;
|
|
64
|
-
readonly quarantineFiles: number;
|
|
65
|
-
readonly anchorSidecars: number;
|
|
66
|
-
/** Sum of the four counts above — zero means nothing was reclaimed. */
|
|
67
|
-
readonly total: number;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Run all four reaps once and return what each reclaimed.
|
|
72
|
-
*
|
|
73
|
-
* Each reap is isolated: one throwing is logged at warn level and counted as
|
|
74
|
-
* zero, and the remaining reaps still run. The caller never sees an exception.
|
|
75
|
-
*/
|
|
76
|
-
export function runDurabilityHousekeeping(input: DurabilityHousekeepingInput): DurabilityHousekeepingOutcome {
|
|
77
|
-
const { surface } = input;
|
|
78
|
-
const currentSessionId = safely('current session id', () => input.currentSessionId?.() ?? null, null);
|
|
79
|
-
|
|
80
|
-
const livenessMarkers = safely('liveness markers', () =>
|
|
81
|
-
reapStaleLivenessMarkers(surface, {
|
|
82
|
-
keepSessionIds: currentSessionId ? [currentSessionId] : [],
|
|
83
|
-
}).reaped, 0);
|
|
84
|
-
|
|
85
|
-
const transcriptJournals = safely('transcript journals', () =>
|
|
86
|
-
reapOrphanedJournals(surface, {
|
|
87
|
-
currentSessionId,
|
|
88
|
-
// Composition happens here so transcript-journal.ts keeps no dependency
|
|
89
|
-
// on the liveness marker: a journal whose session is open in another
|
|
90
|
-
// running instance is live state, not residue.
|
|
91
|
-
isSessionLive: (sessionId) => checkSessionLiveness(surface, sessionId).live,
|
|
92
|
-
}).reaped, 0);
|
|
93
|
-
|
|
94
|
-
const quarantineFiles = safely('quarantine files', () =>
|
|
95
|
-
reapQuarantinedFiles(quarantineDirsFor(input)).reaped, 0);
|
|
96
|
-
|
|
97
|
-
const anchorSidecars = safely('anchor sidecars', () =>
|
|
98
|
-
reapOrphanedAnchorSidecars(surface, { currentSessionId }).reaped, 0);
|
|
99
|
-
|
|
100
|
-
const outcome: DurabilityHousekeepingOutcome = {
|
|
101
|
-
livenessMarkers,
|
|
102
|
-
transcriptJournals,
|
|
103
|
-
quarantineFiles,
|
|
104
|
-
anchorSidecars,
|
|
105
|
-
total: livenessMarkers + transcriptJournals + quarantineFiles + anchorSidecars,
|
|
106
|
-
};
|
|
107
|
-
discloseHousekeeping(outcome);
|
|
108
|
-
return outcome;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Start durability housekeeping: one sweep now, then a repeating sweep every
|
|
113
|
-
* `DURABILITY_HOUSEKEEPING_INTERVAL_MS`. Returns a disposer that stops the
|
|
114
|
-
* timer; calling it more than once is harmless.
|
|
115
|
-
*
|
|
116
|
-
* The timer is `unref()`d, so an undisposed sweeper can never hold the process
|
|
117
|
-
* open — same convention the store-snapshot scheduler and the config-file
|
|
118
|
-
* watchers in durability-services.ts use.
|
|
119
|
-
*/
|
|
120
|
-
export function startDurabilityHousekeeping(
|
|
121
|
-
input: DurabilityHousekeepingInput,
|
|
122
|
-
options: { readonly intervalMs?: number } = {},
|
|
123
|
-
): () => void {
|
|
124
|
-
runDurabilityHousekeeping(input);
|
|
125
|
-
const timer = setInterval(
|
|
126
|
-
() => { runDurabilityHousekeeping(input); },
|
|
127
|
-
options.intervalMs ?? DURABILITY_HOUSEKEEPING_INTERVAL_MS,
|
|
128
|
-
);
|
|
129
|
-
timer.unref?.();
|
|
130
|
-
let stopped = false;
|
|
131
|
-
return () => {
|
|
132
|
-
if (stopped) return;
|
|
133
|
-
stopped = true;
|
|
134
|
-
clearInterval(timer);
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Disclose what was reclaimed — silent deletion is indistinguishable from data
|
|
140
|
-
* loss. Paths and counts only; nothing about a reclaimed file's contents.
|
|
141
|
-
*
|
|
142
|
-
* A sweep that reclaimed nothing logs nothing: the overwhelmingly common boot
|
|
143
|
-
* has no residue at all, and a per-boot "reclaimed 0" line would train readers
|
|
144
|
-
* to ignore the line that actually matters.
|
|
145
|
-
*/
|
|
146
|
-
function discloseHousekeeping(outcome: DurabilityHousekeepingOutcome): void {
|
|
147
|
-
if (outcome.total === 0) return;
|
|
148
|
-
logger.info('durability housekeeping reclaimed crash residue', {
|
|
149
|
-
livenessMarkers: outcome.livenessMarkers,
|
|
150
|
-
transcriptJournals: outcome.transcriptJournals,
|
|
151
|
-
quarantineFiles: outcome.quarantineFiles,
|
|
152
|
-
anchorSidecars: outcome.anchorSidecars,
|
|
153
|
-
total: outcome.total,
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Every directory that can accumulate `.unrecognized` files: the home-scoped
|
|
159
|
-
* surface root (transcript-journal quarantines) and the per-project session,
|
|
160
|
-
* state, recovery and checkpoint directories (versioned-config quarantines),
|
|
161
|
-
* plus whatever the caller adds.
|
|
162
|
-
*/
|
|
163
|
-
function quarantineDirsFor(input: DurabilityHousekeepingInput): readonly string[] {
|
|
164
|
-
const { surface } = input;
|
|
165
|
-
return [
|
|
166
|
-
join(surface.homeDirectory, '.goodvibes', surface.surfaceRoot),
|
|
167
|
-
surface.sessionsDir,
|
|
168
|
-
surface.stateDir,
|
|
169
|
-
surface.recoveryDir,
|
|
170
|
-
surface.checkpointsDir,
|
|
171
|
-
...(input.extraQuarantineDirs ?? []),
|
|
172
|
-
].filter((dir): dir is string => typeof dir === 'string' && dir.length > 0);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/** Run one reap in isolation: never throws, returns `fallback` if it would have. */
|
|
176
|
-
function safely<T>(label: string, run: () => T, fallback: T): T {
|
|
177
|
-
try {
|
|
178
|
-
return run();
|
|
179
|
-
} catch (error) {
|
|
180
|
-
logger.warn('durability housekeeping step failed', { step: label, error: summarizeError(error) });
|
|
181
|
-
return fallback;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* durability-services.ts — the data-safety and remembered-decision stores the
|
|
3
|
-
* daemon composition wires (mirrors the SDK's own createRuntimeServices):
|
|
4
|
-
*
|
|
5
|
-
* - StoreSnapshotScheduler: a daily snapshot of every SQLite store this
|
|
6
|
-
* runtime writes (memory store, memory vector index, code index store),
|
|
7
|
-
* bounded by the retention engine. Timers are unref'd so an undisposed
|
|
8
|
-
* scheduler cannot pin the event loop; hosts that tear down a runtime
|
|
9
|
-
* stop() it themselves.
|
|
10
|
-
* - UserPermissionRuleStore: durable user-origin permission rules
|
|
11
|
-
* (remembered approvals) — one store per project, shared by every
|
|
12
|
-
* PermissionManager built on this runtime; permissions.rules.*
|
|
13
|
-
* lists/deletes. Background init is fail-safe (asks just prompt).
|
|
14
|
-
* - One credential chain (env -> secrets -> subscription): boot applies
|
|
15
|
-
* secrets-backed keys; every secrets write/delete re-registers builtins
|
|
16
|
-
* LIVE (no restart) — badges/picker/chat read the same instances.
|
|
17
|
-
* - Start-time retention janitor: one best-effort append-only sweep over
|
|
18
|
-
* EVERY root the composition knows (working dir, surface root, home,
|
|
19
|
-
* logDir, telemetryDir) — omitting logDir/telemetryDir/home would silently
|
|
20
|
-
* skip the activity-log, telemetry-ledger, and recovery-snapshot stores.
|
|
21
|
-
* - Durability housekeeping: the crash-residue reclaim the retention janitor
|
|
22
|
-
* above does NOT cover — stale liveness markers, orphaned transcript
|
|
23
|
-
* journals, `.unrecognized` quarantine files, and anchor sidecars whose
|
|
24
|
-
* session is gone (see durability-housekeeping.ts). It runs at startup AND
|
|
25
|
-
* on a repeating unref'd timer, because a long-lived process that only
|
|
26
|
-
* sweeps at boot never sweeps; the returned disposer stops that timer.
|
|
27
|
-
* - Live config-file watch: external edits to the settings file apply through
|
|
28
|
-
* the same subscribe() pipeline an in-process set() uses — no restart. The
|
|
29
|
-
* underlying watchers are unref'd, so this never pins the event loop.
|
|
30
|
-
*/
|
|
31
|
-
import { join } from 'node:path';
|
|
32
|
-
import { operations } from '@pellux/goodvibes-sdk/platform/runtime';
|
|
33
|
-
import { resolveMemoryVectorDbPath } from '@pellux/goodvibes-sdk/platform/state';
|
|
34
|
-
import { StoreSnapshotScheduler } from '@pellux/goodvibes-sdk/platform/state/store-snapshots';
|
|
35
|
-
import { UserPermissionRuleStore } from '@pellux/goodvibes-sdk/platform/permissions';
|
|
36
|
-
import { logger, summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
37
|
-
import { startDurabilityHousekeeping } from './durability-housekeeping.ts';
|
|
38
|
-
import type { SessionSurface } from '@/runtime/index.ts';
|
|
39
|
-
|
|
40
|
-
export interface DurabilityServicesInput {
|
|
41
|
-
readonly configManager: {
|
|
42
|
-
getControlPlaneConfigDir(): string;
|
|
43
|
-
get(key: never): unknown;
|
|
44
|
-
watchConfigFiles(options?: { intervalMs?: number }): () => void;
|
|
45
|
-
};
|
|
46
|
-
readonly secretsManager: { onDidChange(listener: (key: string) => void): () => void };
|
|
47
|
-
readonly providerRegistry: { refreshProviderCredentials(): Promise<void> };
|
|
48
|
-
readonly memoryDbPath: string;
|
|
49
|
-
readonly codeIndexDbPath: string;
|
|
50
|
-
/**
|
|
51
|
-
* The app's declare-once storage handle. The retention-sweep roots below
|
|
52
|
-
* are read straight off it rather than re-declared here, so the janitor can
|
|
53
|
-
* never sweep a different scope than the one sessions and recovery
|
|
54
|
-
* snapshots are actually written to.
|
|
55
|
-
*/
|
|
56
|
-
readonly surface: SessionSurface;
|
|
57
|
-
readonly shellPaths: { resolveUserPath(...segments: string[]): string };
|
|
58
|
-
/**
|
|
59
|
-
* The session id this process is currently using, read at each sweep. When
|
|
60
|
-
* omitted the crash-residue reap still protects live artefacts through its
|
|
61
|
-
* age and liveness rules — this is the explicit belt-and-braces guard.
|
|
62
|
-
*/
|
|
63
|
-
/**
|
|
64
|
-
* Resolves the live session id, read fresh on every crash-residue sweep so
|
|
65
|
-
* the running session's own transcript journal and liveness marker are
|
|
66
|
-
* exempt from reaping.
|
|
67
|
-
*
|
|
68
|
-
* A getter, not a value: the id is reassigned in place when a recovery
|
|
69
|
-
* snapshot is accepted, and this sweep repeats for the life of the process.
|
|
70
|
-
*
|
|
71
|
-
* Omitting it is not merely untidy. The journal reaper's other guard is the
|
|
72
|
-
* liveness marker, and that marker goes stale after 150 seconds — so a host
|
|
73
|
-
* that passes nothing here is trusting a heartbeat that a single long
|
|
74
|
-
* blocking turn can outrun, and an in-process sweep landing in that window
|
|
75
|
-
* would delete the journal of the session currently writing it. Passing this
|
|
76
|
-
* makes the exemption unconditional instead of timing-dependent.
|
|
77
|
-
*/
|
|
78
|
-
readonly currentSessionId?: () => string | null;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export interface DurabilityServices {
|
|
82
|
-
readonly storeSnapshotScheduler: StoreSnapshotScheduler;
|
|
83
|
-
/**
|
|
84
|
-
* Re-sweeps every registered append-only store on a cadence. The start-time
|
|
85
|
-
* sweep alone never prunes again in a process that stays up, which is the
|
|
86
|
-
* window in which those stores grow. Unref'd timers; teardown stop()s it.
|
|
87
|
-
*/
|
|
88
|
-
readonly appendOnlyRetentionScheduler: InstanceType<typeof operations.AppendOnlyRetentionScheduler>;
|
|
89
|
-
readonly userPermissionRuleStore: UserPermissionRuleStore;
|
|
90
|
-
/**
|
|
91
|
-
* Stops the repeating crash-residue sweep. The timer is unref'd, so a host
|
|
92
|
-
* that never calls this is not held open by it; teardown calls it to stop
|
|
93
|
-
* the work rather than to release the loop.
|
|
94
|
-
*/
|
|
95
|
-
readonly stopDurabilityHousekeeping: () => void;
|
|
96
|
-
/** Stops the live config-file watch this factory started. */
|
|
97
|
-
readonly stopConfigWatch: () => void;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export function createDurabilityServices(input: DurabilityServicesInput): DurabilityServices {
|
|
101
|
-
const { configManager, secretsManager, providerRegistry } = input;
|
|
102
|
-
secretsManager.onDidChange(() => void providerRegistry.refreshProviderCredentials().catch((error) => logger.warn('live credential refresh failed', { error: summarizeError(error) })));
|
|
103
|
-
void providerRegistry.refreshProviderCredentials().catch((error) => logger.warn('boot credential refresh failed', { error: summarizeError(error) }));
|
|
104
|
-
|
|
105
|
-
const storeSnapshotScheduler = new StoreSnapshotScheduler({
|
|
106
|
-
stores: [
|
|
107
|
-
{ name: 'memory store', dbPath: input.memoryDbPath },
|
|
108
|
-
{ name: 'memory vector index', dbPath: resolveMemoryVectorDbPath(input.memoryDbPath) },
|
|
109
|
-
{ name: 'code index store', dbPath: input.codeIndexDbPath },
|
|
110
|
-
],
|
|
111
|
-
});
|
|
112
|
-
storeSnapshotScheduler.start();
|
|
113
|
-
|
|
114
|
-
// Start-time retention janitor: pass EVERY root the composition knows so the
|
|
115
|
-
// activity-log, telemetry-ledger, and recovery-snapshot stores are all swept.
|
|
116
|
-
// Best-effort — a retention failure never takes startup down (the sweep
|
|
117
|
-
// swallows its own errors).
|
|
118
|
-
const appendOnlyRetentionRoots = {
|
|
119
|
-
workingDirectory: input.surface.workingDirectory,
|
|
120
|
-
surfaceRoot: input.surface.surfaceRoot,
|
|
121
|
-
homeDirectory: input.surface.homeDirectory,
|
|
122
|
-
logDir: input.shellPaths.resolveUserPath('logs'),
|
|
123
|
-
telemetryDir: input.shellPaths.resolveUserPath('telemetry'),
|
|
124
|
-
};
|
|
125
|
-
const appendOnlyRetentionConfigGet = (k: string): unknown => configManager.get(k as never);
|
|
126
|
-
operations.runStartupAppendOnlySweep(appendOnlyRetentionRoots, appendOnlyRetentionConfigGet);
|
|
127
|
-
// ...and again on a cadence for as long as this process lives. A start-time
|
|
128
|
-
// sweep alone never prunes a long-lived session again after boot, which is
|
|
129
|
-
// exactly the window in which these stores grow. Unref'd timers; the disposer
|
|
130
|
-
// below stops it, same posture as storeSnapshotScheduler.
|
|
131
|
-
const appendOnlyRetentionScheduler = new operations.AppendOnlyRetentionScheduler({
|
|
132
|
-
roots: appendOnlyRetentionRoots,
|
|
133
|
-
configGet: appendOnlyRetentionConfigGet,
|
|
134
|
-
});
|
|
135
|
-
appendOnlyRetentionScheduler.start();
|
|
136
|
-
// Crash-residue reclaim the sweep above does not cover (liveness markers,
|
|
137
|
-
// orphaned transcript journals, .unrecognized quarantine files, anchor
|
|
138
|
-
// sidecars whose session file is gone). Runs once now and every few hours
|
|
139
|
-
// after that; the timer is unref'd and the disposer stops it.
|
|
140
|
-
const stopDurabilityHousekeeping = startDurabilityHousekeeping({
|
|
141
|
-
surface: input.surface,
|
|
142
|
-
currentSessionId: input.currentSessionId,
|
|
143
|
-
extraQuarantineDirs: [configManager.getControlPlaneConfigDir()],
|
|
144
|
-
});
|
|
145
|
-
// External config edits apply LIVE through the same subscribe() pipeline an
|
|
146
|
-
// in-process set() uses; the underlying watchers are unref'd. The returned
|
|
147
|
-
// handle is the only way to stop them — dropping it left a 250ms poll running
|
|
148
|
-
// for the life of the process after the graph that started it was gone.
|
|
149
|
-
const stopConfigWatch = configManager.watchConfigFiles();
|
|
150
|
-
|
|
151
|
-
const userPermissionRuleStore = new UserPermissionRuleStore(join(configManager.getControlPlaneConfigDir(), 'permission-rules.json'));
|
|
152
|
-
void userPermissionRuleStore.init().catch((error) => logger.warn('user permission rule store init failed; asks will prompt', { error: summarizeError(error) }));
|
|
153
|
-
|
|
154
|
-
return { storeSnapshotScheduler, appendOnlyRetentionScheduler, userPermissionRuleStore, stopDurabilityHousekeeping, stopConfigWatch };
|
|
155
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* embedded-service-factories.ts — the factories startExternalServices uses to
|
|
3
|
-
* construct the interactive session's embedded DaemonServer + HttpListener
|
|
4
|
-
* (extracted from bootstrap.ts for file-size hygiene).
|
|
5
|
-
*
|
|
6
|
-
* The embedded DaemonServer suppresses the facade's internal auto-updater:
|
|
7
|
-
* that loop compares the sdk package's version — not this binary's — against
|
|
8
|
-
* this repo's releases, so inside the interactive session it would find an
|
|
9
|
-
* "update" hourly and exit the user's process on "restart". Clients update at
|
|
10
|
-
* launch (cli/launch-auto-update.ts); the embedded daemon runs no update loop
|
|
11
|
-
* at all. See daemon/lifecycle.ts.
|
|
12
|
-
*/
|
|
13
|
-
import { DaemonServer, HttpListener } from '@pellux/goodvibes-sdk/platform/daemon';
|
|
14
|
-
import type { ClusterCoordinator, ClusterGroupVerbSurface } from '@pellux/goodvibes-sdk/platform/cluster';
|
|
15
|
-
import { createSafeHostServeFactory } from '../daemon/safe-serve.ts';
|
|
16
|
-
import type { startExternalServices } from '@/runtime/index.ts';
|
|
17
|
-
|
|
18
|
-
export type ExternalServiceFactories = NonNullable<Parameters<typeof startExternalServices>[4]>;
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* `clusterCoordinator` is threaded through explicitly rather than read off the
|
|
22
|
-
* runtimeServices the SDK hands the factory: that object is typed by the SDK,
|
|
23
|
-
* which has no field for a coordinator this product composed. Passing it here
|
|
24
|
-
* keeps the ONE-coordinator-per-process rule enforceable by the type checker.
|
|
25
|
-
*/
|
|
26
|
-
export function createEmbeddedServiceFactories(
|
|
27
|
-
sharedDaemonToken: string,
|
|
28
|
-
clusterCoordinator: ClusterCoordinator,
|
|
29
|
-
clusterGroupVerbs: ClusterGroupVerbSurface,
|
|
30
|
-
): ExternalServiceFactories {
|
|
31
|
-
return {
|
|
32
|
-
sharedDaemonToken,
|
|
33
|
-
createDaemonServer: (bus, userAuth, runtimeServices) => {
|
|
34
|
-
// No updateArtifact: the embedded daemon keeps updates host-managed (the
|
|
35
|
-
// TUI client updates itself), so the facade runs no self-update loop.
|
|
36
|
-
const daemonServer = new DaemonServer({
|
|
37
|
-
runtimeBus: bus,
|
|
38
|
-
userAuth,
|
|
39
|
-
runtimeServices,
|
|
40
|
-
serveFactory: createSafeHostServeFactory('Embedded daemon'),
|
|
41
|
-
// The SAME coordinator the inbox poller registered with. Letting the
|
|
42
|
-
// facade compose its own would put two election nodes in one process,
|
|
43
|
-
// and whichever lost would silence consumers the other owns.
|
|
44
|
-
clusterCoordinator,
|
|
45
|
-
// The `cluster` verbs (/api/cluster/*), so the CLI and the TUI command
|
|
46
|
-
// reach one implementation whether the daemon is embedded or standalone.
|
|
47
|
-
clusterGroupVerbs,
|
|
48
|
-
});
|
|
49
|
-
return daemonServer;
|
|
50
|
-
},
|
|
51
|
-
createHttpListener: (dispatcher, userAuth, configManager) => new HttpListener({
|
|
52
|
-
hookDispatcher: dispatcher,
|
|
53
|
-
userAuth,
|
|
54
|
-
configManager,
|
|
55
|
-
serveFactory: createSafeHostServeFactory('Embedded HTTP listener'),
|
|
56
|
-
}),
|
|
57
|
-
};
|
|
58
|
-
}
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* feature-settings — TUI-side helpers over the SDK's FEATURE_SETTINGS surface.
|
|
3
|
-
*
|
|
4
|
-
* Every platform capability is configured through a first-class settings key
|
|
5
|
-
* in its natural domain (behavior.compactionStrategy, sandbox.enabled,
|
|
6
|
-
* surfaces.slack.enabled, ...). There is no separate enablement namespace.
|
|
7
|
-
* These helpers answer the recurring TUI questions:
|
|
8
|
-
* - which feature a given id names, and whether a stock config has it on,
|
|
9
|
-
* - whether a raw config value means the feature is currently on,
|
|
10
|
-
* - which single settings write turns a feature on or off.
|
|
11
|
-
*
|
|
12
|
-
* Enablement kinds (from the SDK binding layer):
|
|
13
|
-
* - boolean : the key's boolean value is the enablement.
|
|
14
|
-
* - enum : active while the key's value is in enabledValues (several
|
|
15
|
-
* features can share one key, e.g. behavior.compactionStrategy).
|
|
16
|
-
* - constant: the capability has no separate off switch. When its key is a
|
|
17
|
-
* boolean (the surface enabled keys) that value is the honest
|
|
18
|
-
* user-facing switch; a non-boolean constant key (e.g.
|
|
19
|
-
* fetch.sanitizeMode) has no off position at all.
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
import { CONFIG_SCHEMA, type ConfigKey, type ConfigManager, type ConfigSetting } from '@pellux/goodvibes-sdk/platform/config';
|
|
23
|
-
import {
|
|
24
|
-
FEATURE_SETTINGS,
|
|
25
|
-
deriveFeatureState,
|
|
26
|
-
type FeatureSetting,
|
|
27
|
-
} from '@pellux/goodvibes-sdk/platform/runtime/state';
|
|
28
|
-
|
|
29
|
-
export type { FeatureSetting } from '@pellux/goodvibes-sdk/platform/runtime/state';
|
|
30
|
-
|
|
31
|
-
// ---------------------------------------------------------------------------
|
|
32
|
-
// Lookups
|
|
33
|
-
// ---------------------------------------------------------------------------
|
|
34
|
-
|
|
35
|
-
/** Every capability by id, in SDK declaration order. */
|
|
36
|
-
export const FEATURE_SETTINGS_BY_ID: ReadonlyMap<string, FeatureSetting> = new Map(
|
|
37
|
-
FEATURE_SETTINGS.map((feature) => [feature.id, feature]),
|
|
38
|
-
);
|
|
39
|
-
|
|
40
|
-
const SCHEMA_BY_KEY: ReadonlyMap<string, ConfigSetting> = new Map(
|
|
41
|
-
CONFIG_SCHEMA.map((setting) => [setting.key, setting]),
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
export function getFeatureSetting(featureId: string): FeatureSetting | null {
|
|
45
|
-
return FEATURE_SETTINGS_BY_ID.get(featureId) ?? null;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/** The CONFIG_SCHEMA descriptor for a settings key, or null for TUI-local synthetics. */
|
|
49
|
-
export function getConfigSchemaSetting(key: string): ConfigSetting | null {
|
|
50
|
-
return SCHEMA_BY_KEY.get(key) ?? null;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/** Whether a stock configuration has the feature active. */
|
|
54
|
-
export function isFeatureDefaultEnabled(featureId: string): boolean {
|
|
55
|
-
return getFeatureSetting(featureId)?.defaultEnabled === true;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/** All features whose enablement key is `key` (several features can share one). */
|
|
59
|
-
export function featuresForEnablementKey(key: string): readonly FeatureSetting[] {
|
|
60
|
-
return FEATURE_SETTINGS.filter((feature) => feature.enablement.key === key);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// ---------------------------------------------------------------------------
|
|
64
|
-
// Enablement derivation
|
|
65
|
-
// ---------------------------------------------------------------------------
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Whether a raw config value means the feature is on, as a settings surface
|
|
69
|
-
* should display it. Boolean/enum kinds derive exactly as the runtime gate
|
|
70
|
-
* does; constant kinds fall back to the boolean key value when there is one
|
|
71
|
-
* (the honest user-facing switch for the surface adapters) and read as always
|
|
72
|
-
* on otherwise.
|
|
73
|
-
*/
|
|
74
|
-
export function isFeatureValueEnabled(feature: FeatureSetting, value: unknown): boolean {
|
|
75
|
-
const { key, kind, enabledValues } = feature.enablement;
|
|
76
|
-
// A capability declared not operable in this build never reads as on, whatever
|
|
77
|
-
// its settings key says. deriveFeatureState already enforces this for the
|
|
78
|
-
// boolean and enum kinds; stating it once here covers the constant kind too,
|
|
79
|
-
// so no surface can show "on" for something that is doing nothing. The user's
|
|
80
|
-
// value is untouched — it is kept for the release that wires the capability up
|
|
81
|
-
// — and `feature.inoperableDetail` is the written reason a surface renders.
|
|
82
|
-
if (feature.operable === false) return false;
|
|
83
|
-
if (kind === 'constant') {
|
|
84
|
-
const schema = getConfigSchemaSetting(key);
|
|
85
|
-
if (schema?.type === 'boolean') return value === true;
|
|
86
|
-
return true;
|
|
87
|
-
}
|
|
88
|
-
return deriveFeatureState(
|
|
89
|
-
{
|
|
90
|
-
featureId: feature.id,
|
|
91
|
-
key,
|
|
92
|
-
kind,
|
|
93
|
-
...(enabledValues !== undefined ? { enabledValues } : {}),
|
|
94
|
-
},
|
|
95
|
-
value,
|
|
96
|
-
) === 'enabled';
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/** Whether the live config currently has the feature on (see isFeatureValueEnabled). */
|
|
100
|
-
export function isFeatureConfigEnabled(
|
|
101
|
-
configManager: Pick<ConfigManager, 'get'>,
|
|
102
|
-
featureId: string,
|
|
103
|
-
): boolean {
|
|
104
|
-
const feature = getFeatureSetting(featureId);
|
|
105
|
-
if (!feature) return false;
|
|
106
|
-
return isFeatureValueEnabled(feature, configManager.get(feature.enablement.key));
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// ---------------------------------------------------------------------------
|
|
110
|
-
// Enablement writes
|
|
111
|
-
// ---------------------------------------------------------------------------
|
|
112
|
-
|
|
113
|
-
export interface FeatureEnablementWrite {
|
|
114
|
-
readonly key: ConfigKey;
|
|
115
|
-
readonly value: boolean | string;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* The single settings write that turns a feature on or off, or null when the
|
|
120
|
-
* feature has no off position (a constant-kind capability on a non-boolean
|
|
121
|
-
* key). For enum kinds the schema default is preferred whenever it sits on
|
|
122
|
-
* the requested side, so "enable" lands on the feature's stock mode and
|
|
123
|
-
* "disable" lands on the stock off mode rather than an arbitrary value.
|
|
124
|
-
*/
|
|
125
|
-
export function featureEnablementWrite(
|
|
126
|
-
featureId: string,
|
|
127
|
-
enabled: boolean,
|
|
128
|
-
): FeatureEnablementWrite | null {
|
|
129
|
-
const feature = getFeatureSetting(featureId);
|
|
130
|
-
if (!feature) return null;
|
|
131
|
-
const { key, kind, enabledValues } = feature.enablement;
|
|
132
|
-
|
|
133
|
-
if (kind === 'boolean') return { key, value: enabled };
|
|
134
|
-
|
|
135
|
-
const schema = getConfigSchemaSetting(key);
|
|
136
|
-
if (kind === 'constant') {
|
|
137
|
-
return schema?.type === 'boolean' ? { key, value: enabled } : null;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// enum
|
|
141
|
-
const active = enabledValues ?? [];
|
|
142
|
-
const allValues = schema?.enumValues ?? [];
|
|
143
|
-
const schemaDefault = typeof schema?.default === 'string' ? schema.default : undefined;
|
|
144
|
-
if (enabled) {
|
|
145
|
-
const target = schemaDefault !== undefined && active.includes(schemaDefault)
|
|
146
|
-
? schemaDefault
|
|
147
|
-
: active[0];
|
|
148
|
-
return target !== undefined ? { key, value: target } : null;
|
|
149
|
-
}
|
|
150
|
-
const offTarget = schemaDefault !== undefined && !active.includes(schemaDefault)
|
|
151
|
-
? schemaDefault
|
|
152
|
-
: allValues.find((value) => !active.includes(value));
|
|
153
|
-
return offTarget !== undefined ? { key, value: offTarget } : null;
|
|
154
|
-
}
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* fleet-needs-input-push.ts — wires the fleet "needs-input" browser-push fan-out
|
|
3
|
-
* that the SDK's registerGatewayVerbGroups (reached through the terminal-shell's
|
|
4
|
-
* attachWsOnlyGatewayVerbHandlers wrapper) gates on two deps: `runtimeBus` and
|
|
5
|
-
* `sessionPresence` (see push/service.ts's attachFleetNeedsInputSource). When a
|
|
6
|
-
* fleet node blocks on the operator, it pushes an "Input needed" notification
|
|
7
|
-
* carrying the session/node deep link to every registered device — suppressed
|
|
8
|
-
* while an operator surface is already attached to that node's session.
|
|
9
|
-
*
|
|
10
|
-
* TWO seams have to be live for a real push, not just descriptor presence:
|
|
11
|
-
* 1. Fleet lifecycle deltas (FLEET_NODE_BLOCKED_ON_USER etc.) have to reach the
|
|
12
|
-
* runtime bus's 'fleet' domain in the first place. That is
|
|
13
|
-
* attachFleetEmitBridge, diffing the process registry's own coalesced
|
|
14
|
-
* snapshot tick into per-node lifecycle events. The terminal-shell package's
|
|
15
|
-
* createArchivableFleetRegistry only builds the registry — it does not
|
|
16
|
-
* attach the bridge — so the composition root that owns both the registry
|
|
17
|
-
* and the bus (this file) is the one place responsible for it. The SDK's
|
|
18
|
-
* own daemon composition (platform/runtime/services.ts) attaches this same
|
|
19
|
-
* bridge right after building its process registry.
|
|
20
|
-
* 2. registerGatewayVerbGroups has to be handed `runtimeBus` (to subscribe onto
|
|
21
|
-
* that domain) and `sessionPresence` (to suppress a push when an operator is
|
|
22
|
-
* already looking at the session).
|
|
23
|
-
*
|
|
24
|
-
* sessionPresence mirrors the SDK's own daemon composition: a session counts as
|
|
25
|
-
* attended when any participant heartbeated within the freshness window below.
|
|
26
|
-
* Reimplemented here on the broker's public getSession()/SharedSessionRecord
|
|
27
|
-
* shape rather than imported, because the SDK does not expose its own
|
|
28
|
-
* hasFreshSurfaceParticipant helper as public API.
|
|
29
|
-
*/
|
|
30
|
-
import { attachFleetEmitBridge } from '@pellux/goodvibes-sdk/platform/runtime/fleet';
|
|
31
|
-
import type { ArchivableProcessRegistry } from '@pellux/goodvibes-sdk/platform/runtime/fleet';
|
|
32
|
-
import type { SharedSessionBroker } from '@pellux/goodvibes-sdk/platform/control-plane';
|
|
33
|
-
import type { RuntimeEventBus } from '@/runtime/index.ts';
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* How recently a participant must have been seen to count as actively
|
|
37
|
-
* attached. Mirrors the SDK's own (private) SURFACE_ROUTE_FRESHNESS_MS.
|
|
38
|
-
*/
|
|
39
|
-
const SURFACE_ATTACHED_FRESHNESS_MS = 2 * 60 * 1000;
|
|
40
|
-
|
|
41
|
-
export interface FleetNeedsInputPushDeps {
|
|
42
|
-
readonly runtimeBus: RuntimeEventBus;
|
|
43
|
-
readonly sessionPresence: { isAttached(sessionId: string): boolean };
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Attach the fleet emit-bridge (registry snapshot deltas -> runtime bus `fleet`
|
|
48
|
-
* domain) and build the presence lookup, returning both as the deps
|
|
49
|
-
* `attachWsOnlyGatewayVerbHandlers` spreads into its call so the needs-input
|
|
50
|
-
* push source registers for real instead of gracefully degrading to absent.
|
|
51
|
-
*/
|
|
52
|
-
export function wireFleetNeedsInputPush(input: {
|
|
53
|
-
readonly registry: ArchivableProcessRegistry;
|
|
54
|
-
readonly runtimeBus: RuntimeEventBus;
|
|
55
|
-
readonly sessionBroker: Pick<SharedSessionBroker, 'getSession'>;
|
|
56
|
-
}): FleetNeedsInputPushDeps {
|
|
57
|
-
attachFleetEmitBridge({ registry: input.registry, bus: input.runtimeBus });
|
|
58
|
-
const sessionBroker = input.sessionBroker;
|
|
59
|
-
return {
|
|
60
|
-
runtimeBus: input.runtimeBus,
|
|
61
|
-
sessionPresence: {
|
|
62
|
-
isAttached: (sessionId: string) => {
|
|
63
|
-
const session = sessionBroker.getSession(sessionId);
|
|
64
|
-
if (!session) return false;
|
|
65
|
-
const now = Date.now();
|
|
66
|
-
return session.participants.some((participant) => now - participant.lastSeenAt < SURFACE_ATTACHED_FRESHNESS_MS);
|
|
67
|
-
},
|
|
68
|
-
},
|
|
69
|
-
};
|
|
70
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* fleet-services.ts — the shared, archive-aware fleet registry construction,
|
|
3
|
-
* lifted out of services.ts so the composition root stays under the file-size
|
|
4
|
-
* cap. Mirrors the sibling create*Services helpers (durability, code-index,
|
|
5
|
-
* workstream): a single dependency-injected call that services.ts makes once.
|
|
6
|
-
*
|
|
7
|
-
* Also owns the daemon-side observed foreign-agent source: when
|
|
8
|
-
* observeExternalAgents is on (the standalone daemon), externally-launched
|
|
9
|
-
* coding-agent sessions observed read-only on the host fold in as
|
|
10
|
-
* 'observed-external' rows (visibility + steer; never counted against
|
|
11
|
-
* fleet.maxSize, never stopped). The interactive process leaves it off and
|
|
12
|
-
* reads the daemon's snapshot, so it never double-scans. Absence ⇒ a quiet
|
|
13
|
-
* empty set. Mirrors the SDK's own createRuntimeServices wiring.
|
|
14
|
-
*/
|
|
15
|
-
import { computeUsageCostUsd, type ProviderRegistry } from '@pellux/goodvibes-sdk/platform/providers';
|
|
16
|
-
import { type ArchivableProcessRegistry } from '@pellux/goodvibes-sdk/platform/runtime/fleet';
|
|
17
|
-
import { ObservedAgentSource } from '@pellux/goodvibes-sdk/platform/runtime/fleet/observed';
|
|
18
|
-
import { createArchivableFleetRegistry, type ProcessRegistryDeps } from '@pellux/goodvibes-terminal-shell';
|
|
19
|
-
|
|
20
|
-
export interface FleetServicesDeps
|
|
21
|
-
extends Omit<ProcessRegistryDeps, 'observedAgents' | 'priceUsage'> {
|
|
22
|
-
/** Turns on the daemon-side observed foreign-agent source (daemon only). */
|
|
23
|
-
readonly observeExternalAgents?: boolean | undefined;
|
|
24
|
-
/** Backs the honest pricing resolver folded into priceUsage below. */
|
|
25
|
-
readonly providerRegistry: Pick<ProviderRegistry, 'resolveModelPricing'>;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function createFleetServices(
|
|
29
|
-
deps: FleetServicesDeps,
|
|
30
|
-
): { processRegistry: ArchivableProcessRegistry } {
|
|
31
|
-
const { observeExternalAgents, providerRegistry, ...registryDeps } = deps;
|
|
32
|
-
const observedAgents = observeExternalAgents ? new ObservedAgentSource() : undefined;
|
|
33
|
-
const processRegistry = createArchivableFleetRegistry({
|
|
34
|
-
...registryDeps,
|
|
35
|
-
observedAgents, // Daemon-side observed foreign-agent rows (undefined in the interactive process)
|
|
36
|
-
// Honest-unpriced through the ONE pricing resolver (manual -> registration -> provider-served
|
|
37
|
-
// -> catalog -> unknown); unknown/subscription yields null, never $0. Mirrors the SDK composition.
|
|
38
|
-
priceUsage: (model, usage) => (model ? computeUsageCostUsd(providerRegistry.resolveModelPricing(model), usage) : null),
|
|
39
|
-
});
|
|
40
|
-
return { processRegistry };
|
|
41
|
-
}
|