@pellux/goodvibes-tui 1.27.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +125 -2
- package/README.md +7 -9
- package/bin/goodvibes +2 -2
- package/bin/launcher-support.js +8 -4
- package/docs/README.md +11 -2
- package/docs/foundation-artifacts/operator-contract.json +6403 -2347
- package/package.json +14 -25
- package/scripts/postinstall.js +35 -35
- package/src/audio/spoken-turn-model-routing.ts +1 -1
- package/src/cli/bundle-command.ts +5 -5
- package/src/cli/completions/generate.ts +1 -1
- package/src/cli/doctor.ts +3 -3
- package/src/cli/entrypoint.ts +12 -7
- package/src/cli/help.ts +4 -5
- package/src/cli/hooks-command.ts +1 -1
- package/src/cli/index.ts +1 -4
- package/src/cli/launch-auto-update.ts +59 -7
- package/src/cli/management-commands.ts +6 -6
- package/src/cli/management-utils.ts +5 -5
- package/src/cli/management.ts +7 -7
- package/src/cli/network-posture.ts +1 -1
- package/src/cli/package-verification.ts +7 -6
- package/src/cli/plugin-bundles-command.ts +1 -1
- package/src/cli/plugin-command.ts +2 -2
- package/src/cli/service-command.ts +2 -2
- package/src/cli/service-posture.ts +4 -4
- package/src/cli/status.ts +9 -8
- package/src/cli/surface-command.ts +4 -4
- package/src/cli/tui-startup.ts +12 -3
- package/src/cli-flags.ts +3 -3
- package/src/config/goodvibes-home-audit.ts +0 -2
- package/src/config/secret-config.ts +37 -5
- package/src/config/tui-extension-settings.ts +1 -1
- package/src/core/approval-alert.ts +2 -2
- package/src/core/budget-breach-notifier.ts +3 -3
- package/src/core/conversation-compaction-render.ts +1 -1
- package/src/core/conversation-line-cache.ts +1 -1
- package/src/core/conversation-render-context.ts +1 -1
- package/src/core/conversation-rendering.ts +2 -2
- package/src/core/conversation.ts +10 -10
- package/src/core/long-task-notifier.ts +2 -2
- package/src/core/memory-diagnostics-gateway.ts +1 -1
- package/src/core/scriptable-statusline.ts +1 -1
- package/src/core/session-auto-titler.ts +1 -1
- package/src/core/session-resume-core.ts +2 -2
- package/src/core/stream-event-wiring.ts +24 -44
- package/src/core/terminal-notifier.ts +3 -3
- package/src/core/turn-cancellation.ts +1 -1
- package/src/core/turn-event-wiring.ts +5 -5
- package/src/input/command-registry.ts +6 -6
- package/src/input/commands/channel-pairing.ts +1 -1
- package/src/input/commands/cluster-runtime.ts +1 -1
- package/src/input/commands/codebase-runtime.ts +1 -1
- package/src/input/commands/devices-runtime.ts +1 -1
- package/src/input/commands/hosted-runtime.ts +330 -0
- package/src/input/commands/local-setup-review.ts +1 -1
- package/src/input/commands/local-setup-transfer.ts +2 -2
- package/src/input/commands/local-setup.ts +3 -3
- package/src/input/commands/managed-runtime.ts +2 -2
- package/src/input/commands/operator-rpc.ts +24 -110
- package/src/input/commands/payment-card-intake.ts +1 -1
- package/src/input/commands/permissions-provenance.ts +2 -2
- package/src/input/commands/plugin-runtime.ts +1 -1
- package/src/input/commands/provider.ts +1 -1
- package/src/input/commands/relay-runtime.ts +18 -9
- package/src/input/commands/rewind-runtime.ts +1 -1
- package/src/input/commands/runtime-services.ts +1 -1
- package/src/input/commands/services-runtime.ts +1 -1
- package/src/input/commands/session-workflow.ts +1 -1
- package/src/input/commands/settings-sync-runtime.ts +2 -2
- package/src/input/commands/share-runtime.ts +1 -1
- package/src/input/commands/tasks-runtime.ts +145 -55
- package/src/input/commands/update-runtime.ts +31 -28
- package/src/input/commands/work-plan-runtime.ts +2 -2
- package/src/input/commands/workstream-attempts.ts +1 -1
- package/src/input/commands/workstream-runtime.ts +2 -2
- package/src/input/commands.ts +2 -0
- package/src/input/feature-unit-layout.ts +1 -1
- package/src/input/feed-context-factory.ts +3 -3
- package/src/input/handler-content-actions.ts +2 -2
- package/src/input/handler-feed-routes.ts +1 -1
- package/src/input/handler-feed.ts +3 -3
- package/src/input/handler-modal-token-routes.ts +1 -1
- package/src/input/handler-onboarding-daemon-adopt.ts +3 -2
- package/src/input/handler-onboarding.ts +1 -1
- package/src/input/handler-types.ts +2 -2
- package/src/input/handler-ui-state.ts +1 -1
- package/src/input/handler.ts +4 -4
- package/src/input/input-history.ts +1 -1
- package/src/input/onboarding/onboarding-feature-units.ts +2 -2
- package/src/input/onboarding/onboarding-runtime-status.ts +15 -1
- package/src/input/onboarding/onboarding-wizard-apply.ts +17 -8
- package/src/input/onboarding/onboarding-wizard-cloudflare-step.ts +5 -3
- package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +1 -1
- package/src/input/onboarding/onboarding-wizard-types.ts +1 -1
- package/src/input/payments-config.ts +6 -6
- package/src/input/search.ts +1 -1
- package/src/input/session-picker-modal.ts +46 -0
- package/src/input/settings-modal-activation.ts +1 -1
- package/src/input/settings-modal-behavior.ts +4 -4
- package/src/input/settings-modal-commit.ts +11 -0
- package/src/input/settings-modal-data.ts +13 -80
- package/src/input/settings-modal-mutations.ts +33 -0
- package/src/input/settings-modal-secrets.ts +34 -0
- package/src/input/settings-modal-subscriptions.ts +1 -1
- package/src/input/settings-modal-types.ts +15 -1
- package/src/input/settings-modal.ts +28 -3
- package/src/input/worktree-setup-config.ts +1 -1
- package/src/main.ts +11 -9
- package/src/panels/agent-inspector-shared.ts +2 -2
- package/src/panels/base-panel.ts +2 -2
- package/src/panels/builtin/operations.ts +6 -8
- package/src/panels/builtin/session.ts +17 -0
- package/src/panels/builtin/shared.ts +5 -5
- package/src/panels/builtin-modals.ts +3 -3
- package/src/panels/confirm-state.ts +1 -1
- package/src/panels/cost-tracker-panel.ts +4 -4
- package/src/panels/diff-panel.ts +2 -2
- package/src/panels/diff-review-panel.ts +2 -2
- package/src/panels/expandable-list-panel.ts +2 -2
- package/src/panels/fleet-acts.ts +1 -1
- package/src/panels/fleet-observed-render.ts +1 -1
- package/src/panels/fleet-panel-format.ts +1 -1
- package/src/panels/fleet-panel.ts +1 -1
- package/src/panels/fleet-spawn.ts +1 -1
- package/src/panels/fleet-steer.ts +1 -1
- package/src/panels/fleet-transcript.ts +1 -1
- package/src/panels/git-panel.ts +1 -1
- package/src/panels/hosted-session-feed.ts +352 -0
- package/src/panels/hosted-session-panel.ts +221 -0
- package/src/panels/local-auth-panel.ts +3 -3
- package/src/panels/modals/devices-modal.ts +1 -1
- package/src/panels/modals/local-auth-modal.ts +1 -1
- package/src/panels/modals/pairing-modal.ts +1 -1
- package/src/panels/modals/services-modal.ts +1 -1
- package/src/panels/modals/settings-sync-modal.ts +1 -1
- package/src/panels/modals/subscription-modal.ts +1 -1
- package/src/panels/modals/work-plan-modal.ts +1 -1
- package/src/panels/notifications-panel.ts +1 -1
- package/src/panels/panel-confirm-overlay.ts +1 -1
- package/src/panels/plugins-panel.ts +2 -2
- package/src/panels/polish-core.ts +2 -2
- package/src/panels/polish-tables.ts +1 -1
- package/src/panels/polish.ts +2 -2
- package/src/panels/scrollable-list-panel.ts +3 -3
- package/src/panels/skills-panel.ts +3 -3
- package/src/panels/token-budget-panel.ts +5 -5
- package/src/panels/types.ts +1 -2
- package/src/permissions/prompt.ts +1 -1
- package/src/renderer/autocomplete-overlay.ts +1 -1
- package/src/renderer/block-actions-overlay.ts +1 -1
- package/src/renderer/bookmark-modal.ts +1 -1
- package/src/renderer/buffer.ts +1 -1
- package/src/renderer/code-block.ts +1 -1
- package/src/renderer/composer-fragments.ts +1 -1
- package/src/renderer/compositor.ts +2 -2
- package/src/renderer/config-modal.ts +1 -1
- package/src/renderer/context-inspector.ts +1 -1
- package/src/renderer/conversation-layout.ts +2 -2
- package/src/renderer/conversation-overlays.ts +1 -1
- package/src/renderer/conversation-surface.ts +2 -2
- package/src/renderer/diff-view.ts +1 -1
- package/src/renderer/diff.ts +1 -1
- package/src/renderer/file-picker-overlay.ts +1 -1
- package/src/renderer/fleet-tab-strip.ts +1 -1
- package/src/renderer/fullscreen-primitives.ts +2 -2
- package/src/renderer/fullscreen-workspace.ts +1 -1
- package/src/renderer/header-line.ts +1 -1
- package/src/renderer/help-overlay.ts +1 -1
- package/src/renderer/history-search-overlay.ts +2 -2
- package/src/renderer/layout.ts +5 -8
- package/src/renderer/markdown-inline.ts +1 -1
- package/src/renderer/markdown-table.ts +1 -1
- package/src/renderer/markdown.ts +1 -1
- package/src/renderer/mcp-workspace.ts +1 -1
- package/src/renderer/modal-factory.ts +2 -2
- package/src/renderer/model-workspace.ts +2 -2
- package/src/renderer/onboarding/onboarding-wizard.ts +1 -1
- package/src/renderer/overlay-box.ts +1 -1
- package/src/renderer/panel-composite.ts +1 -1
- package/src/renderer/panel-workspace-bar.ts +1 -1
- package/src/renderer/process-indicator.ts +1 -1
- package/src/renderer/profile-picker-modal.ts +1 -1
- package/src/renderer/search-overlay.ts +2 -2
- package/src/renderer/selection-modal-overlay.ts +1 -1
- package/src/renderer/session-picker-modal.ts +124 -3
- package/src/renderer/settings-modal-helpers.ts +2 -0
- package/src/renderer/settings-modal.ts +2 -2
- package/src/renderer/shell-surface.ts +2 -2
- package/src/renderer/system-message.ts +1 -1
- package/src/renderer/tab-strip.ts +1 -1
- package/src/renderer/theme-mode-config.ts +1 -1
- package/src/renderer/theme.ts +11 -12
- package/src/renderer/thinking.ts +1 -1
- package/src/renderer/tool-call.ts +2 -2
- package/src/renderer/tool-result-expanded-lines.ts +1 -1
- package/src/renderer/turn-injection.ts +7 -8
- package/src/renderer/ui-factory.ts +3 -3
- package/src/runtime/bootstrap-command-context.ts +4 -4
- package/src/runtime/bootstrap-command-parts.ts +5 -5
- package/src/runtime/bootstrap-core.ts +30 -30
- package/src/runtime/bootstrap-shell.ts +4 -4
- package/src/runtime/bootstrap.ts +128 -157
- package/src/runtime/client/approval-updates.ts +82 -0
- package/src/runtime/client/build-floors.ts +167 -0
- package/src/runtime/client/daemon-attach-handshake.ts +115 -0
- package/src/runtime/client/fleet-union.ts +147 -0
- package/src/runtime/client/host-service-status.ts +71 -0
- package/src/runtime/client/hosted-exit.ts +86 -0
- package/src/runtime/client/hosted-roster.ts +110 -0
- package/src/runtime/client/hosted-session-stream.ts +178 -0
- package/src/runtime/client/hosted-sessions.ts +187 -0
- package/src/runtime/client/operator-endpoint.ts +213 -0
- package/src/runtime/client/spine-adoption.ts +99 -0
- package/src/runtime/cloudflare-control-plane.ts +1 -1
- package/src/runtime/context-accounting-source.ts +1 -1
- package/src/runtime/context.ts +2 -2
- package/src/runtime/conversation-rewind-port.ts +42 -14
- package/src/runtime/daemon-attach-notices.ts +55 -19
- package/src/runtime/diagnostics/panels/ops.ts +1 -1
- package/src/runtime/disposal-wiring.ts +4 -24
- package/src/runtime/index.ts +119 -105
- package/src/runtime/install-self-check-startup.ts +2 -2
- package/src/runtime/interaction-seams.ts +1 -1
- package/src/runtime/knowledge-services.ts +8 -2
- package/src/runtime/legacy-daemon-migration.ts +15 -3
- package/src/runtime/legacy-daemon-reconcile.ts +3 -2
- package/src/runtime/memory-spine-transport.ts +37 -267
- package/src/runtime/notification-dispatch.ts +2 -2
- package/src/runtime/onboarding/apply.ts +2 -2
- package/src/runtime/onboarding/derivation.ts +2 -2
- package/src/runtime/onboarding/markers.ts +2 -2
- package/src/runtime/onboarding/progress.ts +2 -2
- package/src/runtime/onboarding/snapshot.ts +1 -1
- package/src/runtime/onboarding/state.ts +2 -2
- package/src/runtime/onboarding/types.ts +2 -2
- package/src/runtime/orchestrator-core-services.ts +1 -1
- package/src/runtime/path-shadow-startup.ts +28 -221
- package/src/runtime/power-keepawake-remote.ts +3 -2
- package/src/runtime/process-lifecycle.ts +2 -2
- package/src/runtime/provider-fallback.ts +10 -66
- package/src/runtime/recovery-autosave.ts +1 -1
- package/src/runtime/recovery-offer-wiring.ts +39 -23
- package/src/runtime/recovery-prompt.ts +31 -17
- package/src/runtime/relay-reachability-bridge.ts +53 -42
- package/src/runtime/release-artifacts.ts +2 -1
- package/src/runtime/runtime-services-types.ts +82 -35
- package/src/runtime/services.ts +305 -300
- package/src/runtime/session-resume-liveness-confirm.ts +1 -1
- package/src/runtime/ui-services.ts +7 -6
- package/src/runtime/update-check.ts +23 -43
- package/src/runtime/workspace-checkpointing.ts +1 -1
- package/src/runtime/wrfc-persistence.ts +2 -2
- package/src/shell/ui-openers.ts +30 -7
- package/src/utils/clipboard.ts +1 -1
- package/src/verification/verification-ledger.ts +10 -8
- package/src/version.ts +1 -1
- package/bin/goodvibes-daemon +0 -90
- package/src/cli/config-overrides.ts +0 -267
- package/src/cli/endpoints.ts +0 -109
- package/src/cli/parser.ts +0 -395
- package/src/cli/redaction.ts +0 -170
- package/src/cli/types.ts +0 -84
- package/src/cluster/commands.ts +0 -382
- package/src/cluster/remote-daemon-target.ts +0 -225
- package/src/cluster/render.ts +0 -268
- package/src/config/atomic-write.ts +0 -70
- package/src/config/credential-availability.ts +0 -122
- package/src/config/goodvibes-home.ts +0 -149
- package/src/config/index.ts +0 -88
- package/src/config/provider-model.ts +0 -23
- package/src/config/read-versioned.ts +0 -228
- package/src/core/alert-gating.ts +0 -67
- package/src/core/consolidation-receipt.ts +0 -41
- package/src/core/focus-tracker.ts +0 -41
- package/src/core/history.ts +0 -45
- package/src/core/memory-status.ts +0 -118
- package/src/core/orchestrator.ts +0 -7
- package/src/core/pairing-devices.ts +0 -49
- package/src/core/pairing-handoff.ts +0 -101
- package/src/core/pairing-offers.ts +0 -83
- package/src/core/pairing-origin.ts +0 -78
- package/src/core/rewind-turn-anchors.ts +0 -311
- package/src/core/session-recovery.ts +0 -210
- package/src/core/stable-host.ts +0 -171
- package/src/core/transcript-journal.ts +0 -503
- package/src/daemon/cli.ts +0 -641
- package/src/daemon/handlers/context.ts +0 -29
- package/src/daemon/handlers/contracts.ts +0 -77
- package/src/daemon/handlers/credentials.ts +0 -139
- package/src/daemon/handlers/drafts/draft-store.ts +0 -427
- package/src/daemon/handlers/drafts/index.ts +0 -17
- package/src/daemon/handlers/drafts/register.ts +0 -331
- package/src/daemon/handlers/errors.ts +0 -18
- package/src/daemon/handlers/inbox/cursor-store.ts +0 -457
- package/src/daemon/handlers/inbox/index.ts +0 -284
- package/src/daemon/handlers/inbox/mapping.ts +0 -192
- package/src/daemon/handlers/inbox/poller.ts +0 -212
- package/src/daemon/handlers/inbox/provider-adapter.ts +0 -156
- package/src/daemon/handlers/inbox/providers/discord.ts +0 -252
- package/src/daemon/handlers/inbox/providers/email.ts +0 -152
- package/src/daemon/handlers/inbox/providers/imap-client.ts +0 -300
- package/src/daemon/handlers/inbox/providers/route-util.ts +0 -24
- package/src/daemon/handlers/inbox/providers/slack.ts +0 -263
- package/src/daemon/handlers/index.ts +0 -117
- package/src/daemon/handlers/register.ts +0 -168
- package/src/daemon/handlers/remote/backends/cloud-terminal.ts +0 -142
- package/src/daemon/handlers/remote/backends/docker.ts +0 -79
- package/src/daemon/handlers/remote/backends/index.ts +0 -40
- package/src/daemon/handlers/remote/backends/local-process.ts +0 -113
- package/src/daemon/handlers/remote/backends/process-runner.ts +0 -127
- package/src/daemon/handlers/remote/backends/ssh.ts +0 -125
- package/src/daemon/handlers/remote/backends/types.ts +0 -97
- package/src/daemon/handlers/remote/dispatcher.ts +0 -181
- package/src/daemon/handlers/remote/index.ts +0 -119
- package/src/daemon/handlers/remote/peer-registry.ts +0 -357
- package/src/daemon/handlers/remote/service.ts +0 -191
- package/src/daemon/handlers/routing/inbox-bridge.ts +0 -71
- package/src/daemon/handlers/routing/index.ts +0 -261
- package/src/daemon/handlers/routing/route-store.ts +0 -319
- package/src/daemon/handlers/routing/routing-resolver.ts +0 -75
- package/src/daemon/handlers/sqlite-store.ts +0 -278
- package/src/daemon/handlers/triage/index.ts +0 -57
- package/src/daemon/handlers/triage/integration.ts +0 -213
- package/src/daemon/handlers/triage/pipeline.ts +0 -274
- package/src/daemon/handlers/triage/scorer.ts +0 -287
- package/src/daemon/handlers/triage/tagger/discord.ts +0 -187
- package/src/daemon/handlers/triage/tagger/imap.ts +0 -384
- package/src/daemon/handlers/triage/tagger/index.ts +0 -184
- package/src/daemon/handlers/triage/tagger/shared.ts +0 -70
- package/src/daemon/handlers/triage/tagger/slack.ts +0 -69
- package/src/daemon/handlers/triage/types.ts +0 -50
- package/src/daemon/lifecycle.ts +0 -41
- package/src/daemon/provision-wake-model.ts +0 -81
- package/src/daemon/safe-serve.ts +0 -61
- package/src/daemon/send/channels.ts +0 -200
- package/src/daemon/send/command.ts +0 -332
- package/src/daemon/send/composition.ts +0 -99
- package/src/daemon/send/failure-text.ts +0 -93
- package/src/daemon/send/inert-text.ts +0 -225
- package/src/daemon/send/stdin.ts +0 -24
- package/src/daemon/service-commands.ts +0 -329
- package/src/export/cost-utils.ts +0 -319
- package/src/input/selection.ts +0 -135
- package/src/plugins/loader.ts +0 -15
- package/src/renderer/bottom-bar.ts +0 -58
- package/src/renderer/conversation-tree.ts +0 -228
- package/src/runtime/agent-graph-composition.ts +0 -78
- package/src/runtime/channel-composition.ts +0 -46
- package/src/runtime/cluster-composition.ts +0 -124
- package/src/runtime/cluster-group-composition.ts +0 -284
- package/src/runtime/code-index-services.ts +0 -167
- package/src/runtime/daemon-handler-composition.ts +0 -76
- package/src/runtime/device-posture-composition.ts +0 -110
- package/src/runtime/durability-housekeeping.ts +0 -183
- package/src/runtime/durability-services.ts +0 -155
- package/src/runtime/embedded-service-factories.ts +0 -58
- package/src/runtime/feature-settings.ts +0 -154
- package/src/runtime/fleet-needs-input-push.ts +0 -70
- package/src/runtime/fleet-services.ts +0 -41
- package/src/runtime/idle-power-services.ts +0 -85
- package/src/runtime/mail-composition.ts +0 -65
- package/src/runtime/memory-fold.ts +0 -55
- package/src/runtime/operator-token-cleanup.ts +0 -109
- package/src/runtime/path-shadow.ts +0 -418
- package/src/runtime/perf/panel-health-monitor.ts +0 -18
- package/src/runtime/provider-optimizer-wiring.ts +0 -55
- package/src/runtime/reachability-notice.ts +0 -100
- package/src/runtime/remote-execution-composition.ts +0 -62
- package/src/runtime/session-liveness-marker.ts +0 -257
- package/src/runtime/session-pointer-surface.ts +0 -22
- package/src/runtime/session-storage-services.ts +0 -41
- package/src/runtime/surface-feature-flags.ts +0 -96
- package/src/runtime/terminal-output-guard.ts +0 -235
- package/src/runtime/trust/workspace-registration.ts +0 -192
- package/src/runtime/trust/workspace-trust.ts +0 -229
- package/src/runtime/ui/index.ts +0 -39
- package/src/runtime/ui/model-picker/data-provider.ts +0 -182
- package/src/runtime/ui/model-picker/health-enrichment.ts +0 -231
- package/src/runtime/ui/model-picker/index.ts +0 -59
- package/src/runtime/ui/model-picker/types.ts +0 -153
- package/src/runtime/ui/provider-health/data-provider.ts +0 -244
- package/src/runtime/ui/provider-health/fallback-visualizer.ts +0 -71
- package/src/runtime/ui/provider-health/index.ts +0 -46
- package/src/runtime/ui/provider-health/types.ts +0 -146
- package/src/runtime/ui-events.ts +0 -1
- package/src/runtime/ui-read-model-helpers.ts +0 -1
- package/src/runtime/ui-read-models-observability-maintenance.ts +0 -1
- package/src/runtime/ui-read-models-observability-options.ts +0 -1
- package/src/runtime/ui-read-models-observability-remote.ts +0 -1
- package/src/runtime/ui-read-models-observability-security.ts +0 -1
- package/src/runtime/ui-read-models-observability-system.ts +0 -1
- package/src/runtime/ui-read-models-observability.ts +0 -1
- package/src/runtime/ui-service-queries.ts +0 -1
- package/src/runtime/voice-setup-services.ts +0 -98
- package/src/runtime/workstream-attempts-validation.ts +0 -77
- package/src/runtime/workstream-draft-edits.ts +0 -95
- package/src/runtime/workstream-draft-store.ts +0 -274
- package/src/runtime/workstream-draft-types.ts +0 -42
- package/src/runtime/workstream-services.ts +0 -373
- package/src/tools/index.ts +0 -1
- package/src/types/grid.ts +0 -48
- package/src/types/sql-js.d.ts +0 -15
- package/src/work-plans/work-plan-store.ts +0 -702
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hosted-roster.ts — the last answer the daemon gave about what it is hosting.
|
|
3
|
+
*
|
|
4
|
+
* The session picker is where a person goes to ask "what sessions are there?",
|
|
5
|
+
* and the honest answer includes conversations running inside the
|
|
6
|
+
* daemon — not only this terminal's saved transcripts and the cross-surface
|
|
7
|
+
* union. The picker is a synchronous render over modal state, though, and
|
|
8
|
+
* `sessions.hosted.list` is a round trip. This roster is the seam between them:
|
|
9
|
+
* it holds the last list the daemon answered with, refreshes on demand, and
|
|
10
|
+
* says plainly when it has never been able to ask.
|
|
11
|
+
*
|
|
12
|
+
* ── Never a fabricated empty list ─────────────────────────────────────────
|
|
13
|
+
*
|
|
14
|
+
* `capturedAt === null` means nobody has successfully read the roster yet,
|
|
15
|
+
* which is a different fact from "the daemon hosts nothing" and renders
|
|
16
|
+
* differently. A refresh that fails keeps the LAST known rows and records the
|
|
17
|
+
* reason, on the same reasoning the fleet poller keeps its rows through a blip:
|
|
18
|
+
* a momentary refusal should not make half the picker blink out.
|
|
19
|
+
*
|
|
20
|
+
* ── Why the client is bound rather than passed ────────────────────────────
|
|
21
|
+
*
|
|
22
|
+
* The picker is constructed in the input layer, which has no config manager and
|
|
23
|
+
* no home directory to resolve a daemon from. The composition root
|
|
24
|
+
* (runtime/services.ts) has both and already builds the verb caller, so it binds
|
|
25
|
+
* the client here once — the same shape the shared notification feed uses for a
|
|
26
|
+
* surface that two unrelated layers read.
|
|
27
|
+
*/
|
|
28
|
+
import type { HostedSessionRecord, HostedSessionsClient } from './hosted-sessions.ts';
|
|
29
|
+
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
30
|
+
|
|
31
|
+
export interface HostedRosterSnapshot {
|
|
32
|
+
readonly sessions: readonly HostedSessionRecord[];
|
|
33
|
+
/** When the daemon last answered, or null when it never has. */
|
|
34
|
+
readonly capturedAt: number | null;
|
|
35
|
+
/** Why the roster is not current. Null when the last read succeeded. */
|
|
36
|
+
readonly note: string | null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const NEVER_READ: HostedRosterSnapshot = { sessions: [], capturedAt: null, note: null };
|
|
40
|
+
|
|
41
|
+
export class HostedSessionRoster {
|
|
42
|
+
private state: HostedRosterSnapshot = NEVER_READ;
|
|
43
|
+
private client: HostedSessionsClient | null = null;
|
|
44
|
+
private readonly listeners = new Set<() => void>();
|
|
45
|
+
private inFlight: Promise<void> | null = null;
|
|
46
|
+
|
|
47
|
+
bindClient(client: HostedSessionsClient | null): void {
|
|
48
|
+
this.client = client;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
snapshot(): HostedRosterSnapshot {
|
|
52
|
+
return this.state;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
subscribe(listener: () => void): () => void {
|
|
56
|
+
this.listeners.add(listener);
|
|
57
|
+
return () => { this.listeners.delete(listener); };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Record a list a caller already has in hand (the `/hosted list` path). */
|
|
61
|
+
accept(sessions: readonly HostedSessionRecord[]): void {
|
|
62
|
+
this.state = { sessions, capturedAt: Date.now(), note: null };
|
|
63
|
+
this.emit();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Ask the daemon again. Never throws and never runs twice at once — a picker
|
|
68
|
+
* opened repeatedly must not stack round trips.
|
|
69
|
+
*/
|
|
70
|
+
async refresh(): Promise<void> {
|
|
71
|
+
if (this.inFlight) return await this.inFlight;
|
|
72
|
+
const client = this.client;
|
|
73
|
+
if (!client) {
|
|
74
|
+
this.state = {
|
|
75
|
+
...this.state,
|
|
76
|
+
note: 'no daemon client is wired in this terminal, so hosted sessions cannot be listed',
|
|
77
|
+
};
|
|
78
|
+
this.emit();
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
this.inFlight = (async () => {
|
|
82
|
+
try {
|
|
83
|
+
this.accept(await client.list());
|
|
84
|
+
} catch (error) {
|
|
85
|
+
this.state = { ...this.state, note: `the daemon did not answer: ${summarizeError(error)}` };
|
|
86
|
+
this.emit();
|
|
87
|
+
} finally {
|
|
88
|
+
this.inFlight = null;
|
|
89
|
+
}
|
|
90
|
+
})();
|
|
91
|
+
await this.inFlight;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private emit(): void {
|
|
95
|
+
for (const listener of [...this.listeners]) listener();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
let sharedRoster: HostedSessionRoster | null = null;
|
|
100
|
+
|
|
101
|
+
/** The one roster the session picker reads and the composition root feeds. */
|
|
102
|
+
export function getSharedHostedSessionRoster(): HostedSessionRoster {
|
|
103
|
+
sharedRoster ??= new HostedSessionRoster();
|
|
104
|
+
return sharedRoster;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Tests build their own roster; this resets the shared one between them. */
|
|
108
|
+
export function resetSharedHostedSessionRoster(): void {
|
|
109
|
+
sharedRoster = null;
|
|
110
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hosted-session-stream.ts — watching a conversation that is running somewhere
|
|
3
|
+
* else.
|
|
4
|
+
*
|
|
5
|
+
* A hosted session's loop is the ORDINARY Orchestrator, composed inside the
|
|
6
|
+
* daemon. So it emits exactly what a local turn emits — STREAM_DELTA, tool
|
|
7
|
+
* starts, tool results, turn transitions — on the `turn` and `tools` runtime
|
|
8
|
+
* domains, and the control-plane forwards those domains to any subscriber with
|
|
9
|
+
* the session id stamped on every envelope. There is no hosted-only token
|
|
10
|
+
* stream to consume and this file does not invent one: it opens the same event
|
|
11
|
+
* stream a client already uses and keeps the frames whose id matches the
|
|
12
|
+
* session it was told to watch.
|
|
13
|
+
*
|
|
14
|
+
* The third domain is `session`, which carries `hosted-session-update` — the
|
|
15
|
+
* lifecycle channel: created, attached, detached, turn started, turn ended,
|
|
16
|
+
* terminated, restored. That is how an attached terminal learns its session was
|
|
17
|
+
* killed from another surface, or that a turn started that it did not begin.
|
|
18
|
+
*
|
|
19
|
+
* ── Why a refused stream is a value, not a throw ──────────────────────────
|
|
20
|
+
*
|
|
21
|
+
* The same reasoning the SDK's approval-update watcher records: a stream can be
|
|
22
|
+
* refused (no daemon, a 401, a proxy that will not hold a connection), and the
|
|
23
|
+
* caller must still be able to act on the session over the verbs. `watch`
|
|
24
|
+
* resolves to null instead of throwing, and the caller says so honestly rather
|
|
25
|
+
* than pretending the conversation ended.
|
|
26
|
+
*/
|
|
27
|
+
import { openServerSentEventStream } from '@/runtime/index.ts';
|
|
28
|
+
import { logger, summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
29
|
+
import type { HostedSessionUpdatePayload } from '@pellux/goodvibes-sdk/platform/hosted-sessions';
|
|
30
|
+
|
|
31
|
+
/** The domains a hosted session's whole story arrives on. */
|
|
32
|
+
export const HOSTED_SESSION_STREAM_DOMAINS = ['turn', 'tools', 'session'] as const;
|
|
33
|
+
|
|
34
|
+
/** The wire event name the daemon publishes hosted lifecycle notices on. */
|
|
35
|
+
export const HOSTED_SESSION_LIFECYCLE_WIRE_EVENT = 'hosted-session-update';
|
|
36
|
+
|
|
37
|
+
/** One runtime-domain frame, already narrowed to the watched session. */
|
|
38
|
+
export interface HostedSessionStreamEvent {
|
|
39
|
+
readonly domain: 'turn' | 'tools';
|
|
40
|
+
/** The runtime event type, e.g. STREAM_DELTA, TOOL_EXECUTING, TURN_COMPLETED. */
|
|
41
|
+
readonly type: string;
|
|
42
|
+
readonly sessionId: string;
|
|
43
|
+
readonly at: number;
|
|
44
|
+
readonly payload: Record<string, unknown>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface WatchHostedSessionOptions {
|
|
48
|
+
/** The daemon's base URL, e.g. `http://127.0.0.1:3421`. */
|
|
49
|
+
readonly baseUrl: string;
|
|
50
|
+
/** The hosted session id to keep frames for. Everything else is discarded. */
|
|
51
|
+
readonly sessionId: string;
|
|
52
|
+
readonly getAuthToken?: (() => string | null | Promise<string | null>) | undefined;
|
|
53
|
+
/** Every turn/tools frame belonging to this session. */
|
|
54
|
+
readonly onEvent: (event: HostedSessionStreamEvent) => void;
|
|
55
|
+
/** Every lifecycle transition of this session. */
|
|
56
|
+
readonly onLifecycle?: ((update: HostedSessionUpdatePayload) => void) | undefined;
|
|
57
|
+
/** The stream dropped for good. The caller decides what to say about it. */
|
|
58
|
+
readonly onTerminate?: ((error: unknown) => void) | undefined;
|
|
59
|
+
readonly fetchImpl?: typeof fetch | undefined;
|
|
60
|
+
readonly signal?: AbortSignal | undefined;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** A live subscription. `close()` is idempotent. */
|
|
64
|
+
export interface HostedSessionSubscription {
|
|
65
|
+
close(): void;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The control-plane events URL narrowed to the three domains a hosted session
|
|
70
|
+
* speaks on. Narrowing matters: an unnarrowed subscriber receives every domain
|
|
71
|
+
* the daemon publishes, which for a client watching one conversation is a great
|
|
72
|
+
* deal of traffic it would immediately discard.
|
|
73
|
+
*/
|
|
74
|
+
export function hostedSessionStreamUrl(baseUrl: string): string {
|
|
75
|
+
const url = new URL('/api/control-plane/events', baseUrl);
|
|
76
|
+
url.searchParams.set('domains', HOSTED_SESSION_STREAM_DOMAINS.join(','));
|
|
77
|
+
return url.toString();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Read a serialized runtime envelope, or null when the frame is not one. */
|
|
81
|
+
export function readHostedStreamEnvelope(
|
|
82
|
+
domain: 'turn' | 'tools',
|
|
83
|
+
payload: unknown,
|
|
84
|
+
): HostedSessionStreamEvent | null {
|
|
85
|
+
if (typeof payload !== 'object' || payload === null) return null;
|
|
86
|
+
const envelope = payload as Record<string, unknown>;
|
|
87
|
+
const type = envelope['type'];
|
|
88
|
+
const sessionId = envelope['sessionId'];
|
|
89
|
+
if (typeof type !== 'string' || type.length === 0) return null;
|
|
90
|
+
if (typeof sessionId !== 'string' || sessionId.length === 0) return null;
|
|
91
|
+
const body = envelope['payload'];
|
|
92
|
+
return {
|
|
93
|
+
domain,
|
|
94
|
+
type,
|
|
95
|
+
sessionId,
|
|
96
|
+
at: typeof envelope['ts'] === 'number' ? envelope['ts'] : Date.now(),
|
|
97
|
+
payload: typeof body === 'object' && body !== null ? body as Record<string, unknown> : {},
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Read a hosted lifecycle notice, or null when the frame is not one. */
|
|
102
|
+
export function readHostedLifecycleNotice(payload: unknown): HostedSessionUpdatePayload | null {
|
|
103
|
+
if (typeof payload !== 'object' || payload === null) return null;
|
|
104
|
+
const record = payload as Record<string, unknown>;
|
|
105
|
+
const session = record['session'];
|
|
106
|
+
const event = record['event'];
|
|
107
|
+
if (typeof event !== 'string' || typeof session !== 'object' || session === null) return null;
|
|
108
|
+
if (typeof (session as Record<string, unknown>)['id'] !== 'string') return null;
|
|
109
|
+
return record as unknown as HostedSessionUpdatePayload;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Open a subscription to one hosted session's live output.
|
|
114
|
+
*
|
|
115
|
+
* Resolves to null when the stream could not be opened; the reason is logged
|
|
116
|
+
* once rather than thrown into a keystroke path, and the caller keeps whatever
|
|
117
|
+
* it had (the verbs still work — the conversation is simply not being watched).
|
|
118
|
+
*/
|
|
119
|
+
export async function watchHostedSession(
|
|
120
|
+
options: WatchHostedSessionOptions,
|
|
121
|
+
): Promise<HostedSessionSubscription | null> {
|
|
122
|
+
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
123
|
+
if (typeof fetchImpl !== 'function') {
|
|
124
|
+
logger.debug('[hosted-sessions] no fetch implementation is available for the session stream');
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
const deliver = (event: HostedSessionStreamEvent): void => {
|
|
128
|
+
if (event.sessionId !== options.sessionId) return;
|
|
129
|
+
try {
|
|
130
|
+
options.onEvent(event);
|
|
131
|
+
} catch (error) {
|
|
132
|
+
// A throwing subscriber must not take the stream down for itself.
|
|
133
|
+
logger.debug('[hosted-sessions] a stream subscriber threw', { error: summarizeError(error) });
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
try {
|
|
137
|
+
const close = await openServerSentEventStream(
|
|
138
|
+
fetchImpl,
|
|
139
|
+
hostedSessionStreamUrl(options.baseUrl),
|
|
140
|
+
{
|
|
141
|
+
onEvent: (eventName, payload) => {
|
|
142
|
+
if (eventName === 'turn' || eventName === 'tools') {
|
|
143
|
+
const event = readHostedStreamEnvelope(eventName, payload);
|
|
144
|
+
if (event) deliver(event);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (eventName !== HOSTED_SESSION_LIFECYCLE_WIRE_EVENT) return;
|
|
148
|
+
const notice = readHostedLifecycleNotice(payload);
|
|
149
|
+
if (!notice || notice.session.id !== options.sessionId) return;
|
|
150
|
+
try {
|
|
151
|
+
options.onLifecycle?.(notice);
|
|
152
|
+
} catch (error) {
|
|
153
|
+
logger.debug('[hosted-sessions] a lifecycle subscriber threw', { error: summarizeError(error) });
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
onTerminate: ({ error }) => options.onTerminate?.(error),
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
...(options.getAuthToken ? { getAuthToken: options.getAuthToken } : {}),
|
|
160
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
161
|
+
},
|
|
162
|
+
);
|
|
163
|
+
let closed = false;
|
|
164
|
+
return {
|
|
165
|
+
close: (): void => {
|
|
166
|
+
if (closed) return;
|
|
167
|
+
closed = true;
|
|
168
|
+
close();
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
} catch (error) {
|
|
172
|
+
logger.info('[hosted-sessions] the session stream could not be opened; the session is still steerable over its verbs', {
|
|
173
|
+
sessionId: options.sessionId,
|
|
174
|
+
error: summarizeError(error),
|
|
175
|
+
});
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hosted-sessions.ts — this terminal's handle on a conversation the DAEMON runs.
|
|
3
|
+
*
|
|
4
|
+
* A local session's loop lives in this process: the orchestrator, the tools and
|
|
5
|
+
* the permission gate are all here, and closing the terminal ends the work. A
|
|
6
|
+
* HOSTED session is the same loop composed on the other side of the wire, in
|
|
7
|
+
* the daemon, driven by the five `sessions.hosted.*` verbs. Local stays the
|
|
8
|
+
* default experience; hosting is something the user asks for.
|
|
9
|
+
*
|
|
10
|
+
* ── What this module is, and what it deliberately is not ──────────────────
|
|
11
|
+
*
|
|
12
|
+
* It is the five lifecycle verbs plus the ORDINARY session verbs a hosted id
|
|
13
|
+
* also answers to. There is no `sessions.hosted.steer` and this file does not
|
|
14
|
+
* invent one: `sessions.steer`, `sessions.followUp` and
|
|
15
|
+
* `sessions.toolCalls.cancel` resolve a hosted session's id daemon-side, so
|
|
16
|
+
* steering a hosted conversation is the same call as steering any other. Two
|
|
17
|
+
* spellings of one action is how they drift apart.
|
|
18
|
+
*
|
|
19
|
+
* It is not the stream. The hosted loop is the ordinary Orchestrator, so its
|
|
20
|
+
* tokens, tool calls and turn transitions arrive on the `turn` and `tools`
|
|
21
|
+
* event domains stamped with the hosted session's id — see
|
|
22
|
+
* hosted-session-stream.ts, which watches exactly what a local session watches
|
|
23
|
+
* and filters on the id `attach` handed back.
|
|
24
|
+
*
|
|
25
|
+
* ── Why the verbs go through DaemonVerbCaller ─────────────────────────────
|
|
26
|
+
*
|
|
27
|
+
* Every `sessions.hosted.*` verb is declared `transport: ['ws']` with no REST
|
|
28
|
+
* path of its own, so `sdk.operator.invoke` refuses them before making a
|
|
29
|
+
* request. `createDaemonVerbCaller` (operator-endpoint.ts) already handles that
|
|
30
|
+
* exact class by falling through to the generic gateway route — which IS the
|
|
31
|
+
* binding for a ws-declared verb reached over HTTP.
|
|
32
|
+
*/
|
|
33
|
+
import { hostname } from 'node:os';
|
|
34
|
+
import type {
|
|
35
|
+
CreateHostedSessionInput,
|
|
36
|
+
HostedDetachPolicy,
|
|
37
|
+
HostedSessionHistoryMessage,
|
|
38
|
+
HostedSessionRecord,
|
|
39
|
+
} from '@pellux/goodvibes-sdk/platform/hosted-sessions';
|
|
40
|
+
import type { DaemonVerbCaller } from './operator-endpoint.ts';
|
|
41
|
+
|
|
42
|
+
export type {
|
|
43
|
+
CreateHostedSessionInput,
|
|
44
|
+
HostedDetachPolicy,
|
|
45
|
+
HostedSessionHistoryMessage,
|
|
46
|
+
HostedSessionRecord,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/** What `sessions.hosted.attach` answers with: the record, and what was said so far. */
|
|
50
|
+
export interface HostedSessionAttachment {
|
|
51
|
+
readonly session: HostedSessionRecord;
|
|
52
|
+
readonly history: readonly HostedSessionHistoryMessage[];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* How this terminal names itself to the daemon when it attaches.
|
|
57
|
+
*
|
|
58
|
+
* It has to be stable for the life of the process and distinct between two
|
|
59
|
+
* terminals on one machine, because the detach policy is applied when the LAST
|
|
60
|
+
* client leaves: two windows sharing one id would make the first `detach` look
|
|
61
|
+
* like the last one. Host + pid is both, and it is readable in
|
|
62
|
+
* `attachedClients` — a person reading the record can tell which terminal that
|
|
63
|
+
* is.
|
|
64
|
+
*/
|
|
65
|
+
let cachedClientId: string | null = null;
|
|
66
|
+
export function terminalHostedClientId(): string {
|
|
67
|
+
cachedClientId ??= `tui-${hostname()}-${process.pid}`;
|
|
68
|
+
return cachedClientId;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** The narrow client the command layer and the panel share. */
|
|
72
|
+
export interface HostedSessionsClient {
|
|
73
|
+
/** This terminal's attach identity — the same string the records carry. */
|
|
74
|
+
readonly clientId: string;
|
|
75
|
+
list(options?: { readonly includeTerminated?: boolean }): Promise<readonly HostedSessionRecord[]>;
|
|
76
|
+
create(input: Omit<CreateHostedSessionInput, 'clientId'>): Promise<HostedSessionRecord>;
|
|
77
|
+
attach(sessionId: string): Promise<HostedSessionAttachment>;
|
|
78
|
+
detach(sessionId: string): Promise<HostedSessionRecord>;
|
|
79
|
+
kill(sessionId: string): Promise<HostedSessionRecord>;
|
|
80
|
+
/** Send a message into a running hosted session — the ordinary steer verb. */
|
|
81
|
+
steer(sessionId: string, body: string): Promise<void>;
|
|
82
|
+
/** Queue a message for after the current turn — the ordinary follow-up verb. */
|
|
83
|
+
followUp(sessionId: string, body: string): Promise<void>;
|
|
84
|
+
/** Cancel one in-flight tool call — the ordinary live-turn verb. */
|
|
85
|
+
cancelToolCall(sessionId: string, callId: string): Promise<boolean>;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Drop the keys a verb was not given a value for.
|
|
90
|
+
*
|
|
91
|
+
* The gateway validates against the declared input schema and a present-but-
|
|
92
|
+
* undefined property is not the same as an absent one on the wire; sending
|
|
93
|
+
* `{ title: undefined }` is how a caller that meant "no title" gets a
|
|
94
|
+
* validation refusal instead.
|
|
95
|
+
*/
|
|
96
|
+
function withoutUndefined(input: Record<string, unknown>): Record<string, unknown> {
|
|
97
|
+
const out: Record<string, unknown> = {};
|
|
98
|
+
for (const [key, value] of Object.entries(input)) {
|
|
99
|
+
if (value !== undefined) out[key] = value;
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function createHostedSessionsClient(verbs: DaemonVerbCaller): HostedSessionsClient {
|
|
105
|
+
const clientId = terminalHostedClientId();
|
|
106
|
+
return {
|
|
107
|
+
clientId,
|
|
108
|
+
async list(options): Promise<readonly HostedSessionRecord[]> {
|
|
109
|
+
const result = await verbs.invoke<{ sessions: readonly HostedSessionRecord[] }>(
|
|
110
|
+
'sessions.hosted.list',
|
|
111
|
+
withoutUndefined({ includeTerminated: options?.includeTerminated }),
|
|
112
|
+
);
|
|
113
|
+
return result.sessions ?? [];
|
|
114
|
+
},
|
|
115
|
+
async create(input): Promise<HostedSessionRecord> {
|
|
116
|
+
const result = await verbs.invoke<{ session: HostedSessionRecord }>(
|
|
117
|
+
'sessions.hosted.create',
|
|
118
|
+
withoutUndefined({
|
|
119
|
+
workspaceRoot: input.workspaceRoot,
|
|
120
|
+
title: input.title,
|
|
121
|
+
modelId: input.modelId,
|
|
122
|
+
initialPrompt: input.initialPrompt,
|
|
123
|
+
detachPolicy: input.detachPolicy,
|
|
124
|
+
clientId,
|
|
125
|
+
}),
|
|
126
|
+
);
|
|
127
|
+
return result.session;
|
|
128
|
+
},
|
|
129
|
+
async attach(sessionId): Promise<HostedSessionAttachment> {
|
|
130
|
+
const result = await verbs.invoke<HostedSessionAttachment>(
|
|
131
|
+
'sessions.hosted.attach',
|
|
132
|
+
{ sessionId, clientId },
|
|
133
|
+
);
|
|
134
|
+
return { session: result.session, history: result.history ?? [] };
|
|
135
|
+
},
|
|
136
|
+
async detach(sessionId): Promise<HostedSessionRecord> {
|
|
137
|
+
const result = await verbs.invoke<{ session: HostedSessionRecord }>(
|
|
138
|
+
'sessions.hosted.detach',
|
|
139
|
+
{ sessionId, clientId },
|
|
140
|
+
);
|
|
141
|
+
return result.session;
|
|
142
|
+
},
|
|
143
|
+
async kill(sessionId): Promise<HostedSessionRecord> {
|
|
144
|
+
const result = await verbs.invoke<{ session: HostedSessionRecord }>(
|
|
145
|
+
'sessions.hosted.kill',
|
|
146
|
+
{ sessionId },
|
|
147
|
+
);
|
|
148
|
+
return result.session;
|
|
149
|
+
},
|
|
150
|
+
async steer(sessionId, body): Promise<void> {
|
|
151
|
+
await verbs.invoke('sessions.steer', { sessionId, body, surfaceKind: 'tui' });
|
|
152
|
+
},
|
|
153
|
+
async followUp(sessionId, body): Promise<void> {
|
|
154
|
+
await verbs.invoke('sessions.followUp', { sessionId, body, surfaceKind: 'tui' });
|
|
155
|
+
},
|
|
156
|
+
async cancelToolCall(sessionId, callId): Promise<boolean> {
|
|
157
|
+
const result = await verbs.invoke<{ cancelled: boolean }>(
|
|
158
|
+
'sessions.toolCalls.cancel',
|
|
159
|
+
{ sessionId, callId },
|
|
160
|
+
);
|
|
161
|
+
return result.cancelled === true;
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* One line describing what leaving this session would do right now, in the
|
|
168
|
+
* words the record itself carries.
|
|
169
|
+
*
|
|
170
|
+
* The policy is the daemon's answer, never this client's guess: a session
|
|
171
|
+
* created with an override reports the override, and one following the setting
|
|
172
|
+
* reports the setting's current value. Rendering a locally-remembered default
|
|
173
|
+
* here is how a user learns the wrong thing about a quit.
|
|
174
|
+
*/
|
|
175
|
+
export function describeDetachEffect(record: HostedSessionRecord): string {
|
|
176
|
+
const source = record.detachPolicy === null
|
|
177
|
+
? 'the hostedSessions.detachPolicy setting'
|
|
178
|
+
: 'this session\'s own override';
|
|
179
|
+
return record.effectiveDetachPolicy === 'survive'
|
|
180
|
+
? `leaving keeps it running (survive, from ${source})`
|
|
181
|
+
: `leaving ends it (kill, from ${source})`;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** A short, stable label for a record in a list. Never invents a title. */
|
|
185
|
+
export function hostedSessionLabel(record: HostedSessionRecord): string {
|
|
186
|
+
return record.title.trim() || record.id;
|
|
187
|
+
}
|