@pellux/goodvibes-tui 2.0.21 → 2.1.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 +65 -1
- package/README.md +30 -29
- package/docs/README.md +8 -5
- package/docs/foundation-artifacts/operator-contract.json +1 -1
- package/package.json +28 -19
- package/src/cli/management.ts +5 -2
- package/src/cli/tui-startup.ts +34 -39
- package/src/core/agent-view-render.ts +157 -0
- package/src/core/approval-alert.ts +49 -11
- package/src/core/bookmark-navigation.ts +7 -1
- package/src/core/budget-breach-notifier.ts +27 -8
- package/src/core/compaction-receipt.ts +1 -1
- package/src/core/context-usage.ts +1 -1
- package/src/core/conversation-compaction-render.ts +3 -3
- package/src/core/conversation-display-only.ts +86 -0
- package/src/core/conversation-event-navigation.ts +27 -0
- package/src/core/conversation-line-cache.ts +216 -447
- package/src/core/conversation-render-context.ts +20 -27
- package/src/core/conversation-rendering.ts +157 -575
- package/src/core/conversation-splash-state.ts +6 -1
- package/src/core/conversation-types.ts +35 -0
- package/src/core/conversation-usage.ts +12 -0
- package/src/core/conversation.ts +130 -131
- package/src/core/long-task-notifier.ts +100 -31
- package/src/core/notices.ts +75 -0
- package/src/core/pairing-tailscale-gateway.ts +1 -1
- package/src/core/process-output-log.ts +143 -0
- package/src/core/rewind-receipt.ts +1 -1
- package/src/core/session-focus.ts +190 -0
- package/src/core/session-resume-core.ts +23 -81
- package/src/core/stream-event-wiring.ts +31 -5
- package/src/core/system-message-noise.ts +2 -2
- package/src/core/system-message-router.ts +16 -18
- package/src/core/turn-event-wiring.ts +272 -33
- package/src/core/work-tree-focus.ts +290 -0
- package/src/core/work-tree-fold-store.ts +143 -0
- package/src/core/work-tree-model.ts +799 -0
- package/src/core/work-tree-render.ts +384 -0
- package/src/core/work-tree-sources.ts +184 -0
- package/src/core/work-tree-wiring.ts +176 -0
- package/src/core/workstream-notification.ts +36 -6
- package/src/core/wrfc-notice-titles.ts +37 -0
- package/src/input/agents-modal-text.ts +119 -0
- package/src/input/agents-modal.ts +699 -0
- package/src/input/bookmark-modal.ts +33 -9
- package/src/input/changes-git.ts +251 -0
- package/src/input/changes-modal.ts +629 -0
- package/src/input/changes-model.ts +125 -0
- package/src/input/command-palette-types.ts +41 -0
- package/src/input/command-palette.ts +272 -0
- package/src/input/command-reference.ts +2 -2
- package/src/input/command-registry.ts +77 -35
- package/src/input/commands/acp-runtime.ts +19 -9
- package/src/input/commands/channel-runtime.ts +2 -2
- package/src/input/commands/ci-runtime.ts +1 -1
- package/src/input/commands/codebase-runtime.ts +1 -1
- package/src/input/commands/config.ts +7 -1
- package/src/input/commands/context-window.ts +25 -19
- package/src/input/commands/control-room-runtime.ts +18 -18
- package/src/input/commands/conversation-runtime.ts +0 -1
- package/src/input/commands/cost-runtime.ts +29 -20
- package/src/input/commands/diff-runtime.ts +35 -181
- package/src/input/commands/eval.ts +1 -1
- package/src/input/commands/experience-runtime.ts +3 -7
- package/src/input/commands/fresh-conversation.ts +86 -0
- package/src/input/commands/git-runtime.ts +5 -22
- package/src/input/commands/graph-runtime.ts +1 -1
- package/src/input/commands/guidance-runtime.ts +1 -1
- package/src/input/commands/health-runtime.ts +8 -9
- package/src/input/commands/hooks-runtime.ts +2 -2
- package/src/input/commands/hosted-runtime.ts +3 -3
- package/src/input/commands/incident-runtime.ts +3 -3
- package/src/input/commands/intelligence-runtime.ts +6 -9
- package/src/input/commands/knowledge.ts +2 -2
- package/src/input/commands/legacy-panel-command.ts +57 -0
- package/src/input/commands/local-auth-runtime.ts +8 -9
- package/src/input/commands/local-provider-runtime.ts +7 -4
- package/src/input/commands/local-runtime.ts +12 -15
- package/src/input/commands/local-setup-review.ts +1 -1
- package/src/input/commands/local-setup.ts +13 -19
- package/src/input/commands/memory.ts +1 -1
- package/src/input/commands/notify-runtime.ts +9 -0
- package/src/input/commands/operator-runtime.ts +10 -12
- package/src/input/commands/permissions-provenance.ts +3 -3
- package/src/input/commands/planning-runtime.ts +7 -7
- package/src/input/commands/platform-sandbox-runtime.ts +2 -2
- package/src/input/commands/platform-services-runtime.ts +1 -1
- package/src/input/commands/policy-dispatch.ts +2 -2
- package/src/input/commands/policy.ts +3 -3
- package/src/input/commands/provider.ts +1 -1
- package/src/input/commands/remote-runtime.ts +1 -1
- package/src/input/commands/review-runtime.ts +39 -94
- package/src/input/commands/rewind-runtime.ts +81 -108
- package/src/input/commands/runtime-services.ts +15 -17
- package/src/input/commands/services-runtime.ts +2 -2
- package/src/input/commands/session-content.ts +21 -5
- package/src/input/commands/session-workflow.ts +13 -38
- package/src/input/commands/settings-sync-runtime.ts +2 -2
- package/src/input/commands/shell-core.ts +19 -4
- package/src/input/commands/skills-runtime.ts +2 -2
- package/src/input/commands/status-runtime.ts +13 -0
- package/src/input/commands/subscription-runtime.ts +1 -1
- package/src/input/commands/tasks-runtime.ts +2 -8
- package/src/input/commands/work-plan-runtime.ts +4 -4
- package/src/input/commands/workstream-attempts.ts +24 -47
- package/src/input/commands/workstream-runtime.ts +2 -2
- package/src/input/commands/worktree-runtime.ts +3 -3
- package/src/input/commands.ts +3 -1
- package/src/input/composer-editor.ts +44 -0
- package/src/input/concealed-input.ts +1 -1
- package/src/input/config-modal-types.ts +20 -8
- package/src/input/config-modal.ts +106 -105
- package/src/input/confirm-dialog-types.ts +28 -0
- package/src/input/confirm-dialog.ts +70 -0
- package/src/input/delete-key-policy.ts +3 -3
- package/src/input/feed-context-factory.ts +15 -16
- package/src/input/handler-command-route.ts +7 -42
- package/src/input/handler-content-actions.ts +16 -15
- package/src/input/handler-feed-routes.ts +26 -211
- package/src/input/handler-feed.ts +68 -95
- package/src/input/handler-interactions.ts +5 -3
- package/src/input/handler-modal-routes.ts +215 -216
- package/src/input/handler-modal-stack.ts +81 -11
- package/src/input/handler-modal-token-routes.ts +4 -0
- package/src/input/handler-onboarding.ts +0 -1
- package/src/input/handler-picker-routes.ts +65 -107
- package/src/input/handler-session-view-route.ts +149 -0
- package/src/input/handler-shortcuts.ts +16 -154
- package/src/input/handler-types.ts +4 -2
- package/src/input/handler-ui-state.ts +45 -15
- package/src/input/handler-work-tree-route.ts +123 -0
- package/src/input/handler.ts +25 -70
- package/src/input/keybindings.ts +16 -71
- package/src/input/masked-entry-modal.ts +117 -0
- package/src/input/mcp-workspace.ts +48 -17
- package/src/input/model-picker-filter.ts +8 -0
- package/src/input/model-picker-open.ts +99 -0
- package/src/input/model-picker-types.ts +1 -1
- package/src/input/model-picker.ts +92 -9
- package/src/input/notifications-modal.ts +123 -0
- package/src/input/overlay-filter.ts +26 -0
- package/src/input/profile-picker-modal.ts +37 -8
- package/src/input/search.ts +3 -3
- package/src/input/selection-modal-queue.ts +2 -0
- package/src/input/selection-modal.ts +28 -16
- package/src/input/session-picker-modal.ts +33 -8
- package/src/input/settings-modal-activation.ts +7 -2
- package/src/input/settings-modal-data.ts +11 -6
- package/src/input/settings-modal-types.ts +1 -1
- package/src/input/settings-modal.ts +20 -20
- package/src/input/submission-intent.ts +1 -1
- package/src/input/submission-router.ts +4 -9
- package/src/input/surface-modal-host.ts +110 -0
- package/src/input/theme-settings-actions.ts +82 -0
- package/src/input/usage-modal.ts +183 -0
- package/src/input/views.ts +97 -0
- package/src/main.ts +175 -165
- package/src/permissions/hunk-selection.ts +1 -1
- package/src/permissions/prompt-card.ts +511 -0
- package/src/permissions/prompt.ts +59 -622
- package/src/renderer/agents-modal.ts +310 -0
- package/src/renderer/autocomplete-overlay.ts +42 -220
- package/src/renderer/back-to-bottom.ts +80 -0
- package/src/renderer/block-actions-overlay.ts +30 -48
- package/src/renderer/bookmark-modal.ts +48 -79
- package/src/renderer/changes-modal.ts +325 -0
- package/src/renderer/code-block.ts +184 -106
- package/src/renderer/command-palette.ts +93 -0
- package/src/renderer/composer-fragments.ts +4 -4
- package/src/renderer/composer.ts +146 -0
- package/src/renderer/compositor.ts +26 -160
- package/src/renderer/config-modal.ts +185 -81
- package/src/renderer/confirm-dialog.ts +50 -0
- package/src/renderer/context-inspector.ts +116 -154
- package/src/renderer/context-status-hint.ts +4 -4
- package/src/renderer/conversation-layout.ts +20 -0
- package/src/renderer/conversation-overlays.ts +94 -84
- package/src/renderer/conversation-surface.ts +19 -38
- package/src/renderer/diff-tint.ts +98 -0
- package/src/renderer/file-picker-overlay.ts +63 -90
- package/src/renderer/footer-targets.ts +38 -0
- package/src/renderer/git-status.ts +3 -4
- package/src/renderer/header-line.ts +140 -117
- package/src/renderer/help-overlay.ts +133 -219
- package/src/renderer/history-search-overlay.ts +15 -12
- package/src/renderer/lane-graph/bead.ts +800 -0
- package/src/renderer/lane-graph/glyphs.ts +134 -0
- package/src/renderer/lane-graph/layout.ts +248 -0
- package/src/renderer/lane-graph/paint.ts +537 -0
- package/src/renderer/lane-graph/semantic-memo.ts +77 -0
- package/src/renderer/layout-engine.ts +2 -57
- package/src/renderer/layout.ts +7 -13
- package/src/renderer/markdown-inline.ts +2 -1
- package/src/renderer/markdown-table.ts +17 -12
- package/src/renderer/markdown.ts +92 -34
- package/src/renderer/masked-entry-modal.ts +78 -0
- package/src/renderer/mcp-workspace.ts +166 -203
- package/src/renderer/model-workspace.ts +381 -436
- package/src/renderer/notifications-modal.ts +116 -0
- package/src/renderer/onboarding/onboarding-wizard.ts +178 -588
- package/src/renderer/process-view.ts +192 -0
- package/src/renderer/profile-picker-modal.ts +58 -110
- package/src/renderer/search-overlay.ts +6 -3
- package/src/renderer/selection-modal-overlay.ts +87 -327
- package/src/renderer/semantic-diff.ts +2 -2
- package/src/renderer/session-chips.ts +102 -0
- package/src/renderer/session-picker-modal.ts +177 -297
- package/src/renderer/settings-modal-connections.ts +14 -23
- package/src/renderer/settings-modal-context.ts +367 -0
- package/src/renderer/settings-modal-helpers.ts +6 -11
- package/src/renderer/settings-modal.ts +322 -726
- package/src/renderer/shell-surface.ts +277 -175
- package/src/renderer/shortcuts-overlay.ts +217 -0
- package/src/renderer/status-line.ts +475 -0
- package/src/renderer/surface-compose.ts +174 -0
- package/src/renderer/surface-kit-extra.ts +303 -0
- package/src/renderer/surface-kit-list.ts +366 -0
- package/src/renderer/surface-kit-parts.ts +360 -0
- package/src/renderer/surface-kit.ts +568 -0
- package/src/renderer/syntax-highlighter.ts +280 -197
- package/src/renderer/syntax-theme.ts +51 -0
- package/src/renderer/system-message.ts +0 -1
- package/src/renderer/term-caps.ts +42 -1
- package/src/renderer/terminal-bg-probe.ts +116 -19
- package/src/renderer/terminal-palette-probe.ts +322 -0
- package/src/renderer/terminal-palette-reader.ts +183 -0
- package/src/renderer/terminal-palette.ts +58 -0
- package/src/renderer/theme-mode-config.ts +16 -1
- package/src/renderer/theme.ts +234 -207
- package/src/renderer/thinking.ts +0 -1
- package/src/renderer/throbber.ts +188 -0
- package/src/renderer/toast-center.ts +119 -0
- package/src/renderer/ui-factory.ts +39 -602
- package/src/renderer/ui-primitives.ts +8 -25
- package/src/renderer/usage-modal.ts +353 -0
- package/src/renderer/user-message.ts +58 -0
- package/src/renderer/voice-capture-chip.ts +53 -0
- package/src/runtime/bootstrap-command-context.ts +0 -5
- package/src/runtime/bootstrap-command-parts.ts +38 -89
- package/src/runtime/bootstrap-core.ts +13 -15
- package/src/runtime/bootstrap-hook-bridge.ts +12 -21
- package/src/runtime/bootstrap-notifier-sync.ts +17 -1
- package/src/runtime/bootstrap-shell.ts +16 -50
- package/src/runtime/bootstrap.ts +14 -14
- package/src/runtime/client/fleet-union.ts +5 -5
- package/src/runtime/client/hosted-exit.ts +2 -2
- package/src/runtime/client/hosted-sessions.ts +1 -1
- package/src/runtime/client/spine-adoption.ts +1 -1
- package/src/runtime/context-accounting-source.ts +5 -4
- package/src/runtime/index.ts +1 -0
- package/src/runtime/notification-dispatch.ts +102 -29
- package/src/runtime/recovery-offer-wiring.ts +4 -1
- package/src/runtime/recovery-prompt.ts +31 -15
- package/src/runtime/runtime-services-types.ts +10 -6
- package/src/runtime/services.ts +17 -12
- package/src/runtime/session-continuity-hints.ts +2 -5
- package/src/runtime/session-inbound-inputs.ts +1 -1
- package/src/runtime/session-resume-liveness-confirm.ts +14 -2
- package/src/runtime/startup-typeahead-gate.ts +103 -0
- package/src/runtime/store/domains/domain-read-matrix.ts +0 -1
- package/src/runtime/store/domains/index.ts +0 -8
- package/src/runtime/store/selectors/index.ts +0 -28
- package/src/runtime/store/state.ts +0 -8
- package/src/runtime/ui-services.ts +4 -6
- package/src/runtime/usage-tracker.ts +348 -0
- package/src/runtime/wrfc-persistence.ts +4 -4
- package/src/shell/blocking-input.ts +92 -2
- package/src/shell/recovery-input-helpers.ts +1 -1
- package/src/shell/session-views.ts +539 -0
- package/src/shell/status-report.ts +86 -0
- package/src/shell/transcript-scroll.ts +68 -0
- package/src/shell/ui-openers.ts +84 -172
- package/src/shell/view-openers.ts +205 -0
- package/src/tools/wrfc-agent-guard.ts +22 -2
- package/src/utils/format-duration.ts +4 -8
- package/src/utils/splash-lines.ts +9 -1
- package/src/utils/terminal-width.ts +56 -0
- package/src/verification/live-verifier.ts +0 -10
- package/src/version.ts +1 -1
- package/src/{panels → views}/agent-inspector-shared.ts +2 -2
- package/src/{panels → views}/builtin-modals.ts +22 -21
- package/src/views/builtin-views.ts +135 -0
- package/src/{panels → views}/confirm-state.ts +9 -9
- package/src/{panels → views}/diff-review-model.ts +10 -3
- package/src/{panels → views}/eval-registry.ts +1 -1
- package/src/{panels → views}/fleet-acts.ts +49 -85
- package/src/{panels/fleet-panel-format.ts → views/fleet-format.ts} +40 -40
- package/src/{panels → views}/fleet-gateway.ts +5 -5
- package/src/{panels → views}/fleet-observed-render.ts +16 -16
- package/src/{panels → views}/fleet-read-model.ts +9 -9
- package/src/{panels/fleet-tabs.ts → views/fleet-session-tabs.ts} +13 -13
- package/src/{panels → views}/fleet-spawn.ts +33 -49
- package/src/{panels → views}/fleet-steer.ts +12 -12
- package/src/{panels → views}/fleet-stop.ts +4 -4
- package/src/{panels → views}/fleet-transcript.ts +26 -27
- package/src/{panels/fleet-panel-worktree-detail.ts → views/fleet-worktree-detail.ts} +7 -7
- package/src/{panels → views}/hosted-session-feed.ts +6 -6
- package/src/views/modal-surface-registry.ts +41 -0
- package/src/{panels → views}/modals/devices-modal.ts +2 -2
- package/src/{panels → views}/modals/hooks-modal.ts +13 -13
- package/src/{panels → views}/modals/keybindings-modal.ts +19 -27
- package/src/{panels → views}/modals/knowledge-modal.ts +6 -5
- package/src/{panels → views}/modals/local-auth-modal.ts +22 -21
- package/src/{panels → views}/modals/marketplace-modal.ts +11 -11
- package/src/{panels → views}/modals/memory-modal.ts +8 -8
- package/src/{panels → views}/modals/modal-surface-helpers.ts +15 -5
- package/src/{panels → views}/modals/pairing-modal.ts +9 -9
- package/src/{panels → views}/modals/planning-modal.ts +15 -20
- package/src/{panels → views}/modals/plugins-modal.ts +6 -6
- package/src/{panels → views}/modals/policy-modal.ts +13 -5
- package/src/{panels → views}/modals/provider-health-modal.ts +4 -4
- package/src/{panels → views}/modals/remote-modal.ts +3 -3
- package/src/{panels → views}/modals/sandbox-modal.ts +4 -4
- package/src/{panels → views}/modals/security-modal.ts +22 -16
- package/src/{panels → views}/modals/services-modal.ts +3 -3
- package/src/{panels → views}/modals/settings-sync-modal.ts +4 -4
- package/src/{panels → views}/modals/skills-modal.ts +9 -9
- package/src/{panels → views}/modals/subscription-modal.ts +3 -3
- package/src/{panels → views}/modals/work-plan-modal.ts +10 -13
- package/src/views/notifications-feed.ts +314 -0
- package/src/{panels → views}/polish-core.ts +38 -36
- package/src/{panels → views}/polish-tables.ts +35 -26
- package/src/{panels → views}/polish.ts +101 -95
- package/src/{panels → views}/project-planning-answer-actions.ts +3 -3
- package/src/{panels → views}/provider-health-routes.ts +14 -14
- package/src/{panels → views}/search-focus.ts +8 -8
- package/src/{panels → views}/session-maintenance.ts +11 -11
- package/src/views/skills-discovery.ts +159 -0
- package/src/views/view-deps.ts +80 -0
- package/src/views/view-panel-adapter.ts +53 -0
- package/src/{panels → views}/workstream-graph-render.ts +1 -1
- package/src/core/conversation-fold.ts +0 -101
- package/src/core/conversation-turn-structure.ts +0 -464
- package/src/core/conversation-user-receipts.ts +0 -42
- package/src/input/commands/operator-panel-runtime.ts +0 -186
- package/src/input/panel-integration-actions.ts +0 -33
- package/src/input/panel-mouse-geometry.ts +0 -97
- package/src/input/panel-paste-flood-guard.ts +0 -86
- package/src/panels/base-panel.ts +0 -301
- package/src/panels/builtin/agent.ts +0 -27
- package/src/panels/builtin/development.ts +0 -92
- package/src/panels/builtin/knowledge.ts +0 -12
- package/src/panels/builtin/operations.ts +0 -177
- package/src/panels/builtin/session.ts +0 -81
- package/src/panels/builtin/shared.ts +0 -357
- package/src/panels/builtin-panels.ts +0 -28
- package/src/panels/cost-tracker-panel.ts +0 -746
- package/src/panels/diff-panel.ts +0 -797
- package/src/panels/diff-review-panel.ts +0 -395
- package/src/panels/expandable-list-panel.ts +0 -189
- package/src/panels/fleet-deep-link.ts +0 -31
- package/src/panels/fleet-panel.ts +0 -799
- package/src/panels/git-panel.ts +0 -800
- package/src/panels/hosted-session-panel.ts +0 -221
- package/src/panels/index.ts +0 -21
- package/src/panels/local-auth-panel.ts +0 -478
- package/src/panels/notifications-feed.ts +0 -127
- package/src/panels/notifications-panel.ts +0 -160
- package/src/panels/panel-confirm-overlay.ts +0 -72
- package/src/panels/panel-manager.ts +0 -791
- package/src/panels/plugins-panel.ts +0 -371
- package/src/panels/scrollable-list-panel.ts +0 -498
- package/src/panels/skills-panel.ts +0 -454
- package/src/panels/token-budget-panel.ts +0 -688
- package/src/panels/types.ts +0 -191
- package/src/panels/wrfc-panel-format.ts +0 -133
- package/src/renderer/diff-view.ts +0 -134
- package/src/renderer/fleet-tab-strip.ts +0 -56
- package/src/renderer/footer-tips.ts +0 -53
- package/src/renderer/fullscreen-primitives.ts +0 -146
- package/src/renderer/fullscreen-workspace.ts +0 -194
- package/src/renderer/hint-grammar.ts +0 -52
- package/src/renderer/modal-factory.ts +0 -480
- package/src/renderer/overlay-box.ts +0 -154
- package/src/renderer/overlay-viewport.ts +0 -104
- package/src/renderer/panel-composite.ts +0 -145
- package/src/renderer/panel-workspace-bar.ts +0 -54
- package/src/renderer/process-indicator.ts +0 -163
- package/src/renderer/tab-strip.ts +0 -222
- package/src/renderer/text-layout.ts +0 -31
- package/src/renderer/tool-call.ts +0 -276
- package/src/renderer/tool-result-expanded-lines.ts +0 -48
- package/src/renderer/tool-result-summary.ts +0 -219
- package/src/runtime/diagnostics/panels/index.ts +0 -21
- package/src/runtime/diagnostics/panels/ops.ts +0 -156
- package/src/runtime/perf/panel-contracts.ts +0 -32
- package/src/runtime/store/domains/panels.ts +0 -117
- package/src/verification/verification-ledger.ts +0 -707
- /package/src/{panels → views}/memory-consolidation-gateway.ts +0 -0
- /package/src/{panels → views}/modals/modal-theme.ts +0 -0
- /package/src/{panels → views}/provider-health-domains.ts +0 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* agent-view-render.ts, one agent opened full screen.
|
|
3
|
+
*
|
|
4
|
+
* The agent becomes the session: its own transcript is the spine (drawn by
|
|
5
|
+
* the same lane-graph path as main, conversation-rendering.ts), the agents
|
|
6
|
+
* it spawned branch off it as lanes, and steers you sent that it has not
|
|
7
|
+
* picked up yet sit at the bottom marked as queued. Its lanes are drawn with
|
|
8
|
+
* the color base shifted (lane-graph/paint.ts withLaneColorBase) so its spine
|
|
9
|
+
* keeps the color its lane has in main.
|
|
10
|
+
*
|
|
11
|
+
* row 0 ◆ engineer · the task · started by main 2m 14s ago
|
|
12
|
+
* row 1 (blank)
|
|
13
|
+
* rows the agent's transcript
|
|
14
|
+
*
|
|
15
|
+
* A WRFC chain owner takes no turns of its own: its view is its chain, each
|
|
16
|
+
* phase agent a lane off its spine drawn from that agent's own transcript
|
|
17
|
+
* (work-tree-model.ts buildWrfcOwnerModel).
|
|
18
|
+
*
|
|
19
|
+
* The thin bar down column 0, the blank rows above and below the body, and
|
|
20
|
+
* the scroll window are the caller's (shell/session-views.ts); this returns
|
|
21
|
+
* every line.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { createEmptyLine, type Line } from '@pellux/goodvibes-sdk/platform/types';
|
|
25
|
+
import type { ConversationMessageSnapshot } from '@pellux/goodvibes-sdk/platform/core';
|
|
26
|
+
import { appendConversationMessages, drawConversationTurn, type ConversationRenderContext } from './conversation-rendering.ts';
|
|
27
|
+
import { buildWrfcOwnerModel } from './work-tree-model.ts';
|
|
28
|
+
import type { AgentLaneInfo, WorkTreeSources } from './work-tree-sources.ts';
|
|
29
|
+
import { laneColor, withLaneColorBase } from '../renderer/lane-graph/paint.ts';
|
|
30
|
+
import type { TreeGlyphSetName } from '../renderer/lane-graph/glyphs.ts';
|
|
31
|
+
import { UIFactory } from '../renderer/ui-factory.ts';
|
|
32
|
+
import { activeTokens } from '../renderer/theme.ts';
|
|
33
|
+
import { formatElapsed } from '../utils/format-elapsed.ts';
|
|
34
|
+
import { getDisplayWidth, truncateDisplay } from '../utils/terminal-width.ts';
|
|
35
|
+
|
|
36
|
+
export interface AgentViewInput {
|
|
37
|
+
readonly width: number;
|
|
38
|
+
readonly agent: AgentLaneInfo;
|
|
39
|
+
/** The agent's transcript (its lane info carries none for a WRFC owner). */
|
|
40
|
+
readonly messages: readonly ConversationMessageSnapshot[];
|
|
41
|
+
/** The agent's lane color index in main. */
|
|
42
|
+
readonly colorIndex: number;
|
|
43
|
+
/** Who started it: "main" or the parent agent's name. */
|
|
44
|
+
readonly parentName: string;
|
|
45
|
+
/** Main's work-tree sources (timings, spawned agents, waiting calls). */
|
|
46
|
+
readonly sources: WorkTreeSources;
|
|
47
|
+
/** Fold state for this view's beads; kept by the caller across frames. */
|
|
48
|
+
readonly collapse: Map<string, boolean>;
|
|
49
|
+
readonly glyphSet: TreeGlyphSetName;
|
|
50
|
+
readonly frame: number;
|
|
51
|
+
readonly now: number;
|
|
52
|
+
/** Steers sent from this view that the agent has not picked up yet, oldest first. */
|
|
53
|
+
readonly queuedSteers: readonly string[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const TEXT_X = 3;
|
|
57
|
+
|
|
58
|
+
function put(line: Line, x: number, endX: number, text: string, style: { fg: string; bold?: boolean }): number {
|
|
59
|
+
let cx = x;
|
|
60
|
+
for (const ch of text) {
|
|
61
|
+
const w = getDisplayWidth(ch);
|
|
62
|
+
if (w <= 0) continue;
|
|
63
|
+
if (cx + w > endX || cx >= line.length) break;
|
|
64
|
+
line[cx] = { char: ch, fg: style.fg, bg: '', bold: style.bold ?? false, dim: false, underline: false, italic: false, strikethrough: false };
|
|
65
|
+
if (w === 2 && cx + 1 < line.length) line[cx + 1] = { ...line[cx]!, char: '' };
|
|
66
|
+
cx += w;
|
|
67
|
+
}
|
|
68
|
+
return cx;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function stateText(agent: AgentLaneInfo, parentName: string, now: number): { text: string; tone: 'faint' | 'good' | 'bad' | 'warn' } {
|
|
72
|
+
const started = agent.startedAt !== undefined ? `started by ${parentName} ${formatElapsed(Math.max(0, now - agent.startedAt))} ago` : `started by ${parentName}`;
|
|
73
|
+
switch (agent.status) {
|
|
74
|
+
case 'running': return { text: started, tone: 'faint' };
|
|
75
|
+
case 'pending': return { text: `waiting to start · ${started}`, tone: 'faint' };
|
|
76
|
+
case 'completed': {
|
|
77
|
+
const took = agent.startedAt !== undefined && agent.completedAt !== undefined ? ` in ${formatElapsed(agent.completedAt - agent.startedAt)}` : '';
|
|
78
|
+
return { text: `finished${took} · ${agent.toolCallCount} tool${agent.toolCallCount === 1 ? '' : 's'}`, tone: 'good' };
|
|
79
|
+
}
|
|
80
|
+
case 'failed': return { text: `failed${agent.error ? `: ${agent.error.split('\n')[0]}` : ''}`, tone: 'bad' };
|
|
81
|
+
case 'cancelled': return { text: 'stopped', tone: 'warn' };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** The ◆ head row. */
|
|
86
|
+
function headRow(input: AgentViewInput): Line {
|
|
87
|
+
const t = activeTokens();
|
|
88
|
+
const line = createEmptyLine(input.width);
|
|
89
|
+
for (const cell of line) cell.bg = '';
|
|
90
|
+
const end = input.width - 3;
|
|
91
|
+
const color = laneColor(input.colorIndex);
|
|
92
|
+
let x = put(line, TEXT_X, end, '◆', { fg: color, bold: true });
|
|
93
|
+
// A chain owner is named for what it is, as its lane in main is.
|
|
94
|
+
x = put(line, x + 1, end, input.agent.wrfcPhases ? 'WRFC chain' : input.agent.hostedLabel ?? input.agent.name, { fg: color, bold: true });
|
|
95
|
+
const state = stateText(input.agent, input.parentName, input.now);
|
|
96
|
+
const stateFg = state.tone === 'good' ? t.success : state.tone === 'bad' ? t.error : state.tone === 'warn' ? t.warning : t.textFaint;
|
|
97
|
+
const task = input.agent.task.split('\n').find((l) => l.trim().length > 0)?.trim() ?? '';
|
|
98
|
+
// The state is kept whole; the task yields its room first.
|
|
99
|
+
const stateW = getDisplayWidth(state.text) + 3;
|
|
100
|
+
const taskRoom = end - x - 3 - stateW;
|
|
101
|
+
if (task && taskRoom >= 8) {
|
|
102
|
+
x = put(line, x + 1, end, '·', { fg: t.textFaint });
|
|
103
|
+
x = put(line, x + 1, end, truncateDisplay(task, taskRoom), { fg: t.textMuted });
|
|
104
|
+
}
|
|
105
|
+
x = put(line, x + 1, end, '·', { fg: t.textFaint });
|
|
106
|
+
put(line, x + 1, end, truncateDisplay(state.text, Math.max(0, end - x - 1)), { fg: stateFg });
|
|
107
|
+
return line;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Every line of the agent view, top to bottom. */
|
|
111
|
+
export function renderAgentView(input: AgentViewInput): Line[] {
|
|
112
|
+
const t = activeTokens();
|
|
113
|
+
const lines: Line[] = [headRow(input), createEmptyLine(input.width)];
|
|
114
|
+
const running = input.agent.status === 'running' || input.agent.status === 'pending';
|
|
115
|
+
const context: ConversationRenderContext = {
|
|
116
|
+
history: { addLine: (l) => { lines.push(l); }, addLines: (ls) => { lines.push(...ls); }, getLineCount: () => lines.length },
|
|
117
|
+
blockRegistry: [],
|
|
118
|
+
collapseState: input.collapse,
|
|
119
|
+
errorLineRegistry: [],
|
|
120
|
+
messageKindRegistry: new Map(),
|
|
121
|
+
configManager: null,
|
|
122
|
+
splashOptions: {},
|
|
123
|
+
// The agent's own turns are live while the agent runs, not while main does.
|
|
124
|
+
workTreeSources: { ...input.sources, turnActive: () => running, turnTiming: undefined },
|
|
125
|
+
treeGlyphSet: input.glyphSet,
|
|
126
|
+
focusId: null,
|
|
127
|
+
frame: input.frame,
|
|
128
|
+
};
|
|
129
|
+
const phases = input.agent.wrfcPhases;
|
|
130
|
+
if (phases) {
|
|
131
|
+
if (phases.length > 0) {
|
|
132
|
+
const model = buildWrfcOwnerModel({ info: input.agent, sources: context.workTreeSources ?? {}, collapse: input.collapse });
|
|
133
|
+
withLaneColorBase(input.colorIndex, () => { lines.push(...drawConversationTurn(context, model, input.width).lines); });
|
|
134
|
+
}
|
|
135
|
+
} else {
|
|
136
|
+
withLaneColorBase(input.colorIndex, () => appendConversationMessages(context, [...input.messages], input.width, []));
|
|
137
|
+
}
|
|
138
|
+
const empty = phases ? phases.length === 0 : input.messages.length === 0;
|
|
139
|
+
if (empty) {
|
|
140
|
+
const note = createEmptyLine(input.width);
|
|
141
|
+
for (const cell of note) cell.bg = '';
|
|
142
|
+
const text = phases
|
|
143
|
+
? (running ? 'The chain has not started a phase yet.' : 'This chain ran no phases.')
|
|
144
|
+
: running ? 'No transcript yet: the agent has not taken a turn.' : 'This agent left no transcript.';
|
|
145
|
+
put(note, TEXT_X + 2, input.width - 3, text, { fg: t.textFaint });
|
|
146
|
+
lines.push(note);
|
|
147
|
+
}
|
|
148
|
+
for (const steer of input.queuedSteers) {
|
|
149
|
+
lines.push(createEmptyLine(input.width));
|
|
150
|
+
lines.push(...UIFactory.createMessageBar(input.width, steer));
|
|
151
|
+
const note = createEmptyLine(input.width);
|
|
152
|
+
for (const cell of note) cell.bg = '';
|
|
153
|
+
put(note, TEXT_X + 2, input.width - 3, '⧗ queued · it arrives at the agent\'s next turn', { fg: t.textFaint });
|
|
154
|
+
lines.push(note);
|
|
155
|
+
}
|
|
156
|
+
return lines;
|
|
157
|
+
}
|
|
@@ -14,16 +14,30 @@
|
|
|
14
14
|
* wrapped promise settles) so it reflects "a prompt just appeared", not
|
|
15
15
|
* "a prompt was just resolved".
|
|
16
16
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
17
|
+
* Text (owner ruling 2026-09-29, SDK runtime/turn-notification.ts): the
|
|
18
|
+
* notice names what is waiting, the command for exec, the file for write and
|
|
19
|
+
* edit, the URL for fetch, and the turn that asked for it. When
|
|
20
|
+
* behavior.notificationsMetadataOnly is on (default off) every channel here
|
|
21
|
+
* (OSC 9, desktop, webhook) sends the tool name and permission category only,
|
|
22
|
+
* never a command, a path or the turn's name.
|
|
20
23
|
*/
|
|
21
24
|
import { notifyCompletion } from '@pellux/goodvibes-sdk/platform/utils';
|
|
22
25
|
import { logger } from '@pellux/goodvibes-sdk/platform/utils';
|
|
23
26
|
import type { WebhookNotifier } from '@pellux/goodvibes-sdk/platform/integrations';
|
|
24
27
|
import type { PermissionRequestHandler, PermissionPromptRequest } from '@pellux/goodvibes-sdk/platform/permissions';
|
|
25
28
|
import type { FocusTracker } from '@pellux/goodvibes-sdk/platform/runtime/operations';
|
|
26
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
shouldFireAlert,
|
|
31
|
+
FORCE_NOTIFY_DURATION_MS,
|
|
32
|
+
NOTIFICATION_TEXT_LIMITS,
|
|
33
|
+
buildApprovalNotification,
|
|
34
|
+
describeToolTarget,
|
|
35
|
+
formatWebhookText,
|
|
36
|
+
joinNotificationLine,
|
|
37
|
+
readNotificationsMetadataOnly,
|
|
38
|
+
resolveTurnName,
|
|
39
|
+
type ConfigGet,
|
|
40
|
+
} from '@pellux/goodvibes-sdk/platform/runtime/operations';
|
|
27
41
|
import type { TerminalNotifier } from './terminal-notifier.ts';
|
|
28
42
|
|
|
29
43
|
export interface ApprovalAlertDeps {
|
|
@@ -37,6 +51,21 @@ export interface ApprovalAlertDeps {
|
|
|
37
51
|
* desktop-alert gate below). Absent in tests/headless.
|
|
38
52
|
*/
|
|
39
53
|
readonly terminalNotifier?: TerminalNotifier | null;
|
|
54
|
+
/**
|
|
55
|
+
* The live conversation, read for the name of the turn that is asking: a
|
|
56
|
+
* title the user set, else the message that started this turn (the last
|
|
57
|
+
* user message while a turn runs). Absent means the notice names no turn.
|
|
58
|
+
*/
|
|
59
|
+
readonly conversation?: ApprovalTurnSource | null;
|
|
60
|
+
/** Desktop delivery; defaults to the SDK notifyCompletion. Tests pass a spy to read the text. */
|
|
61
|
+
readonly notifyDesktop?: typeof notifyCompletion;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** The conversation surface the approval notice names the turn from. */
|
|
65
|
+
export interface ApprovalTurnSource {
|
|
66
|
+
readonly title: string;
|
|
67
|
+
getTitleSource(): string;
|
|
68
|
+
getLastUserMessage(): string | null;
|
|
40
69
|
}
|
|
41
70
|
|
|
42
71
|
/**
|
|
@@ -56,19 +85,28 @@ export function wrapRequestPermissionWithAlert(
|
|
|
56
85
|
}
|
|
57
86
|
|
|
58
87
|
function fireApprovalAlert(request: PermissionPromptRequest, deps: ApprovalAlertDeps): void {
|
|
59
|
-
|
|
60
|
-
const
|
|
88
|
+
const metadataOnly = readNotificationsMetadataOnly(deps.configGet);
|
|
89
|
+
const conversation = deps.conversation;
|
|
90
|
+
const titleSource = conversation?.getTitleSource();
|
|
91
|
+
const notice = buildApprovalNotification({
|
|
92
|
+
tool: request.tool,
|
|
93
|
+
category: request.category,
|
|
94
|
+
target: metadataOnly ? null : describeToolTarget(request.args, request.analysis?.target),
|
|
95
|
+
turnName: metadataOnly || !conversation ? null : resolveTurnName({
|
|
96
|
+
title: conversation.title,
|
|
97
|
+
titleSource: titleSource === 'user' || titleSource === 'system' ? titleSource : null,
|
|
98
|
+
turnText: conversation.getLastUserMessage(),
|
|
99
|
+
}),
|
|
100
|
+
}, { metadataOnly });
|
|
61
101
|
|
|
62
102
|
// In-terminal (OSC 9) notification fires on its OWN gating (independent of the
|
|
63
103
|
// desktop-alert gate below), so it is emitted before the early return.
|
|
64
|
-
deps.terminalNotifier?.notify('approval-wait',
|
|
104
|
+
deps.terminalNotifier?.notify('approval-wait', joinNotificationLine(notice, NOTIFICATION_TEXT_LIMITS.terminal));
|
|
65
105
|
|
|
66
106
|
if (!shouldFireAlert(deps.focusTracker, deps.configGet, 'behavior.notifyOnApprovalPending')) return;
|
|
67
107
|
|
|
68
|
-
const title = 'GoodVibes: approval needed';
|
|
69
|
-
|
|
70
108
|
try {
|
|
71
|
-
notifyCompletion(title,
|
|
109
|
+
(deps.notifyDesktop ?? notifyCompletion)(notice.title, notice.body, FORCE_NOTIFY_DURATION_MS);
|
|
72
110
|
} catch (err) {
|
|
73
111
|
logger.debug('approval-alert: desktop notify error', { error: String(err) });
|
|
74
112
|
}
|
|
@@ -77,7 +115,7 @@ function fireApprovalAlert(request: PermissionPromptRequest, deps: ApprovalAlert
|
|
|
77
115
|
if (webhookNotifier) {
|
|
78
116
|
const urls = webhookNotifier.getUrls();
|
|
79
117
|
if (urls.length > 0) {
|
|
80
|
-
webhookNotifier.send(
|
|
118
|
+
webhookNotifier.send(formatWebhookText(notice)).catch((err: unknown) => {
|
|
81
119
|
logger.debug('approval-alert: webhook send error', { error: String(err) });
|
|
82
120
|
});
|
|
83
121
|
}
|
|
@@ -25,7 +25,13 @@
|
|
|
25
25
|
|
|
26
26
|
import type { ConversationManager } from './conversation.ts';
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
/**
|
|
29
|
+
* `msg_<idx>` (a message block), or `bead_c:<idx>:<call>` (a work-tree bead of
|
|
30
|
+
* the main transcript: its call's message index). A bead inside a folded turn
|
|
31
|
+
* or lane has no block of its own; its message line is the row that stands
|
|
32
|
+
* for it (the folded turn's header, or the ◉ of its folded lane's spawn).
|
|
33
|
+
*/
|
|
34
|
+
const MESSAGE_BOOKMARK_KEY = /^(?:msg_|bead_c:)(\d+)(?::\d+)?$/;
|
|
29
35
|
|
|
30
36
|
/**
|
|
31
37
|
* Resolve a bookmark key that missed a direct BlockMeta lookup. Returns the
|
|
@@ -13,18 +13,28 @@
|
|
|
13
13
|
* (notifyCompletion) + outbound webhook (WebhookNotifier), both gated by
|
|
14
14
|
* the shared focus/config gating in alert-gating.ts. Skips evaluation
|
|
15
15
|
* entirely when the session model is unpriced, a $0 placeholder cost must
|
|
16
|
-
* never be reported as a real breach (mirrors
|
|
16
|
+
* never be reported as a real breach (mirrors the usage modal's "unpriced"
|
|
17
17
|
* display convention).
|
|
18
18
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
19
|
+
* Text (owner ruling 2026-09-29, SDK runtime/turn-notification.ts): the
|
|
20
|
+
* notice names the budget that tripped (cost against the configured
|
|
21
|
+
* threshold) and the turn during which it tripped. When
|
|
22
|
+
* behavior.notificationsMetadataOnly is on (default off) the desktop and
|
|
23
|
+
* webhook text carries the numbers and the session id prefix only.
|
|
21
24
|
*/
|
|
22
25
|
import { notifyCompletion } from '@pellux/goodvibes-sdk/platform/utils';
|
|
23
26
|
import { logger } from '@pellux/goodvibes-sdk/platform/utils';
|
|
24
27
|
import type { WebhookNotifier } from '@pellux/goodvibes-sdk/platform/integrations';
|
|
25
28
|
import { calcSessionCost, computeBudgetBreach, isModelPriced } from '@pellux/goodvibes-sdk/platform/providers';
|
|
26
29
|
import type { FocusTracker } from '@pellux/goodvibes-sdk/platform/runtime/operations';
|
|
27
|
-
import {
|
|
30
|
+
import {
|
|
31
|
+
shouldFireAlert,
|
|
32
|
+
FORCE_NOTIFY_DURATION_MS,
|
|
33
|
+
buildBudgetNotification,
|
|
34
|
+
formatWebhookText,
|
|
35
|
+
readNotificationsMetadataOnly,
|
|
36
|
+
type ConfigGet,
|
|
37
|
+
} from '@pellux/goodvibes-sdk/platform/runtime/operations';
|
|
28
38
|
|
|
29
39
|
export interface BudgetBreachUsageSnapshot {
|
|
30
40
|
readonly input: number;
|
|
@@ -38,6 +48,10 @@ export interface BudgetBreachNotifierDeps {
|
|
|
38
48
|
readonly configGet: ConfigGet;
|
|
39
49
|
readonly webhookNotifier?: WebhookNotifier | null;
|
|
40
50
|
readonly sessionId: string;
|
|
51
|
+
/** The name of the turn that just ended (the one during which the budget tripped). */
|
|
52
|
+
readonly getTurnName?: (() => string | null) | undefined;
|
|
53
|
+
/** Desktop delivery; defaults to the SDK notifyCompletion. Tests pass a spy to read the text. */
|
|
54
|
+
readonly notifyDesktop?: typeof notifyCompletion | undefined;
|
|
41
55
|
}
|
|
42
56
|
|
|
43
57
|
/** Stateful edge-trigger checker, construct once per session, call on every TURN_COMPLETED. */
|
|
@@ -85,11 +99,16 @@ export function createBudgetBreachNotifier(deps: BudgetBreachNotifierDeps): Budg
|
|
|
85
99
|
function fireBudgetBreachAlert(deps: BudgetBreachNotifierDeps, sessionCost: number, budgetThresholdUsd: number): void {
|
|
86
100
|
if (!shouldFireAlert(deps.focusTracker, deps.configGet, 'behavior.notifyOnBudgetBreach')) return;
|
|
87
101
|
|
|
88
|
-
const
|
|
89
|
-
const
|
|
102
|
+
const metadataOnly = readNotificationsMetadataOnly(deps.configGet);
|
|
103
|
+
const notice = buildBudgetNotification({
|
|
104
|
+
sessionCostUsd: sessionCost,
|
|
105
|
+
budgetUsd: budgetThresholdUsd,
|
|
106
|
+
sessionId: deps.sessionId,
|
|
107
|
+
turnName: metadataOnly ? null : deps.getTurnName?.() ?? null,
|
|
108
|
+
}, { metadataOnly });
|
|
90
109
|
|
|
91
110
|
try {
|
|
92
|
-
notifyCompletion(title,
|
|
111
|
+
(deps.notifyDesktop ?? notifyCompletion)(notice.title, notice.body, FORCE_NOTIFY_DURATION_MS);
|
|
93
112
|
} catch (err) {
|
|
94
113
|
logger.debug('budget-breach-notifier: desktop notify error', { error: String(err) });
|
|
95
114
|
}
|
|
@@ -98,7 +117,7 @@ function fireBudgetBreachAlert(deps: BudgetBreachNotifierDeps, sessionCost: numb
|
|
|
98
117
|
if (webhookNotifier) {
|
|
99
118
|
const urls = webhookNotifier.getUrls();
|
|
100
119
|
if (urls.length > 0) {
|
|
101
|
-
webhookNotifier.send(
|
|
120
|
+
webhookNotifier.send(formatWebhookText(notice)).catch((err: unknown) => {
|
|
102
121
|
logger.debug('budget-breach-notifier: webhook send error', { error: String(err) });
|
|
103
122
|
});
|
|
104
123
|
}
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*
|
|
16
16
|
* The `[Compaction]` prefix is one of the FORCE_CONVERSATION_PREFIXES the
|
|
17
17
|
* system-message router always surfaces inline (see system-message-router.ts),
|
|
18
|
-
* so the receipt cannot be routed away
|
|
18
|
+
* so the receipt cannot be routed away from the transcript and vanish.
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
/** The shape of the SDK's COMPACTION_RECEIPT event payload we render from. */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared context-window usage computation.
|
|
3
3
|
*
|
|
4
|
-
* Centralises the tokens/contextWindow ratio so all
|
|
4
|
+
* Centralises the tokens/contextWindow ratio so all views, auto-compact,
|
|
5
5
|
* and session-maintenance read from one formula rather than six diverging
|
|
6
6
|
* hand-rolled copies.
|
|
7
7
|
*
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* architecture line-count gate; behaviour is unchanged.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { activeTheme } from '../renderer/theme.ts';
|
|
10
|
+
import { activeTheme, activeTokens } from '../renderer/theme.ts';
|
|
11
11
|
import { renderMarkdownTracked } from '../renderer/markdown.ts';
|
|
12
12
|
import { renderConversationEventLine, renderConversationFoldedRow } from '../renderer/conversation-surface.ts';
|
|
13
13
|
import { GLYPHS } from '../renderer/ui-primitives.ts';
|
|
@@ -54,10 +54,10 @@ export function renderCompactionContinuationMessage(
|
|
|
54
54
|
markerFg: T.toolAccent,
|
|
55
55
|
label: 'compaction handoff',
|
|
56
56
|
labelFg: T.toolAccent,
|
|
57
|
-
detailFg:
|
|
57
|
+
detailFg: activeTokens().textMuted,
|
|
58
58
|
};
|
|
59
59
|
const details = [
|
|
60
|
-
{ text: ` ${isCollapsed ? GLYPHS.navigation.collapsed : GLYPHS.navigation.expanded} ${lineCount} line${lineCount === 1 ? '' : 's'} `, fg:
|
|
60
|
+
{ text: ` ${isCollapsed ? GLYPHS.navigation.collapsed : GLYPHS.navigation.expanded} ${lineCount} line${lineCount === 1 ? '' : 's'} `, fg: activeTokens().textFaint },
|
|
61
61
|
];
|
|
62
62
|
|
|
63
63
|
if (isCollapsed) {
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* conversation-display-only.ts, the display-only output a conversation keeps.
|
|
3
|
+
*
|
|
4
|
+
* A command's printed text or a tool result row is drawn into the transcript
|
|
5
|
+
* but is not a message, so a rebuild (a resize, a theme change, the splash
|
|
6
|
+
* leaving) would drop it. ConversationManager keeps each such draw here, with
|
|
7
|
+
* the number of messages that existed when it was drawn, and draws it again
|
|
8
|
+
* in the same place on every rebuild. While a reply streams, draws made during
|
|
9
|
+
* the turn sit below the streamed text and are drawn again after each delta.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** Display-only draws kept for redraw; the oldest leave first. */
|
|
13
|
+
const MAX_DISPLAY_ONLY_ENTRIES = 500;
|
|
14
|
+
|
|
15
|
+
interface DisplayOnlyEntry {
|
|
16
|
+
readonly seq: number;
|
|
17
|
+
/** Messages that existed when it was drawn: it goes right after the unit holding message `anchor - 1`. */
|
|
18
|
+
readonly anchor: number;
|
|
19
|
+
readonly draw: (width: number) => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** One rebuild's walk over the kept draws, in order. */
|
|
23
|
+
export interface DisplayOnlyCursor {
|
|
24
|
+
/** Draw every kept entry printed while messages up to `lastIndex` existed. */
|
|
25
|
+
through(lastIndex: number): void;
|
|
26
|
+
/** During a streaming rebuild: the entries not yet drawn go below the streamed text. */
|
|
27
|
+
trailStreamed(lineCount: () => number): void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class DisplayOnlyOutput {
|
|
31
|
+
private entries: DisplayOnlyEntry[] = [];
|
|
32
|
+
private seq = 0;
|
|
33
|
+
/** While a reply streams, draws from this seq on sit below the streamed text. */
|
|
34
|
+
private trailingFromSeq = Number.POSITIVE_INFINITY;
|
|
35
|
+
/** Lines those trailing draws take, below the streamed text. */
|
|
36
|
+
trailingLineCount = 0;
|
|
37
|
+
|
|
38
|
+
get count(): number { return this.entries.length; }
|
|
39
|
+
|
|
40
|
+
drop(): void {
|
|
41
|
+
this.entries = [];
|
|
42
|
+
this.resetTrailing();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
resetTrailing(): void {
|
|
46
|
+
this.trailingFromSeq = Number.POSITIVE_INFINITY;
|
|
47
|
+
this.trailingLineCount = 0;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Keep a draw; `anchor` is the message count when it was printed. */
|
|
51
|
+
keep(anchor: number, draw: (width: number) => void): number {
|
|
52
|
+
const seq = ++this.seq;
|
|
53
|
+
this.entries.push({ seq, anchor, draw });
|
|
54
|
+
if (this.entries.length > MAX_DISPLAY_ONLY_ENTRIES) this.entries.splice(0, this.entries.length - MAX_DISPLAY_ONLY_ENTRIES);
|
|
55
|
+
return seq;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** A kept draw was just drawn at the end of the buffer; while streaming it trails the streamed text. */
|
|
59
|
+
noteDrawn(seq: number, lines: number, streaming: boolean): void {
|
|
60
|
+
if (!streaming) return;
|
|
61
|
+
if (this.trailingFromSeq === Number.POSITIVE_INFINITY) this.trailingFromSeq = seq;
|
|
62
|
+
this.trailingLineCount += lines;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Draw the entries kept below the streamed text again, and count their lines. */
|
|
66
|
+
drawTrailing(width: number, lineCount: () => number): void {
|
|
67
|
+
const before = lineCount();
|
|
68
|
+
for (const entry of this.entries) if (entry.seq >= this.trailingFromSeq) entry.draw(width);
|
|
69
|
+
this.trailingLineCount = lineCount() - before;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
cursor(width: number): DisplayOnlyCursor {
|
|
73
|
+
const kept = this.entries;
|
|
74
|
+
let next = 0;
|
|
75
|
+
return {
|
|
76
|
+
through: (lastIndex) => {
|
|
77
|
+
while (next < kept.length && kept[next]!.anchor <= lastIndex + 1) kept[next++]!.draw(width);
|
|
78
|
+
},
|
|
79
|
+
trailStreamed: (lineCount) => {
|
|
80
|
+
if (next >= kept.length) return;
|
|
81
|
+
this.trailingFromSeq = kept[next]!.seq;
|
|
82
|
+
this.drawTrailing(width, lineCount);
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -40,3 +40,30 @@ export function resolveTranscriptEventLine(
|
|
|
40
40
|
}
|
|
41
41
|
return [...lines].reverse().find((line) => line < currentLine) ?? lines[lines.length - 1]!;
|
|
42
42
|
}
|
|
43
|
+
|
|
44
|
+
/** The next (or previous) line in a sorted list after (or before) `currentLine`, wrapping around; -1 when the list is empty. */
|
|
45
|
+
export function wrapToLine(lines: readonly number[], currentLine: number, direction: 'next' | 'prev'): number {
|
|
46
|
+
if (lines.length === 0) return -1;
|
|
47
|
+
if (direction === 'next') return lines.find((line) => line > currentLine) ?? lines[0]!;
|
|
48
|
+
return [...lines].reverse().find((line) => line < currentLine) ?? lines[lines.length - 1]!;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** The block containing `lineIndex`, else the one starting nearest it (optionally only blocks of `typeFilter`). */
|
|
52
|
+
export function nearestBlock<B extends { readonly type: string; readonly startLine: number; readonly lineCount: number }>(
|
|
53
|
+
blocks: readonly B[],
|
|
54
|
+
lineIndex: number,
|
|
55
|
+
typeFilter?: string,
|
|
56
|
+
): B | null {
|
|
57
|
+
let nearest: B | null = null;
|
|
58
|
+
let nearestDist = Infinity;
|
|
59
|
+
for (const block of blocks) {
|
|
60
|
+
if (typeFilter !== undefined && block.type !== typeFilter) continue;
|
|
61
|
+
if (lineIndex >= block.startLine && lineIndex < block.startLine + block.lineCount) return block;
|
|
62
|
+
const dist = Math.abs(block.startLine - lineIndex);
|
|
63
|
+
if (dist < nearestDist) {
|
|
64
|
+
nearestDist = dist;
|
|
65
|
+
nearest = block;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return nearest;
|
|
69
|
+
}
|