@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,243 +1,50 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* path-shadow-startup.ts —
|
|
3
|
-
* are actually reaching, and is it the current one".
|
|
2
|
+
* path-shadow-startup.ts — this terminal's boot-time reachability report.
|
|
4
3
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* Cost discipline, because this runs on every start:
|
|
12
|
-
* - the first scan is existence-only: no process is spawned while there is
|
|
13
|
-
* nothing to report, which is the overwhelmingly common case;
|
|
14
|
-
* - versions are probed only after a shadow has already been found, and
|
|
15
|
-
* only with `--version`, bounded by a short timeout;
|
|
16
|
-
* - the latest-release lookup only happens when it can actually change what
|
|
17
|
-
* the user should do: a package-managed or source install (which will
|
|
18
|
-
* never swap itself), or an install that has just been found unreachable.
|
|
19
|
-
* A healthy binary install has already been brought to the latest release
|
|
20
|
-
* by the launch auto-updater, so asking again would be a network round
|
|
21
|
-
* trip that can only confirm what just happened.
|
|
22
|
-
*
|
|
23
|
-
* Every failure is swallowed. A reachability check must never block or crash
|
|
24
|
-
* boot.
|
|
4
|
+
* The check itself (the PATH scan, the `--version` probes, the cost discipline
|
|
5
|
+
* that keeps a healthy start free of spawns and network calls, and the wording)
|
|
6
|
+
* belongs to the platform. All this supplies is what only this product knows:
|
|
7
|
+
* which command name the shell resolves it by, which package a package-managed
|
|
8
|
+
* install would be upgraded through, which release lookup answers "what is
|
|
9
|
+
* current", and where the lines are printed.
|
|
25
10
|
*/
|
|
26
11
|
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import { basename, dirname, join } from 'node:path';
|
|
30
|
-
import { detectInstallKind, fallbackUpdateCommand, type UpdateFetchLike } from './update-check.ts';
|
|
12
|
+
import { announceReachability, boundedLatestRelease } from '@pellux/goodvibes-sdk/platform/runtime/operations';
|
|
13
|
+
import type { UpdateFetchLike } from './update-check.ts';
|
|
31
14
|
import { checkForUpdate } from '../input/commands/update-runtime.ts';
|
|
32
|
-
import {
|
|
33
|
-
scanCommandShadows,
|
|
34
|
-
splitPathEntries,
|
|
35
|
-
type ShadowScanResult,
|
|
36
|
-
} from './path-shadow.ts';
|
|
37
|
-
import { buildReachabilityNotices, type ReachabilityNotice } from './reachability-notice.ts';
|
|
38
15
|
import { VERSION } from '../version.ts';
|
|
39
16
|
import type { SystemMessageRouter } from '../core/system-message-router.ts';
|
|
40
17
|
|
|
41
|
-
/** The
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
/** How long a `<path> --version` probe may take before it is abandoned. */
|
|
45
|
-
const VERSION_PROBE_TIMEOUT_MS = 3000;
|
|
46
|
-
/** How long the latest-release lookup may take before startup moves on without it. */
|
|
47
|
-
const LATEST_LOOKUP_TIMEOUT_MS = 2500;
|
|
48
|
-
|
|
49
|
-
function safeRealPath(path: string): string {
|
|
50
|
-
try {
|
|
51
|
-
return realpathSync(path);
|
|
52
|
-
} catch {
|
|
53
|
-
return path;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function isExecutableFile(path: string): boolean {
|
|
58
|
-
try {
|
|
59
|
-
const stats = statSync(path);
|
|
60
|
-
if (!stats.isFile()) return false;
|
|
61
|
-
// Any execute bit is enough: the shell searches for an executable file,
|
|
62
|
-
// and which bit applies depends on ownership we are not going to compute.
|
|
63
|
-
return (stats.mode & 0o111) !== 0;
|
|
64
|
-
} catch {
|
|
65
|
-
return false;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Runs `<path> --version` and returns its first line. Never inherits stdio, so
|
|
71
|
-
* a binary that tries to draw a terminal cannot disturb this one.
|
|
72
|
-
*/
|
|
73
|
-
export function probeVersionLine(path: string): string | undefined {
|
|
74
|
-
try {
|
|
75
|
-
const result = spawnSync(path, ['--version'], {
|
|
76
|
-
timeout: VERSION_PROBE_TIMEOUT_MS,
|
|
77
|
-
encoding: 'utf8',
|
|
78
|
-
stdio: ['ignore', 'pipe', 'ignore'],
|
|
79
|
-
windowsHide: true,
|
|
80
|
-
});
|
|
81
|
-
if (result.error || typeof result.stdout !== 'string') return undefined;
|
|
82
|
-
return result.stdout.split('\n')[0];
|
|
83
|
-
} catch {
|
|
84
|
-
return undefined;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* The PATH directory that provides THIS running executable, which is the
|
|
90
|
-
* position everything else is measured against.
|
|
91
|
-
*
|
|
92
|
-
* Preferred answer: the first PATH entry whose `<dir>/<command>` resolves,
|
|
93
|
-
* through symlinks, to this same file. That is exactly "where the shell would
|
|
94
|
-
* find me", and it is right for a linked package install as well as a
|
|
95
|
-
* standalone binary.
|
|
96
|
-
*
|
|
97
|
-
* Fallback, for a standalone binary only: the directory the executable sits
|
|
98
|
-
* in. That directory being absent from PATH is itself worth reporting — an
|
|
99
|
-
* installed binary nobody can reach by name. For a package-managed install
|
|
100
|
-
* the executable lives inside node_modules, which is never on PATH and never
|
|
101
|
-
* meant to be, so there is nothing honest to say and the check stays silent.
|
|
102
|
-
*/
|
|
103
|
-
export function resolveSelfDirectory(input: {
|
|
104
|
-
readonly execPath: string;
|
|
105
|
-
readonly command: string;
|
|
106
|
-
readonly pathEntries: readonly string[];
|
|
107
|
-
readonly realPath: (path: string) => string;
|
|
108
|
-
readonly isExecutableFile: (path: string) => boolean;
|
|
109
|
-
}): string | undefined {
|
|
110
|
-
const self = input.realPath(input.execPath);
|
|
111
|
-
for (const directory of input.pathEntries) {
|
|
112
|
-
const candidate = join(directory, input.command);
|
|
113
|
-
if (!input.isExecutableFile(candidate)) continue;
|
|
114
|
-
if (input.realPath(candidate) === self) return directory;
|
|
115
|
-
}
|
|
116
|
-
if (detectInstallKind(input.execPath) === 'binary') return dirname(self);
|
|
117
|
-
return undefined;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/** Which of the installed commands actually sit in this directory. */
|
|
121
|
-
function commandsPresentIn(directory: string, exists: (path: string) => boolean): string[] {
|
|
122
|
-
return INSTALLED_COMMANDS.filter((command) => exists(join(directory, command)));
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export interface ReachabilityCheckResult {
|
|
126
|
-
readonly notices: readonly ReachabilityNotice[];
|
|
127
|
-
readonly scan?: ShadowScanResult | undefined;
|
|
128
|
-
}
|
|
18
|
+
/** The package a package-managed install of this terminal is upgraded through. */
|
|
19
|
+
const TERMINAL_PACKAGE_NAME = '@pellux/goodvibes-tui';
|
|
129
20
|
|
|
130
|
-
/**
|
|
131
|
-
|
|
132
|
-
* Returns the notices to print; an empty list is the healthy case.
|
|
133
|
-
*/
|
|
134
|
-
export async function runReachabilityCheck(input: {
|
|
135
|
-
readonly execPath: string;
|
|
136
|
-
readonly pathValue: string | undefined;
|
|
137
|
-
readonly homeDir: string;
|
|
138
|
-
readonly runningVersion: string;
|
|
139
|
-
readonly commandName?: string | undefined;
|
|
140
|
-
/** Resolves the newest released version, or undefined when it cannot be determined. */
|
|
141
|
-
readonly resolveLatest: () => Promise<string | undefined>;
|
|
142
|
-
// The host touches, injectable so a test drives a whole scenario with fake
|
|
143
|
-
// paths and never spawns anything. Production passes none of these.
|
|
144
|
-
readonly isExecutableFile?: ((path: string) => boolean) | undefined;
|
|
145
|
-
readonly realPath?: ((path: string) => string) | undefined;
|
|
146
|
-
readonly probeVersion?: ((path: string) => string | undefined) | undefined;
|
|
147
|
-
}): Promise<ReachabilityCheckResult> {
|
|
148
|
-
const command = input.commandName ?? basename(input.execPath);
|
|
149
|
-
const pathEntries = splitPathEntries(input.pathValue);
|
|
150
|
-
const installKind = detectInstallKind(input.execPath);
|
|
151
|
-
const fileIsExecutable = input.isExecutableFile ?? isExecutableFile;
|
|
152
|
-
const resolvePath = input.realPath ?? safeRealPath;
|
|
153
|
-
const versionProbe = input.probeVersion ?? probeVersionLine;
|
|
21
|
+
/** The command name the shell resolves this terminal by. */
|
|
22
|
+
const TERMINAL_COMMAND_NAME = 'goodvibes';
|
|
154
23
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
execPath: input.execPath,
|
|
161
|
-
command,
|
|
162
|
-
pathEntries,
|
|
163
|
-
realPath: resolvePath,
|
|
164
|
-
isExecutableFile: fileIsExecutable,
|
|
24
|
+
/** The same release lookup `/update` uses — there is deliberately no second source of truth. */
|
|
25
|
+
function resolveLatestRelease(): Promise<string | undefined> {
|
|
26
|
+
return boundedLatestRelease(async () => {
|
|
27
|
+
const result = await checkForUpdate(fetch as UpdateFetchLike, VERSION);
|
|
28
|
+
return result.latestTag;
|
|
165
29
|
});
|
|
166
|
-
if (!selfDirectory) return { notices: [] };
|
|
167
|
-
|
|
168
|
-
const commands = commandsPresentIn(selfDirectory, fileIsExecutable);
|
|
169
|
-
const base = {
|
|
170
|
-
commands: commands.length > 0 ? commands : [command],
|
|
171
|
-
installDir: selfDirectory,
|
|
172
|
-
pathEntries,
|
|
173
|
-
homeDir: input.homeDir,
|
|
174
|
-
isExecutableFile: fileIsExecutable,
|
|
175
|
-
realPath: resolvePath,
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
// Existence-only first: nothing is spawned while there is nothing to report.
|
|
179
|
-
const cheapScan = scanCommandShadows(base);
|
|
180
|
-
const scan = cheapScan.hasProblem
|
|
181
|
-
? scanCommandShadows({ ...base, probeVersion: versionProbe })
|
|
182
|
-
: cheapScan;
|
|
183
|
-
|
|
184
|
-
const latestVersion = scan.hasProblem || installKind !== 'binary'
|
|
185
|
-
? await input.resolveLatest()
|
|
186
|
-
: undefined;
|
|
187
|
-
|
|
188
|
-
return {
|
|
189
|
-
scan,
|
|
190
|
-
notices: buildReachabilityNotices({
|
|
191
|
-
scan,
|
|
192
|
-
runningVersion: input.runningVersion,
|
|
193
|
-
latestVersion,
|
|
194
|
-
updateCommand: installKind === 'binary'
|
|
195
|
-
? 'curl -fsSL https://goodvibes.sh/install.sh | sh'
|
|
196
|
-
: fallbackUpdateCommand(installKind),
|
|
197
|
-
}),
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/** The real latest-release lookup, bounded so a slow network cannot hold up boot. */
|
|
202
|
-
async function resolveLatestReleaseBounded(): Promise<string | undefined> {
|
|
203
|
-
try {
|
|
204
|
-
// The same release lookup /update uses — there is deliberately no second
|
|
205
|
-
// source of truth for what the latest version is.
|
|
206
|
-
const lookup = checkForUpdate(fetch as UpdateFetchLike, VERSION).then((result) => result.latestTag);
|
|
207
|
-
const timed = await Promise.race([
|
|
208
|
-
lookup,
|
|
209
|
-
new Promise<undefined>((resolve) => {
|
|
210
|
-
const timer = setTimeout(() => resolve(undefined), LATEST_LOOKUP_TIMEOUT_MS);
|
|
211
|
-
timer.unref?.();
|
|
212
|
-
}),
|
|
213
|
-
]);
|
|
214
|
-
return timed ?? undefined;
|
|
215
|
-
} catch {
|
|
216
|
-
return undefined;
|
|
217
|
-
}
|
|
218
30
|
}
|
|
219
31
|
|
|
220
32
|
/**
|
|
221
33
|
* Boot-time wiring. Prints one high-priority system message per line so the
|
|
222
34
|
* report reads as prose rather than a single wrapped paragraph, and swallows
|
|
223
|
-
* everything.
|
|
35
|
+
* everything — a reachability check must never block or crash boot.
|
|
224
36
|
*/
|
|
225
|
-
export async function
|
|
226
|
-
|
|
227
|
-
|
|
37
|
+
export async function announceInstallReachability(router: SystemMessageRouter): Promise<void> {
|
|
38
|
+
await announceReachability(
|
|
39
|
+
{
|
|
228
40
|
execPath: process.execPath,
|
|
229
41
|
pathValue: process.env['PATH'],
|
|
230
42
|
homeDir: process.env['HOME'] ?? '',
|
|
231
43
|
runningVersion: VERSION,
|
|
232
|
-
commandName:
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
} catch {
|
|
241
|
-
// Best-effort — a reachability check must never block or crash boot.
|
|
242
|
-
}
|
|
44
|
+
commandName: TERMINAL_COMMAND_NAME,
|
|
45
|
+
packageName: TERMINAL_PACKAGE_NAME,
|
|
46
|
+
resolveLatest: resolveLatestRelease,
|
|
47
|
+
},
|
|
48
|
+
(line) => router.high(`[Install] ${line}`),
|
|
49
|
+
);
|
|
243
50
|
}
|
|
@@ -13,8 +13,9 @@
|
|
|
13
13
|
// A single config subscriber carries ALL THREE toggle paths (/power, Alt+A, the
|
|
14
14
|
// settings modal): each lands on the power.keepAwake config key — the local
|
|
15
15
|
// PowerManager persists it on set, and the settings modal writes it directly —
|
|
16
|
-
// so subscribing to that one key catches every origin.
|
|
17
|
-
// this is a no-op:
|
|
16
|
+
// so subscribing to that one key catches every origin. When there is no adopted
|
|
17
|
+
// external daemon (`isExternalDaemon()` false), this is a no-op: there is nothing
|
|
18
|
+
// to forward to.
|
|
18
19
|
// ---------------------------------------------------------------------------
|
|
19
20
|
|
|
20
21
|
import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
@@ -22,10 +22,10 @@ import { existsSync } from 'node:fs';
|
|
|
22
22
|
import { flushActivityLogSync, logger, summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
23
23
|
import { createTerminalLifecycle, TERMINAL_ESCAPES } from '@pellux/goodvibes-terminal-shell';
|
|
24
24
|
import { formatUserFacingErrorLine } from '../core/format-user-error.ts';
|
|
25
|
-
import { allowTerminalWrite } from '
|
|
25
|
+
import { allowTerminalWrite } from '@pellux/goodvibes-terminal-shell/terminal-output-guard';
|
|
26
26
|
import { buildPersistedSessionContext, deleteRecoveryFile } from '@/runtime/index.ts';
|
|
27
27
|
import type { SessionSurface } from '@/runtime/index.ts';
|
|
28
|
-
import { removeLivenessMarker } from '
|
|
28
|
+
import { removeLivenessMarker } from '@pellux/goodvibes-sdk/platform/runtime/operations';
|
|
29
29
|
import type { BootstrapContext } from './bootstrap.ts';
|
|
30
30
|
import type { InputHandler } from '../input/handler.ts';
|
|
31
31
|
import type { ConversationMessageSnapshot } from '../core/conversation.ts';
|
|
@@ -1,74 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* provider-fallback.ts —
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
2
|
+
* provider-fallback.ts — boot-time model routability, the part the SDK does
|
|
3
|
+
* not cover.
|
|
4
|
+
*
|
|
5
|
+
* The pre-catalog fallback registration for the configured model
|
|
6
|
+
* (`ensureConfiguredModelIsRoutable`) is the SDK's own
|
|
7
|
+
* (`@pellux/goodvibes-sdk/platform/providers`) — this file only imports it for
|
|
8
|
+
* `ensureBootModelResolvable` below, which is NOT in the SDK: it is the
|
|
9
|
+
* boot-time custom-provider-readiness wrapper this app's boot path needs and
|
|
10
|
+
* the SDK's floor does not compose.
|
|
7
11
|
*/
|
|
8
12
|
import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
9
|
-
import {
|
|
10
|
-
inferFallbackContextWindow,
|
|
11
|
-
resolveReasoningEffortSpec,
|
|
12
|
-
type ModelDefinition,
|
|
13
|
-
type ProviderRegistry,
|
|
14
|
-
} from '@pellux/goodvibes-sdk/platform/providers';
|
|
13
|
+
import { ensureConfiguredModelIsRoutable, type ProviderRegistry } from '@pellux/goodvibes-sdk/platform/providers';
|
|
15
14
|
import { logger } from '@pellux/goodvibes-sdk/platform/utils';
|
|
16
15
|
|
|
17
|
-
function buildFallbackModelDefinition(provider: string, modelId: string): ModelDefinition {
|
|
18
|
-
const providerLower = provider.toLowerCase();
|
|
19
|
-
const isReasoningProvider = providerLower.includes('openai')
|
|
20
|
-
|| providerLower.includes('anthropic')
|
|
21
|
-
|| providerLower.includes('gemini')
|
|
22
|
-
|| providerLower.includes('google');
|
|
23
|
-
|
|
24
|
-
return {
|
|
25
|
-
id: modelId,
|
|
26
|
-
provider,
|
|
27
|
-
registryKey: `${provider}:${modelId}`,
|
|
28
|
-
displayName: modelId,
|
|
29
|
-
description: 'Configured model available before the model catalog cache has loaded.',
|
|
30
|
-
capabilities: {
|
|
31
|
-
toolCalling: true,
|
|
32
|
-
codeEditing: true,
|
|
33
|
-
reasoning: isReasoningProvider,
|
|
34
|
-
multimodal: isReasoningProvider,
|
|
35
|
-
},
|
|
36
|
-
contextWindow: inferFallbackContextWindow(provider, modelId),
|
|
37
|
-
contextWindowProvenance: 'fallback',
|
|
38
|
-
selectable: true,
|
|
39
|
-
tier: 'standard',
|
|
40
|
-
// Which levels this model accepts is a property of the model, not of the
|
|
41
|
-
// provider it sits behind: the hardcoded four ('instant', 'low', 'medium',
|
|
42
|
-
// 'high') offered 'instant' to models that reject it and hid 'xhigh',
|
|
43
|
-
// 'max' and 'none' from models that accept them. The SDK resolver answers
|
|
44
|
-
// from the curated family table when the catalog has not loaded yet, and
|
|
45
|
-
// otherwise returns its own labelled best guess, which the adapters treat
|
|
46
|
-
// as "send nothing" rather than as verified levels.
|
|
47
|
-
...(isReasoningProvider
|
|
48
|
-
? { reasoningEffort: resolveReasoningEffortSpec({ modelId }) }
|
|
49
|
-
: {}),
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export function ensureConfiguredModelIsRoutable(providerRegistry: ProviderRegistry, configManager: ConfigManager): void {
|
|
54
|
-
const configuredModel = String(configManager.get('provider.model') ?? '').trim();
|
|
55
|
-
if (!configuredModel.includes(':')) return;
|
|
56
|
-
if (providerRegistry.listModels().some((model) => model.registryKey === configuredModel)) return;
|
|
57
|
-
|
|
58
|
-
const [providerId, ...modelParts] = configuredModel.split(':');
|
|
59
|
-
const modelId = modelParts.join(':').trim();
|
|
60
|
-
if (!providerId || !modelId) return;
|
|
61
|
-
|
|
62
|
-
const provider = providerRegistry.tryGet(providerId);
|
|
63
|
-
if (!provider) return;
|
|
64
|
-
|
|
65
|
-
providerRegistry.registerRuntimeProvider({
|
|
66
|
-
provider,
|
|
67
|
-
replace: true,
|
|
68
|
-
models: [buildFallbackModelDefinition(providerId, modelId)],
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
|
|
72
16
|
/**
|
|
73
17
|
* Boot-time custom-provider readiness. Custom providers register
|
|
74
18
|
* asynchronously (services.ts fires initCustomProviders() without awaiting),
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { buildPersistedSessionContext, writeRecoveryFile } from '@/runtime/index.ts';
|
|
16
16
|
import type { SessionSurface } from '@/runtime/index.ts';
|
|
17
|
-
import { writeLivenessMarker } from '
|
|
17
|
+
import { writeLivenessMarker } from '@pellux/goodvibes-sdk/platform/runtime/operations';
|
|
18
18
|
import type { ConversationManager, ConversationMessageSnapshot } from '../core/conversation.ts';
|
|
19
19
|
|
|
20
20
|
export interface StartRecoveryAutosaveOptions {
|
|
@@ -8,21 +8,25 @@
|
|
|
8
8
|
* booted runtime).
|
|
9
9
|
*
|
|
10
10
|
* What accepting a snapshot actually does, and why each step is here:
|
|
11
|
-
* - `runtime.sessionId` moves to the snapshot's session
|
|
12
|
-
* transcript journal rebinds on that field
|
|
13
|
-
* every later turn snapshot keys off it.
|
|
14
|
-
* conversation would be written back under the
|
|
15
|
-
* the recovery point would be gone and its content
|
|
16
|
-
* the user never asked for.
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
11
|
+
* - `runtime.sessionId` moves to the snapshot's session, once the restore has
|
|
12
|
+
* actually happened. The transcript journal rebinds on that field
|
|
13
|
+
* (turn-event-wiring.ts), and every later turn snapshot keys off it.
|
|
14
|
+
* Without this the restored conversation would be written back under the
|
|
15
|
+
* fresh boot session id — the recovery point would be gone and its content
|
|
16
|
+
* would live somewhere the user never asked for. It moves AFTER the apply
|
|
17
|
+
* rather than before, so a refusal (nothing on disk, a damaged file) leaves
|
|
18
|
+
* this boot on its own session instead of adopting one it never loaded.
|
|
19
|
+
* - The conversation is rebuilt through the SDK's `applyRecoverySnapshot`,
|
|
20
|
+
* which reads and retires the snapshot in one operation and runs the same
|
|
21
|
+
* reset/fromJSON/rebuild + journal-replay sequence a normal resume runs.
|
|
22
|
+
* This module supplies the live conversation, the persist callback and the
|
|
23
|
+
* user's answer; nothing else about the restore is decided here.
|
|
20
24
|
* - The last-session pointer is written, so `--continue` on the next launch
|
|
21
25
|
* reaches the session the user just recovered.
|
|
22
26
|
* - Footer token counters are rehydrated from the restored history, so the
|
|
23
27
|
* numbers on screen describe the conversation actually in memory.
|
|
24
28
|
*/
|
|
25
|
-
import { applyRecoverySnapshot,
|
|
29
|
+
import { applyRecoverySnapshot, confirmRecoveryRestore } from '@pellux/goodvibes-sdk/platform/runtime/operations';
|
|
26
30
|
import type { RecoveryPromptDeps } from './recovery-prompt.ts';
|
|
27
31
|
import type { ConversationManager, ConversationMessageSnapshot } from '../core/conversation.ts';
|
|
28
32
|
import type { CommandContext } from '../input/command-registry.ts';
|
|
@@ -50,12 +54,10 @@ export function buildRecoveryOfferWiring(input: RecoveryOfferWiringInput): Recov
|
|
|
50
54
|
get openSelection() { return input.commandContext.openSelection; },
|
|
51
55
|
receipt: input.receipt,
|
|
52
56
|
render: input.render,
|
|
53
|
-
applySnapshot: ({
|
|
54
|
-
input.runtime.sessionId = sessionId;
|
|
55
|
-
const payload = snapshot as unknown as RecoverySnapshotPayload;
|
|
57
|
+
applySnapshot: ({ sessionId }) => {
|
|
56
58
|
const persist = (messages: ConversationMessageSnapshot[]): void => {
|
|
57
59
|
input.sessionManager.save(sessionId, messages as never[], {
|
|
58
|
-
title: input.conversation.title ||
|
|
60
|
+
title: input.conversation.title || '',
|
|
59
61
|
model: input.runtime.model,
|
|
60
62
|
provider: input.runtime.provider,
|
|
61
63
|
timestamp: Date.now(),
|
|
@@ -65,25 +67,39 @@ export function buildRecoveryOfferWiring(input: RecoveryOfferWiringInput): Recov
|
|
|
65
67
|
saveSource: 'user',
|
|
66
68
|
});
|
|
67
69
|
};
|
|
68
|
-
|
|
69
|
-
|
|
70
|
+
// The user picked "Resume it" in the offer modal — that answer, and
|
|
71
|
+
// nothing else, is what unlocks the restore.
|
|
72
|
+
const result = applyRecoverySnapshot({
|
|
70
73
|
sessionId,
|
|
71
74
|
conversation: input.conversation,
|
|
72
75
|
surface: input.surface,
|
|
73
76
|
persistSnapshot: persist,
|
|
77
|
+
confirmation: confirmRecoveryRestore(true)!,
|
|
74
78
|
});
|
|
79
|
+
// A refusal leaves this boot exactly as it was: the fresh session id
|
|
80
|
+
// stands, nothing is persisted, and no last-session pointer is written.
|
|
81
|
+
// The prompt turns the refusal into a receipt.
|
|
82
|
+
if (!result.applied) return result;
|
|
83
|
+
// Now that the conversation really is the recovered one, the runtime
|
|
84
|
+
// moves to its session. The transcript journal rebinds on this field
|
|
85
|
+
// (turn-event-wiring.ts) and every later turn snapshot keys off it, so
|
|
86
|
+
// leaving it on the fresh boot session would write the restored
|
|
87
|
+
// conversation back under an id the user never asked for. Both the
|
|
88
|
+
// persist above and the one below name the session explicitly, so
|
|
89
|
+
// neither depends on this assignment having happened first.
|
|
90
|
+
input.runtime.sessionId = sessionId;
|
|
75
91
|
// Unconditional, and the reason is the whole point of this feature: a
|
|
76
92
|
// crash-recovery snapshot is NOT a session-store entry. The snapshot
|
|
77
|
-
// file has just been retired by
|
|
78
|
-
// lands the recovered conversation exists only in memory — the
|
|
79
|
-
// below would name a session nothing could load, and `/session
|
|
80
|
-
// <id>` would still not reach it. The gap-closure persist above
|
|
81
|
-
// fires when the journal actually had records to replay, so it
|
|
82
|
-
// be relied on for this.
|
|
93
|
+
// file has just been retired by the read that loaded it, so until this
|
|
94
|
+
// write lands the recovered conversation exists only in memory — the
|
|
95
|
+
// pointer below would name a session nothing could load, and `/session
|
|
96
|
+
// resume <id>` would still not reach it. The gap-closure persist above
|
|
97
|
+
// only fires when the journal actually had records to replay, so it
|
|
98
|
+
// cannot be relied on for this.
|
|
83
99
|
persist(input.conversation.getMessageSnapshot() as ConversationMessageSnapshot[]);
|
|
84
100
|
input.writeLastSessionPointer(sessionId);
|
|
85
101
|
input.commandContext.session?.hydrateSessionUsage?.();
|
|
86
|
-
return
|
|
102
|
+
return result;
|
|
87
103
|
},
|
|
88
104
|
};
|
|
89
105
|
}
|
|
@@ -14,11 +14,14 @@
|
|
|
14
14
|
* Rules this flow keeps:
|
|
15
15
|
* - Nothing is ever applied without the user picking Resume. There is no
|
|
16
16
|
* silent auto-restore path here, and this module never loads a snapshot
|
|
17
|
-
* just to look at it — `checkRecoveryFile` reports,
|
|
18
|
-
*
|
|
19
|
-
* - The SDK's `
|
|
20
|
-
*
|
|
21
|
-
*
|
|
17
|
+
* just to look at it — `checkRecoveryFile` reports, and only an explicit
|
|
18
|
+
* yes reaches the code that opens the file.
|
|
19
|
+
* - The SDK's `applyRecoverySnapshot` is what runs on that yes, and it will
|
|
20
|
+
* not run without the confirmation token this flow hands it. It reads,
|
|
21
|
+
* retires and applies in one operation, load-then-delete: the file is
|
|
22
|
+
* retired only after a successful load, so a failed read can never destroy
|
|
23
|
+
* state that was never actually recovered — and a successful load can
|
|
24
|
+
* never leave a restored snapshot behind to be offered again.
|
|
22
25
|
* - Every fact shown is one we actually have (session id, snapshot age,
|
|
23
26
|
* title when the snapshot carries one, byte size when the file is where
|
|
24
27
|
* we can stat it). Nothing is estimated to fill the sentence out.
|
|
@@ -51,10 +54,11 @@
|
|
|
51
54
|
* decisions cannot quietly grow forever and nothing is deleted in silence.
|
|
52
55
|
*/
|
|
53
56
|
import { statSync } from 'node:fs';
|
|
54
|
-
import { checkRecoveryFile, checkRecoveryForSession,
|
|
57
|
+
import { checkRecoveryFile, checkRecoveryForSession, removeRecoveryPoint } from '@/runtime/index.ts';
|
|
58
|
+
import type { ApplyRecoverySnapshotResult } from '@pellux/goodvibes-sdk/platform/runtime/operations';
|
|
55
59
|
import type { RecoveryFileInfo, SessionSurface } from '@/runtime/index.ts';
|
|
56
60
|
import { isRecoveryRemovalRecorded, pruneRecoveryDecisions, recordRecoveryRemoval } from './recovery-decisions.ts';
|
|
57
|
-
import { checkSessionLiveness } from '
|
|
61
|
+
import { checkSessionLiveness } from '@pellux/goodvibes-sdk/platform/runtime/operations';
|
|
58
62
|
import type { SelectionItem, SelectionResult } from '../input/selection-modal.ts';
|
|
59
63
|
|
|
60
64
|
/** How the offer ended. Returned so startup wiring (and tests) can assert the real outcome. */
|
|
@@ -79,10 +83,16 @@ export interface RecoveryPromptDeps {
|
|
|
79
83
|
readonly openSelection: SelectionOpener | undefined;
|
|
80
84
|
/**
|
|
81
85
|
* Apply an explicitly-accepted snapshot to the live conversation and report
|
|
82
|
-
*
|
|
83
|
-
*
|
|
86
|
+
* what happened. Supplied by the startup wiring so this module never reaches
|
|
87
|
+
* into conversation state itself.
|
|
88
|
+
*
|
|
89
|
+
* It takes a session id, not a loaded snapshot: reading the snapshot and
|
|
90
|
+
* retiring it are one operation in the SDK's `applyRecoverySnapshot`, so this
|
|
91
|
+
* flow cannot load one and then leave it behind. That is why "could not read
|
|
92
|
+
* it" arrives here as a refusal in the result rather than as a separate
|
|
93
|
+
* consume step this module runs first.
|
|
84
94
|
*/
|
|
85
|
-
readonly applySnapshot: (payload: { readonly
|
|
95
|
+
readonly applySnapshot: (payload: { readonly sessionId: string }) => ApplyRecoverySnapshotResult;
|
|
86
96
|
/** One-line honest receipt into the transcript. */
|
|
87
97
|
readonly receipt: (line: string) => void;
|
|
88
98
|
readonly render: () => void;
|
|
@@ -301,16 +311,20 @@ export async function offerRecoverySnapshot(deps: RecoveryPromptDeps): Promise<R
|
|
|
301
311
|
const answer = await ask(open, RECOVERY_OFFER_TITLE, buildRecoveryOfferItems(facts));
|
|
302
312
|
|
|
303
313
|
if (answer === 'resume') {
|
|
304
|
-
const
|
|
305
|
-
if (!
|
|
306
|
-
//
|
|
307
|
-
//
|
|
308
|
-
|
|
314
|
+
const result = deps.applySnapshot({ sessionId: info.sessionId });
|
|
315
|
+
if (!result.applied) {
|
|
316
|
+
// Each refusal is a different fact and gets its own sentence, because
|
|
317
|
+
// "left on disk, try again next launch" and "the file was damaged and
|
|
318
|
+
// is gone" are not the same news.
|
|
319
|
+
deps.receipt(
|
|
320
|
+
result.refusal === 'no-snapshot'
|
|
321
|
+
? 'Recovery snapshot could not be read — it was left on disk and will be offered again next launch.'
|
|
322
|
+
: `Recovery snapshot for session ${info.sessionId} could not be restored — the file was damaged, and reading it retired it. Nothing was changed in this session.`,
|
|
323
|
+
);
|
|
309
324
|
deps.render();
|
|
310
325
|
return 'resume-failed';
|
|
311
326
|
}
|
|
312
|
-
|
|
313
|
-
deps.receipt(`Recovery snapshot restored: ${messageCount} message(s) from session ${info.sessionId}. The recovery point has been retired.`);
|
|
327
|
+
deps.receipt(`Recovery snapshot restored: ${result.messageCount} message(s) from session ${info.sessionId}. The recovery point has been retired.`);
|
|
314
328
|
deps.render();
|
|
315
329
|
return 'resumed';
|
|
316
330
|
}
|