@pellux/goodvibes-tui 1.27.1 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +116 -2
- package/README.md +7 -9
- package/bin/goodvibes +2 -2
- package/bin/launcher-support.js +8 -4
- package/docs/README.md +11 -2
- package/docs/foundation-artifacts/operator-contract.json +6403 -2347
- package/package.json +14 -25
- package/scripts/postinstall.js +35 -35
- package/src/audio/spoken-turn-model-routing.ts +1 -1
- package/src/cli/bundle-command.ts +5 -5
- package/src/cli/completions/generate.ts +1 -1
- package/src/cli/doctor.ts +3 -3
- package/src/cli/entrypoint.ts +12 -7
- package/src/cli/help.ts +4 -5
- package/src/cli/hooks-command.ts +1 -1
- package/src/cli/index.ts +1 -4
- package/src/cli/launch-auto-update.ts +59 -7
- package/src/cli/management-commands.ts +6 -6
- package/src/cli/management-utils.ts +5 -5
- package/src/cli/management.ts +7 -7
- package/src/cli/network-posture.ts +1 -1
- package/src/cli/package-verification.ts +7 -6
- package/src/cli/plugin-bundles-command.ts +1 -1
- package/src/cli/plugin-command.ts +2 -2
- package/src/cli/service-command.ts +2 -2
- package/src/cli/service-posture.ts +4 -4
- package/src/cli/status.ts +9 -8
- package/src/cli/surface-command.ts +4 -4
- package/src/cli/tui-startup.ts +3 -3
- package/src/cli-flags.ts +3 -3
- package/src/config/goodvibes-home-audit.ts +0 -2
- package/src/config/secret-config.ts +37 -5
- package/src/config/tui-extension-settings.ts +1 -1
- package/src/core/approval-alert.ts +2 -2
- package/src/core/budget-breach-notifier.ts +3 -3
- package/src/core/conversation-compaction-render.ts +1 -1
- package/src/core/conversation-line-cache.ts +1 -1
- package/src/core/conversation-render-context.ts +1 -1
- package/src/core/conversation-rendering.ts +2 -2
- package/src/core/conversation.ts +10 -10
- package/src/core/long-task-notifier.ts +2 -2
- package/src/core/memory-diagnostics-gateway.ts +1 -1
- package/src/core/scriptable-statusline.ts +1 -1
- package/src/core/session-auto-titler.ts +1 -1
- package/src/core/session-resume-core.ts +2 -2
- package/src/core/stream-event-wiring.ts +24 -44
- package/src/core/terminal-notifier.ts +3 -3
- package/src/core/turn-cancellation.ts +1 -1
- package/src/core/turn-event-wiring.ts +5 -5
- package/src/input/command-registry.ts +6 -6
- package/src/input/commands/channel-pairing.ts +1 -1
- package/src/input/commands/cluster-runtime.ts +1 -1
- package/src/input/commands/codebase-runtime.ts +1 -1
- package/src/input/commands/devices-runtime.ts +1 -1
- package/src/input/commands/hosted-runtime.ts +330 -0
- package/src/input/commands/local-setup-review.ts +1 -1
- package/src/input/commands/local-setup-transfer.ts +2 -2
- package/src/input/commands/local-setup.ts +3 -3
- package/src/input/commands/managed-runtime.ts +2 -2
- package/src/input/commands/operator-rpc.ts +24 -110
- package/src/input/commands/payment-card-intake.ts +1 -1
- package/src/input/commands/permissions-provenance.ts +2 -2
- package/src/input/commands/plugin-runtime.ts +1 -1
- package/src/input/commands/provider.ts +1 -1
- package/src/input/commands/relay-runtime.ts +18 -9
- package/src/input/commands/rewind-runtime.ts +1 -1
- package/src/input/commands/runtime-services.ts +1 -1
- package/src/input/commands/services-runtime.ts +1 -1
- package/src/input/commands/session-workflow.ts +1 -1
- package/src/input/commands/settings-sync-runtime.ts +2 -2
- package/src/input/commands/share-runtime.ts +1 -1
- package/src/input/commands/tasks-runtime.ts +145 -55
- package/src/input/commands/update-runtime.ts +31 -28
- package/src/input/commands/work-plan-runtime.ts +2 -2
- package/src/input/commands/workstream-attempts.ts +1 -1
- package/src/input/commands/workstream-runtime.ts +2 -2
- package/src/input/commands.ts +2 -0
- package/src/input/feature-unit-layout.ts +1 -1
- package/src/input/feed-context-factory.ts +3 -3
- package/src/input/handler-content-actions.ts +2 -2
- package/src/input/handler-feed-routes.ts +1 -1
- package/src/input/handler-feed.ts +3 -3
- package/src/input/handler-modal-token-routes.ts +1 -1
- package/src/input/handler-onboarding-daemon-adopt.ts +3 -2
- package/src/input/handler-onboarding.ts +1 -1
- package/src/input/handler-types.ts +2 -2
- package/src/input/handler-ui-state.ts +1 -1
- package/src/input/handler.ts +4 -4
- package/src/input/input-history.ts +1 -1
- package/src/input/onboarding/onboarding-feature-units.ts +2 -2
- package/src/input/onboarding/onboarding-runtime-status.ts +15 -1
- package/src/input/onboarding/onboarding-wizard-apply.ts +17 -8
- package/src/input/onboarding/onboarding-wizard-cloudflare-step.ts +5 -3
- package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +1 -1
- package/src/input/onboarding/onboarding-wizard-types.ts +1 -1
- package/src/input/payments-config.ts +6 -6
- package/src/input/search.ts +1 -1
- package/src/input/session-picker-modal.ts +46 -0
- package/src/input/settings-modal-activation.ts +1 -1
- package/src/input/settings-modal-behavior.ts +4 -4
- package/src/input/settings-modal-commit.ts +11 -0
- package/src/input/settings-modal-data.ts +13 -80
- package/src/input/settings-modal-mutations.ts +33 -0
- package/src/input/settings-modal-secrets.ts +34 -0
- package/src/input/settings-modal-subscriptions.ts +1 -1
- package/src/input/settings-modal-types.ts +15 -1
- package/src/input/settings-modal.ts +28 -3
- package/src/input/worktree-setup-config.ts +1 -1
- package/src/main.ts +11 -9
- package/src/panels/agent-inspector-shared.ts +2 -2
- package/src/panels/base-panel.ts +2 -2
- package/src/panels/builtin/operations.ts +6 -8
- package/src/panels/builtin/session.ts +17 -0
- package/src/panels/builtin/shared.ts +5 -5
- package/src/panels/builtin-modals.ts +3 -3
- package/src/panels/confirm-state.ts +1 -1
- package/src/panels/cost-tracker-panel.ts +4 -4
- package/src/panels/diff-panel.ts +2 -2
- package/src/panels/diff-review-panel.ts +2 -2
- package/src/panels/expandable-list-panel.ts +2 -2
- package/src/panels/fleet-acts.ts +1 -1
- package/src/panels/fleet-observed-render.ts +1 -1
- package/src/panels/fleet-panel-format.ts +1 -1
- package/src/panels/fleet-panel.ts +1 -1
- package/src/panels/fleet-spawn.ts +1 -1
- package/src/panels/fleet-steer.ts +1 -1
- package/src/panels/fleet-transcript.ts +1 -1
- package/src/panels/git-panel.ts +1 -1
- package/src/panels/hosted-session-feed.ts +352 -0
- package/src/panels/hosted-session-panel.ts +221 -0
- package/src/panels/local-auth-panel.ts +3 -3
- package/src/panels/modals/devices-modal.ts +1 -1
- package/src/panels/modals/local-auth-modal.ts +1 -1
- package/src/panels/modals/pairing-modal.ts +1 -1
- package/src/panels/modals/services-modal.ts +1 -1
- package/src/panels/modals/settings-sync-modal.ts +1 -1
- package/src/panels/modals/subscription-modal.ts +1 -1
- package/src/panels/modals/work-plan-modal.ts +1 -1
- package/src/panels/notifications-panel.ts +1 -1
- package/src/panels/panel-confirm-overlay.ts +1 -1
- package/src/panels/plugins-panel.ts +2 -2
- package/src/panels/polish-core.ts +2 -2
- package/src/panels/polish-tables.ts +1 -1
- package/src/panels/polish.ts +2 -2
- package/src/panels/scrollable-list-panel.ts +3 -3
- package/src/panels/skills-panel.ts +3 -3
- package/src/panels/token-budget-panel.ts +5 -5
- package/src/panels/types.ts +1 -2
- package/src/permissions/prompt.ts +1 -1
- package/src/renderer/autocomplete-overlay.ts +1 -1
- package/src/renderer/block-actions-overlay.ts +1 -1
- package/src/renderer/bookmark-modal.ts +1 -1
- package/src/renderer/buffer.ts +1 -1
- package/src/renderer/code-block.ts +1 -1
- package/src/renderer/composer-fragments.ts +1 -1
- package/src/renderer/compositor.ts +2 -2
- package/src/renderer/config-modal.ts +1 -1
- package/src/renderer/context-inspector.ts +1 -1
- package/src/renderer/conversation-layout.ts +2 -2
- package/src/renderer/conversation-overlays.ts +1 -1
- package/src/renderer/conversation-surface.ts +2 -2
- package/src/renderer/diff-view.ts +1 -1
- package/src/renderer/diff.ts +1 -1
- package/src/renderer/file-picker-overlay.ts +1 -1
- package/src/renderer/fleet-tab-strip.ts +1 -1
- package/src/renderer/fullscreen-primitives.ts +2 -2
- package/src/renderer/fullscreen-workspace.ts +1 -1
- package/src/renderer/header-line.ts +1 -1
- package/src/renderer/help-overlay.ts +1 -1
- package/src/renderer/history-search-overlay.ts +2 -2
- package/src/renderer/layout.ts +5 -8
- package/src/renderer/markdown-inline.ts +1 -1
- package/src/renderer/markdown-table.ts +1 -1
- package/src/renderer/markdown.ts +1 -1
- package/src/renderer/mcp-workspace.ts +1 -1
- package/src/renderer/modal-factory.ts +2 -2
- package/src/renderer/model-workspace.ts +2 -2
- package/src/renderer/onboarding/onboarding-wizard.ts +1 -1
- package/src/renderer/overlay-box.ts +1 -1
- package/src/renderer/panel-composite.ts +1 -1
- package/src/renderer/panel-workspace-bar.ts +1 -1
- package/src/renderer/process-indicator.ts +1 -1
- package/src/renderer/profile-picker-modal.ts +1 -1
- package/src/renderer/search-overlay.ts +2 -2
- package/src/renderer/selection-modal-overlay.ts +1 -1
- package/src/renderer/session-picker-modal.ts +124 -3
- package/src/renderer/settings-modal-helpers.ts +2 -0
- package/src/renderer/settings-modal.ts +2 -2
- package/src/renderer/shell-surface.ts +2 -2
- package/src/renderer/system-message.ts +1 -1
- package/src/renderer/tab-strip.ts +1 -1
- package/src/renderer/theme-mode-config.ts +1 -1
- package/src/renderer/theme.ts +11 -12
- package/src/renderer/thinking.ts +1 -1
- package/src/renderer/tool-call.ts +2 -2
- package/src/renderer/tool-result-expanded-lines.ts +1 -1
- package/src/renderer/turn-injection.ts +7 -8
- package/src/renderer/ui-factory.ts +3 -3
- package/src/runtime/bootstrap-command-context.ts +4 -4
- package/src/runtime/bootstrap-command-parts.ts +5 -5
- package/src/runtime/bootstrap-core.ts +30 -30
- package/src/runtime/bootstrap-shell.ts +4 -4
- package/src/runtime/bootstrap.ts +128 -157
- package/src/runtime/client/approval-updates.ts +82 -0
- package/src/runtime/client/build-floors.ts +167 -0
- package/src/runtime/client/daemon-attach-handshake.ts +115 -0
- package/src/runtime/client/fleet-union.ts +147 -0
- package/src/runtime/client/host-service-status.ts +71 -0
- package/src/runtime/client/hosted-exit.ts +86 -0
- package/src/runtime/client/hosted-roster.ts +110 -0
- package/src/runtime/client/hosted-session-stream.ts +178 -0
- package/src/runtime/client/hosted-sessions.ts +187 -0
- package/src/runtime/client/operator-endpoint.ts +213 -0
- package/src/runtime/client/spine-adoption.ts +99 -0
- package/src/runtime/cloudflare-control-plane.ts +1 -1
- package/src/runtime/context-accounting-source.ts +1 -1
- package/src/runtime/context.ts +2 -2
- package/src/runtime/conversation-rewind-port.ts +42 -14
- package/src/runtime/daemon-attach-notices.ts +55 -19
- package/src/runtime/diagnostics/panels/ops.ts +1 -1
- package/src/runtime/disposal-wiring.ts +4 -24
- package/src/runtime/index.ts +119 -105
- package/src/runtime/install-self-check-startup.ts +2 -2
- package/src/runtime/interaction-seams.ts +1 -1
- package/src/runtime/knowledge-services.ts +8 -2
- package/src/runtime/legacy-daemon-migration.ts +4 -3
- package/src/runtime/legacy-daemon-reconcile.ts +3 -2
- package/src/runtime/memory-spine-transport.ts +37 -267
- package/src/runtime/notification-dispatch.ts +2 -2
- package/src/runtime/onboarding/apply.ts +2 -2
- package/src/runtime/onboarding/derivation.ts +2 -2
- package/src/runtime/onboarding/markers.ts +2 -2
- package/src/runtime/onboarding/progress.ts +2 -2
- package/src/runtime/onboarding/snapshot.ts +1 -1
- package/src/runtime/onboarding/state.ts +2 -2
- package/src/runtime/onboarding/types.ts +2 -2
- package/src/runtime/orchestrator-core-services.ts +1 -1
- package/src/runtime/path-shadow-startup.ts +28 -221
- package/src/runtime/power-keepawake-remote.ts +3 -2
- package/src/runtime/process-lifecycle.ts +2 -2
- package/src/runtime/provider-fallback.ts +10 -66
- package/src/runtime/recovery-autosave.ts +1 -1
- package/src/runtime/recovery-offer-wiring.ts +39 -23
- package/src/runtime/recovery-prompt.ts +31 -17
- package/src/runtime/relay-reachability-bridge.ts +53 -42
- package/src/runtime/release-artifacts.ts +2 -1
- package/src/runtime/runtime-services-types.ts +82 -35
- package/src/runtime/services.ts +305 -300
- package/src/runtime/session-resume-liveness-confirm.ts +1 -1
- package/src/runtime/ui-services.ts +7 -6
- package/src/runtime/update-check.ts +23 -43
- package/src/runtime/workspace-checkpointing.ts +1 -1
- package/src/runtime/wrfc-persistence.ts +2 -2
- package/src/shell/ui-openers.ts +30 -7
- package/src/utils/clipboard.ts +1 -1
- package/src/verification/verification-ledger.ts +10 -8
- package/src/version.ts +1 -1
- package/bin/goodvibes-daemon +0 -90
- package/src/cli/config-overrides.ts +0 -267
- package/src/cli/endpoints.ts +0 -109
- package/src/cli/parser.ts +0 -395
- package/src/cli/redaction.ts +0 -170
- package/src/cli/types.ts +0 -84
- package/src/cluster/commands.ts +0 -382
- package/src/cluster/remote-daemon-target.ts +0 -225
- package/src/cluster/render.ts +0 -268
- package/src/config/atomic-write.ts +0 -70
- package/src/config/credential-availability.ts +0 -122
- package/src/config/goodvibes-home.ts +0 -149
- package/src/config/index.ts +0 -88
- package/src/config/provider-model.ts +0 -23
- package/src/config/read-versioned.ts +0 -228
- package/src/core/alert-gating.ts +0 -67
- package/src/core/consolidation-receipt.ts +0 -41
- package/src/core/focus-tracker.ts +0 -41
- package/src/core/history.ts +0 -45
- package/src/core/memory-status.ts +0 -118
- package/src/core/orchestrator.ts +0 -7
- package/src/core/pairing-devices.ts +0 -49
- package/src/core/pairing-handoff.ts +0 -101
- package/src/core/pairing-offers.ts +0 -83
- package/src/core/pairing-origin.ts +0 -78
- package/src/core/rewind-turn-anchors.ts +0 -311
- package/src/core/session-recovery.ts +0 -210
- package/src/core/stable-host.ts +0 -171
- package/src/core/transcript-journal.ts +0 -503
- package/src/daemon/cli.ts +0 -693
- package/src/daemon/fatal-boot-report.ts +0 -111
- package/src/daemon/handlers/context.ts +0 -29
- package/src/daemon/handlers/contracts.ts +0 -77
- package/src/daemon/handlers/credentials.ts +0 -139
- package/src/daemon/handlers/drafts/draft-store.ts +0 -427
- package/src/daemon/handlers/drafts/index.ts +0 -17
- package/src/daemon/handlers/drafts/register.ts +0 -331
- package/src/daemon/handlers/errors.ts +0 -18
- package/src/daemon/handlers/inbox/cursor-store.ts +0 -457
- package/src/daemon/handlers/inbox/index.ts +0 -284
- package/src/daemon/handlers/inbox/mapping.ts +0 -192
- package/src/daemon/handlers/inbox/poller.ts +0 -212
- package/src/daemon/handlers/inbox/provider-adapter.ts +0 -156
- package/src/daemon/handlers/inbox/providers/discord.ts +0 -252
- package/src/daemon/handlers/inbox/providers/email.ts +0 -152
- package/src/daemon/handlers/inbox/providers/imap-client.ts +0 -300
- package/src/daemon/handlers/inbox/providers/route-util.ts +0 -24
- package/src/daemon/handlers/inbox/providers/slack.ts +0 -263
- package/src/daemon/handlers/index.ts +0 -117
- package/src/daemon/handlers/register.ts +0 -168
- package/src/daemon/handlers/remote/backends/cloud-terminal.ts +0 -142
- package/src/daemon/handlers/remote/backends/docker.ts +0 -79
- package/src/daemon/handlers/remote/backends/index.ts +0 -40
- package/src/daemon/handlers/remote/backends/local-process.ts +0 -113
- package/src/daemon/handlers/remote/backends/process-runner.ts +0 -127
- package/src/daemon/handlers/remote/backends/ssh.ts +0 -125
- package/src/daemon/handlers/remote/backends/types.ts +0 -97
- package/src/daemon/handlers/remote/dispatcher.ts +0 -181
- package/src/daemon/handlers/remote/index.ts +0 -119
- package/src/daemon/handlers/remote/peer-registry.ts +0 -357
- package/src/daemon/handlers/remote/service.ts +0 -191
- package/src/daemon/handlers/routing/inbox-bridge.ts +0 -71
- package/src/daemon/handlers/routing/index.ts +0 -261
- package/src/daemon/handlers/routing/route-store.ts +0 -319
- package/src/daemon/handlers/routing/routing-resolver.ts +0 -75
- package/src/daemon/handlers/sqlite-store.ts +0 -278
- package/src/daemon/handlers/triage/index.ts +0 -57
- package/src/daemon/handlers/triage/integration.ts +0 -213
- package/src/daemon/handlers/triage/pipeline.ts +0 -274
- package/src/daemon/handlers/triage/scorer.ts +0 -287
- package/src/daemon/handlers/triage/tagger/discord.ts +0 -187
- package/src/daemon/handlers/triage/tagger/imap.ts +0 -384
- package/src/daemon/handlers/triage/tagger/index.ts +0 -184
- package/src/daemon/handlers/triage/tagger/shared.ts +0 -70
- package/src/daemon/handlers/triage/tagger/slack.ts +0 -69
- package/src/daemon/handlers/triage/types.ts +0 -50
- package/src/daemon/lifecycle.ts +0 -41
- package/src/daemon/provision-wake-model.ts +0 -81
- package/src/daemon/safe-serve.ts +0 -61
- package/src/daemon/send/channels.ts +0 -200
- package/src/daemon/send/command.ts +0 -332
- package/src/daemon/send/composition.ts +0 -99
- package/src/daemon/send/failure-text.ts +0 -93
- package/src/daemon/send/inert-text.ts +0 -225
- package/src/daemon/send/stdin.ts +0 -24
- package/src/daemon/service-commands.ts +0 -329
- package/src/export/cost-utils.ts +0 -319
- package/src/input/selection.ts +0 -135
- package/src/plugins/loader.ts +0 -15
- package/src/renderer/bottom-bar.ts +0 -58
- package/src/renderer/conversation-tree.ts +0 -228
- package/src/runtime/agent-graph-composition.ts +0 -78
- package/src/runtime/channel-composition.ts +0 -46
- package/src/runtime/cluster-composition.ts +0 -124
- package/src/runtime/cluster-group-composition.ts +0 -284
- package/src/runtime/code-index-services.ts +0 -167
- package/src/runtime/daemon-handler-composition.ts +0 -76
- package/src/runtime/device-posture-composition.ts +0 -110
- package/src/runtime/durability-housekeeping.ts +0 -183
- package/src/runtime/durability-services.ts +0 -155
- package/src/runtime/embedded-service-factories.ts +0 -74
- package/src/runtime/feature-settings.ts +0 -154
- package/src/runtime/fleet-needs-input-push.ts +0 -70
- package/src/runtime/fleet-services.ts +0 -41
- package/src/runtime/idle-power-services.ts +0 -85
- package/src/runtime/mail-composition.ts +0 -65
- package/src/runtime/memory-fold.ts +0 -55
- package/src/runtime/operator-token-cleanup.ts +0 -109
- package/src/runtime/path-shadow.ts +0 -418
- package/src/runtime/perf/panel-health-monitor.ts +0 -18
- package/src/runtime/provider-optimizer-wiring.ts +0 -55
- package/src/runtime/reachability-notice.ts +0 -100
- package/src/runtime/remote-execution-composition.ts +0 -62
- package/src/runtime/session-liveness-marker.ts +0 -257
- package/src/runtime/session-pointer-surface.ts +0 -22
- package/src/runtime/session-storage-services.ts +0 -41
- package/src/runtime/surface-feature-flags.ts +0 -96
- package/src/runtime/terminal-output-guard.ts +0 -235
- package/src/runtime/trust/workspace-registration.ts +0 -192
- package/src/runtime/trust/workspace-trust.ts +0 -229
- package/src/runtime/ui/index.ts +0 -39
- package/src/runtime/ui/model-picker/data-provider.ts +0 -182
- package/src/runtime/ui/model-picker/health-enrichment.ts +0 -231
- package/src/runtime/ui/model-picker/index.ts +0 -59
- package/src/runtime/ui/model-picker/types.ts +0 -153
- package/src/runtime/ui/provider-health/data-provider.ts +0 -244
- package/src/runtime/ui/provider-health/fallback-visualizer.ts +0 -71
- package/src/runtime/ui/provider-health/index.ts +0 -46
- package/src/runtime/ui/provider-health/types.ts +0 -146
- package/src/runtime/ui-events.ts +0 -1
- package/src/runtime/ui-read-model-helpers.ts +0 -1
- package/src/runtime/ui-read-models-observability-maintenance.ts +0 -1
- package/src/runtime/ui-read-models-observability-options.ts +0 -1
- package/src/runtime/ui-read-models-observability-remote.ts +0 -1
- package/src/runtime/ui-read-models-observability-security.ts +0 -1
- package/src/runtime/ui-read-models-observability-system.ts +0 -1
- package/src/runtime/ui-read-models-observability.ts +0 -1
- package/src/runtime/ui-service-queries.ts +0 -1
- package/src/runtime/voice-setup-services.ts +0 -98
- package/src/runtime/workstream-attempts-validation.ts +0 -77
- package/src/runtime/workstream-draft-edits.ts +0 -95
- package/src/runtime/workstream-draft-store.ts +0 -274
- package/src/runtime/workstream-draft-types.ts +0 -42
- package/src/runtime/workstream-services.ts +0 -373
- package/src/tools/index.ts +0 -1
- package/src/types/grid.ts +0 -48
- package/src/types/sql-js.d.ts +0 -15
- package/src/work-plans/work-plan-store.ts +0 -702
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* operator-endpoint.ts — the one place this product resolves "the daemon I talk
|
|
3
|
+
* to", for every seam that talks to it.
|
|
4
|
+
*
|
|
5
|
+
* The terminal app is a pure client: it runs its own conversation loop and asks
|
|
6
|
+
* a separately-running daemon for everything else. Every one of those asks is
|
|
7
|
+
* an operator verb over HTTP, and every one of them needs the same three
|
|
8
|
+
* things — is the daemon enabled, what base URL does it answer on, and what
|
|
9
|
+
* bearer token proves this surface may call it. Resolving that in each seam is
|
|
10
|
+
* how the old composition ended up with a DirectTransport in one place and a
|
|
11
|
+
* real fetch in another.
|
|
12
|
+
*
|
|
13
|
+
* `resolveOperatorRpc` was previously the command layer's private helper
|
|
14
|
+
* (input/commands/operator-rpc.ts). It moved here unchanged in behaviour so the
|
|
15
|
+
* runtime seams — approvals, config, credentials, sessions, fleet, tasks,
|
|
16
|
+
* devices, checkpoints — reach the daemon through the SAME resolution the
|
|
17
|
+
* commands already used, and the command module now re-exports it.
|
|
18
|
+
*
|
|
19
|
+
* Refusals are values, never throws: a disabled daemon or an underivable base
|
|
20
|
+
* URL comes back as `{ available: false, reason }` so a caller prints an honest
|
|
21
|
+
* line instead of guessing. Once a call is made, a non-2xx (including the 404 a
|
|
22
|
+
* daemon that has not wired a verb returns) throws `GoodVibesSdkError` and
|
|
23
|
+
* `describeOperatorRpcError` renders it.
|
|
24
|
+
*
|
|
25
|
+
* Auth is the loopback file-token bootstrap: the token
|
|
26
|
+
* is read (or minted) from the daemon's own state directory. A daemon on
|
|
27
|
+
* another machine is reached by the network-adopt path, which writes that
|
|
28
|
+
* daemon's bearer into the same file — so this resolution covers both.
|
|
29
|
+
*/
|
|
30
|
+
import { join } from 'node:path';
|
|
31
|
+
import { createGoodVibesSdk } from '@pellux/goodvibes-sdk';
|
|
32
|
+
import type { GoodVibesSdk } from '@pellux/goodvibes-sdk';
|
|
33
|
+
import { GoodVibesSdkError } from '@pellux/goodvibes-sdk';
|
|
34
|
+
import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
35
|
+
import { resolveDaemonEnabled } from '@pellux/goodvibes-sdk/platform/config';
|
|
36
|
+
import { getOrCreateCompanionToken } from '@pellux/goodvibes-sdk/platform/pairing';
|
|
37
|
+
import type { DaemonVerbCaller } from '@pellux/goodvibes-sdk/platform/runtime/client';
|
|
38
|
+
|
|
39
|
+
/** Re-exported so this repo's importers keep one import site for the shape
|
|
40
|
+
* every client seam takes. See the doc comment further down for why
|
|
41
|
+
* `OperatorRpc`/`OperatorRpcAvailable` (declared below, carrying `sdk`)
|
|
42
|
+
* structurally satisfy the SDK's `DaemonReachability`, and so `DaemonVerbCaller`
|
|
43
|
+
* built here still type-checks as the SDK's own. */
|
|
44
|
+
export type { DaemonVerbCaller };
|
|
45
|
+
|
|
46
|
+
export interface OperatorRpcUnavailable {
|
|
47
|
+
readonly available: false;
|
|
48
|
+
readonly reason: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface OperatorRpcAvailable {
|
|
52
|
+
readonly available: true;
|
|
53
|
+
readonly sdk: GoodVibesSdk;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export type OperatorRpc = OperatorRpcUnavailable | OperatorRpcAvailable;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Derive the dial address of the configured control-plane daemon.
|
|
60
|
+
*
|
|
61
|
+
* An explicitly declared external address wins; otherwise the URL is DERIVED
|
|
62
|
+
* from hostMode/host/port/tls.mode. This deliberately does not prefer a stored
|
|
63
|
+
* `controlPlane.baseUrl`: that key had no writers and so drifted from the bind
|
|
64
|
+
* on port, scheme, and host at once.
|
|
65
|
+
*/
|
|
66
|
+
export function resolveControlPlaneBaseUrl(configManager: ConfigManager): string | null {
|
|
67
|
+
const explicit = String(configManager.get('controlPlane.publicBaseUrl' as never) ?? '').trim();
|
|
68
|
+
if (explicit) return explicit.replace(/\/+$/, '');
|
|
69
|
+
const host = String(configManager.get('controlPlane.host') ?? '').trim();
|
|
70
|
+
const port = configManager.get('controlPlane.port');
|
|
71
|
+
if (host && typeof port === 'number' && port > 0) {
|
|
72
|
+
const scheme = String(configManager.get('controlPlane.tls.mode') ?? 'off') !== 'off' ? 'https' : 'http';
|
|
73
|
+
// A wildcard bind is not a dial target; loopback is the interface it answers on.
|
|
74
|
+
const dialHost = host === '0.0.0.0' || host === '::' ? '127.0.0.1' : host;
|
|
75
|
+
return `${scheme}://${dialHost.includes(':') && !dialHost.startsWith('[') ? `[${dialHost}]` : dialHost}:${port}`;
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** The daemon's own state directory — where the shared bearer token lives. */
|
|
81
|
+
export function resolveDaemonStateDirectory(homeDirectory: string): string {
|
|
82
|
+
return join(homeDirectory, '.goodvibes', 'daemon');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Resolve (or honestly refuse to resolve) an operator SDK client wired to this
|
|
87
|
+
* workspace's control-plane daemon, from just the two things the resolution
|
|
88
|
+
* actually needs — the config manager and the home directory.
|
|
89
|
+
*/
|
|
90
|
+
export function resolveOperatorRpc(deps: {
|
|
91
|
+
readonly configManager: ConfigManager;
|
|
92
|
+
readonly homeDirectory: string | (() => string);
|
|
93
|
+
}): OperatorRpc {
|
|
94
|
+
const { configManager } = deps;
|
|
95
|
+
// A refusal is a VALUE here, never a throw — that is the whole contract of
|
|
96
|
+
// this function, and callers rely on it to print an honest line rather than
|
|
97
|
+
// crash a keystroke. A context whose config manager cannot answer (a narrow
|
|
98
|
+
// embed, a partially-wired test double) is one more case of "no daemon can be
|
|
99
|
+
// resolved", not an exception to raise from a probe.
|
|
100
|
+
if (typeof (configManager as { get?: unknown } | null)?.get !== 'function') {
|
|
101
|
+
return { available: false, reason: 'no config manager is wired here, so no control-plane daemon can be resolved.' };
|
|
102
|
+
}
|
|
103
|
+
if (!resolveDaemonEnabled(configManager)) {
|
|
104
|
+
return { available: false, reason: 'the daemon is disabled (daemon.enabled=false) — no operator surface to reach. Enable it in /settings, then retry.' };
|
|
105
|
+
}
|
|
106
|
+
const baseUrl = resolveControlPlaneBaseUrl(configManager);
|
|
107
|
+
if (!baseUrl) {
|
|
108
|
+
return { available: false, reason: 'no control-plane base URL is configured (controlPlane.publicBaseUrl / controlPlane.host+port) — cannot reach the operator surface.' };
|
|
109
|
+
}
|
|
110
|
+
// Resolve the home directory only AFTER the static refusals — a caller whose
|
|
111
|
+
// shell paths are not wired (a disabled-daemon path) must still get the honest
|
|
112
|
+
// unavailable reason above, never a "shell paths not wired" throw.
|
|
113
|
+
const homeDirectory = typeof deps.homeDirectory === 'function' ? deps.homeDirectory() : deps.homeDirectory;
|
|
114
|
+
const token = getOrCreateCompanionToken('tui', { daemonHomeDir: resolveDaemonStateDirectory(homeDirectory) }).token;
|
|
115
|
+
const sdk = createGoodVibesSdk({ baseUrl, authToken: token });
|
|
116
|
+
return { available: true, sdk };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Render a thrown operator-rpc error honestly: a 404 means this daemon has not
|
|
121
|
+
* wired a handler for the verb (a live daemon that simply doesn't implement it
|
|
122
|
+
* yet), never "no result" or a fabricated empty state.
|
|
123
|
+
*/
|
|
124
|
+
export function describeOperatorRpcError(error: unknown): string {
|
|
125
|
+
if (error instanceof GoodVibesSdkError) {
|
|
126
|
+
if (error.status === 404) {
|
|
127
|
+
return 'the connected daemon returned 404 — this operator verb is not wired up on that daemon yet.';
|
|
128
|
+
}
|
|
129
|
+
if (error.status === 401 || error.status === 403) {
|
|
130
|
+
return `the connected daemon rejected the request (${error.status}): ${error.message}`;
|
|
131
|
+
}
|
|
132
|
+
return `operator request failed${error.status ? ` (${error.status})` : ''}: ${error.message}`;
|
|
133
|
+
}
|
|
134
|
+
return error instanceof Error ? error.message : String(error);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* The generic gateway-method route, for a verb the contract carries with a
|
|
139
|
+
* WEBSOCKET binding only.
|
|
140
|
+
*
|
|
141
|
+
* This matters more than it looks. `sdk.operator.invoke` resolves a method's
|
|
142
|
+
* declared `http` route and refuses — with a contract error, before any request
|
|
143
|
+
* is made — when the method has none. A large part of what this client needs is
|
|
144
|
+
* exactly that class: `approvals.raise`, `credentials.set`/`delete`,
|
|
145
|
+
* `checkpoints.*`, `rewind.*` and the `fleet.*` reads are all `transport: ws`
|
|
146
|
+
* in the operator contract, with no REST path of their own.
|
|
147
|
+
*
|
|
148
|
+
* The daemon serves every catalogued verb over one generic route regardless of
|
|
149
|
+
* its declared transport, which is what a client that is not holding a
|
|
150
|
+
* websocket uses. Calling that route directly is not a workaround for a missing
|
|
151
|
+
* binding: it IS the binding for a ws-declared verb reached over HTTP.
|
|
152
|
+
*/
|
|
153
|
+
async function invokeGatewayMethodOverHttp<T>(
|
|
154
|
+
rpc: OperatorRpcAvailable,
|
|
155
|
+
baseUrl: string,
|
|
156
|
+
authToken: string,
|
|
157
|
+
methodId: string,
|
|
158
|
+
input: unknown,
|
|
159
|
+
): Promise<T> {
|
|
160
|
+
void rpc;
|
|
161
|
+
const response = await fetch(`${baseUrl}/api/control-plane/methods/${encodeURIComponent(methodId)}/invoke`, {
|
|
162
|
+
method: 'POST',
|
|
163
|
+
headers: { Authorization: `Bearer ${authToken}`, 'Content-Type': 'application/json' },
|
|
164
|
+
// The envelope the route requires: `body` must be PRESENT even when empty,
|
|
165
|
+
// or the route refuses with a 400 naming the shape it wanted.
|
|
166
|
+
body: JSON.stringify({ body: input ?? {} }),
|
|
167
|
+
});
|
|
168
|
+
const payload = await response.json().catch(() => null) as unknown;
|
|
169
|
+
if (!response.ok) {
|
|
170
|
+
const described = payload && typeof payload === 'object' && 'error' in payload
|
|
171
|
+
? String((payload as { error: unknown }).error)
|
|
172
|
+
: `HTTP ${response.status}`;
|
|
173
|
+
throw new GoodVibesSdkError(`'${methodId}' failed: ${described}`, {
|
|
174
|
+
// Mapped from the status rather than fixed, so `describeOperatorRpcError`
|
|
175
|
+
// and every caller that branches on category sees the same classification
|
|
176
|
+
// it would have seen through the typed client path.
|
|
177
|
+
category: response.status === 401 || response.status === 403
|
|
178
|
+
? 'authorization'
|
|
179
|
+
: response.status === 404 ? 'not_found' : response.status >= 500 ? 'service' : 'bad_request',
|
|
180
|
+
source: 'runtime',
|
|
181
|
+
recoverable: response.status >= 500,
|
|
182
|
+
status: response.status,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
return payload as T;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function createDaemonVerbCaller(deps: {
|
|
189
|
+
readonly configManager: ConfigManager;
|
|
190
|
+
readonly homeDirectory: string | (() => string);
|
|
191
|
+
}): DaemonVerbCaller {
|
|
192
|
+
return {
|
|
193
|
+
probe: () => resolveOperatorRpc(deps),
|
|
194
|
+
invoke: async <T,>(methodId: string, input?: unknown): Promise<T> => {
|
|
195
|
+
const rpc = resolveOperatorRpc(deps);
|
|
196
|
+
if (!rpc.available) throw new Error(`cannot invoke '${methodId}': ${rpc.reason}`);
|
|
197
|
+
try {
|
|
198
|
+
return await rpc.sdk.operator.invoke(methodId as never, (input ?? {}) as never) as T;
|
|
199
|
+
} catch (error) {
|
|
200
|
+
// A CONTRACT_MISMATCH here means exactly one thing: this verb declares
|
|
201
|
+
// no HTTP route. That is a routing fact, not a failure of the call, so
|
|
202
|
+
// it falls through to the generic gateway route rather than surfacing
|
|
203
|
+
// as "the daemon refused" to a user who did nothing wrong.
|
|
204
|
+
if (!(error instanceof GoodVibesSdkError) || error.code !== 'CONTRACT_MISMATCH') throw error;
|
|
205
|
+
const baseUrl = resolveControlPlaneBaseUrl(deps.configManager);
|
|
206
|
+
if (!baseUrl) throw error;
|
|
207
|
+
const homeDirectory = typeof deps.homeDirectory === 'function' ? deps.homeDirectory() : deps.homeDirectory;
|
|
208
|
+
const token = getOrCreateCompanionToken('tui', { daemonHomeDir: resolveDaemonStateDirectory(homeDirectory) }).token;
|
|
209
|
+
return await invokeGatewayMethodOverHttp<T>(rpc, baseUrl, token, methodId, input);
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* spine-adoption.ts — wiring this surface's session and memory spines to the
|
|
3
|
+
* daemon it adopted, over the SDK's adoption policy.
|
|
4
|
+
*
|
|
5
|
+
* ── What moved to the SDK, and what stays here ────────────────────────────
|
|
6
|
+
*
|
|
7
|
+
* The SDK's `@pellux/goodvibes-sdk/platform/runtime/client` now owns WHEN the
|
|
8
|
+
* wire comes up and goes down: idempotent per base URL, torn down on a
|
|
9
|
+
* change, the one-time legacy-store fold. This module owns WHAT the wire is —
|
|
10
|
+
* this terminal's own `createHttpTransport` + `createTuiSpineTransport` +
|
|
11
|
+
* `createTuiMemorySpineTransport`, injected as the SDK's `connect(baseUrl,
|
|
12
|
+
* authToken)` callback — because building the actual HTTP client is the same
|
|
13
|
+
* connection-resolution concern the verb caller already keeps product-side.
|
|
14
|
+
*
|
|
15
|
+
* `activation: 'adopt-on-status'` is passed explicitly: this terminal gates
|
|
16
|
+
* adoption on its boot discovery probe's `external` verdict (it can render
|
|
17
|
+
* "no daemon" honestly), rather than wiring live-immediately the moment it is
|
|
18
|
+
* handed a base URL.
|
|
19
|
+
*
|
|
20
|
+
* ── What crosses the wire ──────────────────────────────────────────────────
|
|
21
|
+
*
|
|
22
|
+
* Session IDENTITY, not session execution. The conversation itself runs here;
|
|
23
|
+
* what the daemon holds is the register —
|
|
24
|
+
* which sessions exist, which surface is live on each, and the inputs queued
|
|
25
|
+
* against them. Concretely, on adoption this wires:
|
|
26
|
+
*
|
|
27
|
+
* - `sessions.register` / `sessions.close` — the identity mirror, deliberately
|
|
28
|
+
* fire-and-forget so a slow daemon never shows up in a keystroke.
|
|
29
|
+
* - `sessions.inputs.list` / `sessions.inputs.deliver` — the inbound steer
|
|
30
|
+
* path, so a message another surface queued for THIS session lands in the
|
|
31
|
+
* turn machinery here and is acknowledged on the wire.
|
|
32
|
+
* - `sessions.list` — the cross-surface union the panels read, interval-
|
|
33
|
+
* refreshed and served synchronously.
|
|
34
|
+
* - the memory spine's wire transport, folded into the same handler by the
|
|
35
|
+
* SDK — this module's own `syncMemorySpineToHostStatus` call is retired;
|
|
36
|
+
* supplying `memoryTransport` in the bundle and `memorySpine` in the
|
|
37
|
+
* options is what activates it now.
|
|
38
|
+
*
|
|
39
|
+
* Plus a one-time, marker-guarded fold of this project's own pre-spine
|
|
40
|
+
* `control-plane/sessions.json` into the adopted daemon, done by the SDK.
|
|
41
|
+
*
|
|
42
|
+
* ── Why this module's exported shape does not change ──────────────────────
|
|
43
|
+
*
|
|
44
|
+
* `bootstrap.ts` gates on `HostServiceStatus` and hands a bare
|
|
45
|
+
* `(daemonStatus, sharedDaemonToken)` pair to whatever this returns. The SDK's
|
|
46
|
+
* handler takes a narrower `{ mode, baseUrl }` signal instead, so the adapting
|
|
47
|
+
* happens inside this module rather than at the call site.
|
|
48
|
+
*/
|
|
49
|
+
import { createHttpTransport, type HostServiceStatus } from '@/runtime/index.ts';
|
|
50
|
+
import { createSpineAdoptionSync as createSdkSpineAdoptionSync } from '@pellux/goodvibes-sdk/platform/runtime/client';
|
|
51
|
+
import type { SpineAdoptionOptions as SdkSpineAdoptionOptions } from '@pellux/goodvibes-sdk/platform/runtime/client';
|
|
52
|
+
|
|
53
|
+
import { createTuiSpineTransport, type SpineSessionsClient } from '../session-spine-transport.ts';
|
|
54
|
+
import { createTuiMemorySpineTransport } from '../memory-spine-transport.ts';
|
|
55
|
+
|
|
56
|
+
/** This module's own options: the SDK's shape minus the two fields it builds itself. */
|
|
57
|
+
export type SpineAdoptionOptions = Omit<SdkSpineAdoptionOptions, 'connect' | 'activation'>;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Build the "the adopted daemon changed" handler.
|
|
61
|
+
*
|
|
62
|
+
* Idempotent per base URL (the SDK's own guarantee): re-running it against the
|
|
63
|
+
* same adopted daemon is a no-op, so a re-probe after an autostart does not
|
|
64
|
+
* tear a live mirror down and put it back up.
|
|
65
|
+
*/
|
|
66
|
+
export function createSpineAdoptionSync(
|
|
67
|
+
options: SpineAdoptionOptions,
|
|
68
|
+
): (daemonStatus: HostServiceStatus, sharedDaemonToken: string) => void {
|
|
69
|
+
const sync = createSdkSpineAdoptionSync({
|
|
70
|
+
...options,
|
|
71
|
+
// This terminal has a boot discovery probe and can render "no daemon"
|
|
72
|
+
// honestly, so it gates on the probe's verdict rather than wiring the
|
|
73
|
+
// moment it is handed a base URL.
|
|
74
|
+
activation: 'adopt-on-status',
|
|
75
|
+
connect: (baseUrl, authToken) => {
|
|
76
|
+
const httpTransport = createHttpTransport({ baseUrl, authToken });
|
|
77
|
+
const sessionsClient: SpineSessionsClient = {
|
|
78
|
+
register: (input) => httpTransport.operator.sessions.register(input),
|
|
79
|
+
close: (sessionId) => httpTransport.operator.sessions.close(sessionId),
|
|
80
|
+
};
|
|
81
|
+
return {
|
|
82
|
+
sessionTransport: createTuiSpineTransport(sessionsClient),
|
|
83
|
+
inboundInputs: {
|
|
84
|
+
listInputs: async (sessionId, opts) => ({
|
|
85
|
+
inputs: await httpTransport.operator.sessions.inputs(sessionId, opts.limit, { state: opts.state, since: opts.since }),
|
|
86
|
+
}),
|
|
87
|
+
deliverInput: (sessionId, inputId, opts) =>
|
|
88
|
+
httpTransport.operator.sessions.deliverInput(sessionId, inputId, opts),
|
|
89
|
+
},
|
|
90
|
+
sessionList: { list: (limit: number) => httpTransport.operator.sessions.list(limit) },
|
|
91
|
+
memoryTransport: createTuiMemorySpineTransport({ baseUrl, authToken }),
|
|
92
|
+
};
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
return (daemonStatus, sharedDaemonToken) => {
|
|
97
|
+
sync({ mode: daemonStatus.mode, baseUrl: daemonStatus.baseUrl }, sharedDaemonToken);
|
|
98
|
+
};
|
|
99
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { join } from 'node:path';
|
|
2
2
|
import { getOrCreateCompanionToken } from '@pellux/goodvibes-sdk/platform/pairing';
|
|
3
|
-
import type { ConfigManager } from '
|
|
3
|
+
import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
4
4
|
|
|
5
5
|
export const CLOUDFLARE_COMPONENT_IDS = [
|
|
6
6
|
'workers',
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Orchestrator } from '
|
|
1
|
+
import type { Orchestrator } from '@pellux/goodvibes-sdk/platform/core';
|
|
2
2
|
import type { ProviderRegistry } from '@pellux/goodvibes-sdk/platform/providers';
|
|
3
3
|
import type { SessionLineageTracker } from '@pellux/goodvibes-sdk/platform/core';
|
|
4
4
|
import type { ContextAccountingSource } from '@pellux/goodvibes-sdk/platform/tools';
|
package/src/runtime/context.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* the runtime loop.
|
|
7
7
|
*/
|
|
8
8
|
import type { ConversationManager } from '../core/conversation';
|
|
9
|
-
import type { Orchestrator } from '
|
|
9
|
+
import type { Orchestrator } from '@pellux/goodvibes-sdk/platform/core';
|
|
10
10
|
import type { ToolRegistry } from '@pellux/goodvibes-sdk/platform/tools';
|
|
11
11
|
import type { PermissionManager } from '@pellux/goodvibes-sdk/platform/permissions';
|
|
12
12
|
import type { HookDispatcher } from '@pellux/goodvibes-sdk/platform/hooks';
|
|
@@ -19,7 +19,7 @@ import type { FeatureFlagManager } from '@/runtime/index.ts';
|
|
|
19
19
|
import type { MutableRuntimeState } from '@/runtime/index.ts';
|
|
20
20
|
import type { SessionSnapshot } from '@/runtime/index.ts';
|
|
21
21
|
import type { RuntimeServices } from './services.ts';
|
|
22
|
-
import type { ComponentHealthMonitor } from '
|
|
22
|
+
import type { ComponentHealthMonitor } from '@pellux/goodvibes-sdk/platform/runtime/observability';
|
|
23
23
|
import type { WorktreeRegistry } from '@/runtime/index.ts';
|
|
24
24
|
import type { SandboxSessionRegistry } from '@/runtime/index.ts';
|
|
25
25
|
|
|
@@ -11,11 +11,22 @@
|
|
|
11
11
|
// for the anchor's turnId at TURN_COMPLETED (rewind-turn-anchors.ts) — the same
|
|
12
12
|
// join key files rewind uses against the workspace checkpoint.
|
|
13
13
|
//
|
|
14
|
-
// This module is the single implementation of that port for the TUI,
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
14
|
+
// This module is the single implementation of that port for the TUI, and it has
|
|
15
|
+
// two consumers again — but the second one reaches it differently now.
|
|
16
|
+
//
|
|
17
|
+
// It used to be the composed daemon's own rewind.plan/apply verbs, resolving a
|
|
18
|
+
// conversation out of the registry below because the daemon was this process.
|
|
19
|
+
// It is not any more. FILES rewind still works from anywhere (the workspace
|
|
20
|
+
// checkpoint store is the daemon's), but the messages live in whichever process
|
|
21
|
+
// runs the loop, and for a while a rewind driven from another surface got a
|
|
22
|
+
// confident "0 messages to drop" from a daemon registry nothing could populate.
|
|
23
|
+
//
|
|
24
|
+
// The daemon now ASKS. This surface offers the conversation it is holding
|
|
25
|
+
// (rewind.conversation.host.register), takes the questions the daemon puts to it
|
|
26
|
+
// and answers them (the SDK's client/conversation-rewind-host.ts); this port is what
|
|
27
|
+
// produces those answers. So conversation rewind works from any surface, and a
|
|
28
|
+
// session nobody has offered is reported unavailable with the reason rather
|
|
29
|
+
// than as a zero that cannot be told apart from a real one.
|
|
19
30
|
// ---------------------------------------------------------------------------
|
|
20
31
|
|
|
21
32
|
import type {
|
|
@@ -25,7 +36,7 @@ import type {
|
|
|
25
36
|
RewindConversationPreview,
|
|
26
37
|
} from '@pellux/goodvibes-sdk/platform/rewind';
|
|
27
38
|
import type { ConversationManager } from '../core/conversation.ts';
|
|
28
|
-
import { resolveTurnAnchor } from '
|
|
39
|
+
import { resolveTurnAnchor } from '@pellux/goodvibes-sdk/platform/rewind';
|
|
29
40
|
|
|
30
41
|
type ConversationJson = Parameters<ConversationManager['fromJSON']>[0];
|
|
31
42
|
|
|
@@ -101,10 +112,13 @@ export function createConversationRewindPort(
|
|
|
101
112
|
}
|
|
102
113
|
|
|
103
114
|
// ---------------------------------------------------------------------------
|
|
104
|
-
// Live per-session conversation registry
|
|
105
|
-
//
|
|
106
|
-
//
|
|
107
|
-
//
|
|
115
|
+
// Live per-session conversation registry.
|
|
116
|
+
//
|
|
117
|
+
// This process's conversations, by session id. Two readers now: the local
|
|
118
|
+
// /rewind command, and the host loop that answers the DAEMON's questions about
|
|
119
|
+
// a session this surface is running (the SDK's client/conversation-rewind-host.ts) —
|
|
120
|
+
// which is what makes conversation rewind work from any surface again, since
|
|
121
|
+
// only the process holding the messages can count or drop them.
|
|
108
122
|
// ---------------------------------------------------------------------------
|
|
109
123
|
|
|
110
124
|
const liveConversations = new Map<string, ConversationManager>();
|
|
@@ -120,10 +134,24 @@ export function unregisterSessionConversation(sessionId: string): void {
|
|
|
120
134
|
}
|
|
121
135
|
|
|
122
136
|
/**
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
137
|
+
* Whether this process is actually holding a session's conversation right now.
|
|
138
|
+
*
|
|
139
|
+
* The port below answers "nothing to drop" for a session it cannot resolve,
|
|
140
|
+
* which is the right degrade for a LOCAL /rewind (there is nothing to truncate
|
|
141
|
+
* here). It is the wrong answer for a rewind driven from another surface: a
|
|
142
|
+
* confident zero is indistinguishable from a real zero, and that is exactly the
|
|
143
|
+
* failure the surface-hosted rewind contract exists to end. The host loop
|
|
144
|
+
* (the SDK's client/conversation-rewind-host.ts) checks this first and answers
|
|
145
|
+
* `unavailable` with a reason instead.
|
|
146
|
+
*/
|
|
147
|
+
export function hasSessionConversation(sessionId: string): boolean {
|
|
148
|
+
return liveConversations.has(sessionId);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* The port that answers for whichever of this process's conversations an anchor
|
|
153
|
+
* names. Used by the local /rewind command and, through the host loop, by the
|
|
154
|
+
* daemon when a rewind driven from anywhere touches a session running here.
|
|
127
155
|
*/
|
|
128
156
|
export function createSessionConversationRewindPort(): ConversationRewindPort {
|
|
129
157
|
return createConversationRewindPort((sessionId) => liveConversations.get(sessionId) ?? null);
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
* for the embedded daemon.
|
|
12
12
|
*/
|
|
13
13
|
import { FeatureAnnouncementStore, featureAnnouncementsPath, createSandboxContainmentAnnouncer } from '@pellux/goodvibes-sdk/platform/runtime/feature-announcements';
|
|
14
|
-
import
|
|
14
|
+
import { readClientCompatibilityFloor } from '@pellux/goodvibes-sdk/platform/control-plane';
|
|
15
|
+
import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
15
16
|
|
|
16
17
|
/** A daemon receipt line (already a human one-liner, e.g. "restarted after a crash at 14:32"). */
|
|
17
18
|
export interface DaemonReceiptLike {
|
|
@@ -51,43 +52,73 @@ export function consumeDaemonAttachNotices(deps: DaemonAttachNoticesDeps): strin
|
|
|
51
52
|
return notices;
|
|
52
53
|
}
|
|
53
54
|
|
|
54
|
-
/** Milliseconds an external-daemon
|
|
55
|
+
/** Milliseconds an external-daemon `/status` read waits before giving up. */
|
|
55
56
|
const EXTERNAL_RECEIPTS_TIMEOUT_MS = 1500;
|
|
56
57
|
|
|
57
|
-
export interface
|
|
58
|
+
export interface ExternalDaemonAttachReadDeps {
|
|
58
59
|
/** The adopted daemon's base URL, e.g. "http://127.0.0.1:3421". */
|
|
59
60
|
readonly baseUrl: string;
|
|
60
61
|
/** The shared bearer the adopted daemon authenticates (the companion-pairing token). */
|
|
61
62
|
readonly authToken: string;
|
|
63
|
+
/**
|
|
64
|
+
* Ask for the daemon's undelivered receipts on this read. Delivery at the
|
|
65
|
+
* daemon is destructive — served once, to the consuming reader — so only the
|
|
66
|
+
* read that RENDERS them asks. A reconnect read that just refreshes the two
|
|
67
|
+
* build floors leaves this off, and no receipt is eaten before a surface can
|
|
68
|
+
* show it.
|
|
69
|
+
*/
|
|
70
|
+
readonly consumeReceipts?: boolean;
|
|
62
71
|
/** Injectable fetch for tests; defaults to the global fetch. */
|
|
63
72
|
readonly fetchImpl?: typeof fetch;
|
|
64
73
|
/** Injectable timeout (ms); defaults to EXTERNAL_RECEIPTS_TIMEOUT_MS. */
|
|
65
74
|
readonly timeoutMs?: number;
|
|
66
75
|
}
|
|
67
76
|
|
|
77
|
+
/** Everything one `/status` read of an adopted daemon tells this terminal. */
|
|
78
|
+
export interface ExternalDaemonAttachRead {
|
|
79
|
+
/** True only when the daemon answered with a usable 2xx body. */
|
|
80
|
+
readonly answered: boolean;
|
|
81
|
+
/** Receipt lines to render; empty unless `consumeReceipts` was asked for. */
|
|
82
|
+
readonly notices: string[];
|
|
83
|
+
/**
|
|
84
|
+
* The minimum CLIENT build the daemon announced on the
|
|
85
|
+
* `X-Goodvibes-Client-Floor` response header, or undefined when it announced
|
|
86
|
+
* none (a daemon too old to publish one is not asking for anything).
|
|
87
|
+
*/
|
|
88
|
+
readonly clientFloor: string | undefined;
|
|
89
|
+
/** The parsed body, which carries the daemon's own `version`. */
|
|
90
|
+
readonly statusPayload: unknown;
|
|
91
|
+
}
|
|
92
|
+
|
|
68
93
|
/**
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
94
|
+
* One `/status` read of a separately-hosted (external/systemd) daemon, carrying
|
|
95
|
+
* the three things attaching to it decides on: its undelivered receipts, the
|
|
96
|
+
* client build floor it publishes, and its own build.
|
|
97
|
+
*
|
|
98
|
+
* The default deployment adopts such a daemon with no in-process handle
|
|
99
|
+
* (daemonServer is null), so the embedded in-process fold sees nothing; this
|
|
100
|
+
* hits the daemon's own endpoint instead — GET <baseUrl>/status with the shared
|
|
101
|
+
* bearer, plus `?receipts=consume` when the caller is going to render them. The
|
|
102
|
+
* daemon marks the served receipts delivered as it responds (its own
|
|
103
|
+
* exactly-once store), so a second attach with nothing new returns no notices.
|
|
104
|
+
* Any transport, auth, or parse failure reads as unanswered rather than
|
|
105
|
+
* throwing into the attach path.
|
|
78
106
|
*/
|
|
79
|
-
export async function
|
|
80
|
-
deps:
|
|
81
|
-
): Promise<
|
|
107
|
+
export async function readExternalDaemonAttach(
|
|
108
|
+
deps: ExternalDaemonAttachReadDeps,
|
|
109
|
+
): Promise<ExternalDaemonAttachRead> {
|
|
110
|
+
const unanswered: ExternalDaemonAttachRead = {
|
|
111
|
+
answered: false, notices: [], clientFloor: undefined, statusPayload: null,
|
|
112
|
+
};
|
|
82
113
|
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
83
114
|
const controller = new AbortController();
|
|
84
115
|
const timer = setTimeout(() => controller.abort(), deps.timeoutMs ?? EXTERNAL_RECEIPTS_TIMEOUT_MS);
|
|
85
116
|
try {
|
|
86
117
|
const headers = new Headers();
|
|
87
118
|
if (deps.authToken.trim()) headers.set('Authorization', `Bearer ${deps.authToken.trim()}`);
|
|
88
|
-
const url = `${deps.baseUrl.replace(/\/+$/, '')}/status?receipts=consume`;
|
|
119
|
+
const url = `${deps.baseUrl.replace(/\/+$/, '')}/status${deps.consumeReceipts ? '?receipts=consume' : ''}`;
|
|
89
120
|
const response = await fetchImpl(url, { headers, signal: controller.signal });
|
|
90
|
-
if (!response.ok) return
|
|
121
|
+
if (!response.ok) return unanswered;
|
|
91
122
|
const body = (await response.json()) as { receipts?: unknown } | null;
|
|
92
123
|
const receipts = Array.isArray(body?.receipts) ? body.receipts : [];
|
|
93
124
|
const notices: string[] = [];
|
|
@@ -97,9 +128,14 @@ export async function consumeExternalDaemonAttachNotices(
|
|
|
97
128
|
if (text.trim().length > 0) notices.push(text);
|
|
98
129
|
}
|
|
99
130
|
}
|
|
100
|
-
return
|
|
131
|
+
return {
|
|
132
|
+
answered: true,
|
|
133
|
+
notices,
|
|
134
|
+
clientFloor: readClientCompatibilityFloor(response.headers),
|
|
135
|
+
statusPayload: body,
|
|
136
|
+
};
|
|
101
137
|
} catch {
|
|
102
|
-
return
|
|
138
|
+
return unanswered;
|
|
103
139
|
} finally {
|
|
104
140
|
clearTimeout(timer);
|
|
105
141
|
}
|
|
@@ -13,7 +13,7 @@ import type { PanelConfig } from '@/runtime/index.ts';
|
|
|
13
13
|
import { DEFAULT_PANEL_CONFIG, appendBounded, applyFilter } from '@/runtime/index.ts';
|
|
14
14
|
import type { DiagnosticFilter } from '@/runtime/index.ts';
|
|
15
15
|
import type { OpsInterventionReason, OpsEvent } from '@/runtime/index.ts';
|
|
16
|
-
import type { UiEventFeed } from '
|
|
16
|
+
import type { UiEventFeed } from '@/runtime/index.ts';
|
|
17
17
|
|
|
18
18
|
// ---------------------------------------------------------------------------
|
|
19
19
|
// Audit entry
|
|
@@ -46,21 +46,6 @@ export interface SurfaceRuntimePollerOwners extends Omit<RuntimePollerOwners, 's
|
|
|
46
46
|
* teardown path to have no way to stop it.
|
|
47
47
|
*/
|
|
48
48
|
readonly stopWakeHousekeeping: () => void;
|
|
49
|
-
/**
|
|
50
|
-
* Fork-only: the daemon handler surfaces (daemon-handler-composition.ts).
|
|
51
|
-
*
|
|
52
|
-
* `unregister()` detaches the gateway handlers AND stops the two pollers this
|
|
53
|
-
* product's inbox surface owns — the retention sweep inside `InboxCursorStore`
|
|
54
|
-
* and the per-provider `InboundPoller` intervals. The SDK does not know either
|
|
55
|
-
* exists, so if this surface does not stop them nothing does.
|
|
56
|
-
*/
|
|
57
|
-
readonly daemonHandlers: { readonly unregister: () => void };
|
|
58
|
-
/**
|
|
59
|
-
* Fork-only: the paired-phone feature's housekeeping timer
|
|
60
|
-
* (device-posture-composition.ts). Started by whichever entry point boots the
|
|
61
|
-
* host, so the stop belongs on this list rather than in one shutdown path.
|
|
62
|
-
*/
|
|
63
|
-
readonly devicePosture: { readonly stopHousekeeping: () => void };
|
|
64
49
|
}
|
|
65
50
|
|
|
66
51
|
/**
|
|
@@ -86,14 +71,9 @@ export function registerSurfaceRuntimePollers(
|
|
|
86
71
|
): void {
|
|
87
72
|
registerRuntimePollers(registry, { ...services, stopConfigWatch: extras.stopConfigWatch });
|
|
88
73
|
registry.add('durability housekeeping', services.stopDurabilityHousekeeping);
|
|
89
|
-
registry.add('device housekeeping', () => services.devicePosture.stopHousekeeping());
|
|
90
74
|
registry.add('wake-word housekeeping', services.stopWakeHousekeeping);
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
// `dispose()` total: every shutdown path (daemon/cli.ts, main.ts's teardown
|
|
96
|
-
// registry, the one-shot CLI commands) now stops these two, not just the one
|
|
97
|
-
// that remembered to call `unregister()` itself.
|
|
98
|
-
registry.add('daemon handler surfaces', () => services.daemonHandlers.unregister());
|
|
75
|
+
// The inbox surfaces and the device-posture housekeeping used to be stopped
|
|
76
|
+
// here too. Both moved to the daemon product with the runtimes that started
|
|
77
|
+
// them: nothing in this process polls a mailbox or reaps a device grant any
|
|
78
|
+
// more, so there is nothing left here to stop.
|
|
99
79
|
}
|