@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
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hosted-session-feed.ts — what this terminal knows about the daemon-hosted
|
|
3
|
+
* session it is attached to.
|
|
4
|
+
*
|
|
5
|
+
* One place holds it because two surfaces read it: the `/hosted` command family
|
|
6
|
+
* (which prints status and drives the verbs) and the Hosted Session panel
|
|
7
|
+
* (which renders the conversation). The same shared-feed shape
|
|
8
|
+
* notifications-feed.ts already uses in this repo — a module-level instance a
|
|
9
|
+
* command writes and a panel subscribes to — rather than threading a service
|
|
10
|
+
* through the command context and the panel deps for one feature.
|
|
11
|
+
*
|
|
12
|
+
* ── What is a fact here, and what is a rendering ──────────────────────────
|
|
13
|
+
*
|
|
14
|
+
* The RECORD is the daemon's, verbatim: status, attached clients, turn count,
|
|
15
|
+
* and `effectiveDetachPolicy` — what leaving would do right now. This feed
|
|
16
|
+
* never recomputes those; it stores the last record a verb or a lifecycle event
|
|
17
|
+
* handed it.
|
|
18
|
+
*
|
|
19
|
+
* The ROWS are this terminal's rendering of the transcript: the history
|
|
20
|
+
* `attach` backfilled, plus what the live `turn` and `tools` frames have said
|
|
21
|
+
* since. A row that is still being streamed is marked `streaming` so the panel
|
|
22
|
+
* can show it growing rather than waiting for the turn to end — the same thing
|
|
23
|
+
* a local turn does.
|
|
24
|
+
*
|
|
25
|
+
* ── Bounded, because it is a live buffer ──────────────────────────────────
|
|
26
|
+
*
|
|
27
|
+
* Rows are capped and the oldest are dropped with an honest note saying how
|
|
28
|
+
* many, rather than growing without limit for a session that runs for days. The
|
|
29
|
+
* daemon keeps the authoritative transcript; a reattach backfills it again.
|
|
30
|
+
*/
|
|
31
|
+
import type {
|
|
32
|
+
HostedSessionHistoryMessage,
|
|
33
|
+
HostedSessionRecord,
|
|
34
|
+
} from '@pellux/goodvibes-sdk/platform/hosted-sessions';
|
|
35
|
+
import type { HostedSessionStreamEvent } from '../runtime/client/hosted-session-stream.ts';
|
|
36
|
+
|
|
37
|
+
/** How many transcript rows are kept before the oldest are dropped. */
|
|
38
|
+
export const MAX_HOSTED_ROWS = 400;
|
|
39
|
+
|
|
40
|
+
export type HostedRowKind = 'user' | 'assistant' | 'system' | 'tool' | 'error';
|
|
41
|
+
|
|
42
|
+
/** One rendered line-group of the hosted conversation. */
|
|
43
|
+
export interface HostedSessionRow {
|
|
44
|
+
readonly kind: HostedRowKind;
|
|
45
|
+
readonly text: string;
|
|
46
|
+
readonly at: number;
|
|
47
|
+
/** True while this row is still being appended to by a live stream. */
|
|
48
|
+
readonly streaming: boolean;
|
|
49
|
+
/** Set for tool rows: the call this row is about, so a cancel can name it. */
|
|
50
|
+
readonly callId?: string | undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** A tool call the stream says is running right now. */
|
|
54
|
+
export interface HostedRunningToolCall {
|
|
55
|
+
readonly callId: string;
|
|
56
|
+
readonly tool: string;
|
|
57
|
+
readonly startedAt: number;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Everything the panel renders and the command reports. */
|
|
61
|
+
export interface HostedSessionFeedState {
|
|
62
|
+
readonly record: HostedSessionRecord | null;
|
|
63
|
+
readonly rows: readonly HostedSessionRow[];
|
|
64
|
+
readonly runningToolCalls: readonly HostedRunningToolCall[];
|
|
65
|
+
/** True when a live event stream is open for this session. */
|
|
66
|
+
readonly streaming: boolean;
|
|
67
|
+
/** Why there is no live stream, when there is not one. Never a guess. */
|
|
68
|
+
readonly streamNote: string | null;
|
|
69
|
+
/** How many rows were dropped by the bound, for an honest header. */
|
|
70
|
+
readonly droppedRows: number;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const EMPTY_STATE: HostedSessionFeedState = {
|
|
74
|
+
record: null,
|
|
75
|
+
rows: [],
|
|
76
|
+
runningToolCalls: [],
|
|
77
|
+
streaming: false,
|
|
78
|
+
streamNote: null,
|
|
79
|
+
droppedRows: 0,
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
function historyRow(message: HostedSessionHistoryMessage): HostedSessionRow {
|
|
83
|
+
const kind: HostedRowKind = message.role === 'assistant'
|
|
84
|
+
? 'assistant'
|
|
85
|
+
: message.role === 'user' ? 'user' : message.role === 'tool' ? 'tool' : 'system';
|
|
86
|
+
return { kind, text: message.content, at: message.at ?? 0, streaming: false };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function readString(payload: Record<string, unknown>, key: string): string {
|
|
90
|
+
const value = payload[key];
|
|
91
|
+
return typeof value === 'string' ? value : '';
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The attached hosted session, as this terminal sees it.
|
|
96
|
+
*
|
|
97
|
+
* Deliberately a plain observable object rather than a store domain: nothing
|
|
98
|
+
* about a hosted session participates in the local conversation's state
|
|
99
|
+
* machine, and putting it there would make the local reducers answer for a
|
|
100
|
+
* conversation running in another process.
|
|
101
|
+
*/
|
|
102
|
+
export class HostedSessionFeed {
|
|
103
|
+
private state: HostedSessionFeedState = EMPTY_STATE;
|
|
104
|
+
private readonly listeners = new Set<() => void>();
|
|
105
|
+
/** The assistant row currently being streamed, by index into `rows`. */
|
|
106
|
+
private streamingRowIndex: number | null = null;
|
|
107
|
+
/**
|
|
108
|
+
* How to close the live subscription feeding this session, when one is open.
|
|
109
|
+
*
|
|
110
|
+
* Held here rather than in whichever module opened it because two unrelated
|
|
111
|
+
* callers have to be able to close it: the `/hosted detach` command, and the
|
|
112
|
+
* app's own exit path, which must leave the session cleanly before the
|
|
113
|
+
* process goes away.
|
|
114
|
+
*/
|
|
115
|
+
private streamCloser: (() => void) | null = null;
|
|
116
|
+
|
|
117
|
+
getState(): HostedSessionFeedState {
|
|
118
|
+
return this.state;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
subscribe(listener: () => void): () => void {
|
|
122
|
+
this.listeners.add(listener);
|
|
123
|
+
return () => { this.listeners.delete(listener); };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** True when this terminal is attached to a hosted session that has not ended. */
|
|
127
|
+
isAttached(): boolean {
|
|
128
|
+
return this.state.record !== null && this.state.record.status !== 'terminated';
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Start rendering a session: the record the verb returned and its backfilled history. */
|
|
132
|
+
attach(record: HostedSessionRecord, history: readonly HostedSessionHistoryMessage[]): void {
|
|
133
|
+
this.streamingRowIndex = null;
|
|
134
|
+
this.state = {
|
|
135
|
+
record,
|
|
136
|
+
rows: history.map(historyRow).slice(-MAX_HOSTED_ROWS),
|
|
137
|
+
runningToolCalls: [],
|
|
138
|
+
streaming: false,
|
|
139
|
+
streamNote: null,
|
|
140
|
+
droppedRows: Math.max(0, history.length - MAX_HOSTED_ROWS),
|
|
141
|
+
};
|
|
142
|
+
this.emit();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Replace the record — every verb answer and every lifecycle notice lands here. */
|
|
146
|
+
setRecord(record: HostedSessionRecord): void {
|
|
147
|
+
if (this.state.record && this.state.record.id !== record.id) return;
|
|
148
|
+
this.state = { ...this.state, record };
|
|
149
|
+
this.emit();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Say whether a live stream is open, and why not when it is not. */
|
|
153
|
+
setStreaming(streaming: boolean, note: string | null = null): void {
|
|
154
|
+
this.state = { ...this.state, streaming, streamNote: note };
|
|
155
|
+
this.emit();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Hand over the live subscription so any owner of this feed can close it. */
|
|
159
|
+
bindStream(close: (() => void) | null): void {
|
|
160
|
+
this.streamCloser = close;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Close the live subscription, if one is open. Idempotent. */
|
|
164
|
+
closeStream(): void {
|
|
165
|
+
const close = this.streamCloser;
|
|
166
|
+
this.streamCloser = null;
|
|
167
|
+
close?.();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Append a note of this terminal's own — a detach, a refusal, a stream drop. */
|
|
171
|
+
note(text: string, kind: HostedRowKind = 'system'): void {
|
|
172
|
+
this.appendRow({ kind, text, at: Date.now(), streaming: false });
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Forget the session entirely (detached, killed, or replaced). */
|
|
176
|
+
clear(): void {
|
|
177
|
+
this.closeStream();
|
|
178
|
+
this.streamingRowIndex = null;
|
|
179
|
+
this.state = EMPTY_STATE;
|
|
180
|
+
this.emit();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Fold one live frame into the transcript.
|
|
185
|
+
*
|
|
186
|
+
* Frames for another session never reach here (the stream filters on the id),
|
|
187
|
+
* but the guard stays because a feed that rendered another conversation's
|
|
188
|
+
* tokens would be indistinguishable from this one working.
|
|
189
|
+
*/
|
|
190
|
+
apply(event: HostedSessionStreamEvent): void {
|
|
191
|
+
if (!this.state.record || event.sessionId !== this.state.record.id) return;
|
|
192
|
+
if (event.domain === 'turn') { this.applyTurnEvent(event); return; }
|
|
193
|
+
this.applyToolEvent(event);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
private applyTurnEvent(event: HostedSessionStreamEvent): void {
|
|
197
|
+
switch (event.type) {
|
|
198
|
+
case 'TURN_SUBMITTED': {
|
|
199
|
+
const prompt = readString(event.payload, 'prompt');
|
|
200
|
+
if (prompt) this.appendRow({ kind: 'user', text: prompt, at: event.at, streaming: false });
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
case 'STREAM_DELTA': {
|
|
204
|
+
// `accumulated` is the whole response so far, which is exactly what a
|
|
205
|
+
// row wants: a client that joined mid-turn renders the full text rather
|
|
206
|
+
// than the tail it happened to catch.
|
|
207
|
+
const accumulated = readString(event.payload, 'accumulated')
|
|
208
|
+
|| readString(event.payload, 'content');
|
|
209
|
+
if (!accumulated) return;
|
|
210
|
+
this.updateStreamingRow(accumulated, event.at);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
case 'TURN_COMPLETED': {
|
|
214
|
+
const response = readString(event.payload, 'response');
|
|
215
|
+
if (response) this.updateStreamingRow(response, event.at);
|
|
216
|
+
this.settleStreamingRow();
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
case 'TURN_ERROR': {
|
|
220
|
+
this.settleStreamingRow();
|
|
221
|
+
this.appendRow({
|
|
222
|
+
kind: 'error',
|
|
223
|
+
text: readString(event.payload, 'error') || 'the hosted turn ended with an error',
|
|
224
|
+
at: event.at,
|
|
225
|
+
streaming: false,
|
|
226
|
+
});
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
case 'TURN_CANCEL': {
|
|
230
|
+
this.settleStreamingRow();
|
|
231
|
+
const reason = readString(event.payload, 'reason');
|
|
232
|
+
this.appendRow({
|
|
233
|
+
kind: 'system',
|
|
234
|
+
text: reason ? `turn cancelled — ${reason}` : 'turn cancelled',
|
|
235
|
+
at: event.at,
|
|
236
|
+
streaming: false,
|
|
237
|
+
});
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
default:
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
private applyToolEvent(event: HostedSessionStreamEvent): void {
|
|
246
|
+
const callId = readString(event.payload, 'callId');
|
|
247
|
+
const tool = readString(event.payload, 'tool');
|
|
248
|
+
if (!callId || !tool) return;
|
|
249
|
+
switch (event.type) {
|
|
250
|
+
case 'TOOL_EXECUTING': {
|
|
251
|
+
this.settleStreamingRow();
|
|
252
|
+
this.state = {
|
|
253
|
+
...this.state,
|
|
254
|
+
runningToolCalls: [
|
|
255
|
+
...this.state.runningToolCalls.filter((call) => call.callId !== callId),
|
|
256
|
+
{ callId, tool, startedAt: event.at },
|
|
257
|
+
],
|
|
258
|
+
};
|
|
259
|
+
this.appendRow({ kind: 'tool', text: `${tool} — running`, at: event.at, streaming: true, callId });
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
case 'TOOL_SUCCEEDED':
|
|
263
|
+
case 'TOOL_FAILED':
|
|
264
|
+
case 'TOOL_CANCELLED': {
|
|
265
|
+
const outcome = event.type === 'TOOL_SUCCEEDED'
|
|
266
|
+
? 'done'
|
|
267
|
+
: event.type === 'TOOL_FAILED'
|
|
268
|
+
? `failed — ${readString(event.payload, 'error') || 'no reason reported'}`
|
|
269
|
+
: `cancelled${readString(event.payload, 'reason') ? ` — ${readString(event.payload, 'reason')}` : ''}`;
|
|
270
|
+
this.state = {
|
|
271
|
+
...this.state,
|
|
272
|
+
runningToolCalls: this.state.runningToolCalls.filter((call) => call.callId !== callId),
|
|
273
|
+
rows: this.state.rows.map((row) => (
|
|
274
|
+
row.callId === callId && row.streaming
|
|
275
|
+
? { ...row, text: `${tool} — ${outcome}`, streaming: false }
|
|
276
|
+
: row
|
|
277
|
+
)),
|
|
278
|
+
};
|
|
279
|
+
this.emit();
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
default:
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/** Fold a hosted lifecycle transition in: the record, plus a line when it matters. */
|
|
288
|
+
applyLifecycle(update: { readonly event: string; readonly session: HostedSessionRecord; readonly detail?: string | undefined }): void {
|
|
289
|
+
this.setRecord(update.session);
|
|
290
|
+
if (update.event === 'hosted-session-terminated') {
|
|
291
|
+
this.settleStreamingRow();
|
|
292
|
+
const reason = update.session.terminatedReason ?? 'no reason recorded';
|
|
293
|
+
this.note(`this hosted session ended — ${reason}${update.detail ? ` (${update.detail})` : ''}`);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
private updateStreamingRow(text: string, at: number): void {
|
|
298
|
+
if (this.streamingRowIndex === null) {
|
|
299
|
+
this.appendRow({ kind: 'assistant', text, at, streaming: true });
|
|
300
|
+
this.streamingRowIndex = this.state.rows.length - 1;
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
const rows = [...this.state.rows];
|
|
304
|
+
const existing = rows[this.streamingRowIndex];
|
|
305
|
+
if (!existing) { this.streamingRowIndex = null; return; }
|
|
306
|
+
rows[this.streamingRowIndex] = { ...existing, text, at };
|
|
307
|
+
this.state = { ...this.state, rows };
|
|
308
|
+
this.emit();
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
private settleStreamingRow(): void {
|
|
312
|
+
if (this.streamingRowIndex === null) return;
|
|
313
|
+
const rows = [...this.state.rows];
|
|
314
|
+
const existing = rows[this.streamingRowIndex];
|
|
315
|
+
if (existing) rows[this.streamingRowIndex] = { ...existing, streaming: false };
|
|
316
|
+
this.streamingRowIndex = null;
|
|
317
|
+
this.state = { ...this.state, rows };
|
|
318
|
+
this.emit();
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
private appendRow(row: HostedSessionRow): void {
|
|
322
|
+
const rows = [...this.state.rows, row];
|
|
323
|
+
let dropped = this.state.droppedRows;
|
|
324
|
+
while (rows.length > MAX_HOSTED_ROWS) {
|
|
325
|
+
rows.shift();
|
|
326
|
+
dropped += 1;
|
|
327
|
+
if (this.streamingRowIndex !== null) this.streamingRowIndex -= 1;
|
|
328
|
+
}
|
|
329
|
+
if (this.streamingRowIndex !== null && this.streamingRowIndex < 0) this.streamingRowIndex = null;
|
|
330
|
+
this.state = { ...this.state, rows, droppedRows: dropped };
|
|
331
|
+
this.emit();
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
private emit(): void {
|
|
335
|
+
// Snapshot before iterating: a listener that unsubscribes during dispatch
|
|
336
|
+
// must not make the next listener be skipped.
|
|
337
|
+
for (const listener of [...this.listeners]) listener();
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
let sharedFeed: HostedSessionFeed | null = null;
|
|
342
|
+
|
|
343
|
+
/** The one feed the `/hosted` command writes and the Hosted Session panel reads. */
|
|
344
|
+
export function getSharedHostedSessionFeed(): HostedSessionFeed {
|
|
345
|
+
sharedFeed ??= new HostedSessionFeed();
|
|
346
|
+
return sharedFeed;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/** Tests build their own feed; this resets the shared one between them. */
|
|
350
|
+
export function resetSharedHostedSessionFeed(): void {
|
|
351
|
+
sharedFeed = null;
|
|
352
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HostedSessionPanel — the conversation the daemon is running, rendered here.
|
|
3
|
+
*
|
|
4
|
+
* A hosted session's turn arrives on the same `turn` and `tools` event domains
|
|
5
|
+
* a local turn does, so what this panel shows is the same thing the transcript
|
|
6
|
+
* shows for a local conversation: what was said, what tool is running, and
|
|
7
|
+
* whether text is still streaming. The difference is where the loop lives, and
|
|
8
|
+
* the header says so plainly — including `effectiveDetachPolicy`, so a person
|
|
9
|
+
* about to quit knows whether that ends the work.
|
|
10
|
+
*
|
|
11
|
+
* The panel renders; it decides nothing. Every fact in the header is a field of
|
|
12
|
+
* the daemon's own record (hosted-session-feed.ts), and the rows are the
|
|
13
|
+
* backfilled history plus the live frames. When there is no stream open the
|
|
14
|
+
* panel says why rather than showing a still conversation that looks finished.
|
|
15
|
+
*/
|
|
16
|
+
import type { Line } from '@pellux/goodvibes-sdk/platform/types';
|
|
17
|
+
import { BasePanel } from './base-panel.ts';
|
|
18
|
+
import {
|
|
19
|
+
buildEmptyState,
|
|
20
|
+
buildKeyboardHints,
|
|
21
|
+
buildPanelWorkspace,
|
|
22
|
+
buildStyledPanelLine,
|
|
23
|
+
DEFAULT_PANEL_PALETTE,
|
|
24
|
+
resolveScrollablePanelSection,
|
|
25
|
+
} from './polish.ts';
|
|
26
|
+
import { wrapText } from '../utils/terminal-width.ts';
|
|
27
|
+
import {
|
|
28
|
+
getSharedHostedSessionFeed,
|
|
29
|
+
type HostedSessionFeed,
|
|
30
|
+
type HostedSessionFeedState,
|
|
31
|
+
type HostedSessionRow,
|
|
32
|
+
} from './hosted-session-feed.ts';
|
|
33
|
+
|
|
34
|
+
const C = DEFAULT_PANEL_PALETTE;
|
|
35
|
+
|
|
36
|
+
const INTRO = 'A conversation whose loop runs inside the daemon. Steer it with /hosted say, leave it with /hosted detach — the header says what leaving would do.';
|
|
37
|
+
|
|
38
|
+
function rowColor(kind: HostedSessionRow['kind']): string {
|
|
39
|
+
switch (kind) {
|
|
40
|
+
case 'user': return C.accent;
|
|
41
|
+
case 'assistant': return C.value;
|
|
42
|
+
case 'tool': return C.info;
|
|
43
|
+
case 'error': return C.bad;
|
|
44
|
+
default: return C.dim;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function rowPrefix(kind: HostedSessionRow['kind']): string {
|
|
49
|
+
switch (kind) {
|
|
50
|
+
case 'user': return 'you';
|
|
51
|
+
case 'assistant': return 'hosted';
|
|
52
|
+
case 'tool': return 'tool';
|
|
53
|
+
case 'error': return 'error';
|
|
54
|
+
default: return '·';
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Every line one transcript row needs. Text is wrapped in full, never clipped. */
|
|
59
|
+
export function renderHostedRow(row: HostedSessionRow, width: number): Line[] {
|
|
60
|
+
const lines: Line[] = [];
|
|
61
|
+
const fg = rowColor(row.kind);
|
|
62
|
+
const marker = row.streaming ? ' …' : '';
|
|
63
|
+
const body = `${rowPrefix(row.kind)}: ${row.text}${marker}`;
|
|
64
|
+
const wrapWidth = Math.max(1, width - 1);
|
|
65
|
+
for (const [index, wrapped] of wrapText(body, wrapWidth).entries()) {
|
|
66
|
+
lines.push(buildStyledPanelLine(width, [{ text: ` ${wrapped}`, fg, bold: index === 0 && row.kind === 'user' }]));
|
|
67
|
+
}
|
|
68
|
+
return lines;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The header block: who this session is, what it is doing, and what leaving it
|
|
73
|
+
* would do. Every value comes from the record; nothing is derived locally.
|
|
74
|
+
*/
|
|
75
|
+
export function buildHostedHeaderLines(state: HostedSessionFeedState, width: number): Line[] {
|
|
76
|
+
const record = state.record;
|
|
77
|
+
if (!record) return [];
|
|
78
|
+
const policyText = record.effectiveDetachPolicy === 'survive'
|
|
79
|
+
? 'detaching keeps it running (survive)'
|
|
80
|
+
: 'detaching ends it (kill)';
|
|
81
|
+
const policySource = record.detachPolicy === null ? 'from the setting' : 'per-session override';
|
|
82
|
+
const rows: { text: string; fg: string }[] = [
|
|
83
|
+
{ text: `${record.title.trim() || record.id} — ${record.status}`, fg: C.header },
|
|
84
|
+
{ text: `id ${record.id}`, fg: C.dim },
|
|
85
|
+
{ text: `workspace ${record.workspaceRoot}`, fg: C.dim },
|
|
86
|
+
{
|
|
87
|
+
text: `${policyText}, ${policySource}`,
|
|
88
|
+
fg: record.effectiveDetachPolicy === 'survive' ? C.good : C.warn,
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
text: `${record.turnCount} turn(s), ${record.messageCount} message(s); attached: ${record.attachedClients.length > 0 ? record.attachedClients.join(', ') : 'nobody'}`,
|
|
92
|
+
fg: C.label,
|
|
93
|
+
},
|
|
94
|
+
];
|
|
95
|
+
if (record.status === 'terminated') {
|
|
96
|
+
rows.push({ text: `ended — ${record.terminatedReason ?? 'no reason recorded'}`, fg: C.bad });
|
|
97
|
+
}
|
|
98
|
+
if (record.restoredFromDisk) {
|
|
99
|
+
rows.push({ text: 'restored from disk after a daemon restart', fg: C.warn });
|
|
100
|
+
}
|
|
101
|
+
rows.push(state.streaming
|
|
102
|
+
? { text: 'live event stream open', fg: C.good }
|
|
103
|
+
: { text: `no live stream — ${state.streamNote ?? 'not subscribed'}`, fg: C.warn });
|
|
104
|
+
if (state.runningToolCalls.length > 0) {
|
|
105
|
+
rows.push({
|
|
106
|
+
text: `running: ${state.runningToolCalls.map((call) => `${call.tool} (${call.callId})`).join(', ')}`,
|
|
107
|
+
fg: C.info,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
if (state.droppedRows > 0) {
|
|
111
|
+
rows.push({ text: `${state.droppedRows} earlier row(s) dropped by this panel's buffer — reattach to backfill`, fg: C.dim });
|
|
112
|
+
}
|
|
113
|
+
const lines: Line[] = [];
|
|
114
|
+
for (const row of rows) {
|
|
115
|
+
for (const wrapped of wrapText(row.text, Math.max(1, width - 1))) {
|
|
116
|
+
lines.push(buildStyledPanelLine(width, [{ text: ` ${wrapped}`, fg: row.fg }]));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return lines;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export class HostedSessionPanel extends BasePanel {
|
|
123
|
+
private readonly unsubscribe: () => void;
|
|
124
|
+
private scrollOffset = 0;
|
|
125
|
+
/** Off until the user scrolls: a live conversation should follow its own tail. */
|
|
126
|
+
private pinnedToTail = true;
|
|
127
|
+
|
|
128
|
+
constructor(private readonly feed: HostedSessionFeed = getSharedHostedSessionFeed()) {
|
|
129
|
+
// '◈' verified free against the registered builtin panel icons.
|
|
130
|
+
super('hosted', 'Hosted Session', '◈', 'session');
|
|
131
|
+
this.unsubscribe = this.feed.subscribe(() => {
|
|
132
|
+
if (this.pinnedToTail) this.scrollOffset = Number.MAX_SAFE_INTEGER;
|
|
133
|
+
this.markDirty();
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
override onDestroy(): void {
|
|
138
|
+
this.unsubscribe();
|
|
139
|
+
super.onDestroy();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
handleInput(key: string): boolean {
|
|
143
|
+
switch (key) {
|
|
144
|
+
case 'up': return this.scroll(-1);
|
|
145
|
+
case 'down': return this.scroll(1);
|
|
146
|
+
case 'pageup': return this.scroll(-10);
|
|
147
|
+
case 'pagedown': return this.scroll(10);
|
|
148
|
+
case 'end': {
|
|
149
|
+
this.pinnedToTail = true;
|
|
150
|
+
this.scrollOffset = Number.MAX_SAFE_INTEGER;
|
|
151
|
+
this.markDirty();
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
default: return false;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private scroll(delta: number): boolean {
|
|
159
|
+
const rows = this.feed.getState().rows;
|
|
160
|
+
if (rows.length === 0) return false;
|
|
161
|
+
// Scrolling up unpins; reaching the bottom re-pins, so a reader who catches
|
|
162
|
+
// up starts following again without a second key.
|
|
163
|
+
this.pinnedToTail = false;
|
|
164
|
+
this.scrollOffset = Math.max(0, this.scrollOffset + delta);
|
|
165
|
+
this.markDirty();
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
render(width: number, height: number): Line[] {
|
|
170
|
+
if (width <= 0 || height <= 0) return [];
|
|
171
|
+
const state = this.feed.getState();
|
|
172
|
+
const hintRow = buildKeyboardHints(width, [
|
|
173
|
+
{ keys: 'Up/Down', label: 'scroll' },
|
|
174
|
+
{ keys: 'PgUp/PgDn', label: 'page' },
|
|
175
|
+
{ keys: 'End', label: 'follow' },
|
|
176
|
+
], DEFAULT_PANEL_PALETTE);
|
|
177
|
+
|
|
178
|
+
if (!state.record) {
|
|
179
|
+
return buildPanelWorkspace(width, height, {
|
|
180
|
+
title: ' Hosted Session',
|
|
181
|
+
intro: INTRO,
|
|
182
|
+
sections: [{
|
|
183
|
+
lines: buildEmptyState(
|
|
184
|
+
width,
|
|
185
|
+
' Not attached to a hosted session',
|
|
186
|
+
'A hosted session runs inside the daemon instead of this terminal, so it can outlive the window it was started from. Start one with /hosted new, or join one that already exists with /hosted list and /hosted attach <id>. Local sessions are unchanged and remain the default.',
|
|
187
|
+
[],
|
|
188
|
+
DEFAULT_PANEL_PALETTE,
|
|
189
|
+
),
|
|
190
|
+
}],
|
|
191
|
+
footerLines: [hintRow],
|
|
192
|
+
palette: DEFAULT_PANEL_PALETTE,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const headerSection = { lines: buildHostedHeaderLines(state, width - 2) };
|
|
197
|
+
const scrollableLines = state.rows.flatMap((row) => renderHostedRow(row, width - 2));
|
|
198
|
+
const section = resolveScrollablePanelSection(width, height, {
|
|
199
|
+
intro: INTRO,
|
|
200
|
+
footerLines: [hintRow],
|
|
201
|
+
palette: DEFAULT_PANEL_PALETTE,
|
|
202
|
+
beforeSections: [headerSection],
|
|
203
|
+
section: {
|
|
204
|
+
title: `Transcript (${state.rows.length})`,
|
|
205
|
+
scrollableLines,
|
|
206
|
+
scrollOffset: this.scrollOffset,
|
|
207
|
+
minRows: 4,
|
|
208
|
+
appendWindowSummary: scrollableLines.length > 0 ? { dimColor: DEFAULT_PANEL_PALETTE.dim } : undefined,
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
this.scrollOffset = section.scrollOffset;
|
|
212
|
+
|
|
213
|
+
return buildPanelWorkspace(width, height, {
|
|
214
|
+
title: ' Hosted Session',
|
|
215
|
+
intro: INTRO,
|
|
216
|
+
sections: [headerSection, section.section],
|
|
217
|
+
footerLines: [hintRow],
|
|
218
|
+
palette: DEFAULT_PANEL_PALETTE,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Line } from '
|
|
2
|
-
import { createEmptyLine } from '
|
|
1
|
+
import type { Line } from '@pellux/goodvibes-sdk/platform/types';
|
|
2
|
+
import { createEmptyLine } from '@pellux/goodvibes-sdk/platform/types';
|
|
3
3
|
import { fitDisplay, truncateDisplay } from '../utils/terminal-width.ts';
|
|
4
4
|
import { ScrollableListPanel } from './scrollable-list-panel.ts';
|
|
5
5
|
import {
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
type PanelPalette,
|
|
15
15
|
} from './polish.ts';
|
|
16
16
|
import type { LocalAuthSnapshot, UserAuthManager } from '@pellux/goodvibes-sdk/platform/security';
|
|
17
|
-
import type { LocalAuthInspectionQuery } from '
|
|
17
|
+
import type { LocalAuthInspectionQuery } from '@/runtime/index.ts';
|
|
18
18
|
import type { KeyName } from './types.ts';
|
|
19
19
|
import { isTextBackspace } from '../input/delete-key-policy.ts';
|
|
20
20
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
@@ -7,7 +7,7 @@ import type {
|
|
|
7
7
|
ConfigModalView,
|
|
8
8
|
} from '../../input/config-modal-types.ts';
|
|
9
9
|
import type { MintedPairingToken, PublicPairingToken } from '@pellux/goodvibes-sdk/platform/pairing';
|
|
10
|
-
import { formatDeviceLine, shortTokenId } from '
|
|
10
|
+
import { formatDeviceLine, shortTokenId } from '@pellux/goodvibes-sdk/platform/pairing';
|
|
11
11
|
|
|
12
12
|
// ---------------------------------------------------------------------------
|
|
13
13
|
// Paired Devices — the settings security-domain device/token management surface.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ConfigModalAction, ConfigModalActionContext, ConfigModalRow, ConfigModalSurface, ConfigModalView } from '../../input/config-modal-types.ts';
|
|
2
|
-
import type { LocalAuthInspectionQuery } from '
|
|
2
|
+
import type { LocalAuthInspectionQuery } from '@/runtime/index.ts';
|
|
3
3
|
import type { UserAuthManager } from '@pellux/goodvibes-sdk/platform/security';
|
|
4
4
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
5
5
|
import { postureLine, kv } from './modal-surface-helpers.ts';
|
|
@@ -7,7 +7,7 @@ import type {
|
|
|
7
7
|
ConfigModalView,
|
|
8
8
|
} from '../../input/config-modal-types.ts';
|
|
9
9
|
import { describeOriginPosture, generateQrMatrix, renderQrToString, type OriginPosture, type PairingHandoffOfferKind } from '@pellux/goodvibes-sdk/platform/pairing';
|
|
10
|
-
import { formatPairingOffers, formatPostureCapabilities, pairingPostureNotice } from '
|
|
10
|
+
import { formatPairingOffers, formatPostureCapabilities, pairingPostureNotice } from '@pellux/goodvibes-sdk/platform/pairing';
|
|
11
11
|
|
|
12
12
|
// ---------------------------------------------------------------------------
|
|
13
13
|
// Device Pairing config-modal surface. Shows the web-app origin, the per-device
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ConfigModalActionContext, ConfigModalSurface, ConfigModalView } from '../../input/config-modal-types.ts';
|
|
2
|
-
import type { ServiceInspectionQuery, SubscriptionAccessQuery } from '
|
|
2
|
+
import type { ServiceInspectionQuery, SubscriptionAccessQuery } from '@/runtime/index.ts';
|
|
3
3
|
import type { ServiceConfig, ServiceInspection, ServiceConnectionTestResult } from '@pellux/goodvibes-sdk/platform/config';
|
|
4
4
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
5
5
|
import { toneStyle, statusGlyph, pad, postureLine, kv, type Tone } from './modal-surface-helpers.ts';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ConfigModalAction, ConfigModalActionContext, ConfigModalRow, ConfigModalSurface, ConfigModalView } from '../../input/config-modal-types.ts';
|
|
2
2
|
import { getSettingsControlPlaneSnapshot } from '@/runtime/index.ts';
|
|
3
|
-
import type { ConfigManager } from '
|
|
3
|
+
import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
4
4
|
import { toneStyle, pad, postureLine, kv, type Tone } from './modal-surface-helpers.ts';
|
|
5
5
|
|
|
6
6
|
type Snapshot = ReturnType<typeof getSettingsControlPlaneSnapshot>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ConfigModalActionContext, ConfigModalSurface, ConfigModalView } from '../../input/config-modal-types.ts';
|
|
2
|
-
import type { ServiceInspectionQuery, SubscriptionAccessQuery } from '
|
|
2
|
+
import type { ServiceInspectionQuery, SubscriptionAccessQuery } from '@/runtime/index.ts';
|
|
3
3
|
import type { ProviderSubscription, PendingSubscriptionLogin } from '@pellux/goodvibes-sdk/platform/config';
|
|
4
4
|
import { listBuiltinSubscriptionProviders } from '@pellux/goodvibes-sdk/platform/config';
|
|
5
5
|
import { formatElapsed } from '../../utils/format-elapsed.ts';
|
|
@@ -4,7 +4,7 @@ import type {
|
|
|
4
4
|
ConfigModalSurface,
|
|
5
5
|
ConfigModalView,
|
|
6
6
|
} from '../../input/config-modal-types.ts';
|
|
7
|
-
import type { WorkPlanItemStatus } from '
|
|
7
|
+
import type { WorkPlanItemStatus } from '@pellux/goodvibes-sdk/platform/workflow';
|
|
8
8
|
|
|
9
9
|
// ---------------------------------------------------------------------------
|
|
10
10
|
// Work Plan → config-modal surface (group-B port). Lists the active
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* the conversation. Descriptive text (titles, bodies, reason descriptions)
|
|
9
9
|
* is always wrapped in full — never clipped to a column width.
|
|
10
10
|
*/
|
|
11
|
-
import type { Line } from '
|
|
11
|
+
import type { Line } from '@pellux/goodvibes-sdk/platform/types';
|
|
12
12
|
import type { RoutingDecision } from '@/runtime/index.ts';
|
|
13
13
|
import { BasePanel } from './base-panel.ts';
|
|
14
14
|
import {
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
// way don't each hand-roll the same field + dispatch + render wiring.
|
|
13
13
|
// ---------------------------------------------------------------------------
|
|
14
14
|
|
|
15
|
-
import type { Line } from '
|
|
15
|
+
import type { Line } from '@pellux/goodvibes-sdk/platform/types';
|
|
16
16
|
import { type ConfirmState, handleConfirmInput, renderConfirmLines } from './confirm-state.ts';
|
|
17
17
|
|
|
18
18
|
export interface PanelConfirmOverlayHandlers {
|