@pellux/goodvibes-tui 1.27.1 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +127 -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 +9 -122
- 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 +21 -33
- 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,192 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* workspace-registration.ts — first-open workspace REGISTRATION half.
|
|
3
|
-
*
|
|
4
|
-
* Companion to the per-workspace trust gate (workspace-trust.ts). Trust is
|
|
5
|
-
* TUI-local (a decision persisted under <cwd>/.goodvibes/tui); REGISTRATION is
|
|
6
|
-
* the platform-wide "which project roots has the operator opted GoodVibes into"
|
|
7
|
-
* registry, owned by the SDK.
|
|
8
|
-
*
|
|
9
|
-
* SINGLE SOURCE OF TRUTH. Resolution and mutation both go through the SDK's
|
|
10
|
-
* shared WorkspaceRegistrationStore — the exact class the daemon composes its
|
|
11
|
-
* `workspaces.*` gateway verbs over (control-plane/routes/register-gateway-verb-
|
|
12
|
-
* groups.ts constructs `new WorkspaceRegistrationStore({ path: resolveUserPath(
|
|
13
|
-
* 'control-plane', 'workspace-registrations.json'), homeDir: dirname(daemonState
|
|
14
|
-
* Dir), daemonStateDir })`). We point an in-process store at that identical path
|
|
15
|
-
* and roots, so whether the daemon's `workspaces.resolve` verb reads it over the
|
|
16
|
-
* wire or we read it in-process, both operate on ONE persisted document. This is
|
|
17
|
-
* not a divergent reimplementation: same SDK store class, same file, same
|
|
18
|
-
* root-guard. In-process (rather than an RPC to a composed daemon) because the
|
|
19
|
-
* first-open prompt renders synchronously at startup and must not depend on a
|
|
20
|
-
* daemon being up — the file is the shared source either way.
|
|
21
|
-
*
|
|
22
|
-
* Coverage semantics are the store's (implemented in the SDK, not here): coverage
|
|
23
|
-
* flows DOWN a registered root's subtree, a linked git worktree inherits its main
|
|
24
|
-
* repo's registration, and a remembered decline is subtree-scoped at the root it
|
|
25
|
-
* was asked. So the register half of the first-open prompt only appears when
|
|
26
|
-
* resolution says UNKNOWN; a covered or declined path never re-asks.
|
|
27
|
-
*/
|
|
28
|
-
import { dirname } from 'node:path';
|
|
29
|
-
import { parse as parsePath } from 'node:path';
|
|
30
|
-
import {
|
|
31
|
-
WorkspaceRegistrationStore,
|
|
32
|
-
WorkspaceRegistrationError,
|
|
33
|
-
normalizeWorkspaceRoot,
|
|
34
|
-
type WorkspaceCoverageStatus,
|
|
35
|
-
type RegisterWorkspaceResult,
|
|
36
|
-
} from '@pellux/goodvibes-sdk/platform/workspace';
|
|
37
|
-
|
|
38
|
-
/** The slice of ShellPathService this manager needs. */
|
|
39
|
-
export interface WorkspaceRegistrationShellPaths {
|
|
40
|
-
readonly workingDirectory: string;
|
|
41
|
-
readonly homeDirectory: string;
|
|
42
|
-
resolveUserPath(...segments: string[]): string;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/** Filename of the shared control-plane registry document (matches the daemon composition). */
|
|
46
|
-
const REGISTRATIONS_FILE = 'workspace-registrations.json';
|
|
47
|
-
|
|
48
|
-
export interface WorkspaceRegistrationEvaluation {
|
|
49
|
-
/** Normalized absolute working directory the prompt would ask about. */
|
|
50
|
-
readonly root: string;
|
|
51
|
-
/** Coverage verdict from the shared store: covered / declined / unknown. */
|
|
52
|
-
readonly status: WorkspaceCoverageStatus;
|
|
53
|
-
/** The nearest registered root covering the path, or null. */
|
|
54
|
-
readonly coveredBy: string | null;
|
|
55
|
-
/** True when coverage was inherited through the git worktree→main-repo link. */
|
|
56
|
-
readonly viaWorktreeLink: boolean;
|
|
57
|
-
/** True when the root is one the store would refuse ($HOME, filesystem root, ~/.goodvibes). */
|
|
58
|
-
readonly broad: boolean;
|
|
59
|
-
/**
|
|
60
|
-
* Whether to OFFER the register half of the first-open prompt: only when the
|
|
61
|
-
* store says UNKNOWN and the root is registrable. Broad roots are never
|
|
62
|
-
* offered — the store refuses them, so we do not prompt for what would be
|
|
63
|
-
* refused. Covered / declined roots never re-ask.
|
|
64
|
-
*/
|
|
65
|
-
readonly offerRegister: boolean;
|
|
66
|
-
/** Human-readable justification from the resolver. */
|
|
67
|
-
readonly reason: string;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export interface WorkspaceRegistrationManagerOptions {
|
|
71
|
-
readonly shellPaths: WorkspaceRegistrationShellPaths;
|
|
72
|
-
/**
|
|
73
|
-
* Inject a pre-built store (e.g. a `:memory:` store for tests). Defaults to an
|
|
74
|
-
* in-process store at the shared control-plane path, configured identically to
|
|
75
|
-
* the daemon's.
|
|
76
|
-
*/
|
|
77
|
-
readonly store?: WorkspaceRegistrationStore;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export class WorkspaceRegistrationManager {
|
|
81
|
-
private readonly store: WorkspaceRegistrationStore;
|
|
82
|
-
private readonly workingDirectory: string;
|
|
83
|
-
private readonly homeDir: string;
|
|
84
|
-
private readonly daemonStateDir: string;
|
|
85
|
-
|
|
86
|
-
constructor(options: WorkspaceRegistrationManagerOptions) {
|
|
87
|
-
const { shellPaths } = options;
|
|
88
|
-
this.workingDirectory = shellPaths.workingDirectory;
|
|
89
|
-
// Mirror the daemon's composition exactly (register-gateway-verb-groups.ts):
|
|
90
|
-
// the daemon state dir IS resolveUserPath() (~/.goodvibes); its parent is the
|
|
91
|
-
// home directory — both refused as broad roots by the same guard.
|
|
92
|
-
this.daemonStateDir = shellPaths.resolveUserPath();
|
|
93
|
-
this.homeDir = dirname(this.daemonStateDir);
|
|
94
|
-
this.store =
|
|
95
|
-
options.store ??
|
|
96
|
-
new WorkspaceRegistrationStore({
|
|
97
|
-
path: shellPaths.resolveUserPath('control-plane', REGISTRATIONS_FILE),
|
|
98
|
-
homeDir: this.homeDir,
|
|
99
|
-
daemonStateDir: this.daemonStateDir,
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* isBroadRoot — mirrors the SDK store's broad-root refusal SET (filesystem
|
|
105
|
-
* root, home directory, daemon state dir) so the register half is never
|
|
106
|
-
* offered for a root the store would refuse. This is a UI-suppression predicate
|
|
107
|
-
* only; the store's `add` remains the enforcement authority (see register()),
|
|
108
|
-
* which is why register() also catches WorkspaceRegistrationError.
|
|
109
|
-
*/
|
|
110
|
-
private isBroadRoot(normalizedRoot: string): boolean {
|
|
111
|
-
if (normalizedRoot === parsePath(normalizedRoot).root) return true;
|
|
112
|
-
if (normalizedRoot === normalizeWorkspaceRoot(this.homeDir)) return true;
|
|
113
|
-
if (normalizedRoot === normalizeWorkspaceRoot(this.daemonStateDir)) return true;
|
|
114
|
-
return false;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Resolve the current working directory against the shared registry and decide
|
|
119
|
-
* whether the register half of the first-open prompt should appear. Read-only —
|
|
120
|
-
* never writes, so it is safe to call from `status`/`doctor`.
|
|
121
|
-
*/
|
|
122
|
-
async evaluate(): Promise<WorkspaceRegistrationEvaluation> {
|
|
123
|
-
const resolution = await this.store.resolve(this.workingDirectory);
|
|
124
|
-
const broad = this.isBroadRoot(resolution.path);
|
|
125
|
-
return {
|
|
126
|
-
root: resolution.path,
|
|
127
|
-
status: resolution.status,
|
|
128
|
-
coveredBy: resolution.coveredBy,
|
|
129
|
-
viaWorktreeLink: resolution.viaWorktreeLink,
|
|
130
|
-
broad,
|
|
131
|
-
offerRegister: resolution.status === 'unknown' && !broad,
|
|
132
|
-
reason: resolution.reason,
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Register the current working directory at its resolved project root. The
|
|
138
|
-
* store refuses broad roots (WorkspaceRegistrationError) — we surface that
|
|
139
|
-
* honestly rather than record a phantom registration.
|
|
140
|
-
*
|
|
141
|
-
* `origin` stamps which surface wrote the record (honest provenance). This
|
|
142
|
-
* NEVER passes `checkpointEligible`: absent means false, so a TUI self-record
|
|
143
|
-
* can never widen the checkpoint-owning consumer's boundary — only that
|
|
144
|
-
* consumer stamps its own roots eligible on boot.
|
|
145
|
-
*/
|
|
146
|
-
async register(label?: string, origin?: string): Promise<
|
|
147
|
-
| { readonly registered: true; readonly result: RegisterWorkspaceResult }
|
|
148
|
-
| { readonly registered: false; readonly refusedReason: string }
|
|
149
|
-
> {
|
|
150
|
-
const opts = label || origin
|
|
151
|
-
? { ...(label ? { label } : {}), ...(origin ? { origin } : {}) }
|
|
152
|
-
: undefined;
|
|
153
|
-
try {
|
|
154
|
-
const result = await this.store.add(this.workingDirectory, opts);
|
|
155
|
-
return { registered: true, result };
|
|
156
|
-
} catch (error) {
|
|
157
|
-
if (error instanceof WorkspaceRegistrationError) {
|
|
158
|
-
return { registered: false, refusedReason: error.message };
|
|
159
|
-
}
|
|
160
|
-
throw error;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/** Remember a subtree-scoped decline at the current working directory. Idempotent. */
|
|
165
|
-
async decline(): Promise<{ readonly root: string; readonly alreadyDeclined: boolean }> {
|
|
166
|
-
return this.store.decline(this.workingDirectory);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/** The normalized working directory this manager asks about. */
|
|
170
|
-
getWorkingRoot(): string {
|
|
171
|
-
return normalizeWorkspaceRoot(this.workingDirectory);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* A one-liner registration posture for `status`/`doctor`. Read-only.
|
|
176
|
-
*/
|
|
177
|
-
async describe(): Promise<string> {
|
|
178
|
-
const evaluation = await this.evaluate();
|
|
179
|
-
switch (evaluation.status) {
|
|
180
|
-
case 'covered':
|
|
181
|
-
return evaluation.viaWorktreeLink
|
|
182
|
-
? `registered (via worktree link to ${evaluation.coveredBy})`
|
|
183
|
-
: `registered (covered by ${evaluation.coveredBy})`;
|
|
184
|
-
case 'declined':
|
|
185
|
-
return 'not registered (registration declined for this directory)';
|
|
186
|
-
case 'unknown':
|
|
187
|
-
return evaluation.broad
|
|
188
|
-
? 'not registered (this root is too broad to register)'
|
|
189
|
-
: 'not registered (never asked)';
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
@@ -1,229 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* workspace-trust.ts — per-workspace trust gate.
|
|
3
|
-
*
|
|
4
|
-
* The first time GoodVibes opens a workspace (a cwd / project root it has no
|
|
5
|
-
* prior decision for), that workspace is "undecided": only read-category
|
|
6
|
-
* tools run; the first write, execute, or delegate tool request raises the
|
|
7
|
-
* trust question AT THAT MOMENT — the exact point it has a real consequence —
|
|
8
|
-
* instead of silently failing or being decided by a side effect of the
|
|
9
|
-
* product's own droppings. This is NOT a parallel permission checker — the
|
|
10
|
-
* decision is consulted by the real permission machinery at its final ask
|
|
11
|
-
* layer (the requestPermission callback wired in bootstrap-core.ts), so it
|
|
12
|
-
* composes with, and cannot drift from, the existing PermissionManager layer
|
|
13
|
-
* chain.
|
|
14
|
-
*
|
|
15
|
-
* Trust is persisted per-workspace in <cwd>/.goodvibes/tui/trust.json. There
|
|
16
|
-
* is no more grandfathering: a workspace that already carries prior GoodVibes
|
|
17
|
-
* runtime state gets no special treatment (that side-effect-based shortcut
|
|
18
|
-
* used to paper over the fact that the first session's write attempts were
|
|
19
|
-
* silently denied without ever recording a real decision — now that the first
|
|
20
|
-
* attempt properly raises the question and persists the answer, the shortcut
|
|
21
|
-
* is unnecessary and would only hide a decision the user should actually see
|
|
22
|
-
* once, even on an upgrade).
|
|
23
|
-
*/
|
|
24
|
-
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
25
|
-
import { join } from 'node:path';
|
|
26
|
-
import { JsonFileStore } from '@pellux/goodvibes-sdk/platform/state';
|
|
27
|
-
import type { PermissionCategory } from '@pellux/goodvibes-sdk/platform/permissions';
|
|
28
|
-
import type { PermissionPromptRequest, PermissionPromptDecision } from '@pellux/goodvibes-sdk/platform/permissions';
|
|
29
|
-
|
|
30
|
-
type AskCallback = (request: PermissionPromptRequest) => Promise<PermissionPromptDecision>;
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Wrap the permission machinery's final ask callback with the workspace trust
|
|
34
|
-
* gate. This runs inside PermissionManager's own layer chain (it IS the ask
|
|
35
|
-
* layer), so it composes with — and cannot drift from — the real machinery.
|
|
36
|
-
*
|
|
37
|
-
* - Trusted workspace: every category passes through to `ask` unchanged.
|
|
38
|
-
* - Restricted workspace with an EXPLICIT prior decision: non-read categories
|
|
39
|
-
* are denied outright, same as before — that IS what "restricted" means
|
|
40
|
-
* once the user has actually chosen it.
|
|
41
|
-
* - Undecided workspace (no decision yet, ever): the first non-read request
|
|
42
|
-
* raises `requestTrustDecision()` — one modal, persisted via `setLevel` —
|
|
43
|
-
* and, if the answer is 'trusted', the ORIGINAL request is forwarded to
|
|
44
|
-
* `ask` so the very thing the user just approved actually happens instead
|
|
45
|
-
* of failing anyway. Concurrent requests that arrive before the first
|
|
46
|
-
* decision resolves await the same in-flight prompt rather than opening a
|
|
47
|
-
* second one.
|
|
48
|
-
*/
|
|
49
|
-
export function trustGatedAsk(
|
|
50
|
-
manager: Pick<WorkspaceTrustManager, 'isCategoryAllowed' | 'isDecided' | 'setLevel'>,
|
|
51
|
-
ask: AskCallback,
|
|
52
|
-
requestTrustDecision: () => Promise<WorkspaceTrustLevel>,
|
|
53
|
-
): AskCallback {
|
|
54
|
-
let pendingDecision: Promise<WorkspaceTrustLevel> | null = null;
|
|
55
|
-
return async (request) => {
|
|
56
|
-
if (manager.isCategoryAllowed(request.category)) return ask(request);
|
|
57
|
-
if (!manager.isDecided()) {
|
|
58
|
-
if (!pendingDecision) {
|
|
59
|
-
pendingDecision = requestTrustDecision().finally(() => {
|
|
60
|
-
pendingDecision = null;
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
const level = await pendingDecision;
|
|
64
|
-
await manager.setLevel(level);
|
|
65
|
-
if (manager.isCategoryAllowed(request.category)) return ask(request);
|
|
66
|
-
}
|
|
67
|
-
return { approved: false };
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export type WorkspaceTrustLevel = 'trusted' | 'restricted';
|
|
72
|
-
|
|
73
|
-
interface PersistedWorkspaceTrust {
|
|
74
|
-
level: WorkspaceTrustLevel;
|
|
75
|
-
decidedAt: string;
|
|
76
|
-
grandfathered?: boolean;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const TRUST_FILE = 'trust.json';
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* detectPriorWorkspaceState — true if <workingDirectory>/.goodvibes already
|
|
83
|
-
* holds GoodVibes RUNTIME state from a prior session. Deliberately keyed on
|
|
84
|
-
* generated state (sessions/checkpoints/state/memory/onboarding marker), not on
|
|
85
|
-
* committed scaffolding like .goodvibes/agents or GOODVIBES.md, so a checked-out
|
|
86
|
-
* template does not read as "previously used". MUST be sampled before any
|
|
87
|
-
* service initializes the workspace this session (bootstrap-core samples it
|
|
88
|
-
* before createRuntimeServices), or this-run scaffolding would look like prior
|
|
89
|
-
* use.
|
|
90
|
-
*/
|
|
91
|
-
export function detectPriorWorkspaceState(workingDirectory: string): boolean {
|
|
92
|
-
const root = join(workingDirectory, '.goodvibes');
|
|
93
|
-
if (!existsSync(root)) return false;
|
|
94
|
-
|
|
95
|
-
const fileSignals = [
|
|
96
|
-
join(root, 'checkpoints', 'index.json'),
|
|
97
|
-
join(root, 'tui', 'onboarding-checked.json'),
|
|
98
|
-
join(root, 'goodvibes.json'),
|
|
99
|
-
];
|
|
100
|
-
for (const file of fileSignals) {
|
|
101
|
-
if (existsSync(file)) return true;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const dirSignals = ['sessions', 'checkpoints', 'state', 'memory'];
|
|
105
|
-
for (const dir of dirSignals) {
|
|
106
|
-
const path = join(root, dir);
|
|
107
|
-
try {
|
|
108
|
-
if (existsSync(path) && statSync(path).isDirectory() && readdirSync(path).length > 0) {
|
|
109
|
-
return true;
|
|
110
|
-
}
|
|
111
|
-
} catch {
|
|
112
|
-
// Unreadable — treat as absent rather than crash the gate.
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
return false;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
export interface WorkspaceTrustPaths {
|
|
119
|
-
readonly projectGoodVibesRoot: string;
|
|
120
|
-
resolveProjectPath(...segments: string[]): string;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/** Read-only view of the persisted trust decision, for status/doctor reporting. */
|
|
124
|
-
export interface PersistedWorkspaceTrustView {
|
|
125
|
-
/** 'trusted' | 'restricted' when a decision exists; 'undecided' when none is persisted yet. */
|
|
126
|
-
readonly level: WorkspaceTrustLevel | 'undecided';
|
|
127
|
-
readonly grandfathered: boolean;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* readPersistedWorkspaceTrust — read <cwd>/.goodvibes/tui/trust.json WITHOUT the
|
|
132
|
-
* side effects of WorkspaceTrustManager.load() (which grandfathers and persists).
|
|
133
|
-
* Reporting surfaces (`status`/`doctor`) must never mutate trust state, so they
|
|
134
|
-
* use this pure reader instead of constructing a manager.
|
|
135
|
-
*/
|
|
136
|
-
export function readPersistedWorkspaceTrust(paths: WorkspaceTrustPaths): PersistedWorkspaceTrustView {
|
|
137
|
-
const file = paths.resolveProjectPath('tui', TRUST_FILE);
|
|
138
|
-
try {
|
|
139
|
-
if (!existsSync(file)) return { level: 'undecided', grandfathered: false };
|
|
140
|
-
const parsed = JSON.parse(readFileSync(file, 'utf-8')) as Partial<PersistedWorkspaceTrust>;
|
|
141
|
-
if (parsed.level === 'trusted' || parsed.level === 'restricted') {
|
|
142
|
-
return { level: parsed.level, grandfathered: parsed.grandfathered ?? false };
|
|
143
|
-
}
|
|
144
|
-
} catch {
|
|
145
|
-
// Unreadable/corrupt — report undecided rather than crash the report.
|
|
146
|
-
}
|
|
147
|
-
return { level: 'undecided', grandfathered: false };
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
export interface WorkspaceTrustManagerOptions {
|
|
151
|
-
readonly shellPaths: WorkspaceTrustPaths;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
export class WorkspaceTrustManager {
|
|
155
|
-
private level: WorkspaceTrustLevel | null = null; // null = undecided (new place)
|
|
156
|
-
private grandfathered = false;
|
|
157
|
-
private loaded = false;
|
|
158
|
-
private readonly store: JsonFileStore<PersistedWorkspaceTrust>;
|
|
159
|
-
|
|
160
|
-
constructor(options: WorkspaceTrustManagerOptions) {
|
|
161
|
-
this.store = new JsonFileStore<PersistedWorkspaceTrust>(
|
|
162
|
-
options.shellPaths.resolveProjectPath('tui', TRUST_FILE),
|
|
163
|
-
);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Load the persisted decision, if one exists. No grandfathering: a
|
|
168
|
-
* workspace with no persisted decision stays undecided (the gate treats it
|
|
169
|
-
* as restricted, and the first non-read tool request raises the trust
|
|
170
|
-
* question via `trustGatedAsk`'s `requestTrustDecision` callback) — even
|
|
171
|
-
* one that already carries prior GoodVibes runtime state. `grandfathered`
|
|
172
|
-
* on an already-persisted decision is read-only history from before this
|
|
173
|
-
* fix (status/doctor still report it honestly); nothing new is ever
|
|
174
|
-
* grandfathered.
|
|
175
|
-
*/
|
|
176
|
-
async load(): Promise<void> {
|
|
177
|
-
if (this.loaded) return;
|
|
178
|
-
const persisted = await this.store.load().catch(() => null);
|
|
179
|
-
if (persisted && (persisted.level === 'trusted' || persisted.level === 'restricted')) {
|
|
180
|
-
this.level = persisted.level;
|
|
181
|
-
this.grandfathered = persisted.grandfathered ?? false;
|
|
182
|
-
}
|
|
183
|
-
this.loaded = true;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
isDecided(): boolean {
|
|
187
|
-
return this.level !== null;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/** Undecided workspaces read as 'restricted' — the safe default before a choice. */
|
|
191
|
-
getLevel(): WorkspaceTrustLevel {
|
|
192
|
-
return this.level ?? 'restricted';
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
isTrusted(): boolean {
|
|
196
|
-
return this.level === 'trusted';
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
wasGrandfathered(): boolean {
|
|
200
|
-
return this.grandfathered;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
async setLevel(level: WorkspaceTrustLevel): Promise<void> {
|
|
204
|
-
this.level = level;
|
|
205
|
-
this.grandfathered = false;
|
|
206
|
-
this.loaded = true;
|
|
207
|
-
await this.persist();
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* The restriction rule the permission machinery consults. Read tools always
|
|
212
|
-
* run (read-only exploration is the whole point of "restricted"); every other
|
|
213
|
-
* category (write / execute / delegate) is denied until the workspace is
|
|
214
|
-
* trusted.
|
|
215
|
-
*/
|
|
216
|
-
isCategoryAllowed(category: PermissionCategory): boolean {
|
|
217
|
-
if (this.isTrusted()) return true;
|
|
218
|
-
return category === 'read';
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
private async persist(): Promise<void> {
|
|
222
|
-
if (this.level === null) return;
|
|
223
|
-
await this.store.save({
|
|
224
|
-
level: this.level,
|
|
225
|
-
decidedAt: new Date().toISOString(),
|
|
226
|
-
grandfathered: this.grandfathered,
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
}
|
package/src/runtime/ui/index.ts
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Runtime UI data surface barrel.
|
|
3
|
-
*
|
|
4
|
-
* Exports enriched data providers and one-shot factory functions for
|
|
5
|
-
* the model picker and provider health surfaces.
|
|
6
|
-
*
|
|
7
|
-
* These modules produce structured data for renderers — no UI rendering
|
|
8
|
-
* logic lives here.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
// ── Model picker ──────────────────────────────────────────────────────────────
|
|
12
|
-
export type {
|
|
13
|
-
CapabilityFlags,
|
|
14
|
-
ProviderLatencyStats,
|
|
15
|
-
ProviderHealthContext,
|
|
16
|
-
ModelPickerEntry,
|
|
17
|
-
ModelPickerGroup,
|
|
18
|
-
ModelPickerData,
|
|
19
|
-
} from '@/runtime/index.ts';
|
|
20
|
-
export type { ProviderStatus as ModelPickerProviderStatus } from '@/runtime/index.ts';
|
|
21
|
-
export { ModelPickerDataProvider, createModelPickerData } from '@/runtime/index.ts';
|
|
22
|
-
export type { ModelPickerDataProviderOptions } from '@/runtime/index.ts';
|
|
23
|
-
|
|
24
|
-
// ── Provider health ───────────────────────────────────────────────────────────
|
|
25
|
-
export type {
|
|
26
|
-
HealthTimelinePoint,
|
|
27
|
-
HealthTimeline,
|
|
28
|
-
ProviderHealthEntry,
|
|
29
|
-
FallbackChainNode,
|
|
30
|
-
FallbackChainData,
|
|
31
|
-
ProviderHealthData,
|
|
32
|
-
CompositeHealthStatus,
|
|
33
|
-
} from '@/runtime/index.ts';
|
|
34
|
-
export type { ProviderStatus } from '@/runtime/index.ts';
|
|
35
|
-
export {
|
|
36
|
-
ProviderHealthDataProvider,
|
|
37
|
-
buildFallbackChainData,
|
|
38
|
-
createProviderHealthData,
|
|
39
|
-
} from '@/runtime/index.ts';
|
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ModelPickerDataProvider — enriched model picker data surface.
|
|
3
|
-
*
|
|
4
|
-
* Combines ModelDefinition records from the provider registry with
|
|
5
|
-
* ProviderHealthDomainState and ModelDomainState to produce a single,
|
|
6
|
-
* sorted, health-enriched ModelPickerData snapshot for UI consumption.
|
|
7
|
-
*
|
|
8
|
-
* This class is a data provider only — it contains no rendering logic.
|
|
9
|
-
* Subscribe to change notifications and call getSnapshot() to render.
|
|
10
|
-
*/
|
|
11
|
-
import type { ModelDefinition } from '@pellux/goodvibes-sdk/platform/providers';
|
|
12
|
-
import type { ProviderRegistry } from '@pellux/goodvibes-sdk/platform/providers';
|
|
13
|
-
import type { BenchmarkStore } from '@pellux/goodvibes-sdk/platform/providers';
|
|
14
|
-
import type { ProviderHealthDomainState } from '@/runtime/index.ts';
|
|
15
|
-
import type { ModelDomainState } from '@/runtime/index.ts';
|
|
16
|
-
import { enrichModelEntries, groupEntriesByProvider } from '@/runtime/index.ts';
|
|
17
|
-
import type { ModelPickerData, ModelPickerEntry } from '@/runtime/index.ts';
|
|
18
|
-
|
|
19
|
-
/** Options for constructing a ModelPickerDataProvider. */
|
|
20
|
-
export interface ModelPickerDataProviderOptions {
|
|
21
|
-
/**
|
|
22
|
-
* Initial set of pinned model IDs.
|
|
23
|
-
* Call updatePinnedIds() to update at runtime.
|
|
24
|
-
*/
|
|
25
|
-
readonly pinnedIds?: ReadonlySet<string>;
|
|
26
|
-
readonly benchmarkStore: Pick<BenchmarkStore, 'getBenchmarks'>;
|
|
27
|
-
readonly providerRegistry: Pick<ProviderRegistry, 'getSyntheticModelInfoFromCatalog' | 'getContextWindowForModel'>;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* ModelPickerDataProvider produces enriched model picker data snapshots.
|
|
32
|
-
*
|
|
33
|
-
* Usage:
|
|
34
|
-
* ```ts
|
|
35
|
-
* const provider = new ModelPickerDataProvider(models, healthState, modelState);
|
|
36
|
-
* const unsub = provider.subscribe(() => {
|
|
37
|
-
* const data = provider.getSnapshot();
|
|
38
|
-
* // render data.entries or data.groups
|
|
39
|
-
* });
|
|
40
|
-
* // When health or model state changes:
|
|
41
|
-
* provider.updateHealthState(newHealthState);
|
|
42
|
-
* provider.updateModelState(newModelState);
|
|
43
|
-
* // Cleanup:
|
|
44
|
-
* unsub();
|
|
45
|
-
* provider.dispose();
|
|
46
|
-
* ```
|
|
47
|
-
*/
|
|
48
|
-
export class ModelPickerDataProvider {
|
|
49
|
-
private _models: readonly ModelDefinition[];
|
|
50
|
-
private _healthState: ProviderHealthDomainState;
|
|
51
|
-
private _modelState: ModelDomainState;
|
|
52
|
-
private _pinnedIds: ReadonlySet<string>;
|
|
53
|
-
private _snapshot: ModelPickerData;
|
|
54
|
-
private readonly benchmarkStore: Pick<BenchmarkStore, 'getBenchmarks'>;
|
|
55
|
-
private readonly providerRegistry: Pick<ProviderRegistry, 'getSyntheticModelInfoFromCatalog' | 'getContextWindowForModel'>;
|
|
56
|
-
private readonly _subscribers = new Set<() => void>();
|
|
57
|
-
|
|
58
|
-
constructor(
|
|
59
|
-
models: readonly ModelDefinition[],
|
|
60
|
-
healthState: ProviderHealthDomainState,
|
|
61
|
-
modelState: ModelDomainState,
|
|
62
|
-
options: ModelPickerDataProviderOptions,
|
|
63
|
-
) {
|
|
64
|
-
this._models = models;
|
|
65
|
-
this._healthState = healthState;
|
|
66
|
-
this._modelState = modelState;
|
|
67
|
-
this._pinnedIds = options.pinnedIds ?? new Set();
|
|
68
|
-
this.benchmarkStore = options.benchmarkStore;
|
|
69
|
-
this.providerRegistry = options.providerRegistry;
|
|
70
|
-
this._snapshot = this._buildSnapshot();
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Return the current enriched model picker data snapshot.
|
|
75
|
-
* This is updated synchronously when state changes via update methods.
|
|
76
|
-
*/
|
|
77
|
-
public getSnapshot(): ModelPickerData {
|
|
78
|
-
return this._snapshot;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Register a callback invoked whenever the snapshot changes.
|
|
83
|
-
* @returns An unsubscribe function.
|
|
84
|
-
*/
|
|
85
|
-
public subscribe(callback: () => void): () => void {
|
|
86
|
-
this._subscribers.add(callback);
|
|
87
|
-
return () => this._subscribers.delete(callback);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Update the model list (e.g. after registry reload).
|
|
92
|
-
* Triggers a snapshot rebuild and notifies subscribers.
|
|
93
|
-
*/
|
|
94
|
-
public updateModels(models: readonly ModelDefinition[]): void {
|
|
95
|
-
this._models = models;
|
|
96
|
-
this._rebuild();
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Update health state (e.g. on ProviderHealthDomainState change).
|
|
101
|
-
* Triggers a snapshot rebuild and notifies subscribers.
|
|
102
|
-
*/
|
|
103
|
-
public updateHealthState(healthState: ProviderHealthDomainState): void {
|
|
104
|
-
this._healthState = healthState;
|
|
105
|
-
this._rebuild();
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Update model domain state (e.g. on active model or fallback change).
|
|
110
|
-
* Triggers a snapshot rebuild and notifies subscribers.
|
|
111
|
-
*/
|
|
112
|
-
public updateModelState(modelState: ModelDomainState): void {
|
|
113
|
-
this._modelState = modelState;
|
|
114
|
-
this._rebuild();
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Update the pinned/favorites model ID set.
|
|
119
|
-
* Triggers a snapshot rebuild and notifies subscribers.
|
|
120
|
-
*/
|
|
121
|
-
public updatePinnedIds(pinnedIds: ReadonlySet<string>): void {
|
|
122
|
-
this._pinnedIds = pinnedIds;
|
|
123
|
-
this._rebuild();
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Release all subscriber references.
|
|
128
|
-
* Does not clear internal state — getSnapshot() remains usable after disposal.
|
|
129
|
-
*/
|
|
130
|
-
public dispose(): void {
|
|
131
|
-
this._subscribers.clear();
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// ── Private ────────────────────────────────────────────────────────────────
|
|
135
|
-
|
|
136
|
-
private _rebuild(): void {
|
|
137
|
-
this._snapshot = this._buildSnapshot();
|
|
138
|
-
this._notify();
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
private _buildSnapshot(): ModelPickerData {
|
|
142
|
-
const entries: readonly ModelPickerEntry[] = enrichModelEntries(
|
|
143
|
-
this._models,
|
|
144
|
-
this._healthState,
|
|
145
|
-
this._modelState,
|
|
146
|
-
this._pinnedIds,
|
|
147
|
-
this.benchmarkStore,
|
|
148
|
-
this.providerRegistry,
|
|
149
|
-
);
|
|
150
|
-
|
|
151
|
-
const groups = groupEntriesByProvider(entries);
|
|
152
|
-
|
|
153
|
-
const degradedProviderIds: string[] = [];
|
|
154
|
-
const unavailableProviderIds: string[] = [];
|
|
155
|
-
for (const [id, record] of this._healthState.providers) {
|
|
156
|
-
if (record.status === 'degraded' || record.status === 'rate_limited') {
|
|
157
|
-
degradedProviderIds.push(id);
|
|
158
|
-
} else if (record.status === 'unavailable' || record.status === 'auth_error') {
|
|
159
|
-
unavailableProviderIds.push(id);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
return {
|
|
164
|
-
entries,
|
|
165
|
-
groups,
|
|
166
|
-
degradedProviderIds,
|
|
167
|
-
unavailableProviderIds,
|
|
168
|
-
activeModelId: this._modelState.activeModelId,
|
|
169
|
-
snapshotAt: Date.now(),
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
private _notify(): void {
|
|
174
|
-
for (const cb of this._subscribers) {
|
|
175
|
-
try {
|
|
176
|
-
cb();
|
|
177
|
-
} catch {
|
|
178
|
-
// Non-fatal: subscriber errors must not crash the provider
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|